Reuse a shadow map when its content has not changed - #144
Merged
Merged
Conversation
The shadow pass has known what each view WOULD draw since the plan landed; it just drew it anyway. Every view came out Recorded, which is what let the preparation restructure be verified as decision-identical, and left the saving itself for this branch. ShadowResidencyStore records what each physical view's depth image holds. Preparation compares this frame's prepared content against it and marks each view Reused or Recorded; recordPass skips a reused view entirely — no barrier, no clear, no draw — and a family that records nothing opens no timing span, so its GPU time is absent rather than zero. The store belongs to Shadows, beside the images it describes. That is the whole invalidation story: recreating the images reconstructs the record, so there is no invalidate() for anyone to forget, and no window in which the two can disagree. If in-place recreation ever arrives, the targets and the store move into one private aggregate together. Two rules live inside the store rather than at its call site. Only a Recorded view commits: a Reused view never touched its image, and its prepared work differs in the diagnostic fields, so adopting it would leave the record describing a frame that wrote nothing. An Invalid slot keeps what it had: nothing recorded means nothing overwrote the image. Adoption sits between submission and presentation, and submitAndPresent is split in two to make that expressible. Once submit2 returns the GPU owns the work; presentation is a separate act that can fail — raii presentKHR throws on an out-of-date swapchain — so a resize could have thrown past the commit while the frame's depth was already being rasterised, leaving the store describing the previous frame. A later frame preparing that content would then reuse an image holding something else: the exact failure this whole item exists to prevent, arriving through the error path. Adoption is also noexcept, moving the recorded views out of the plan rather than copying them, because after a submit there is no useful answer to a failed allocation. shadowResidencyReuseEnabled (--no-shadow-reuse, or the overlay's "Reuse unchanged shadow views") forces every engaged view to record. It is scheduling, not pixels, so it is an argument to the law and never part of the content descriptor — a frame recorded with reuse off commits as usual and is reusable the moment it is back on. Each SH-01 row carries the disposition it ended up with, because zero raster passes alone cannot separate "reused" from "never engaged". Measured on the purpose-built ShadowResidencyTest scene (one static point light, six cube faces, camera parked, macOS/arm64 + MoltenVK): forced record holds the point family at a median 0.264 ms per frame while reuse issues no timing span at all. The cold recorded frame, a reused frame 240 frames later, and a same-state forced-record frame are byte-identical captures, VUID-free. What this does not buy is CPU preparation: a reused view is filtered, resolved and observed in full, because the comparison needs the work that produces its operand. The durable win is therefore punctual rather than CSM — a cascade's matrix moves with the camera.
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.
The shadow pass has known what each view WOULD draw since the plan landed; it just drew it anyway. Every view came out Recorded, which is what let the preparation restructure be verified as decision-identical, and left the saving itself for this branch.
ShadowResidencyStore records what each physical view's depth image holds. Preparation compares this frame's prepared content against it and marks each view Reused or Recorded; recordPass skips a reused view entirely — no barrier, no clear, no draw — and a family that records nothing opens no timing span, so its GPU time is absent rather than zero.
The store belongs to Shadows, beside the images it describes. That is the whole invalidation story: recreating the images reconstructs the record, so there is no invalidate() for anyone to forget, and no window in which the two can disagree. If in-place recreation ever arrives, the targets and the store move into one private aggregate together.
Two rules live inside the store rather than at its call site. Only a Recorded view commits: a Reused view never touched its image, and its prepared work differs in the diagnostic fields, so adopting it would leave the record describing a frame that wrote nothing. An Invalid slot keeps what it had: nothing recorded means nothing overwrote the image.
Adoption sits between submission and presentation, and submitAndPresent is split in two to make that expressible. Once submit2 returns the GPU owns the work; presentation is a separate act that can fail — raii presentKHR throws on an out-of-date swapchain — so a resize could have thrown past the commit while the frame's depth was already being rasterised, leaving the store describing the previous frame. A later frame preparing that content would then reuse an image holding something else: the exact failure this whole item exists to prevent, arriving through the error path. Adoption is also noexcept, moving the recorded views out of the plan rather than copying them, because after a submit there is no useful answer to a failed allocation.
shadowResidencyReuseEnabled (--no-shadow-reuse, or the overlay's "Reuse unchanged shadow views") forces every engaged view to record. It is scheduling, not pixels, so it is an argument to the law and never part of the content descriptor — a frame recorded with reuse off commits as usual and is reusable the moment it is back on. Each SH-01 row carries the disposition it ended up with, because zero raster passes alone cannot separate "reused" from "never engaged".
Measured on the purpose-built ShadowResidencyTest scene (one static point light, six cube faces, camera parked, macOS/arm64 + MoltenVK): forced record holds the point family at a median 0.264 ms per frame while reuse issues no timing span at all. The cold recorded frame, a reused frame 240 frames later, and a same-state forced-record frame are byte-identical captures, VUID-free.
What this does not buy is CPU preparation: a reused view is filtered, resolved and observed in full, because the comparison needs the work that produces its operand. The durable win is therefore punctual rather than CSM — a cascade's matrix moves with the camera.