From ab8019be8e0d6f1d950c623df6af575cbb2b5ee1 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Fri, 11 Sep 2026 02:42:46 +0200 Subject: [PATCH] Preserve DX12 indirect identities and restore visibility GPU checks --- docs/evidence/windows-dx12-indirect-v1.md | 63 +++++++++ docs/evidence/windows-example-ci-v1.md | 22 +-- docs/evidence/windows-example-gate-v1.md | 12 +- docs/windows-engine-plan.md | 6 +- native/shared/src/renderer/gpu_driven.rs | 16 ++- .../src/renderer/visibility_buffer_tests.rs | 32 +++-- .../src/virtual_geometry/draw_emission.rs | 6 +- native/shared/src/virtual_geometry/tests.rs | 29 ++-- .../virtual_visibility_tests.rs | 129 +++++++++++------- .../shared/src/virtual_geometry/visibility.rs | 2 +- 10 files changed, 224 insertions(+), 93 deletions(-) create mode 100644 docs/evidence/windows-dx12-indirect-v1.md diff --git a/docs/evidence/windows-dx12-indirect-v1.md b/docs/evidence/windows-dx12-indirect-v1.md new file mode 100644 index 00000000..af8d6b88 --- /dev/null +++ b/docs/evidence/windows-dx12-indirect-v1.md @@ -0,0 +1,63 @@ +# Windows DX12 draw identity and visibility oracle + +Executing the Windows shared tests exposed two failures hidden by the previous +CI shell setup. Both reproduce on AMD Radeon 760M DX12 and Microsoft WARP, +with either FXC or DXC. The original full pixel checks pass under Vulkan. + +## Counted indirect drawing + +The GPU-generated commands contain the correct first-instance values, but the +DX12 counted path renders only the first identity. Submitting the same commands +through ordinary multi-draw or individual indirect calls passes the original +four-pixel identity oracle. Inspection of wgpu-hal 29.0.1 shows that counted +submission uses the common command signature, while ordinary indirect submission +uses the pipeline signature carrying the special first-vertex/instance constants. +The 29.0.4 implementation retains that count path. + +Bloom now uses its existing bounded ordinary multi-draw fallback for DX12. +One device/backend policy governs static GPU-driven submission, virtual draw +emission, and virtual visibility rasterization. Reported counted-submission +support reflects that policy. Vulkan retains counted submission when enabled; +Metal retains its existing fallback. This is a correctness workaround; no DX12 +performance improvement or upstream HAL fix is claimed. + +## Visibility reconstruction oracle + +A reduced shader isolates pipeline error `0x80070057` to the combination of +fragment `position` and `primitive_index`. Each input independently creates a +valid pipeline. This fails with either compiler on both DX12 adapters. The +production visibility ID raster does not consume fragment position; its +reconstruction runs separately. + +The raster oracle carries linearly interpolated NDC and recovers the exact +pixel center before calling the unchanged production barycentric function. +Recovering the center is necessary because interpolation adds subpixel error; +a direct interpolated-coordinate control fails the original limits even under +Vulkan. Draw/primitive identity, opposite face orientation, clear sentinel, +nonuniform clip W, and the original `2e-5` barycentric limit remain checked. +No pipeline-only diagnostic, alternate shader injection, or unconditional test +success is retained in the source change. + +## Validation and limits + +Both focused original GPU oracles pass in five configurations: Radeon Vulkan, +Radeon DX12/FXC, Radeon DX12/DXC, WARP/FXC, and WARP/DXC (10 executions). +Adapter identities and successful execution are checked in the retained logs. +The affected test helpers now honor `WGPU_BACKEND`, shader compiler options, +and the test-only `BLOOM_TEST_FORCE_FALLBACK_ADAPTER` selector. + +The initial full library runs pass on DX12 and Vulkan: 488 passed, zero failed, +one ignored. For the DX12 run, a process-local Vulkan loader override prevents +older test helpers using `Backends::all` from selecting Vulkan. All 32 emitted +adapter identities are DX12. No persistent graphics-driver setting changes. +The final patch also exercises the production virtual-visibility path with its +default submission choice as well as the explicitly forced binned path. +Full shared-suite and hosted validation of that final patch are in progress. The earlier hosted +access violation remains unresolved; subsequent non-crashing executions do not +establish a fix. Unsupported adapter features and missing optional asset fixtures +remain explicit limitations, and no unavailable hardware acceptance is claimed. + +Diagnostic controls, original failures, candidate patches, commands, and results +are retained in `tools/quality/out/windows-engine-plan/hosted-windows-gpu/`. +The [native example build evidence](windows-example-gate-v1.md) is published +separately and does not qualify executable startup or clean installation. diff --git a/docs/evidence/windows-example-ci-v1.md b/docs/evidence/windows-example-ci-v1.md index 186607d1..a1156f48 100644 --- a/docs/evidence/windows-example-ci-v1.md +++ b/docs/evidence/windows-example-ci-v1.md @@ -16,9 +16,9 @@ The remaining `perry-embed` failure is independent. Perry 0.5.1182 does not support the example's `bloomViewGetNativeHandle` call. Official 0.5.1219 supports that API but its prebuilt standard library fails to link missing HTTP extension symbols. Official 0.5.1220 had already exposed the same standard-library issue -in the original #153 work. The embedded-view example remains unqualified; -compiler source/build investigation continues rather than removing the example -from the required inventory. +in the original #153 work. The matching source-built runtime in the +[native example gate](windows-example-gate-v1.md) now links all 20 examples, +including the embedded view. Actual embedded-view startup remains unqualified. ## Hosted Windows execution gap @@ -33,21 +33,25 @@ The Tests workflow now selects Bash explicitly and requires the emitted shared test summary on every host and the native-build summary on Windows. Artifact upload fails when a summary is absent, making another silent non-execution a failure. The CI command contract checks both the shell and required evidence. -Hosted execution on this correction still needs verification from actual Cargo -output and summary contents. +Hosted execution is now verified from Cargo output and retained summaries. The first hosted run at `88bc633` executes Cargo and uploads both failure summaries. It exposes a second setup defect: Git Bash prepends its own `/usr/bin/link.exe` ahead of the MSVC linker. Cargo then invokes the hard-link utility with Microsoft linker arguments. The shared CI entry point now restores the active Visual Studio developer toolchain ahead of Git's tools and fails -with an actionable message if that configured linker is missing. The next -hosted run must verify the build and shared suite with that correction. +with an actionable message if that configured linker is missing. At `636b69a`, the next hosted native build passes. The shared suite +exposes two DX12 GPU failures and an access violation. At `59244b9`, a complete +shared run reports 486 passed, two failed, and one ignored; the earlier crash +is not thereby fixed. The [DX12 follow-up](windows-dx12-indirect-v1.md) retains +the reproductions and corrective work. Audit commands, original failure logs, compiler-release metadata, source patch, and the six corrected executable hashes are retained under `tools/quality/out/windows-engine-plan/all-examples/`. The original audit is also included in the [#159 evidence release](https://github.com/Bloom-Engine/engine/releases/tag/quality-evidence-ssgi-surface-20260911). -This is progress on #140/#142/#74. All-example PR compilation, real starter and -embedded-view startup, and clean package installation are still required. +The [published build evidence](https://github.com/Bloom-Engine/engine/releases/tag/quality-evidence-windows-examples-20260911) +retains these results and the all-20 local/hosted native links. This advances +#140/#142/#74. Real starter and embedded-view startup, clean package installation, +and the complete Windows shared suite remain required. diff --git a/docs/evidence/windows-example-gate-v1.md b/docs/evidence/windows-example-gate-v1.md index 36c364c5..605c2bc1 100644 --- a/docs/evidence/windows-example-gate-v1.md +++ b/docs/evidence/windows-example-gate-v1.md @@ -39,7 +39,12 @@ old output or object-only compilation cannot count as success. Failures and timeouts do not hide later example results. Three orchestration regressions, the full quality-contract component, and -repository contracts pass locally. Hosted example execution is pending. +repository contracts pass locally. At exact source `59244b9`, the local full +CI component passes all 20 links in 87.703 seconds; quick links Pong in 4.703 +seconds. Hosted [Tests run 34544031931](https://github.com/Bloom-Engine/engine/actions/runs/34544031931) +passes the native engine build and all 20 links in 194.778 seconds. Its merge +checkout `962f8b4` has the same Git tree as the tested PR head. These build times +are observations. Source receipts and per-example hashes are retained. The preceding [Windows CI correction](windows-example-ci-v1.md) now proves an actual hosted native build at `636b69a`. Its shared suite exposes two DX12 GPU @@ -51,3 +56,8 @@ No broader CI, startup, packaging, or hardware issue is closed by this report. Commands, failed profiles, successful executable hashes, setup receipts, and logs are retained under `tools/quality/out/windows-engine-plan/all-examples/`. + +The [published report and ZIP](https://github.com/Bloom-Engine/engine/releases/tag/quality-evidence-windows-examples-20260911) +contain 287 verified payloads, including failures and successful local/hosted +receipts. The archive is 9,940,053 bytes with SHA-256 +`2ebcb258a72e473723331dcd9fbaad1781b36d0ea70b0e95a3bcfd90fcf0a9b9`. diff --git a/docs/windows-engine-plan.md b/docs/windows-engine-plan.md index 816e24e1..7e01c969 100644 --- a/docs/windows-engine-plan.md +++ b/docs/windows-engine-plan.md @@ -17,7 +17,7 @@ first nine-scene Radeon evidence are in draft PR #154. Follow-up work starts at | #127 Vulkan PT correctness | Three deterministic progressive and motion runs, both negative controls, finite intermediates, reset/lighting/rigid-motion checks, retained report | Canonical hardware gate, all four focused temporal tests, and CPU reference sanity check pass on Radeon/Vulkan; [report](evidence/issue-127-windows-vulkan-v1.md) and [raw evidence](https://github.com/Bloom-Engine/engine/releases/tag/quality-evidence-155-windows-vulkan-20260910) published | | #128 Windows image discrepancies | Identify the first incorrect stage or document a reviewed backend-specific baseline decision; rerun the full strict corpus and reproducibility checks | Cutout and surface corrections pass all nine Radeon images. At #159 source `d610d6a`, full runs 2 and 3 pass every configured check and reproduce 257 artifacts byte-identically with matching metadata and timing differences inside existing noise bounds. Earlier invalid runs retain their failures; named hardware acceptance remains separate | | #135 / #149 temporal reconstruction | Enforced motion/producer/quality-preset corpus, representative scenes, fractional/native and frozen A/B timing, memory/resize checks, platform evidence | Device/resource, stationary SSGI, and profiler fixes are retained. The surface correction passes original HD startup limits and 154,720 analytic receiver checks on Vulkan, DX12, and hosted Metal; 93 local goldens pass, including lighting recovery. The full Radeon corpus passes twice. Wider representative scenes, frozen A/B performance, memory/resize, and platform acceptance remain open | -| #140 integration gates | Same required local/hosted lanes pass on exact source; release package startup and all-example evidence | #159 has 24 successful CheckRun statuses, but Windows logs reveal no Cargo execution because Bash was invoked through PowerShell without an explicit shell. Local Windows execution is verified. This follow-up selects Bash and requires execution-summary artifacts; actual hosted validation is pending. All-example PR compilation and release-install acceptance remain separate requirements | +| #140 integration gates | Same required local/hosted lanes pass on exact source; release package startup and all-example evidence | #160 fixes silent Windows CI non-execution and MSVC PATH ordering. #161 passes the actual native engine build and all 20 native links locally and in hosted CI; [evidence](https://github.com/Bloom-Engine/engine/releases/tag/quality-evidence-windows-examples-20260911) is published. Two real DX12 shared-test failures and an earlier access violation remain under investigation. Release startup/install acceptance remains open | | #138 capability fallback | Actual constrained-adapter startup and relevant forced-tier corpus, truthful capability outputs | Existing implementation/evidence preserved; physical constrained-limit acceptance still needs proof | | PR integration | Reviewable changes, passing required checks, full issue evidence, merge-ready rendering branch | #147 and the stacked fixes #154–#159 remain drafts; no merge performed | @@ -64,8 +64,8 @@ audit are saved in `tools/quality/out/windows-engine-plan/plan-requirements.json 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 runtime profile. It adds required full-lane Windows PR compilation and - rejects missing or stale executable outputs. Hosted example validation, - actual startup, and clean package installation remain required. + rejects missing or stale executable outputs. Hosted example validation also passes at `59244b9`; actual startup and clean + package installation remain required. 3. Complete the wider temporal/geometry, performance, memory, resize, and capability corpus. The [HD surface correction](evidence/windows-ssgi-surface-v1.md) and two valid diff --git a/native/shared/src/renderer/gpu_driven.rs b/native/shared/src/renderer/gpu_driven.rs index b01b1a20..82c27fe1 100644 --- a/native/shared/src/renderer/gpu_driven.rs +++ b/native/shared/src/renderer/gpu_driven.rs @@ -46,6 +46,18 @@ pub fn request_features_if_supported(supported: wgpu::Features, required: &mut w super::visibility_buffer::request_feature_if_supported(supported, required); } +/// Whether counted indirect submission preserves Bloom's draw identities. +/// wgpu 29's DX12 count path uses the common command signature and loses the +/// per-command first vertex/instance constants. Ordinary multi-draw uses the +/// pipeline signature correctly. Keep DX12 on the existing bounded fallback +/// until the counted path passes the GPU identity oracle with an updated HAL. +pub(crate) fn supports_indirect_count(device: &wgpu::Device) -> bool { + device.adapter_info().backend != wgpu::Backend::Dx12 + && device + .features() + .contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT) +} + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct GeometrySlice { pub vertex_offset: u64, @@ -394,9 +406,7 @@ impl GpuDrivenRenderer { let tier_ready = global_material_layout.is_some(); let enabled = cfg!(not(target_arch = "wasm32")) && feature_ready && tier_ready && !forced_off; - let count_supported = device - .features() - .contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT); + let count_supported = supports_indirect_count(device); let routed_visibility = super::visibility_buffer::requested_mode().shades() && enabled; let draw_capacity = 64; let draw_buffer = create_draw_buffer(device, draw_capacity); diff --git a/native/shared/src/renderer/visibility_buffer_tests.rs b/native/shared/src/renderer/visibility_buffer_tests.rs index b4e98ba5..200bf80f 100644 --- a/native/shared/src/renderer/visibility_buffer_tests.rs +++ b/native/shared/src/renderer/visibility_buffer_tests.rs @@ -157,12 +157,16 @@ fn large_vertex_arenas_split_at_aligned_storage_binding_boundaries() { #[cfg(not(target_arch = "wasm32"))] fn try_device(required_features: wgpu::Features) -> 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::default())) - .ok()?; + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + force_fallback_adapter: std::env::var_os("BLOOM_TEST_FORCE_FALLBACK_ADAPTER").is_some(), + ..Default::default() + })) + .ok()?; + eprintln!("GPU oracle adapter: {:?}", adapter.get_info()); if !adapter.features().contains(required_features) { eprintln!("adapter lacks required visibility-oracle features"); return None; @@ -218,10 +222,17 @@ fn gpu_raster_ids_faces_and_reconstruction_match_the_cpu_oracle() { [2.0, 3.2, 2.0, 4.0], [1.8, -1.6, 1.0, 2.0], ]; + // wgpu 29's DX12 HLSL interface cannot link fragment Position together + // with PrimitiveIndex. Carry linear NDC instead, then recover the exact + // pixel center: raster interpolation has subpixel precision and must not + // add coordinate error to the production reconstruction being checked. let shader_source = format!( "enable primitive_index;\n\ {RECONSTRUCTION_WGSL}\n\ - struct VertexOut {{ @builtin(position) position: vec4, }};\n\ + struct VertexOut {{\n\ + @builtin(position) position: vec4,\n\ + @location(0) @interpolate(linear) ndc: vec2,\n\ + }};\n\ struct FragmentOut {{\n\ @location(0) visibility: vec2,\n\ @location(1) barycentrics: vec4,\n\ @@ -240,17 +251,22 @@ fn gpu_raster_ids_faces_and_reconstruction_match_the_cpu_oracle() { @vertex fn vs_main(@builtin(vertex_index) index: u32) -> VertexOut {{\n\ var out: VertexOut;\n\ out.position = clip_position(index);\n\ + out.ndc = out.position.xy / out.position.w;\n\ return out;\n\ }}\n\ @fragment fn fs_main(\n\ - in: VertexOut,\n\ + @location(0) @interpolate(linear) ndc: vec2,\n\ @builtin(primitive_index) primitive_id: u32,\n\ @builtin(front_facing) front_facing: bool,\n\ ) -> FragmentOut {{\n\ let first = primitive_id * 3u;\n\ + let pixel_center = floor(vec2(\n\ + (ndc.x + 1.0) * 0.5 * {WIDTH}.0,\n\ + (1.0 - ndc.y) * 0.5 * {HEIGHT}.0,\n\ + )) + vec2(0.5);\n\ let point_ndc = vec2(\n\ - in.position.x / {WIDTH}.0 * 2.0 - 1.0,\n\ - 1.0 - in.position.y / {HEIGHT}.0 * 2.0,\n\ + pixel_center.x / {WIDTH}.0 * 2.0 - 1.0,\n\ + 1.0 - pixel_center.y / {HEIGHT}.0 * 2.0,\n\ );\n\ let barycentrics = bloom_perspective_barycentrics(\n\ point_ndc,\n\ diff --git a/native/shared/src/virtual_geometry/draw_emission.rs b/native/shared/src/virtual_geometry/draw_emission.rs index 03eaa6e8..1d3ddef9 100644 --- a/native/shared/src/virtual_geometry/draw_emission.rs +++ b/native/shared/src/virtual_geometry/draw_emission.rs @@ -217,10 +217,8 @@ impl GpuVirtualDrawEmitter { compilation_options: Default::default(), cache: None, }); - let counted = device - .features() - .contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT) - && !force_binned_fallback; + let counted = + crate::renderer::gpu_driven::supports_indirect_count(device) && !force_binned_fallback; let binned_fallback = (!counted) .then(|| create_binned_fallback(device, selector, &state_buffer, draw_capacity)); Ok(Self { diff --git a/native/shared/src/virtual_geometry/tests.rs b/native/shared/src/virtual_geometry/tests.rs index 8fb6bcfb..2960c3ec 100644 --- a/native/shared/src/virtual_geometry/tests.rs +++ b/native/shared/src/virtual_geometry/tests.rs @@ -489,12 +489,16 @@ fn insufficient_budgets_fail_without_mutating_residency() { #[cfg(not(target_arch = "wasm32"))] 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 = - pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) - .ok()?; + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + force_fallback_adapter: std::env::var_os("BLOOM_TEST_FORCE_FALLBACK_ADAPTER").is_some(), + ..Default::default() + })) + .ok()?; + eprintln!("GPU oracle adapter: {:?}", adapter.get_info()); pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { label: Some("virtual_geometry_pool_test_device"), required_limits: wgpu::Limits::downlevel_defaults(), @@ -506,12 +510,16 @@ fn try_device() -> Option<(wgpu::Device, wgpu::Queue)> { #[cfg(not(target_arch = "wasm32"))] fn try_traversal_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::default())) - .ok()?; + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + force_fallback_adapter: std::env::var_os("BLOOM_TEST_FORCE_FALLBACK_ADAPTER").is_some(), + ..Default::default() + })) + .ok()?; + eprintln!("GPU oracle adapter: {:?}", adapter.get_info()); let mut limits = wgpu::Limits::downlevel_defaults(); limits.max_storage_buffers_per_shader_stage = 8; let optional_indirect = wgpu::Features::INDIRECT_FIRST_INSTANCE @@ -1201,10 +1209,7 @@ fn fs_main(input: VertexOutput) -> @location(0) vec4 { multiview_mask: None, }); pass.set_pipeline(&pipeline); - if device - .features() - .contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT) - { + if crate::renderer::gpu_driven::supports_indirect_count(&device) { pass.multi_draw_indirect_count( emitter.command_buffer(), 0, diff --git a/native/shared/src/virtual_geometry/virtual_visibility_tests.rs b/native/shared/src/virtual_geometry/virtual_visibility_tests.rs index 6426fb9d..7b851fef 100644 --- a/native/shared/src/virtual_geometry/virtual_visibility_tests.rs +++ b/native/shared/src/virtual_geometry/virtual_visibility_tests.rs @@ -30,6 +30,15 @@ struct VirtualShadingProbeRecord { #[test] fn raw_virtual_clusters_rasterize_namespaced_visibility_ids_on_the_real_gpu() { + assert_virtual_visibility_raster(true); +} + +#[test] +fn default_virtual_submission_preserves_visibility_and_reconstructed_attributes() { + assert_virtual_visibility_raster(false); +} + +fn assert_virtual_visibility_raster(force_binned: bool) { const WIDTH: u32 = 16; const HEIGHT: u32 = 16; const ROW_BYTES: u32 = 256; @@ -54,10 +63,21 @@ fn raw_virtual_clusters_rasterize_namespaced_visibility_ids_on_the_real_gpu() { .unwrap(); make_hierarchy_fully_resident(&mut pool, &queue, mesh); let selector = GpuVirtualHierarchySelector::new(&device, &pool, traversal_config()).unwrap(); - let emitter = GpuVirtualDrawEmitter::new_binned_for_test(&device, &selector).unwrap(); - assert_eq!( - emitter.submission_mode(), - VirtualGeometrySubmissionMode::BinnedFallback + let emitter = if force_binned { + GpuVirtualDrawEmitter::new_binned_for_test(&device, &selector) + } else { + GpuVirtualDrawEmitter::new(&device, &selector) + } + .unwrap(); + if force_binned || device.adapter_info().backend == wgpu::Backend::Dx12 { + assert_eq!( + emitter.submission_mode(), + VirtualGeometrySubmissionMode::BinnedFallback + ); + } + eprintln!( + "Virtual visibility submission: {:?}", + emitter.submission_mode() ); let raster = GpuVirtualVisibilityRaster::new(&device, &pool, &selector, &emitter).unwrap(); let identity = [ @@ -260,47 +280,50 @@ fn raw_virtual_clusters_rasterize_namespaced_visibility_ids_on_the_real_gpu() { ); let probe_raw = read_gpu_buffer(&device, &queue, &probe_output, probe_bytes); let probe: &[VirtualShadingProbeRecord] = bytemuck::cast_slice(&probe_raw); - let binned_state_raw = read_gpu_buffer( - &device, - &queue, - emitter.binned_state_buffer().unwrap(), - std::mem::size_of::() as u64, - ); - let binned_state = bytemuck::from_bytes::(&binned_state_raw); - assert_eq!(binned_state.counts[0], 4); - assert_eq!(binned_state.offsets[0], 0); - assert_eq!(binned_state.cursors[0], 4); - assert!(binned_state.counts[1..].iter().all(|count| *count == 0)); - let (binned_indices, binned_commands) = emitter.binned_buffers().unwrap(); - let command_raw = read_gpu_buffer( - &device, - &queue, - binned_commands, - u64::from(super::draw_emission::BINNED_FALLBACK_DRAW_COUNT) - * std::mem::size_of::() as u64, - ); - let commands: &[GpuVirtualDrawIndirect] = bytemuck::cast_slice(&command_raw); - assert_eq!( - commands[0], - GpuVirtualDrawIndirect { - vertex_count: 3, - instance_count: 4, - first_vertex: 0, - first_instance: 0, - } - ); - assert!(commands[1..] - .iter() - .all(|command| command.instance_count == 0)); - let index_raw = read_gpu_buffer( - &device, - &queue, - binned_indices, - u64::from(emitter.draw_capacity()) * std::mem::size_of::() as u64, - ); - let mut indices = bytemuck::cast_slice::(&index_raw)[..4].to_vec(); - indices.sort_unstable(); - assert_eq!(indices, [0, 1, 2, 3]); + if emitter.submission_mode() == VirtualGeometrySubmissionMode::BinnedFallback { + let binned_state_raw = read_gpu_buffer( + &device, + &queue, + emitter.binned_state_buffer().unwrap(), + std::mem::size_of::() as u64, + ); + let binned_state = + bytemuck::from_bytes::(&binned_state_raw); + assert_eq!(binned_state.counts[0], 4); + assert_eq!(binned_state.offsets[0], 0); + assert_eq!(binned_state.cursors[0], 4); + assert!(binned_state.counts[1..].iter().all(|count| *count == 0)); + let (binned_indices, binned_commands) = emitter.binned_buffers().unwrap(); + let command_raw = read_gpu_buffer( + &device, + &queue, + binned_commands, + u64::from(super::draw_emission::BINNED_FALLBACK_DRAW_COUNT) + * std::mem::size_of::() as u64, + ); + let commands: &[GpuVirtualDrawIndirect] = bytemuck::cast_slice(&command_raw); + assert_eq!( + commands[0], + GpuVirtualDrawIndirect { + vertex_count: 3, + instance_count: 4, + first_vertex: 0, + first_instance: 0, + } + ); + assert!(commands[1..] + .iter() + .all(|command| command.instance_count == 0)); + let index_raw = read_gpu_buffer( + &device, + &queue, + binned_indices, + u64::from(emitter.draw_capacity()) * std::mem::size_of::() as u64, + ); + let mut indices = bytemuck::cast_slice::(&index_raw)[..4].to_vec(); + indices.sort_unstable(); + assert_eq!(indices, [0, 1, 2, 3]); + } let mut covered = 0usize; let mut background = 0usize; for y in 0..HEIGHT { @@ -355,9 +378,7 @@ fn raw_virtual_clusters_rasterize_namespaced_visibility_ids_on_the_real_gpu() { assert!(background > 0); assert_eq!( raster.counted_submission_supported(), - device - .features() - .contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT) + crate::renderer::gpu_driven::supports_indirect_count(&device) ); } @@ -405,12 +426,16 @@ fn production_renderer_constructs_virtual_four_mrt_pipeline_on_the_real_gpu() { fn try_virtual_pbr_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 = - pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) - .ok()?; + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + force_fallback_adapter: std::env::var_os("BLOOM_TEST_FORCE_FALLBACK_ADAPTER").is_some(), + ..Default::default() + })) + .ok()?; + eprintln!("GPU oracle adapter: {:?}", adapter.get_info()); let required = wgpu::Features::PRIMITIVE_INDEX | wgpu::Features::INDIRECT_FIRST_INSTANCE | crate::renderer::material_indirection::TIER_A_FEATURES; diff --git a/native/shared/src/virtual_geometry/visibility.rs b/native/shared/src/virtual_geometry/visibility.rs index 6a481b87..2980185e 100644 --- a/native/shared/src/virtual_geometry/visibility.rs +++ b/native/shared/src/virtual_geometry/visibility.rs @@ -178,7 +178,7 @@ impl GpuVirtualVisibilityRaster { Ok(Self { selector_id: selector.id(), draw_capacity: emitter.draw_capacity(), - count_supported: features.contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT), + count_supported: crate::renderer::gpu_driven::supports_indirect_count(device), frame_buffer, bind_group, pipeline,