Shadow maps: near-parity with ray-traced shadows at max projector budget (static caching, caster culling, multiview, filtering) #567

Open
opened 2026-10-08 20:11:34 +00:00 by sky_line · 4 comments
Owner

Goal: shadow-map shadows (the path for GPUs without ray tracing, and the fallback) should cost close to the ray-traced path, even at the maximum projector budget.

Baseline (2026-10-08, owner's machine RTX 5090, static scene with 16 projectors, after #564's per-view rigged cull + caster LOD):

shadow maps ray traced
GPU frame 14.2 ms 9.7 ms
pass1 9.6 ms 5.1 ms
sun cascades c_shadows 2.2 ms —
projectors c_projshadow 2.7–3.5 ms ~0 (in the RT mask)
  • With the projector budget raised to 32: ~60 FPS on maps vs ~100 on RT.
  • The remaining cost is mostly static scenery re-rendered into maps, not avatars (the rigged cull costs 0.035 ms).

Target: shadow-map overhead under about 1–1.5 ms over RT at 16 and 32 projectors, without visible quality loss near the camera. Same rules as before: adaptive and targeted, no global cuts, and the user's resolution setting is never changed.

Directions to evaluate (measure each):

  1. Static caching everywhere.
    • Projector tiles and sun cascades: render static casters once, then re-render only on light or caster change (object update dirty tracking, per tile/cascade AABB).
    • Dynamic casters as an overlay (#564 did this for cached cascades and projector re-composites; extend it so static re-renders are rare, not periodic).
    • Cascade scrolling: shift cached cascades in texel steps as the camera moves instead of redrawing.
  2. Caster culling quality.
    • GPU per-view culling for static casters too: compute cull into per-view MDI lists, as #564 did for rigged.
    • Hi-Z or occlusion for shadow casters: cull casters whose shadow lands on receivers that aren't visible ("shadow receiver culling" — only render casters whose shadow volume intersects the visible receiver bounds).
    • Small-caster culling by texel size, per view.
  3. Cheaper rasterisation.
    • Multiview / layered rendering: one draw → all cascades or projector tiles (VK_KHR_multiview or layered instancing), cutting draw submission and vertex work.
    • Depth-only pipelines with minimal vertex attributes (position-only streams for static casters; meshlet/cluster culling if available).
    • Static-mesh shadow LOD (simplified casters for far cascades / small projectors), like #564's rigged caster LOD.
  4. Atlas and resolution policy.
    • Projector atlas allocation by screen influence (#564's cov ladder), with time-sliced updates for projectors far from the camera.
    • Lower update rate for low-influence projectors.
    • Temporal reprojection between updates.
  5. Filtering cost. Check the PCF / soft-shadow sampling cost in pass1 (some of the 4.5 ms delta may be filtering, not map rendering): fewer taps, gather-based PCF, early-out on fully lit or fully shadowed regions.

Done when: the A/B above (and at max projector budget) shows maps within ~1–1.5 ms of RT, with documented quality comparisons.

**Goal:** shadow-map shadows (the path for GPUs without ray tracing, and the fallback) should cost close to the ray-traced path, **even at the maximum projector budget**. **Baseline (2026-10-08, owner's machine RTX 5090, static scene with 16 projectors, after #564's per-view rigged cull + caster LOD):** | | shadow maps | ray traced | |---|---|---| | GPU frame | 14.2 ms | 9.7 ms | | pass1 | 9.6 ms | 5.1 ms | | sun cascades `c_shadows` | 2.2 ms | — | | projectors `c_projshadow` | 2.7–3.5 ms | ~0 (in the RT mask) | - With the projector budget raised to 32: ~60 FPS on maps vs ~100 on RT. - The remaining cost is mostly **static scenery** re-rendered into maps, not avatars (the rigged cull costs 0.035 ms). **Target:** shadow-map overhead under about 1–1.5 ms over RT at 16 and 32 projectors, without visible quality loss near the camera. Same rules as before: adaptive and targeted, no global cuts, and the user's resolution setting is never changed. **Directions to evaluate (measure each):** 1. **Static caching everywhere.** - Projector tiles and sun cascades: render static casters once, then re-render only on light or caster change (object update dirty tracking, per tile/cascade AABB). - Dynamic casters as an overlay (#564 did this for cached cascades and projector re-composites; extend it so static re-renders are rare, not periodic). - Cascade scrolling: shift cached cascades in texel steps as the camera moves instead of redrawing. 2. **Caster culling quality.** - GPU per-view culling for **static** casters too: compute cull into per-view MDI lists, as #564 did for rigged. - Hi-Z or occlusion for shadow casters: cull casters whose shadow lands on receivers that aren't visible ("shadow receiver culling" — only render casters whose shadow volume intersects the visible receiver bounds). - Small-caster culling by texel size, per view. 3. **Cheaper rasterisation.** - Multiview / layered rendering: one draw → all cascades or projector tiles (VK_KHR_multiview or layered instancing), cutting draw submission and vertex work. - Depth-only pipelines with minimal vertex attributes (position-only streams for static casters; meshlet/cluster culling if available). - Static-mesh shadow LOD (simplified casters for far cascades / small projectors), like #564's rigged caster LOD. 4. **Atlas and resolution policy.** - Projector atlas allocation by screen influence (#564's cov ladder), with time-sliced updates for projectors far from the camera. - Lower update rate for low-influence projectors. - Temporal reprojection between updates. 5. **Filtering cost.** Check the PCF / soft-shadow sampling cost in pass1 (some of the 4.5 ms delta may be filtering, not map rendering): fewer taps, gather-based PCF, early-out on fully lit or fully shadowed regions. **Done when:** the A/B above (and at max projector budget) shows maps within ~1–1.5 ms of RT, with documented quality comparisons.
sky_line added this to the Slipstream project 2026-10-08 20:11:34 +00:00
Author
Owner

Started 2026-10-10. Fresh owner measurement: RTX 5090, 4K with FSR, busy sim, 32 projectors shadowed.

FPS GPU frame sun cascades projectors
all ray traced ~90 10.8 ms — — (in c_rtmask 0.82 ms)
sun/moon on maps, projectors RT ~66 14.8 ms 2.55 ms —
sun/moon + projectors on maps ~44 22.5 ms 2.0–2.1 ms 9.4–9.8 ms

Maps cost ~+4 ms for sun/moon and ~+12 ms with 32 projectors, over RT.

Order of work:

  1. Projector tiles: instrument refresh causes, make static re-renders event-driven, GPU per-tile static caster cull, batch dirty tiles.
  2. Sun cascades: texel-step scrolling, per-cascade static cull, dirty-only static re-render.
  3. Filtering cost: deferred light 1.13 → ~1.4 ms with maps.
  4. Mode switch: check whether both paths run during a shadow-mode change (RT mask/AO doubled for ~20 s during the switch).
Started 2026-10-10. Fresh owner measurement: RTX 5090, 4K with FSR, busy sim, 32 projectors shadowed. | | FPS | GPU frame | sun cascades | projectors | |---|---|---|---|---| | all ray traced | ~90 | 10.8 ms | — | — (in c_rtmask 0.82 ms) | | sun/moon on maps, projectors RT | ~66 | 14.8 ms | 2.55 ms | — | | sun/moon + projectors on maps | ~44 | 22.5 ms | 2.0–2.1 ms | **9.4–9.8 ms** | Maps cost ~+4 ms for sun/moon and ~+12 ms with 32 projectors, over RT. Order of work: 1. **Projector tiles:** instrument refresh causes, make static re-renders event-driven, GPU per-tile static caster cull, batch dirty tiles. 2. **Sun cascades:** texel-step scrolling, per-cascade static cull, dirty-only static re-render. 3. **Filtering cost:** deferred light 1.13 → ~1.4 ms with maps. 4. **Mode switch:** check whether both paths run during a shadow-mode change (RT mask/AO doubled for ~20 s during the switch).
Author
Owner

Correction 2026-10-10: the baseline table above mis-attributed the sun cost

Re-reading the log (11:26-11:29), the "all ray traced ~90 fps / 10.8 ms" row had no sun shadow at all: the sun switched on at 11:27:32.

window state GPU ms
≤11:27:30 no sun shadow, projectors RT 10.8
11:27:40-55 sun RT + projectors RT 13.8-14.05
11:28:05-25 sun on maps + projectors RT 14.7-14.8 (+0.85 ms over RT sun)
11:28:35+ everything on maps (32 projectors) 22.5-24.0 (+9.3 ms = projectors)
  • Sun shadow maps are already within the target.
  • Projector tiles are the whole problem. The tile cache missed 100%: projTilesRendered=32 projTilesCached=0 projDirtLight=12 projDirtChanged=20 every frame.
  • The "mode-switch double-run" was not real. That window was an RT-sun period.
  • c_shadows has a ~1.9 ms floor even with no cascades (likely a cross-queue wait), so it isn't a reliable measure of cascade cost.

Work merged:

  • counters;
  • pose-refresh and sub-texel light-move false-invalidation fixes;
  • a static/dynamic caster split for projector tiles (movers drawn as an overlay, baked in after 2 s);
  • invalidation from the instance store (also fixes new rezzes appearing late in cached tiles and deleted objects' shadows staying);
  • a moving-light budget;
  • a sub-texel caster cull fix;
  • event-driven cached sun cascades (modes 1/2).

Expected projector steady state ~3-4 ms; the next step depends on c_proj_cull vs g_proj_static in the new counters.

## Correction 2026-10-10: the baseline table above mis-attributed the sun cost Re-reading the log (11:26-11:29), the "all ray traced ~90 fps / 10.8 ms" row had **no sun shadow at all**: the sun switched on at 11:27:32. | window | state | GPU ms | |---|---|---| | ≤11:27:30 | no sun shadow, projectors RT | 10.8 | | 11:27:40-55 | sun RT + projectors RT | 13.8-14.05 | | 11:28:05-25 | sun on maps + projectors RT | 14.7-14.8 (**+0.85 ms** over RT sun) | | 11:28:35+ | everything on maps (32 projectors) | 22.5-24.0 (**+9.3 ms = projectors**) | - **Sun shadow maps are already within the target.** - **Projector tiles are the whole problem.** The tile cache missed 100%: `projTilesRendered=32 projTilesCached=0 projDirtLight=12 projDirtChanged=20` every frame. - **The "mode-switch double-run" was not real.** That window was an RT-sun period. - **`c_shadows` has a ~1.9 ms floor** even with no cascades (likely a cross-queue wait), so it isn't a reliable measure of cascade cost. **Work merged:** - counters; - pose-refresh and sub-texel light-move false-invalidation fixes; - a static/dynamic caster split for projector tiles (movers drawn as an overlay, baked in after 2 s); - invalidation from the instance store (also fixes new rezzes appearing late in cached tiles and deleted objects' shadows staying); - a moving-light budget; - a sub-texel caster cull fix; - event-driven cached sun cascades (modes 1/2). Expected projector steady state ~3-4 ms; the next step depends on `c_proj_cull` vs `g_proj_static` in the new counters.
Author
Owner

Shipped in 1.3.0 Beta 2: projector static/dynamic caster split, event-driven tile and cascade caches, invalidation from the instance store, sub-texel caster cull. Sun cascades are already within target. Still to do: re-measure projector tiles at 16 and 32 projectors (expected ~3-4 ms), then GPU per-tile static caster cull and the filtering cost if the numbers still miss the 1-1.5 ms target.

Shipped in 1.3.0 Beta 2: projector static/dynamic caster split, event-driven tile and cascade caches, invalidation from the instance store, sub-texel caster cull. Sun cascades are already within target. Still to do: re-measure projector tiles at 16 and 32 projectors (expected ~3-4 ms), then GPU per-tile static caster cull and the filtering cost if the numbers still miss the 1-1.5 ms target.
Author
Owner

Follow-up item (2026-10-14 status audit): the event-driven cascade cache (RenderVKShadowEventCache + static/dynamic split + instance-store invalidation) only runs with RenderVKShadowUpdate 1/2; the default is 0 (Realtime: all 4 cascades every frame, static layers freed). Evaluate making the event-driven cache the default (or auto-selected) for shadow-map users, after an A/B of quality (texel-step swimming, light-move threshold) and GPU ms on a static and a busy scene.

Follow-up item (2026-10-14 status audit): the event-driven cascade cache (RenderVKShadowEventCache + static/dynamic split + instance-store invalidation) only runs with `RenderVKShadowUpdate` 1/2; the default is 0 (Realtime: all 4 cascades every frame, static layers freed). Evaluate making the event-driven cache the default (or auto-selected) for shadow-map users, after an A/B of quality (texel-step swimming, light-move threshold) and GPU ms on a static and a busy scene.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sky_line/Slipstream#567
No description provided.