From 5e415cdd34d287b90a4a9df67eb4f532f6f72d96 Mon Sep 17 00:00:00 2001 From: vioreltus <142961713+vioreltus@users.noreply.github.com> Date: Sun, 1 Nov 2026 11:54:28 +0200 Subject: [PATCH] macOS: Intel Macs skip wgpu's indirect-call validation shaders (fixes #2812) (#3039) * Fix #2812: Intel Macs skip wgpu's indirect-call validation shaders Metal's Intel compiler aborts on wgpu's indirect-validation compute shaders ("Compiler encountered an internal error"; MTLCompilerService crashes in reportUnsupportedMaxWorkGroupSizeMetadataError), the device is lost inside request_device and GridCraft exits before its window opens. GridCraft issues no indirect draws or dispatches, so on Intel Macs the instance leaves VALIDATION_INDIRECT_CALL out; WGPU_VALIDATION_INDIRECT_CALL=1 turns it back on. Every other platform keeps wgpu's default. Same as WordCraft (#2652 there). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk * Review: test that WGPU_VALIDATION_INDIRECT_CALL=1 still turns it on; qualify the doc line The override is checked in a child test process with the variable set, since tests can't set environment variables without unsafe code. The revision line no longer says Intel Macs start: OpenCore Legacy Patcher Macs on macOS 15 can still fail on the MSL version (#1116). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk --------- Co-authored-by: Claude --- apps/gridcraft/src/graphics.rs | 80 +++++++++++++++++++++++++++++++++- apps/gridcraft/src/main.rs | 7 ++- docs/hardware-parity.md | 5 ++- 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/apps/gridcraft/src/graphics.rs b/apps/gridcraft/src/graphics.rs index 83478edde..93ec2e4c9 100644 --- a/apps/gridcraft/src/graphics.rs +++ b/apps/gridcraft/src/graphics.rs @@ -1,4 +1,5 @@ -//! Windows graphics instance configuration, applied before wgpu can initialize any driver. +//! Graphics instance configuration, applied before wgpu can initialize any driver: Windows +//! backends and the DirectX 12 shader compiler, and the Intel Mac indirect-validation skip. //! //! Vulkan is never initialized unless `WGPU_BACKEND` asks for it: enumerating Vulkan adapters //! crashes inside Intel's `igvk64.dll` on some UHD Graphics drivers (#120), and that access @@ -8,12 +9,38 @@ //! whichever `dxcompiler.dll` the DLL search path happens to find (#339, #354): see //! [`dx12_compiler`]. +// On macOS only the indirect-validation items are used outside tests; on Windows the other way +// round, so those two carry their own `allow`. +#![cfg_attr(target_os = "macos", allow(dead_code))] + use eframe::{ NativeOptions, egui_wgpu::WgpuSetup, - wgpu::{Backends, DeviceType, Dx12Compiler}, + wgpu::{Backends, DeviceType, Dx12Compiler, InstanceFlags}, }; +/// wgpu compiles its indirect-call validation compute shaders inside `request_device`. On Intel +/// Macs (Iris 5100 in a Mac mini 2014) Metal's compiler aborts on them ("Compiler encountered an +/// internal error"), the device is lost and eframe exits before the window opens. GridCraft issues +/// no indirect draws or dispatches, so Intel Macs skip them; every other GPU keeps wgpu's default. +/// Same as WordCraft (storytold/wordcraft#2652) and PhotoCraft (storytold/PhotoCraft#3171). +#[cfg_attr(not(target_os = "macos"), allow(dead_code))] +pub fn skips_indirect_validation(macos: bool, intel: bool) -> bool { + macos && intel +} + +/// Leaves wgpu's indirect-call validation out of the instance eframe creates when `skip`. +/// `WGPU_VALIDATION_INDIRECT_CALL=1` still turns it back on. +#[cfg_attr(not(target_os = "macos"), allow(dead_code))] +pub fn configure_indirect_validation(options: &mut NativeOptions, skip: bool) { + if !skip { + return; + } + if let WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup { + create.instance_descriptor.flags = create.instance_descriptor.flags.difference(InstanceFlags::VALIDATION_INDIRECT_CALL).with_env(); + } +} + /// What the start-up probe learned about one DirectX 12 adapter. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Dx12Adapter { @@ -141,6 +168,55 @@ fn probe_dx12(options: &NativeOptions) -> Vec { mod tests { use super::*; + fn instance_flags(options: &NativeOptions) -> InstanceFlags { + let WgpuSetup::CreateNew(create) = &options.wgpu_options.wgpu_setup else { + panic!("default native options must create a wgpu instance"); + }; + create.instance_descriptor.flags + } + + #[test] + fn intel_macs_skip_the_indirect_validation_shaders() { + // Metal's Intel compiler aborted on them, losing the device before the window opened. + assert!(skips_indirect_validation(true, true)); + // Apple silicon, Windows and Linux keep wgpu's validation. + for (macos, intel) in [(true, false), (false, true), (false, false)] { + assert!(!skips_indirect_validation(macos, intel), "macos={macos} intel={intel}"); + } + let mut options = NativeOptions::default(); + let before = instance_flags(&options); + configure_indirect_validation(&mut options, false); + assert_eq!(instance_flags(&options), before, "flags untouched when not skipping"); + if std::env::var_os("WGPU_VALIDATION_INDIRECT_CALL").is_none() { + configure_indirect_validation(&mut options, true); + assert!(!instance_flags(&options).contains(InstanceFlags::VALIDATION_INDIRECT_CALL)); + } + } + + /// `WGPU_VALIDATION_INDIRECT_CALL=1` turns the validation back on even when it is skipped. + /// Tests can't set environment variables (`set_var` is unsafe, and unsafe code is forbidden), + /// so this runs itself again as a child process with the variable set. + #[test] + fn the_environment_turns_indirect_validation_back_on() { + const NAME: &str = "graphics::tests::the_environment_turns_indirect_validation_back_on"; + if std::env::var_os("GRIDCRAFT_INDIRECT_VALIDATION_CHILD").is_some() { + let mut options = NativeOptions::default(); + configure_indirect_validation(&mut options, true); + assert!(instance_flags(&options).contains(InstanceFlags::VALIDATION_INDIRECT_CALL)); + return; + } + let exe = std::env::current_exe().unwrap(); + let out = std::process::Command::new(exe) + .args(["--exact", NAME, "--test-threads=1"]) + .env("GRIDCRAFT_INDIRECT_VALIDATION_CHILD", "1") + .env("WGPU_VALIDATION_INDIRECT_CALL", "1") + .output() + .unwrap(); + let stdout = String::from_utf8_lossy(&out.stdout); + // `--exact` matching nothing would pass too, so the child must report this one test. + assert!(out.status.success() && stdout.contains("1 passed"), "child test failed:\n{stdout}{}", String::from_utf8_lossy(&out.stderr)); + } + fn configured_backends(backends: Backends) -> Backends { let mut options = NativeOptions::default(); configure_backends(&mut options, backends); diff --git a/apps/gridcraft/src/main.rs b/apps/gridcraft/src/main.rs index 9357d0796..3aab1ccfc 100644 --- a/apps/gridcraft/src/main.rs +++ b/apps/gridcraft/src/main.rs @@ -12,7 +12,7 @@ #[cfg(target_os = "macos")] mod apple_events; mod control_server; -#[cfg(any(target_os = "windows", test))] +#[cfg(any(target_os = "windows", target_os = "macos", test))] mod graphics; mod logging; #[cfg(target_os = "macos")] @@ -341,8 +341,11 @@ fn main() -> eframe::Result<()> { } let window_builder = viewport.clone(); let options = eframe::NativeOptions { viewport, ..Default::default() }; - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(unix)] let mut options = options; + // Intel Macs: Metal's compiler aborts on wgpu's indirect-validation shaders (see graphics.rs). + #[cfg(target_os = "macos")] + graphics::configure_indirect_validation(&mut options, graphics::skips_indirect_validation(true, cfg!(target_arch = "x86_64"))); // winit has no file drag-and-drop on Wayland (only on X11), so dropping files from the file // manager showed a "no" cursor. Run through XWayland when it's there; GRIDCRAFT_WAYLAND=1 // keeps the native Wayland backend. diff --git a/docs/hardware-parity.md b/docs/hardware-parity.md index 71cf001d5..65ae312cd 100644 --- a/docs/hardware-parity.md +++ b/docs/hardware-parity.md @@ -1,6 +1,6 @@ # Hardware parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (Arrange All) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** trivial (Intel Mac start-up) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 The hardware Excel uses, per platform, against GridCraft. Part of [target-app-parity.md](target-app-parity.md). **Hardware: ~40% ready, 25–40 h** (estimated). @@ -8,7 +8,7 @@ The hardware Excel uses, per platform, against GridCraft. Part of | Hardware feature | Excel | GridCraft (macOS · Windows · Linux · BSD · web) | Gap and estimate | |---|---|---|---| | Multi-core recalculation | Multi-threaded recalculation on all cores (Windows and Mac), configurable thread count | Single-threaded everywhere (no threading in `crates/calc`) | Parallel recalc of independent dependency chains on native builds; WASM stays single-threaded or uses workers later: 12–18 h | -| GPU rendering of the grid and charts | DirectX / Metal accelerated drawing | wgpu: Metal · DirectX 12 (default since 10-08; OpenGL when DirectX 12 has no GPU driver or fails to start; Vulkan only on request, so the Intel UHD `igvk64.dll` crash (#120) can't happen; shaders compiled with FXC, so no DXC runtime is needed, #339) · Vulkan/GL · Vulkan/GL · WebGPU or WebGL2 | At parity in kind. Driver problems: screen flashing on Windows 11 (#76): 2–4 h | +| GPU rendering of the grid and charts | DirectX / Metal accelerated drawing | wgpu: Metal (Intel Macs skip wgpu's indirect-call validation shaders, which Metal's Intel compiler aborts on, #2812) · DirectX 12 (default since 10-08; OpenGL when DirectX 12 has no GPU driver or fails to start; Vulkan only on request, so the Intel UHD `igvk64.dll` crash (#120) can't happen; shaders compiled with FXC, so no DXC runtime is needed, #339) · Vulkan/GL · Vulkan/GL · WebGPU or WebGL2 | At parity in kind. Driver problems: screen flashing on Windows 11 (#76): 2–4 h | | Pen and touch (Draw tab) | Pen pressure and tilt, eraser end, touch mode, Ink to Shape, ink replay | Pen, highlighter, eraser, Ink to Shape with mouse/pen input; **no pressure or tilt**, no touch mode | Pressure-sensitive strokes where winit reports force; touch-friendly ribbon spacing: 4–6 h | | Trackpad and mouse | Smooth scrolling, pinch zoom, horizontal scroll, Magic Mouse | Smooth scrolling and zoom via egui | Verify pinch zoom on each platform: 1–2 h | | Multiple windows and monitors | One window per workbook, New Window for the same workbook, per-monitor DPI | A native window per workbook window on the desktop, New Window for the same workbook; new windows cascade from the last one on its monitor; View › Arrange All tiles, stacks or cascades them over the main window's monitor (web: one window) | Arrange All across monitors; verify per-monitor DPI with windows on two monitors: 2–3 h | @@ -20,6 +20,7 @@ The hardware Excel uses, per platform, against GridCraft. Part of | Date | Change | Summary | |---|---|---| +| 2026-10-31 | trivial | macOS: Intel Macs skip wgpu's indirect-call validation shaders, so they no longer exit on Metal's compiler error there (#2812). Macs run with OpenCore Legacy Patcher on macOS 15 can still fail earlier, on the MSL version (#1116). | | 2026-10-22 | minor | View › Arrange All over the main window's monitor. | | 2026-10-22 | minor | Multiple windows: each workbook window is a native window of its own (desktop). | | 2026-10-12 | minor | Desktop File > Print now discovers printers and submits PDF jobs through pinned craft-print with printer choice, copies, duplex and grayscale; browser printing and PDF export remain available. Remaining print parity includes page ranges and driver-specific tray/options UI; physical printer fidelity needs hardware validation. |