diff --git a/apps/lightcraft/src/main.rs b/apps/lightcraft/src/main.rs index 00665e87..94aeefa1 100644 --- a/apps/lightcraft/src/main.rs +++ b/apps/lightcraft/src/main.rs @@ -98,6 +98,8 @@ fn window_wgpu_options() -> eframe::egui_wgpu::WgpuConfiguration { if let eframe::egui_wgpu::WgpuSetup::CreateNew(n) = &mut c.wgpu_setup { n.instance_descriptor.backends = lightcraft_engine::gpu::backend::window_backends(); n.instance_descriptor.backend_options = lightcraft_engine::gpu::backend::backend_options(); + // no indirect-call validation pipeline: Intel Metal fails to compile it and the window never opens (#250) + n.instance_descriptor.flags = lightcraft_engine::gpu::backend::instance_flags_from_env(n.instance_descriptor.flags); } c } @@ -999,6 +1001,16 @@ mod tests { } } + /// Issue #250: the window's device is created without wgpu's indirect-call validation pipeline, + /// which Intel Macs' Metal compiler fails to build (the device was lost, the window never opened). + #[test] + fn window_has_no_indirect_call_validation_pipeline() { + let eframe::egui_wgpu::WgpuSetup::CreateNew(n) = window_wgpu_options().wgpu_setup else { panic!("expected CreateNew") }; + if std::env::var_os("WGPU_VALIDATION_INDIRECT_CALL").is_none() { + assert!(!n.instance_descriptor.flags.contains(eframe::wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL)); + } + } + /// Issue #234: Windows opens links with the URL protocol handler (the default browser), not /// File Explorer, and passes the whole map link, `?`, `&` and `#` included, as one argument. #[test] diff --git a/crates/gpu/src/backend.rs b/crates/gpu/src/backend.rs index 041f5773..81bfe719 100644 --- a/crates/gpu/src/backend.rs +++ b/crates/gpu/src/backend.rs @@ -145,6 +145,25 @@ pub fn backend_options() -> wgpu::BackendOptions { o } +/// wgpu's instance flags for every instance LightCraft creates, from `flags` (wgpu's defaults with its +/// environment variables applied) without `VALIDATION_INDIRECT_CALL`, unless +/// `WGPU_VALIDATION_INDIRECT_CALL` asks for it (any value but `0`). That validation builds an internal +/// compute pipeline when each device is created; Intel Macs' Metal compiler fails on it ("Compiler +/// encountered an internal error"), the device is lost and the window never opens (#250). It only +/// guards indirect draws and dispatches, which neither LightCraft nor egui issue. +pub fn instance_flags(flags: wgpu::InstanceFlags, env: Option<&str>) -> wgpu::InstanceFlags { + if env.is_some_and(|v| v != "0") { + flags | wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL + } else { + flags - wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL + } +} + +/// [`instance_flags`] with `WGPU_VALIDATION_INDIRECT_CALL` read from the environment. +pub fn instance_flags_from_env(flags: wgpu::InstanceFlags) -> wgpu::InstanceFlags { + instance_flags(flags, std::env::var("WGPU_VALIDATION_INDIRECT_CALL").ok().as_deref()) +} + /// `LIGHTCRAFT_GPU_BACKEND=off`. pub(crate) fn env_off() -> bool { env_choice() == BackendChoice::Off @@ -195,6 +214,17 @@ pub fn read_init_marker(path: &std::path::Path) -> Option { #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { + + /// #250: the indirect-call validation pipeline is off unless asked for; other flags are kept. + #[test] + fn indirect_call_validation_is_off_unless_asked_for() { + use wgpu::InstanceFlags as F; + let base = F::VALIDATION_INDIRECT_CALL | F::DEBUG; + assert_eq!(instance_flags(base, None), F::DEBUG); + assert_eq!(instance_flags(base, Some("0")), F::DEBUG); + assert_eq!(instance_flags(F::empty(), Some("1")), F::VALIDATION_INDIRECT_CALL); + assert_eq!(instance_flags(F::empty(), None), F::empty()); + } use super::*; #[test] diff --git a/crates/gpu/src/ctx.rs b/crates/gpu/src/ctx.rs index 2f4abd4d..8d73703c 100644 --- a/crates/gpu/src/ctx.rs +++ b/crates/gpu/src/ctx.rs @@ -284,6 +284,8 @@ impl Gpu { desc.backends = backends; // DX12 shaders compile with FXC (issue #471) desc.backend_options = crate::backend::backend_options(); + // no indirect-call validation pipeline: Intel Metal fails to compile it (#250) + desc.flags = crate::backend::instance_flags_from_env(desc.flags); let instance = wgpu::Instance::new(desc); let adapter = pollster::block_on( instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, ..Default::default() }), diff --git a/crates/gpu/src/nn.rs b/crates/gpu/src/nn.rs index 689aecbe..ca0975e9 100644 --- a/crates/gpu/src/nn.rs +++ b/crates/gpu/src/nn.rs @@ -86,6 +86,8 @@ fn make_device(backends: wgpu::Backends) -> Result { desc.backends = backends; // DX12 shaders compile with FXC (issue #471) desc.backend_options = crate::backend::backend_options(); + // no indirect-call validation pipeline: Intel Metal fails to compile it (#250) + desc.flags = crate::backend::instance_flags_from_env(desc.flags); let instance = wgpu::Instance::new(desc); let wanted = std::env::var("LIGHTCRAFT_GPU_ADAPTER").ok().map(|s| s.trim().to_lowercase()).filter(|s| !s.is_empty()); let adapter = match &wanted {