Deferred lighting: cheap tile classification (subgroup reductions, fold into the shadow pass) #831

Closed
opened 2026-10-17 22:16:24 +00:00 by sky_line · 2 comments
Owner

Follow-up to #827. The feature split's tile-classify pass (g_defer_classify) measures 0.8-1.2 ms on a GTX 1080 at 4K. The work it decides is cheap; the way it is computed is not:

  • A full-screen pass of its own. Every pixel re-reads the G-buffer and depth and reconstructs P (deferPosition), which the shadow / reflection passes have just done.
  • Contended shared atomics. All 256 threads of a 16x16 group do atomicMin(s_zmin), atomicMax(s_zmax) and atomicOr(s_zbins) on single shared addresses. These serialize on Pascal: three 256-long chains per tile, 32,400 tiles.
  • Mark attribution. Classify is recorded first and SHADOW follows without a barrier, so the mark also absorbs the drain of the preceding barrier. Compare the group (classify + shadow + reflect + base) against the old combine.

Plan

  1. Subgroup reductions before the shared atomics: subgroupMin / subgroupMax / subgroupOr, then one atomic per subgroup (32x fewer). Respect the full-subgroups requirement (cross-subgroup scans need REQUIRE_FULL_SUBGROUPS even on NVIDIA; see the Beta 5 crash fix).
  2. Fuse the classification into the SHADOW pass, which already touches every surface pixel. This removes the extra full-screen read. Keep the tile list / light grid outputs identical, so LOCAL is unchanged and lossless. If fusing is not possible (group size / occupancy of the shadow kernel), at least drop the second G-buffer read.
  3. Run the lit-guard cluster scan (lgClusterScan, diagnostic) only on sampled frames.
  4. The image must be bit-identical (the tile list is a superset test; same lights reach each pixel).

Measure

Serial on the GTX 1080 at 4K (Firestorm-equivalent and high): classify + shadow + reflect + base vs current; registers of the changed kernels; also the RTX 5090. Expected 0.5-0.9 ms on Pascal.

Follow-up to #827. The feature split's tile-classify pass (`g_defer_classify`) measures 0.8-1.2 ms on a GTX 1080 at 4K. The work it decides is cheap; the way it is computed is not: - **A full-screen pass of its own.** Every pixel re-reads the G-buffer and depth and reconstructs P (`deferPosition`), which the shadow / reflection passes have just done. - **Contended shared atomics.** All 256 threads of a 16x16 group do `atomicMin(s_zmin)`, `atomicMax(s_zmax)` and `atomicOr(s_zbins)` on single shared addresses. These serialize on Pascal: three 256-long chains per tile, 32,400 tiles. - **Mark attribution.** Classify is recorded first and SHADOW follows without a barrier, so the mark also absorbs the drain of the preceding barrier. Compare the group (classify + shadow + reflect + base) against the old combine. ### Plan 1. **Subgroup reductions before the shared atomics:** `subgroupMin` / `subgroupMax` / `subgroupOr`, then one atomic per subgroup (32x fewer). Respect the full-subgroups requirement (cross-subgroup scans need REQUIRE_FULL_SUBGROUPS even on NVIDIA; see the Beta 5 crash fix). 2. **Fuse the classification into the SHADOW pass**, which already touches every surface pixel. This removes the extra full-screen read. Keep the tile list / light grid outputs identical, so LOCAL is unchanged and lossless. If fusing is not possible (group size / occupancy of the shadow kernel), at least drop the second G-buffer read. 3. Run the lit-guard cluster scan (`lgClusterScan`, diagnostic) only on sampled frames. 4. The image must be bit-identical (the tile list is a superset test; same lights reach each pixel). ### Measure Serial on the GTX 1080 at 4K (Firestorm-equivalent and high): classify + shadow + reflect + base vs current; registers of the changed kernels; also the RTX 5090. Expected 0.5-0.9 ms on Pascal.
sky_line added this to the Slipstream project 2026-10-17 22:16:24 +00:00
Author
Owner

Merged (370bf9e20a, 067f690a9d). Classification folded into the SHADOW pass (pass 10, RenderVKDeferClassifyFused, default on), subgroup reductions before the shared atomics, tile builds 256x1 with REQUIRE_FULL_SUBGROUPS, lit-guard cluster scan every 64th frame, new g_defer_prep mark.

GTX 1080, 4K, serial, interleaved on/off:

  • High: fused shadow+classify 1.65 / 1.66 ms vs separate classify 0.79 / 0.83 + shadow 0.97 / 0.99 ms. Frame 29.10 / 29.05 vs 29.58 / 29.27 ms.
  • Firestorm-equivalent: fused 1.13 / 1.35 ms vs 0.77 / 0.85 + 0.56 / 0.57 ms.
  • g_defer_prep shows 0.34 ms of barrier drain that the old classify mark absorbed.
  • Registers: DeferShadowClassify 64, the same as DeferShadow.

Finding: the atomic-contention theory was wrong. With subgroup reductions and the drain split out, classify still costs ~0.6-0.8 ms on Pascal; the cost is the per-tile light testing (32,400 tiles at 4K x every light). The RTX 5090 does it in 0.058 ms. Next lever if wanted: 32x32 classification tiles, or narrowing per tile against the light-cluster lists instead of all lights.

Still to do: tile-list parity across RenderVKLightCulling 1 / 2 and the uber / plain-split grid path; live toggle; devices without computeFullSubgroups.

Merged (370bf9e20a, 067f690a9d). Classification folded into the SHADOW pass (pass 10, `RenderVKDeferClassifyFused`, default on), subgroup reductions before the shared atomics, tile builds 256x1 with REQUIRE_FULL_SUBGROUPS, lit-guard cluster scan every 64th frame, new `g_defer_prep` mark. **GTX 1080, 4K, serial, interleaved on/off:** - High: fused shadow+classify 1.65 / 1.66 ms vs separate classify 0.79 / 0.83 + shadow 0.97 / 0.99 ms. Frame 29.10 / 29.05 vs 29.58 / 29.27 ms. - Firestorm-equivalent: fused 1.13 / 1.35 ms vs 0.77 / 0.85 + 0.56 / 0.57 ms. - `g_defer_prep` shows 0.34 ms of barrier drain that the old classify mark absorbed. - Registers: DeferShadowClassify 64, the same as DeferShadow. **Finding:** the atomic-contention theory was wrong. With subgroup reductions and the drain split out, classify still costs ~0.6-0.8 ms on Pascal; the cost is the per-tile light testing (32,400 tiles at 4K x every light). The RTX 5090 does it in 0.058 ms. Next lever if wanted: 32x32 classification tiles, or narrowing per tile against the light-cluster lists instead of all lights. Still to do: tile-list parity across RenderVKLightCulling 1 / 2 and the uber / plain-split grid path; live toggle; devices without computeFullSubgroups.
Author
Owner

Fixed in Slipstream 1.3.0 (stable): https://git.slipstreamviewer.org/sky_line/Slipstream/releases/tag/v1.3.0

If you still see this on 1.3.0, reply here and we'll reopen it.

Fixed in **Slipstream 1.3.0** (stable): https://git.slipstreamviewer.org/sky_line/Slipstream/releases/tag/v1.3.0 If you still see this on 1.3.0, reply here and we'll reopen it.
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#831
No description provided.