From c643b85a6a73d7f5d3e7db86659f299c0c201243 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Fri, 11 Sep 2026 03:17:15 +0200 Subject: [PATCH] Reject stale temporal color and confidence after camera cuts --- docs/evidence/windows-camera-history-v1.md | 61 +++++++++++++++++++ docs/evidence/windows-dx12-indirect-v1.md | 14 ++++- docs/windows-engine-plan.md | 10 ++- .../src/renderer/shaders/temporal_aa.rs | 7 ++- native/shared/tests/golden_render/device.rs | 1 + .../tests/golden_render/temporal_history.rs | 19 +++--- 6 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 docs/evidence/windows-camera-history-v1.md diff --git a/docs/evidence/windows-camera-history-v1.md b/docs/evidence/windows-camera-history-v1.md new file mode 100644 index 00000000..1ef47705 --- /dev/null +++ b/docs/evidence/windows-camera-history-v1.md @@ -0,0 +1,61 @@ +# Camera cuts must not consume the previous history epoch + +The expanded DX12 shared run exposed a failed exact-equality camera-cut check. +The failure also reproduces on unchanged parent source `59244b9`. With DXC, +one blue byte differs at pixel (179, 152): fresh 169 versus reset 168. FXC and +Vulkan pass the original eight-frame setup. + +Extending the same test to a fully settled 40-frame history exposes visible +residue across backends. The unchanged shader differs at 16,888 pixels under +FXC and 16,893 under Vulkan, with maximum channel error 232. The original zero +tolerance remains required; the longer setup exercises a state the shorter +confidence ramp did not reach. + +## Cause and correction + +The CPU correctly marks reset and bootstrap frames as current-only. The shader +still sampled old color, indirect-light weight, depth, confidence, and detail +provenance whenever reprojected UVs were in bounds. Its existing history-usable +predicate was applied later. Stale confidence could activate settled-history +locks, and a final blend weight of one did not make old color arithmetically +irrelevant under every shader compiler. + +Move the unchanged history-usable predicate to the read itself. Unusable history +now leaves the existing current-frame defaults and zero confidence in place. +Valid accumulation retains its previous predicate and policy. No texture clear, +new allocation, image baseline, or threshold change is introduced. + +The permanent regression runs both eight and 40 old-history frames through the +same camera-cut equality check, then retains its projection/rotation/pan checks. +The golden device helper now honors backend compiler environment options. +Temporary capture instrumentation is retained only in the evidence patches. + +## Verified controls + +With the read guard, both history lengths produce byte-identical fresh/reset +RGBA on Radeon Vulkan, DX12/FXC, and DX12/DXC. All six captured comparisons have +zero differing pixels and zero channel error. Each backend's initial fresh image +also remains byte-identical to its corresponding original-shader fresh image. +The unchanged-shader controls retain their failures and images. + +`Dx12Compiler::default()` in wgpu 29 is Auto, which tries DXC before FXC. The +original golden helper did not apply WGPU_DX12_COMPILER explicitly; with the +SDK DLL on PATH its parent-source control may select DXC automatically. The +isolated compiler matrix adds explicit backend options and distinguishes FXC +from DXC. The existing eight-frame FXC result is not a claim about Auto. + +The complete local shared CI component passes under DX12/DXC and Vulkan: +489 library tests, device negotiation, all 93 golden render tests, and the +remaining integration checks pass. The existing one ignored library test, four +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 +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/`. diff --git a/docs/evidence/windows-dx12-indirect-v1.md b/docs/evidence/windows-dx12-indirect-v1.md index af8d6b88..69b325da 100644 --- a/docs/evidence/windows-dx12-indirect-v1.md +++ b/docs/evidence/windows-dx12-indirect-v1.md @@ -52,7 +52,14 @@ 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 +At committed source `ab8019b` in draft PR #162, the complete Vulkan shared CI +component passes: 489 library tests, device negotiation, 93 goldens, and the +remaining integration checks. The expanded DX12 run passes 489 library tests +and device negotiation, then reports 92 goldens passed, one failed, and four +ignored. The failed camera-cut equality check also fails on the unchanged +parent; its [history-read correction](windows-camera-history-v1.md) is separate. +Hosted Tests run 34547668613 still crashes in the Windows library process with +STATUS_ACCESS_VIOLATION and Bash exit 139 before completing the suite. 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. @@ -61,3 +68,8 @@ 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. + +The [published #162 report and ZIP](https://github.com/Bloom-Engine/engine/releases/tag/quality-evidence-dx12-indirect-20260911) +retain 201 verified payloads, successful local/hosted native build receipts, and +the Windows access violation. The archive is 2,006,103 bytes with SHA-256 +`ff50b4653062f2070508b825fc47f032e5907d5a054285e193ff33d2cb74975e`. diff --git a/docs/windows-engine-plan.md b/docs/windows-engine-plan.md index 7e01c969..858c48a2 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 | #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 | +| #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. Draft #162 corrects the two focused DX12 GPU failures and passes the complete Vulkan shared component. Hosted Windows still crashes with an access violation; the expanded local DX12 goldens expose a separate camera-history reset defect. 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 | @@ -58,8 +58,12 @@ audit are saved in `tools/quality/out/windows-engine-plan/plan-requirements.json [CI and example correction](evidence/windows-example-ci-v1.md) explicitly selects Bash, restores the MSVC linker ahead of Git's tools, and requires execution-summary artifacts. The hosted native build passes at `636b69a`. - The shared suite exposes two DX12 GPU failures and a process crash; both - focused GPU failures also reproduce locally under DX12 and remain open. + Draft #162 corrects the two focused DX12 failures and passes the complete + Vulkan shared component. Hosted Windows still hits an access violation. + 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. 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/renderer/shaders/temporal_aa.rs b/native/shared/src/renderer/shaders/temporal_aa.rs index 8e284454..835b5699 100644 --- a/native/shared/src/renderer/shaders/temporal_aa.rs +++ b/native/shared/src/renderer/shaders/temporal_aa.rs @@ -331,7 +331,11 @@ fn fs_main(in: VsOut) -> TaaOut { let history_in_bounds = prev_uv.x >= 0.0 && prev_uv.x <= 1.0 && prev_uv.y >= 0.0 && prev_uv.y <= 1.0; - if (history_in_bounds) { + // Reset/bootstrapping frames must not sample old color or provenance. + // Even a final blend weight of one cannot make stale inputs harmless: + // floating-point interpolation and confidence locks can retain their effect. + let history_usable = history_in_bounds && current_weight < 0.999; + if (history_usable) { let h_sample = sample_history_reprojected(prev_uv, camera_moving); history = h_sample.rgb; history_w = h_sample.a; @@ -567,7 +571,6 @@ fn fs_main(in: VsOut) -> TaaOut { // motion the established motion policy already bounds stale history, so // disable bootstrap before 0.04 output pixels/frame rather than turning a // safe resolve into visibly noisier current samples during a slow pan. - let history_usable = history_in_bounds && current_weight < 0.999; let history_sample_count = history_confidence * 16.0; let bootstrap_running_alpha = select( 1.0, diff --git a/native/shared/tests/golden_render/device.rs b/native/shared/tests/golden_render/device.rs index 9391c05d..03df5143 100644 --- a/native/shared/tests/golden_render/device.rs +++ b/native/shared/tests/golden_render/device.rs @@ -38,6 +38,7 @@ fn create_context() -> Result, String> { let backends = requested_backends(); let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends, + backend_options: wgpu::BackendOptions::from_env_or_default(), ..wgpu::InstanceDescriptor::new_without_display_handle() }); let mut adapters = pollster::block_on(instance.enumerate_adapters(backends)); diff --git a/native/shared/tests/golden_render/temporal_history.rs b/native/shared/tests/golden_render/temporal_history.rs index fc4d1568..4c7e9c05 100644 --- a/native/shared/tests/golden_render/temporal_history.rs +++ b/native/shared/tests/golden_render/temporal_history.rs @@ -628,14 +628,17 @@ fn camera_motion_sequence_bounds_ghosting_flicker_and_cut_residue() { let new_angle = 0.65; eng.renderer.reset_temporal_history(); let fresh_new_pose = capture(&mut eng, new_angle, 58.0); - advance(&mut eng, 8, old_angle, 42.0); - eng.renderer.reset_temporal_history(); - let cut_new_pose = capture(&mut eng, new_angle, 58.0); - let cut_metrics = calculate_diff_metrics(&fresh_new_pose, &cut_new_pose, W, H); - assert_eq!( - cut_metrics.max_diff, 0, - "an explicit camera cut retained pixels from the prior camera" - ); + // Cover both the initial confidence ramp and a fully settled old epoch. + for warmup_frames in [8, 40] { + advance(&mut eng, warmup_frames, old_angle, 42.0); + eng.renderer.reset_temporal_history(); + let cut_new_pose = capture(&mut eng, new_angle, 58.0); + let cut_metrics = calculate_diff_metrics(&fresh_new_pose, &cut_new_pose, W, H); + assert_eq!( + cut_metrics.max_diff, 0, + "an explicit camera cut retained pixels after {warmup_frames} history frames" + ); + } // A projection-only jump is currently remapped through the common motion // vectors rather than automatically invalidated. Compare it with a fresh