@@ -17,6 +17,8 @@ The implementation contract is now:
1717- reconstruct perspective-correct barycentrics from the referenced
1818 triangle's clip positions rather than storing them or expanding shared
1919 vertices;
20+ - require the optional WebGPU ` primitive-index ` capability and preserve the
21+ existing 96-byte ` Vertex3D ` storage layout as six packed ` vec4<u32> ` lanes;
2022- admit only static opaque/masked shared-arena geometry with Tier-A global
2123 materials; blend, transmission, layered/custom, skinned, deforming, and
2224 unsupported content stays on the forward compatibility path;
@@ -28,12 +30,15 @@ The implementation contract is now:
2830 reconstructed output, and transient allocation.
2931
3032The shared CPU/WGSL ABI and perspective reconstruction live in
31- ` renderer/visibility_buffer.rs ` and
32- ` shaders/visibility_buffer/reconstruct.wgsl ` . The first runtime milestone is
33- a diagnostic raster/readback oracle; production composition follows only
34- after that oracle catches ID, winding, clipping, and interpolation faults.
33+ ` renderer/visibility_buffer.rs ` ,
34+ ` shaders/visibility_buffer/reconstruct.wgsl ` , and
35+ ` shaders/visibility_buffer/geometry.wgsl ` . Hardware readback oracles now prove
36+ ID/winding rasterization, perspective reconstruction, non-zero first-index and
37+ base-vertex addressing, the exact packed ` Vertex3D ` byte layout, and all 24
38+ reconstructed vertex lanes. The next milestone is an opt-in runtime A/B pass;
39+ production composition follows only after it passes the no-regression gate.
3540
36- ## Problem
41+ ## Original problem statement (historical)
3742
3843The ` main_hdr_pass ` writes four MRTs at the full physical resolution:
3944
@@ -44,9 +49,10 @@ The `main_hdr_pass` writes four MRTs at the full physical resolution:
4449| ` velocity_rt ` | Rg16Float | 4 |
4550| ` albedo_rt ` | Rgba8Unorm | 4 |
4651
47- Total: ** 18 bytes per pixel written** by every fragment. At 1600×900 that's
48- 26 MB per pass per frame, with overdraw multiplying the real write count.
49- Bandwidth-bound on integrated GPUs.
52+ Total: ** 18 bytes per winning pixel written** by the current main pass. At
53+ 1600×900 that is 26 MB per full-surface pass before attachment compression or
54+ backend effects. The old claim that overdraw multiplies all four writes is no
55+ longer valid because the alpha-aware depth prepass rejects hidden fragments.
5056
5157UE5's Nanite uses a ** visibility buffer** instead: store only `(triangle_id,
5258barycentrics)` (~ 8 bytes) in the G-buffer, defer material evaluation to the
@@ -66,20 +72,22 @@ This is a significant refactor — do ticket 005 (depth prepass) first. Then:
66723 . ** MRTs that post-FX consumes** (normal, albedo, material, velocity) can
6773 either be rebuilt per-pixel in the shading pass OR kept as separate passes.
6874 Simplest path: the shading pass writes them alongside the final HDR
69- colour — still one write per pixel, vs 4 writes per overdrawn pixel today.
75+ colour. That preserves the winning-pixel attachment footprint, so any gain
76+ must come from the cheaper visibility raster or improved scheduling and
77+ must be demonstrated by the total-pass A/B.
7078
71- ## Simpler intermediate step
79+ ## Simpler intermediate step (landed)
7280
73- If full visibility buffer is too much, consider ** dropping unused MRTs when
74- features are off ** :
81+ Bloom already ** drops unused MRTs when features are off ** on the constrained
82+ ` lean_mrt ` route :
7583
7684- ` velocity_rt ` is only needed when TAA or motion-blur is on.
7785- ` albedo_rt ` is only needed when SSGI or SSR is on.
7886- ` material_rt ` is needed for SSR and the shadow map sampler stuff.
7987
80- Rebuild the ` scene_pipeline ` with 2 or 3 MRT targets when the user has
81- disabled the dependent post-FX. Cut 30-50% of the MRT bandwidth in low-quality
82- modes. This is a ~ 2-day win instead of 2 weeks .
88+ The constrained scene pipeline uses fewer MRT targets when dependent post-FX
89+ is disabled, reducing its attachment bandwidth without changing the full
90+ quality path .
8391
8492## References
8593
@@ -102,9 +110,11 @@ modes. This is a ~2-day win instead of 2 weeks.
102110
103111## Notes for the implementer
104112
105- - wgpu needs a storage buffer of per-mesh vertex data (triangle index buffer
106- + vertex attribute buffer, GPU-indexed by mesh_id). That aligns with
107- ticket 009 (GPU-driven rendering).
113+ - Reuse #28 's STORAGE-capable shared vertex/index arenas and ` GpuDrawRecord ` .
114+ ` draw.y ` is the first index, ` bitcast<i32>(draw.z) ` is the base vertex, and
115+ ` draw.w ` is the material ID. Do not declare ` Vertex3D ` with native WGSL
116+ ` vec3 ` fields: storage alignment would not match Rust's tightly packed
117+ offsets. Use the checked six-` vec4<u32> ` decoder.
108118- Animated meshes (skinned) need special handling — triangle positions change
109119 per frame. Either compute-skin to a fixed buffer first, or keep animated
110120 meshes on the traditional path and use visibility buffer for static only.
@@ -116,40 +126,15 @@ modes. This is a ~2-day win instead of 2 weeks.
116126 pass, SSR/SSGI/SSAO inputs.
117127- ` native/shared/src/scene.rs ` — mesh_id assignment, vertex buffer layout.
118128
119- ## Deferred — reopen criteria
120-
121- Real GPU bandwidth win (~ 14 MB/frame at 1600×900 × overdraw factor, on
122- a benchmark that currently writes 26 MB/pass) but ** invisible behind
123- the vsync cap on Sponza** . The main perf target landed at 60 fps vsync
124- on full quality, so any further pass-cost reduction just gives headroom
125- we can't measure here.
126-
127- Reopen when one of these triggers:
128-
129- - ** A target scene pushes past the 16.7 ms vsync ceiling on the
130- benchmark machine.** The 50%+ fragment-bandwidth reduction from a
131- visibility buffer is the remaining GPU-side lever for Sponza-class
132- bandwidth-bound scenes.
133- - ** Integrated / mobile GPUs become a priority.** Bandwidth matters
134- disproportionately more on tile-based and integrated hardware; this
135- ticket is the single biggest available reduction.
136- - ** Overdraw-heavy scenes** (foliage, hair, transparent-dense
137- particles) become the target. The "every visible pixel shades
138- exactly once" property of a visibility buffer + depth-prepass combo
139- is essentially the only way to keep overdraw from eating bandwidth.
140-
141- Effort when reopening is a 2+ week redesign: main_hdr_pass output
142- becomes ` Rgba32Uint (tri_id, u, v, mesh_id) ` only, a new shading pass
143- fetches vertex data from per-mesh storage buffers and evaluates PBR,
144- downstream MRT consumers (SSR / SSGI / SSAO / post-FX) need to read
145- from the rebuilt material channels rather than the current 4-MRT
146- layout. Ticket 005's depth-prepass is a natural prerequisite (it was
147- deprioritized but would become useful again here). Ticket 009's
148- unified vertex/index buffers are a hard prerequisite (the shading
149- pass needs a single bindless-style fetch across all meshes).
150-
151- The "simpler intermediate step" in the approach section above — drop
152- unused MRTs when features are off — is a legitimate ~ 2-day quick win
153- for low-quality modes (` --quality 1 ` / ` --quality 0 ` users on
154- integrated hardware). That's the most-likely first concrete follow-up
155- when this ticket reopens.
129+ ## Activation state
130+
131+ The ticket is under active qualification, but the shipping path remains off.
132+ The old percentage and MB/frame estimates are not activation evidence because
133+ they predate the alpha-aware depth prepass and omit the visibility shading
134+ pass. Enablement requires uncapped captures on at least the representative
135+ discrete and integrated/mobile tiers, with per-pass GPU timestamps, total
136+ transient bytes, full compatibility composition, and governed image diffs.
137+
138+ The low-quality ` lean_mrt ` intermediate already drops unused material/albedo
139+ attachments on constrained profiles. That remains the safe bandwidth path
140+ for adapters which lack ` primitive-index ` or do not win the runtime A/B.
0 commit comments