LR-BEHAV-GPU: no indirect-call validation pipeline, which Intel Metal fails to compile (issue #250)

On a 2015 MacBook Pro (Intel Iris/Metal), 0.5.0 never opened its window, even with
LIGHTCRAFT_GPU=0: wgpu's indirect-call validation builds an internal compute pipeline when the
window's device is created, Metal's compiler fails on it ("Compiler encountered an internal
error"), the device is lost and eframe exits. That validation only guards indirect draws and
dispatches, which neither LightCraft nor egui issue.

backend::instance_flags clears InstanceFlags::VALIDATION_INDIRECT_CALL for every instance
LightCraft creates (the window's, Gpu::new and the denoise device), unless
WGPU_VALIDATION_INDIRECT_CALL asks for it. Tests: the flag rule, and that the window's instance
descriptor goes without it.
This commit is contained in:
radialmonster 2026-10-10 12:11:41 -04:00
parent b70ae1741e
commit 0bd3f3715d
4 changed files with 46 additions and 0 deletions

View File

@ -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]

View File

@ -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<String> {
#[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]

View File

@ -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() }),

View File

@ -86,6 +86,8 @@ fn make_device(backends: wgpu::Backends) -> Result<Dev, String> {
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 {