diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94a0453..35660b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,6 +72,16 @@ jobs: key: ${{ runner.os }}-shared-${{ hashFiles('native/shared/Cargo.lock') }} restore-keys: ${{ runner.os }}-shared- + - name: Select hosted Windows shader compiler + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + # WARP's DXIL optimizer can crash during concurrent traversal shader + # compilation. Keep this software-renderer lane on explicit FXC; + # physical DX12/DXC qualification remains a separate requirement. + "WGPU_BACKEND=dx12" >> $env:GITHUB_ENV + "WGPU_DX12_COMPILER=fxc" >> $env:GITHUB_ENV + - name: quick / shared-tests # Retain adapter identities and capability skips even when Rust reports # an early-returning GPU test as "ok". diff --git a/docs/evidence/windows-camera-history-v1.md b/docs/evidence/windows-camera-history-v1.md index 1ef4770..7c6eec3 100644 --- a/docs/evidence/windows-camera-history-v1.md +++ b/docs/evidence/windows-camera-history-v1.md @@ -51,11 +51,15 @@ ignored goldens, and two ignored documentation examples retain their status. Formatting, strict Clippy policy, and repository contracts also pass. The DX12 run uses a process-local Vulkan loader override for older helper functions; some of those helpers explicitly use software adapters. The golden captures -require a physical GPU and identify the Radeon/backend. Hosted validation is -pending. The Windows hosted library access violation is separate and -remains unresolved. This advances #135/#149/#140 without closing the wider +require a physical GPU and identify the Radeon/backend. Hosted #163 passes the +macOS shared suite, including all 93 goldens and the expanded camera-cut test, +plus canonical Metal captures. Windows native builds and all 20 example links +pass; its shared library stage still crashes before goldens. That access +violation is separate and remains unresolved. This advances #135/#149/#140 without closing the wider performance, platform, representative-corpus, or hardware requirements. Source patches, commands, exact RGBA/PNG captures, changed-pixel coordinates, and results are retained under `tools/quality/out/windows-engine-plan/camera-cut-dx12/`. +The [published archive](https://github.com/Bloom-Engine/engine/releases/tag/quality-evidence-camera-history-20260911) +includes exact source-match receipts and the failed Windows hosted log. diff --git a/docs/evidence/windows-warp-compiler-v1.md b/docs/evidence/windows-warp-compiler-v1.md new file mode 100644 index 0000000..105b06a --- /dev/null +++ b/docs/evidence/windows-warp-compiler-v1.md @@ -0,0 +1,44 @@ +# Hosted Windows software-renderer compiler + +The actual Windows shared job crashes with `0xc0000005` on #162 and #163. +The native engine build and all 20 example links pass. The Windows crash occurs +in the library stage, before golden tests execute; successful build jobs do not +qualify those render tests. + +## Local diagnosis + +At unchanged #163 source `c643b85`, four-thread library execution also crashes +when the virtual-geometry helpers select the Microsoft Basic Render Driver. +An eight-thread run passes, so a passing rerun does not close the defect. +Windows Event 1000 identifies `d3d10warp.dll` version `10.0.26100.9278`. + +The installed Windows debugger captures a second-chance access violation in +`d3d10warp!JITBaseVariable::OptimizeCopy`, through the WARP DXIL shader-JIT +destructor and compute-pipeline compilation, during a traversal queue submission. +The affected Rust test refines atomic groups that straddle frustum planes. +This identifies the failing driver/compiler path; it does not establish the +underlying driver's root cause or prove that every hosted crash has this stack. + +That existing test passes twice alone with DXC and twice with FXC. The complete +ten-test hierarchy group passes serially with DXC, crashes in both four-thread +DXC controls, and passes with FXC and four threads. Complete library controls with four and eight threads also +pass with FXC selected for environment-aware helpers (489 passed, one ignored). +Older helpers ignored compiler/backend environment options during those controls; +some explicitly select software adapters and others select the physical Radeon. + +## Compatibility change + +The hosted Windows shared lane explicitly selects DX12 and FXC. The remaining +ordinary GPU test helpers now honor backend and compiler environment options, +matching the visibility, virtual-geometry, and golden helpers. Adapter selection, +capability requirements, ignored tests, assertions and thresholds are unchanged. +The ray-query golden helper retains its deliberate DXC requirement. + +This is a software-renderer CI workaround. The WARP/DXIL access violation remains +open, and physical DX12/DXC qualification remains required. The ordinary Windows +engine compiler policy is unchanged. The candidate's library suite passes with +four threads and explicit FXC (489 passed, one ignored). Complete local shared +components and hosted validation are still running. + +Diagnostic commands, logs, debugger stacks, executable hashes and results are +retained in `tools/quality/out/windows-engine-plan/windows-shared-crash/`. diff --git a/docs/windows-engine-plan.md b/docs/windows-engine-plan.md index 858c48a..f119e05 100644 --- a/docs/windows-engine-plan.md +++ b/docs/windows-engine-plan.md @@ -63,7 +63,12 @@ audit are saved in `tools/quality/out/windows-engine-plan/plan-requirements.json The expanded local DX12 goldens expose a [camera-history reset defect](evidence/windows-camera-history-v1.md); its read guard passes exact fresh/reset comparisons after eight and 40 history frames on Vulkan and DX12. Both complete local shared components - now pass, including all 93 goldens; hosted validation remains required. + now pass, including all 93 goldens. Hosted #163 also passes all 93 macOS + goldens and the expanded cut check; its [evidence is published](https://github.com/Bloom-Engine/engine/releases/tag/quality-evidence-camera-history-20260911). + The Windows library crash reproduces locally inside WARP's DXIL shader + optimizer during concurrent traversal tests. An [explicit FXC hosted lane](evidence/windows-warp-compiler-v1.md) + and consistent test compiler selection are being validated. The underlying + WARP/DXIL defect and physical DX12/DXC acceptance remain separate. 2. Finish all-example native linking, real starter/example startup, and clean Windows installation. The [native example gate](evidence/windows-example-gate-v1.md) passes all 20 links locally using Perry 0.5.1220 and one matching source-built diff --git a/native/shared/src/cooked_texture_store_tests.rs b/native/shared/src/cooked_texture_store_tests.rs index 1f31b4e..58681a1 100644 --- a/native/shared/src/cooked_texture_store_tests.rs +++ b/native/shared/src/cooked_texture_store_tests.rs @@ -432,7 +432,8 @@ fn temporary_store(label: &str) -> PathBuf { fn try_device(require_bc: bool) -> Option<(wgpu::Device, wgpu::Queue)> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter = diff --git a/native/shared/src/renderer/gpu_driven.rs b/native/shared/src/renderer/gpu_driven.rs index 82c27fe..7fac9e8 100644 --- a/native/shared/src/renderer/gpu_driven.rs +++ b/native/shared/src/renderer/gpu_driven.rs @@ -1816,7 +1816,8 @@ mod tests { use wgpu::util::DeviceExt as _; let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let Ok(adapter) = diff --git a/native/shared/src/renderer/impulse_field.rs b/native/shared/src/renderer/impulse_field.rs index 65b9675..c501c19 100644 --- a/native/shared/src/renderer/impulse_field.rs +++ b/native/shared/src/renderer/impulse_field.rs @@ -314,7 +314,8 @@ mod tests { /// without storage textures" doc-comment contract). fn try_create_device() -> Option<(wgpu::Device, wgpu::Queue)> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { diff --git a/native/shared/src/renderer/material_indirection.rs b/native/shared/src/renderer/material_indirection.rs index ac807fb..40889fa 100644 --- a/native/shared/src/renderer/material_indirection.rs +++ b/native/shared/src/renderer/material_indirection.rs @@ -1541,7 +1541,11 @@ mod tests { #[cfg(not(target_arch = "wasm32"))] fn try_tier_a_device() -> Option<(wgpu::Device, wgpu::Queue)> { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle()); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), + ..wgpu::InstanceDescriptor::new_without_display_handle() + }); let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, force_fallback_adapter: false, diff --git a/native/shared/src/renderer/material_system_tests.rs b/native/shared/src/renderer/material_system_tests.rs index 220ec51..2068012 100644 --- a/native/shared/src/renderer/material_system_tests.rs +++ b/native/shared/src/renderer/material_system_tests.rs @@ -40,7 +40,8 @@ mod tests { /// (test skips gracefully) when no GPU is available. fn try_create_device() -> Option<(wgpu::Device, wgpu::Queue)> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { diff --git a/native/shared/src/renderer/occlusion.rs b/native/shared/src/renderer/occlusion.rs index 01c290f..ad68bcc 100644 --- a/native/shared/src/renderer/occlusion.rs +++ b/native/shared/src/renderer/occlusion.rs @@ -482,7 +482,8 @@ mod tests { fn try_device() -> Option<(wgpu::Device, wgpu::Queue)> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter = diff --git a/native/shared/src/renderer/planar_reflection.rs b/native/shared/src/renderer/planar_reflection.rs index bb3bcf6..1b67ae1 100644 --- a/native/shared/src/renderer/planar_reflection.rs +++ b/native/shared/src/renderer/planar_reflection.rs @@ -408,7 +408,8 @@ mod tests { /// is available so the test skips gracefully on bare CI. fn try_create_device() -> Option<(wgpu::Device, wgpu::Queue)> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { diff --git a/native/shared/src/renderer/post_pass.rs b/native/shared/src/renderer/post_pass.rs index d918224..e2f0561 100644 --- a/native/shared/src/renderer/post_pass.rs +++ b/native/shared/src/renderer/post_pass.rs @@ -220,7 +220,8 @@ mod tests { /// `transient.rs` so tests run on machines without a real GPU. fn try_create_device() -> Option<(wgpu::Device, wgpu::Queue)> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { diff --git a/native/shared/src/renderer/transient.rs b/native/shared/src/renderer/transient.rs index 34526ea..3430bee 100644 --- a/native/shared/src/renderer/transient.rs +++ b/native/shared/src/renderer/transient.rs @@ -546,7 +546,8 @@ mod tests { /// need a device are skipped gracefully. fn try_create_device() -> Option<(wgpu::Device, wgpu::Queue)> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { diff --git a/native/shared/src/virtual_shadow_gpu_receiver.rs b/native/shared/src/virtual_shadow_gpu_receiver.rs index fea5c92..c0dca54 100644 --- a/native/shared/src/virtual_shadow_gpu_receiver.rs +++ b/native/shared/src/virtual_shadow_gpu_receiver.rs @@ -586,7 +586,8 @@ mod tests { fn try_device() -> Option<(wgpu::Device, wgpu::Queue)> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter = diff --git a/native/shared/tests/render_targets.rs b/native/shared/tests/render_targets.rs index 5dc38b9..0c2b311 100644 --- a/native/shared/tests/render_targets.rs +++ b/native/shared/tests/render_targets.rs @@ -22,7 +22,8 @@ use bloom_shared::scene::SceneGraph; fn try_renderer() -> Option { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), + backends: wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()), + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let adapter =