fix(render): the motion-vector pass waits for a reader - #868
Merged
Conversation
Measured on the OneXFly with the temporal resolve off: 1.994 ms of a 20.5 ms GPU frame, writing a full-resolution buffer nobody sampled. taa.wgsl is the only shader that binds it. Unlike the resolve itself, which is a quality trade, this is an if. The predicate is needs_motion() rather than the field at the call site because FSR (#536) becomes the second consumer of the same condition. The test is its own binary: asserting a GPU scope is ABSENT is unsound in a shared one, because puffin's scope_delta is a delta and the first test to register a name drains it. It reads the texture instead - a fresh wgpu texture is zeroed - and the resolve-on half is what stops the resolve-off half from passing vacuously. Verified failing: 147684 of 160000 bytes non-zero with the gate reverted.
lobinuxsoft
added a commit
that referenced
this pull request
Aug 17, 2026
The user's directive decides the architecture rather than a preference: the engine ships for Windows as well as Linux and for NVIDIA and Intel as well as AMD, and it gets its own upscaler. A vendor SDK cannot be the path a build depends on, so the always-there path is ours (#481) and the vendor backends (#536) become optional on top of it — inverting what #536 said, which was that a vendor's ships and ours is the untested fallback. It also happens to be the largest measured lever here. Per-pixel shading dominates the frame (10.969 ms of 24.8), all shadows total 1.153, and rendering at 67 % linear is 44 % of the pixels: roughly -5 ms, and it compounds with every per-pixel cost added later. #866 is demoted to Phase 2 because its payoff is scale and memory, not cost per pixel. Phase 0 item 1 is closed with its numbers, including the finding it was not looking for: motion vectors ran for nobody at 1.994 ms (#868). Auto exposure moves out of Phase 4 and into Phase 1 as a prerequisite: every temporal resolve takes an exposure input and rejects history in a perceptual space, and this engine's radiance is in the hundreds.
This was referenced Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
~2 ms per frame, measured, with no quality cost. The first item of the graphics Phase 0 to produce code.
Measured on the OneXFly,
many_lights.scene, temporal resolve off:taa.wgslis the only shader that binds the buffer, so with the resolve off the pass writes a full-resolution target nobody samples. The comment above it explained why it runs in every debug mode — true, and silent about whether anything reads the result.needs_motion(), notself.taa_enabledat the call site: FSR (feat(render): FSR over the FidelityFX C ABI (FFI), behind the upscaler trait #536) becomes the second consumer of the same condition, so it extends one gate instead of adding a second.On the test, because the obvious one is unsound. "The
motion vectorsscope is absent" cannot be asserted in a shared test binary: puffin'sscope_deltais a delta, the first test to register a name drains it, andgpu_scopes.rsalready documents that asserting absence there is asserting which test ran first. So this reads the texture — a fresh wgpu texture is zeroed — in its own binary, and the resolve-on half is what stops the resolve-off half from passing vacuously (a gate that skipped the pass unconditionally would pass one and fail the other).🔴 Verified failing with the gate reverted: 147684 of 160000 bytes are non-zero, so the pass is still running for nobody.
temporal_motion,half_rate_shadingandcompute_shading_parityall still pass. Refs #481.