@woosh/meep-engine 3.11.1 → 3.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +294 -290
- package/src/engine/graphics3/ParticipatingMedia.d.ts +111 -0
- package/src/engine/graphics3/ParticipatingMedia.d.ts.map +1 -0
- package/src/engine/graphics3/ParticipatingMedia.js +203 -0
- package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.d.ts +34 -0
- package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.d.ts.map +1 -0
- package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.js +73 -0
- package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts +87 -0
- package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts.map +1 -0
- package/src/engine/graphics3/ParticipatingMediaSystem3.js +184 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts +16 -2
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +28 -4
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts +1 -1
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.js +5 -2
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +564 -451
- package/src/shade/device/ShadeGPUCommandContext.d.ts +18 -2
- package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
- package/src/shade/device/ShadeGPUCommandContext.js +994 -940
- package/src/shade/device/immediate_data_supported.d.ts +33 -0
- package/src/shade/device/immediate_data_supported.d.ts.map +1 -0
- package/src/shade/device/immediate_data_supported.js +53 -0
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts +18 -0
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.js +24 -0
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts +7 -0
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.js +49 -1
- package/src/shade/device/mock/SoftwareGPUDevice.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUDevice.js +20 -2
- package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts +65 -0
- package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts.map +1 -0
- package/src/shade/device/mock/SoftwareGPUImmediateData.js +160 -0
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts +7 -0
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.js +50 -0
- package/src/shade/device/mock/pipeline_immediate_size.d.ts +11 -0
- package/src/shade/device/mock/pipeline_immediate_size.d.ts.map +1 -0
- package/src/shade/device/mock/pipeline_immediate_size.js +18 -0
- package/src/shade/device/pipeline/PipelineLayoutManager.d.ts.map +1 -1
- package/src/shade/device/pipeline/PipelineLayoutManager.js +1 -0
- package/src/shade/playground/volumetrics_froxel/README.md +104 -1
- package/src/shade/playground/volumetrics_froxel/index.html +20 -1
- package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -1
- package/src/shade/playground/volumetrics_froxel/main.js +298 -0
- package/src/shade/playground/volumetrics_froxel/measure.d.ts +119 -0
- package/src/shade/playground/volumetrics_froxel/measure.d.ts.map +1 -0
- package/src/shade/playground/volumetrics_froxel/measure.js +559 -0
- package/src/shade/renderer/GraphicsContext.d.ts +9 -2
- package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
- package/src/shade/renderer/GraphicsContext.js +13 -1
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +22 -18
- package/src/shade/renderer/atmosphere/GPUSky.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/GPUSky.js +145 -142
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts +34 -0
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.js +36 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts +20 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.js +25 -0
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts +14 -1
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.js +163 -177
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts +20 -0
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.js +46 -0
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts +11 -1
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.js +57 -27
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts +19 -1
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.js +69 -33
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts +13 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.js +35 -35
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts +13 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.js +38 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts +17 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.js +29 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts +21 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.js +42 -0
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts +2 -2
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +165 -84
- package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +215 -204
- package/src/shade/renderer/atmosphere/shader_transmittance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_transmittance_lut.js +79 -67
- package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts.map +1 -1
- package/src/shade/renderer/light/cluster/graph_assign_clsuters.js +6 -2
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.js +4 -0
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +341 -345
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +0 -2
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
- package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.js +7 -3
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +77 -81
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +5 -1
- package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
- package/src/shade/renderer/shader/ComputeShader.js +6 -2
- package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
- package/src/shade/renderer/shader/ImageShader.js +4 -1
- package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/ShaderDescriptor.js +26 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts +18 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.js +32 -0
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts +7 -0
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.js +17 -6
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts +21 -0
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.js +69 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts +9 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.js +22 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts +16 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.js +41 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts +11 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.js +15 -19
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts +23 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.js +44 -0
- package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.js +1 -4
- package/src/shade/renderer/shader/compiler/CodeChunk.d.ts +7 -1
- package/src/shade/renderer/shader/compiler/CodeChunk.d.ts.map +1 -1
- package/src/shade/renderer/shader/compiler/CodeChunk.js +37 -2
- package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.js +6 -0
- package/src/shade/renderer/shader/graph/compute/graph_compute_indirect_pass.js +1 -1
- package/src/shade/renderer/shader/graph/compute/graph_compute_pass.js +1 -1
- package/src/shade/renderer/shader/graph/graph_image_pass.js +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_bindings.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_bindings.js +7 -0
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts +3 -1
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.js +12 -1
- package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/pass/RenderPassDescriptor.js +1 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts +21 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts.map +1 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.js +21 -0
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts +59 -1
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +195 -2
- package/src/shade/renderer/shader/type/type_has_arrays.d.ts +14 -0
- package/src/shade/renderer/shader/type/type_has_arrays.d.ts.map +1 -0
- package/src/shade/renderer/shader/type/type_has_arrays.js +33 -0
- package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.d.ts +14 -13
- package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.d.ts.map +1 -1
- package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.js +133 -129
- package/src/shade/renderer/view/GPUViewSkyContext.d.ts.map +1 -1
- package/src/shade/renderer/view/GPUViewSkyContext.js +169 -173
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +480 -315
- package/src/shade/renderer/volumetrics/NOTES.md +887 -8
- package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts +5 -0
- package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.js +21 -2
- package/src/shade/renderer/volumetrics/SceneVolumetrics.d.ts +26 -0
- package/src/shade/renderer/volumetrics/SceneVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/SceneVolumetrics.js +64 -21
- package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.js +84 -28
- package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.d.ts +98 -0
- package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.js +111 -0
- package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.d.ts +84 -0
- package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.js +252 -0
- package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.js +75 -75
- package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.d.ts +43 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.js +145 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.d.ts +20 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.js +48 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.d.ts +38 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.js +130 -0
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +18 -3
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +193 -70
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts +16 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +83 -15
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js +29 -18
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +260 -260
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts +58 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.js +290 -0
- package/src/shade/wgsl/emulator/CPUBitmapData.d.ts +25 -0
- package/src/shade/wgsl/emulator/CPUBitmapData.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/CPUBitmapData.js +33 -0
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2228 -2215
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +210 -2
- package/src/shade/wgsl/emulator/cpu_texture_gather.d.ts +69 -0
- package/src/shade/wgsl/emulator/cpu_texture_gather.d.ts.map +1 -0
- package/src/shade/wgsl/emulator/cpu_texture_gather.js +148 -0
- package/src/shade/wgsl/emulator/cpu_texture_sample.d.ts +16 -0
- package/src/shade/wgsl/emulator/cpu_texture_sample.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/cpu_texture_sample.js +176 -172
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts +0 -2
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.js +0 -1
|
@@ -1,451 +1,564 @@
|
|
|
1
|
-
# WGSL immediate data in Shade — landing plan, 2026-08-28
|
|
2
|
-
|
|
3
|
-
How to bring the WGSL `immediate` address space (`var<immediate>` + `GPUBindingCommandsMixin.setImmediates`)
|
|
4
|
-
into Shade, starting from `ShadeGPUCommandContext.allocateTransientBuffer` and the call sites that
|
|
5
|
-
feed it.
|
|
6
|
-
|
|
7
|
-
**Verdict:** worth landing. Shade already models this exact concept, under this exact name, on top of
|
|
8
|
-
uniform buffers — nineteen of the twenty structs literally called `PUSH_CONSTANTS` fit inside the
|
|
9
|
-
64-byte budget unmodified. With no fallback in scope the remaining work is small and almost entirely
|
|
10
|
-
additive.
|
|
11
|
-
|
|
12
|
-
> **Scope decision (rev 2).** An earlier revision of this plan carried a dual-lowering fallback:
|
|
13
|
-
> `var<immediate>` where supported, `@group(N) @binding(0) var<uniform>` where not, chosen by a
|
|
14
|
-
> capability probe. That is now **out of scope**, and it should never have been in it —
|
|
15
|
-
> [`RENDERER_CONTRACT.md` §2](../RENDERER_CONTRACT.md) already states the rule it violated: *"Shade
|
|
16
|
-
> requires a WebGPU device that meets a fixed floor. There is no feature detection beyond it and no
|
|
17
|
-
> branch that produces a lesser frame."* `ShadeDeviceFailureReason` says the same thing in its header
|
|
18
|
-
> comment, citing D6/I-8: no degradation tiers, no fallbacks, no feature-detection branches.
|
|
19
|
-
>
|
|
20
|
-
> Immediates therefore join the floor rather than getting a compatibility path. That deletes: the
|
|
21
|
-
> import-time capability probe and its unrecoverable failure mode, the extra bind group the fallback
|
|
22
|
-
> lowering needed, the resulting exclusion of the 7 shaders already at `maxBindGroups: 4`, and the
|
|
23
|
-
> `vi.resetModules()` dance every dual-lowering spec would have required. If a fallback is ever
|
|
24
|
-
> genuinely wanted, §9 shows why it belongs in a shader variant rather than in the binding machinery.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
transient
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
`
|
|
178
|
-
|
|
179
|
-
`
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
`
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
`
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
`
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
- `
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
it
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
`
|
|
297
|
-
|
|
298
|
-
- **
|
|
299
|
-
|
|
300
|
-
and `
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
`
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
|
336
|
-
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
`
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
1
|
+
# WGSL immediate data in Shade — landing plan, 2026-08-28
|
|
2
|
+
|
|
3
|
+
How to bring the WGSL `immediate` address space (`var<immediate>` + `GPUBindingCommandsMixin.setImmediates`)
|
|
4
|
+
into Shade, starting from `ShadeGPUCommandContext.allocateTransientBuffer` and the call sites that
|
|
5
|
+
feed it.
|
|
6
|
+
|
|
7
|
+
**Verdict:** worth landing. Shade already models this exact concept, under this exact name, on top of
|
|
8
|
+
uniform buffers — nineteen of the twenty structs literally called `PUSH_CONSTANTS` fit inside the
|
|
9
|
+
64-byte budget unmodified. With no fallback in scope the remaining work is small and almost entirely
|
|
10
|
+
additive.
|
|
11
|
+
|
|
12
|
+
> **Scope decision (rev 2).** An earlier revision of this plan carried a dual-lowering fallback:
|
|
13
|
+
> `var<immediate>` where supported, `@group(N) @binding(0) var<uniform>` where not, chosen by a
|
|
14
|
+
> capability probe. That is now **out of scope**, and it should never have been in it —
|
|
15
|
+
> [`RENDERER_CONTRACT.md` §2](../RENDERER_CONTRACT.md) already states the rule it violated: *"Shade
|
|
16
|
+
> requires a WebGPU device that meets a fixed floor. There is no feature detection beyond it and no
|
|
17
|
+
> branch that produces a lesser frame."* `ShadeDeviceFailureReason` says the same thing in its header
|
|
18
|
+
> comment, citing D6/I-8: no degradation tiers, no fallbacks, no feature-detection branches.
|
|
19
|
+
>
|
|
20
|
+
> Immediates therefore join the floor rather than getting a compatibility path. That deletes: the
|
|
21
|
+
> import-time capability probe and its unrecoverable failure mode, the extra bind group the fallback
|
|
22
|
+
> lowering needed, the resulting exclusion of the 7 shaders already at `maxBindGroups: 4`, and the
|
|
23
|
+
> `vi.resetModules()` dance every dual-lowering spec would have required. If a fallback is ever
|
|
24
|
+
> genuinely wanted, §9 shows why it belongs in a shader variant rather than in the binding machinery.
|
|
25
|
+
>
|
|
26
|
+
> **Scope decision (rev 3).** Immediates are also *not* a device floor. `Renderer.initialize()` does
|
|
27
|
+
> not check for them and the contract's §2 is unchanged; a browser without immediates starts and
|
|
28
|
+
> renders as before. Detection is exported instead (§4), so a shader author decides between refusing
|
|
29
|
+
> and reaching for a variant. This keeps the engine's floor where it was while the feature lands.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 1. What the feature is, restricted to what matters here
|
|
34
|
+
|
|
35
|
+
Immediates are WebGPU's portable spelling of Vulkan push constants / D3D12 root constants / Metal
|
|
36
|
+
`setBytes`. The parts that constrain this plan:
|
|
37
|
+
|
|
38
|
+
| | Value |
|
|
39
|
+
|---|---|
|
|
40
|
+
| WGSL gate | `requires immediate_address_space;` — a **language extension**, not a `GPUFeatureName` |
|
|
41
|
+
| Detection | `navigator.gpu.wgslLanguageFeatures.has('immediate_address_space')` — synchronous, no adapter needed |
|
|
42
|
+
| Size budget | `maxImmediateSize`, default **64 bytes**, multiple of 4, **no separate Compatibility Mode default** |
|
|
43
|
+
| Declaration | Module scope, immutable, **no `@group` / `@binding`** |
|
|
44
|
+
| Store type | Host-shareable constructible, **arrays and structs containing arrays are disallowed** |
|
|
45
|
+
| Count | **At most one** immediate variable statically accessed per entry point |
|
|
46
|
+
| Layout rules | Storage-space (natural alignment), **not** uniform-space |
|
|
47
|
+
| API | `pass.setImmediates(rangeOffset, data, dataOffset, dataSize)`, on render pass, compute pass and bundle encoders |
|
|
48
|
+
| Pipeline layout | `GPUPipelineLayoutDescriptor.immediateSize` (default 0, ≤ `maxImmediateSize`, multiple of 4) |
|
|
49
|
+
| Init | Bytes start zeroed but **slots start unset**; a draw reading an unset 4-byte slot is a validation error |
|
|
50
|
+
| Reset | Per-encoder; survives `setPipeline`, cleared by a new pass and by `executeBundles` |
|
|
51
|
+
|
|
52
|
+
Because `maxImmediateSize` is a `maximum`-class limit with a default of 64 and no compat-mode
|
|
53
|
+
reduction, **any adapter that exposes the WGSL extension reports at least 64 bytes**. There is no
|
|
54
|
+
device where the language feature is present and the budget is not. That is what makes a floor check
|
|
55
|
+
sufficient and a runtime branch unnecessary.
|
|
56
|
+
|
|
57
|
+
Availability is a browser-version question, not a hardware one: Vulkan's `maxPushConstantsSize` is
|
|
58
|
+
core-1.0 with a 128-byte required minimum and no feature bit, and no device in the
|
|
59
|
+
`vulkan.gpuinfo.org` corpus reports below 128. Chrome ships immediates in 149–150.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 2. The current path, traced
|
|
64
|
+
|
|
65
|
+
Shade already has this pattern, three layers deep.
|
|
66
|
+
|
|
67
|
+
**Declaration** — `ShaderResourceGroupDescriptor.addUniform` (`ShaderResourceGroupDescriptor.js:335`):
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const PUSH_CONSTANTS = WebGPUStruct.from({
|
|
71
|
+
probe_index_offset: u32,
|
|
72
|
+
probe_update_count: u32,
|
|
73
|
+
probe_resolution: u32,
|
|
74
|
+
probe_count: u32
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
resources.createGroup()
|
|
78
|
+
.addUniform("settings", PUSH_CONSTANTS)
|
|
79
|
+
.addTexture("tAtlas_radiance");
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Codegen** — `ShaderResourceSetDescriptor.generateCodeChunk` (`ShaderResourceSetDescriptor.js:305`)
|
|
83
|
+
emits `@group(0) @binding(0) var<uniform> settings : PUSH_CONSTANTS;`.
|
|
84
|
+
|
|
85
|
+
**Upload, per dispatch** — `ShadeGPUCommandContext.allocateTransientValueBuffer` (`:598`):
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
const settings = context.encoder.allocateTransientValueBuffer(PUSH_CONSTANTS, { … });
|
|
89
|
+
|
|
90
|
+
shader.dispatch({ encoder: context.encoder, bindings: { settings, … } });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
which lands in `allocateTransientBuffer` (`:538`) → `GPUBufferAllocator.get` (pooled), then
|
|
94
|
+
`writeValueBuffer` (`:620`) → staging buffer → `copyBufferToBuffer`, then a `BindGroupDescriptor` hash
|
|
95
|
+
lookup in `GraphicsContext.setPipelineBindings` (`GraphicsContext.js:211`) and finally
|
|
96
|
+
`pass.setBindGroup(i, bind_group)`.
|
|
97
|
+
|
|
98
|
+
The FrameGraph route reaches the same place: `graph_compute_pass` → `graph_prepare_bindings` →
|
|
99
|
+
`encoder_prepare_shader_binding` (`:34`), which turns a raw value or `ArrayBuffer` into a transient
|
|
100
|
+
uniform buffer on the spot.
|
|
101
|
+
|
|
102
|
+
**Scale of the surface:**
|
|
103
|
+
|
|
104
|
+
- 67 `allocateTransientValueBuffer` call sites, 11 `allocateTransientBufferAndLoad`, 21 `graph_import_typed_buffer`
|
|
105
|
+
- 300 `addUniform` declarations; 22 of those *call sites* name a `*PUSH_CONSTANTS*` type, across
|
|
106
|
+
**20 distinct struct definitions** (`LPV_ACCUMULATION_PUSH_CONSTANTS` is shared by four shaders).
|
|
107
|
+
The table in §6 is per-struct, so it has 20 rows, not 22
|
|
108
|
+
- The single most common transient value type is a bare `u32` (12 sites)
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 3. What the win actually is
|
|
113
|
+
|
|
114
|
+
Not bandwidth. 64 bytes at a few hundred dispatches a frame is noise on any bus.
|
|
115
|
+
|
|
116
|
+
The win is the machinery each of those 20-byte structs currently drags behind it:
|
|
117
|
+
|
|
118
|
+
1. **A pooled `GPUBuffer` acquire/release per value.** `GPUBufferAllocator.get` plus the transient
|
|
119
|
+
tracking array in `ShadeGPUCommandContext`, per dispatch, per frame.
|
|
120
|
+
2. **A staging buffer, a `getMappedRange`, an `unmap` and a `copyBufferToBuffer` per value** —
|
|
121
|
+
`writeValueBuffer` does all four for what is often four bytes.
|
|
122
|
+
3. **A `BindGroupDescriptor` construction plus a `HashMap` lookup per value**, and a cache entry
|
|
123
|
+
whose key changes every time the value changes — so the small-uniform bind groups are the ones
|
|
124
|
+
least likely to hit the cache.
|
|
125
|
+
4. **Two JS allocations per value, in the serialiser.** `writeValueBuffer` reaches
|
|
126
|
+
`write_gpu_typed_buffer`, which calls `scratch_buffer.fromArrayBuffer(data)`, which constructs a
|
|
127
|
+
fresh `DataView` and `Uint8Array` over the target every single call. Per dispatch, per frame, in a
|
|
128
|
+
codebase that advertises "zero-allocation" in its own package keywords. The immediate path writes
|
|
129
|
+
into a persistent `BinaryBuffer` and allocates nothing (§6, L3).
|
|
130
|
+
5. **A `@binding` slot** in the group the settings share.
|
|
131
|
+
|
|
132
|
+
Point 5 is *only* a binding, not a group. Measured across all twenty candidates: the settings uniform
|
|
133
|
+
shares its group with textures and storage buffers in every case, at a median of about four resources
|
|
134
|
+
per group. No bind group slot is recovered anywhere in the current codebase. (An earlier revision
|
|
135
|
+
claimed otherwise and built an argument on it; that argument is withdrawn.)
|
|
136
|
+
|
|
137
|
+
**The strongest case is `graph_import_typed_buffer`** (`renderer/shader/graph/graph_import_typed_buffer.js`,
|
|
138
|
+
21 call sites). It does not use the transient pool at all — per execution it calls `device.createBuffer`
|
|
139
|
+
with `mappedAtCreation`, `getMappedRange`, `unmap`, `copyBufferToBuffer`, then `destroy` on
|
|
140
|
+
`encoder.onFinished`. A full buffer create-and-destroy per frame per value, carrying things like
|
|
141
|
+
`LPV_ACCUMULATION_PUSH_CONSTANTS` at 20 bytes. The source carries a standing TODO ("this is pretty
|
|
142
|
+
inefficient, figure out a way to skip needing 2 buffers") and a 2025-09-13 note that the obvious fix —
|
|
143
|
+
transient buffer plus `queue.writeBuffer` — produced garbage for reasons never tracked down.
|
|
144
|
+
Immediates make the two-buffer dance unnecessary by construction for the ≤64-byte cases rather than
|
|
145
|
+
requiring that bug to be found first.
|
|
146
|
+
|
|
147
|
+
Caveat: converting a `graph_import_typed_buffer` site is *not* a drop-in. It returns a graph resource
|
|
148
|
+
id that passes consume as an ordinary binding, so the consuming pass stops declaring a graph input.
|
|
149
|
+
Higher payoff per site, higher cost per site.
|
|
150
|
+
|
|
151
|
+
Honest counterweight: on hardware, a 64-byte immediate may well land in a driver-managed constant
|
|
152
|
+
bank rather than in registers, so do not promise a shader-side speedup. Promise the removal of
|
|
153
|
+
CPU-side per-draw allocation, and measure the rest.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 4. Capability, not a floor
|
|
158
|
+
|
|
159
|
+
**Built as optional.** `Renderer.initialize()` does not consult it and nothing was added to
|
|
160
|
+
`RENDERER_CONTRACT.md` §2 — a browser without immediates still starts and still renders. It simply
|
|
161
|
+
cannot run a shader that declares one, and deciding what to do about that belongs to whoever wrote
|
|
162
|
+
the shader.
|
|
163
|
+
|
|
164
|
+
`device/immediate_data_supported.js` answers the two separate questions:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
immediate_data_supported() // does this browser's WGSL have the address space?
|
|
168
|
+
device_supports_immediate_size(device, bytes) // does this adapter have the budget?
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The first is synchronous and needs no adapter — `wgslLanguageFeatures` hangs off `navigator.gpu` —
|
|
172
|
+
so it can be asked at module scope, where shader descriptors are built. That is what makes the
|
|
173
|
+
shader-variant approach in §9 workable: one `if` at the point a shader is chosen.
|
|
174
|
+
|
|
175
|
+
Nothing goes in `requiredFeatures`: `immediate_address_space` is a WGSL *language* extension, not a
|
|
176
|
+
`GPUFeatureName`, and putting it there would make `requestDevice` reject on every device. Nothing
|
|
177
|
+
goes in `requiredLimits` either — 64 is the spec default, granted without asking.
|
|
178
|
+
|
|
179
|
+
`ShaderDescriptor.validate_against_device` does check the budget, and throws naming the shader and
|
|
180
|
+
the shortfall. That is a per-draw guard against a device that disagrees with the language extension,
|
|
181
|
+
not a startup gate; it fires only for a shader that actually declares immediate data.
|
|
182
|
+
|
|
183
|
+
## 5. The one structural obstacle
|
|
184
|
+
|
|
185
|
+
`ShaderResourceSetDescriptor` is a list of groups; a group is a list of resources; a resource has a
|
|
186
|
+
`binding_index`. Everything downstream — `generateBindings` returning `GPUBindingResource[][]`,
|
|
187
|
+
`generatePipelineLayoutDescriptor` producing one `BindGroupLayoutDescriptor` per group,
|
|
188
|
+
`generateCodeChunk` prefixing every declaration with `@group(g) @binding(b)` — assumes that shape.
|
|
189
|
+
|
|
190
|
+
An immediate has no group, no binding, and there is at most one per entry point. It has to live
|
|
191
|
+
*beside* the groups, and its value needs a second route to the pass encoder that ends in
|
|
192
|
+
`setImmediates` rather than `setBindGroup`.
|
|
193
|
+
|
|
194
|
+
Nothing about that requires a device, so the whole thing stays where Shade already does its work:
|
|
195
|
+
in descriptors built at module scope. `PipelineLayoutDescriptor` is device-free by design and stays
|
|
196
|
+
that way.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 6. The landing plan
|
|
201
|
+
|
|
202
|
+
**L1 — declaration + codegen**
|
|
203
|
+
|
|
204
|
+
- `ShaderResourceSetDescriptor`: an `#immediate` slot beside `#groups`, a `setImmediate(name, type)`
|
|
205
|
+
that rejects a second call, plus validation at declaration time: `type.size <= 64`, no arrays
|
|
206
|
+
anywhere in the type (walk it — `WebGPUArray` members and nested structs both), no atomics, size a
|
|
207
|
+
multiple of 4.
|
|
208
|
+
- `generateCodeChunk` (`ShaderResourceSetDescriptor.js:305`): emit `var<immediate> name : Type;`
|
|
209
|
+
first, with no `@group`/`@binding` prefix.
|
|
210
|
+
- `ShaderDescriptor.compile` (`ShaderDescriptor.js:53`): the existing mechanism emits
|
|
211
|
+
`enable ${extension};`. Language extensions use **`requires`**. That needs a second channel on
|
|
212
|
+
`CodeChunk` — `addLanguageExtension` beside `addExtension` — and a `requires` line in the prelude.
|
|
213
|
+
Do not overload `addExtension`; its assert is against `WebGPUExtensionType`, which is the
|
|
214
|
+
`GPUFeatureName` namespace.
|
|
215
|
+
|
|
216
|
+
**L2 — pipeline layout**
|
|
217
|
+
|
|
218
|
+
- `PipelineLayoutDescriptor`: an `immediateSize = 0` field, threaded through `from`, `forStage`,
|
|
219
|
+
`fromJSON`, `hash`, `equals`, `copy`, `clone`. Missing it from `hash`/`equals` would silently alias
|
|
220
|
+
two layouts that differ only in immediate size — cheap mistake, expensive symptom.
|
|
221
|
+
- `PipelineLayoutManager.#create_pipeline_layout` (`PipelineLayoutManager.js:85`): pass it to
|
|
222
|
+
`createPipelineLayout`.
|
|
223
|
+
- `generatePipelineLayoutDescriptor` / `generatePipelineLayout`: set it from `roundUp(4, type.size)`.
|
|
224
|
+
|
|
225
|
+
**L3 — binding resolution and the call**
|
|
226
|
+
|
|
227
|
+
**Do not change the return shape of `generateBindings`.** Returning `{ groups, immediate }` would
|
|
228
|
+
break all **10** external callers (`GPUParticularRenderer`, `GPUDynamicMeshResidency`,
|
|
229
|
+
`graph_draw_lightmap_viz_buffer`, `prototypeParticleSystem`, `graph_atorus_denoise_luma`,
|
|
230
|
+
`graph_rasterize_meshes_transparent_oit`, `ComputeShader`, `ImageShader`, `RenderPassDescriptor`,
|
|
231
|
+
`GPUWaterRenderer`) and force the new field through the **22** external
|
|
232
|
+
`constructRenderPass`/`constructComputePass` call sites. Purely additive instead:
|
|
233
|
+
|
|
234
|
+
- `ShaderResourceSetDescriptor.writeImmediate(map, buffer)` — a *new* method that **allocates
|
|
235
|
+
nothing**. It writes the immediate value into a caller-owned `BinaryBuffer` at that buffer's
|
|
236
|
+
current `position`, advances `position` by the type's size, and returns `false` when the shader
|
|
237
|
+
declares no immediate. Every existing `generateBindings` caller is untouched.
|
|
238
|
+
|
|
239
|
+
Use the already-exported `write_wgsl_type_value(value, buffer, type)`
|
|
240
|
+
(`serde/write_gpu_typed_buffer.js:114`), which takes a `BinaryBuffer` directly. **Do not** route
|
|
241
|
+
this through `write_gpu_typed_buffer` (`:188`): that wrapper calls
|
|
242
|
+
`scratch_buffer.fromArrayBuffer(data)`, and `fromArrayBuffer` constructs `new DataView(data)` and
|
|
243
|
+
`new Uint8Array(data)` on every call — two allocations per write, and it resets `position` to 0.
|
|
244
|
+
The zero-allocation entry point already exists and is public; the wrapper is the thing to avoid.
|
|
245
|
+
|
|
246
|
+
- `GraphicsContext.setPipelineBindings` (`GraphicsContext.js:211`) gains an optional fourth
|
|
247
|
+
parameter — the `BinaryBuffer`, plus the byte offset and length written. After the `setBindGroup`
|
|
248
|
+
loop: `pass.setImmediates(0, buffer.data, byte_offset, byte_length)`. `setImmediates` takes
|
|
249
|
+
`AllowSharedBufferSource`, and for a plain `ArrayBuffer` the offset and size arguments are in
|
|
250
|
+
**bytes**, so the backing store is passed through with no copy on our side. All four existing
|
|
251
|
+
callers keep working.
|
|
252
|
+
|
|
253
|
+
- `ImageShader.draw` and `ComputeShader.constructPass`/`dispatch` set `position`, call
|
|
254
|
+
`writeImmediate`, and thread the buffer plus range through as optional fields.
|
|
255
|
+
|
|
256
|
+
Three properties of `BinaryBuffer` that this design has to respect:
|
|
257
|
+
|
|
258
|
+
1. **`data` is not stable.** `setCapacity` (and therefore `ensureCapacity`) reallocates the backing
|
|
259
|
+
`ArrayBuffer` and rebinds `dataView` / `__data_uint8`. Read `buffer.data` at the point of the
|
|
260
|
+
`setImmediates` call, never cache it — and size the buffer once at construction so it never grows
|
|
261
|
+
mid-frame.
|
|
262
|
+
2. **Endianness is not the default.** `BinaryBuffer` defaults to `EndianType.LittleEndian`, but the
|
|
263
|
+
serde module's own scratch sets `endianness = platform_compute_endianness()` (`:179`) because GPU
|
|
264
|
+
buffer contents must match the host. A caller-supplied buffer that skips this writes correct-looking
|
|
265
|
+
bytes that are wrong on a big-endian host and identical on the one you test on. Set it at
|
|
266
|
+
construction, and assert it in `writeImmediate`.
|
|
267
|
+
3. **Ownership.** One long-lived buffer on `ShadeGPUCommandContext`, sized to `maxImmediateSize` at
|
|
268
|
+
construction, is enough — `setImmediates` copies the bytes at call time, so the region is free for
|
|
269
|
+
reuse immediately after. The API is still parameterised on `position` rather than hardcoding 0,
|
|
270
|
+
because that costs nothing and leaves room for a caller that wants to stage several values or keep
|
|
271
|
+
a frame log of them.
|
|
272
|
+
- `encoder_prepare_shader_binding` (`:34`): in the immediate case, return the raw value rather than
|
|
273
|
+
allocating a transient buffer.
|
|
274
|
+
|
|
275
|
+
**The eight bypass paths.** `setPipelineBindings` does not cover everything. Eight files drive
|
|
276
|
+
`pass.setBindGroup` directly and never touch it — `GPUMaterialManager`, `prototypeParticleSystem`,
|
|
277
|
+
`graph_build_depth_mipmap`, `render_copy_texture`, `graph_rasterize_meshes_alpha_tested`,
|
|
278
|
+
`graph_rasterize_meshes_transparent_oit`, `draw_texture_as_rgba`, `WebGPUMipmapGenerator`. A shader
|
|
279
|
+
migrated in one of those needs its own `setImmediates` call at the right point in the loop. None of
|
|
280
|
+
the twenty candidates lives there, so this constrains future migration rather than this landing — but
|
|
281
|
+
it must not be assumed away.
|
|
282
|
+
|
|
283
|
+
**L4 — the test tiers**
|
|
284
|
+
|
|
285
|
+
Without a fallback these are not optional: they are the only way immediates get exercised outside a
|
|
286
|
+
browser.
|
|
287
|
+
|
|
288
|
+
- **WGSL validation** (`wgsl/validate_wgsl_source.js`) — teach it that `var<immediate>` is legal and
|
|
289
|
+
`requires immediate_address_space;` is a valid directive, so the new declaration does not trip the
|
|
290
|
+
foreign-token or undeclared-call rules.
|
|
291
|
+
- **JS emulator** — a real trap. `collectGlobalVariable`
|
|
292
|
+
(`wgsl/emulator/WGSLJavaScriptCompiler.js:825`) only pushes to `globalBindings` when *both* `group`
|
|
293
|
+
and `binding` are non-null (`:862`). A `var<immediate>` has neither, is not `workgroup`, and falls
|
|
294
|
+
through to the `privateGlobals` branch, where it is **silently default-initialised**. Every emulator
|
|
295
|
+
spec for an immediate-using chunk would pass while reading zeros. Route
|
|
296
|
+
`address_space === 'immediate'` into `globalBindings` with a null group/binding so
|
|
297
|
+
`emu.bindings.settings = {…}` keeps working by name.
|
|
298
|
+
- **SoftwareGPUDevice** — `DEFAULT_LIMITS.maxImmediateSize` (`mock/SoftwareGPUDevice.js:67`) is
|
|
299
|
+
already present, set to `0`. Raise it to 64, add `setImmediates` to `SoftwareGPURenderPassEncoder`
|
|
300
|
+
(`:130`) and `SoftwareGPUComputePassEncoder` (`:108`) with the spec's `[[immediate_data]]` /
|
|
301
|
+
`[[immediate_slots_set]]` state, validate `rangeOffset % 4`, size `% 4` and the range bound, and
|
|
302
|
+
snapshot the bytes into each recorded draw/dispatch the way `#snapshot_bindings` already snapshots
|
|
303
|
+
bind groups. Follow the folder's stated policy in `mock/IMPLEMENTATION_PLAN.md` §2: validation
|
|
304
|
+
errors go through `generate_error`, they do not throw. `createPipelineLayout` (`:766`) gains the
|
|
305
|
+
`immediateSize ≤ maxImmediateSize` and `% 4` checks. Emulate the unset-slot rule too — it is the
|
|
306
|
+
mock's chance to catch a missing `setImmediates` before a browser does.
|
|
307
|
+
- **Playground** — one pass converted end to end and run in Chrome before any bulk migration.
|
|
308
|
+
|
|
309
|
+
**L5 — migration**
|
|
310
|
+
|
|
311
|
+
Smallest first. `shader_read_out_bucket` (4 bytes, one `u32`) is the ideal first patient;
|
|
312
|
+
`shader_generate_rays` and `shader_shadow_upscale` are the same shape.
|
|
313
|
+
|
|
314
|
+
**It is not one line per shader.** Only 5 of the 20 candidates supply their settings implicitly, by
|
|
315
|
+
passing a plain object in `bindings` and letting `encoder_prepare_shader_binding` allocate. The other
|
|
316
|
+
~15 have an explicit `allocateTransientValueBuffer` or `graph_import_typed_buffer` call that
|
|
317
|
+
pre-builds the buffer and passes the `GPUBuffer`. Those need two edits: the declaration, and the call
|
|
318
|
+
site that stops pre-allocating.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 7. Candidate inventory
|
|
323
|
+
|
|
324
|
+
Every struct named `*PUSH_CONSTANTS*`, sized by `WebGPUStruct` (the same natural-alignment layout the
|
|
325
|
+
`immediate` address space uses). Sizes were re-derived programmatically — field lists extracted from
|
|
326
|
+
source and fed back through the real `WebGPUStruct.from`, not computed by hand.
|
|
327
|
+
|
|
328
|
+
| Struct | Size | Verdict |
|
|
329
|
+
|---|---:|---|
|
|
330
|
+
| `shader_read_out_bucket` | 4 | fits |
|
|
331
|
+
| `shader_generate_rays` | 4 | fits |
|
|
332
|
+
| `shader_shadow_upscale` | 4 | fits |
|
|
333
|
+
| `shader_sharc_evict` | 8 | fits |
|
|
334
|
+
| `shader_lightmap_accummulate_tile` | 8 | fits |
|
|
335
|
+
| `shader_lightmap_tile_composit` | 8 | fits |
|
|
336
|
+
| `graph_lpv_store_sh` | 16 | fits |
|
|
337
|
+
| `shader_perfect_perspective` | 16 | fits, see note |
|
|
338
|
+
| `graph_lvp_atlas_to_sh` | 20 | fits |
|
|
339
|
+
| `LPV_ACCUMULATION_PUSH_CONSTANTS` | 20 | fits |
|
|
340
|
+
| `shader_lpv_accumulate_depth` | 20 | fits |
|
|
341
|
+
| `graph_sample_light_probes` | 20 | fits |
|
|
342
|
+
| `graph_reduce_atlas_to_sh` | 24 | fits |
|
|
343
|
+
| `shader_lpv_build_gbuffer` | 24 | fits |
|
|
344
|
+
| `shader_update_clipmap_cascade` | 32 | fits |
|
|
345
|
+
| `graph_lpv_shade` | 32 | fits |
|
|
346
|
+
| `shader_build_scene_sdf_f32` | 32 | fits |
|
|
347
|
+
| `shader_scene_sdf_debug_viz` | 32 | fits |
|
|
348
|
+
| `shader_scene_sdf_soft_shadows` | 48 | fits |
|
|
349
|
+
| `LIGHT_CLUSTER_ASSIGNMENT_PUSH_CONSTANTS` | 128 | **cannot move** |
|
|
350
|
+
|
|
351
|
+
`LIGHT_CLUSTER_ASSIGNMENT_PUSH_CONSTANTS` fails twice: 128 bytes against a 64-byte budget, and
|
|
352
|
+
`frustum: WebGPUArray.from(vec4f, 6)` is an array, which the `immediate` address space disallows
|
|
353
|
+
outright. It stays a uniform buffer. If the rest is worth moving later, the split is
|
|
354
|
+
`{cluster_params, screen_resolution, distance_min, distance_max}` at 32 bytes as an immediate with the
|
|
355
|
+
frustum left in a buffer — a follow-up, not part of this landing.
|
|
356
|
+
|
|
357
|
+
Note on `shader_perfect_perspective`: its `use_vignette` member is typed `bool`, and Shade's `bool`
|
|
358
|
+
primitive (`WEBGPU_WGSL_PRIMITIVE_TYPES.js:8`) is a 4-byte type. WGSL's host-shareable set is numeric
|
|
359
|
+
scalars, vectors, matrices, atomics, arrays and structs of those — `bool` is in none of them, in *any*
|
|
360
|
+
address space, uniform included. So this is a pre-existing condition rather than something immediates
|
|
361
|
+
introduce, but the member needs to become `u32` before this struct can move. Worth checking whether
|
|
362
|
+
that shader is reachable at all; grep finds no importer.
|
|
363
|
+
|
|
364
|
+
Beyond the named ones, the 12 bare-`u32` `allocateTransientValueBuffer` sites are the same pattern
|
|
365
|
+
without the naming, and are equally good candidates.
|
|
366
|
+
|
|
367
|
+
### What must not move
|
|
368
|
+
|
|
369
|
+
`CAMERA_UNIFORM_STRUCT` (~640 bytes: eight `mat4x4f`, a six-element `vec4f` frustum array, and more)
|
|
370
|
+
and `VIEW_UNIFORM_STRUCT` account for 116 of the 300 `addUniform` calls. They are per-view, written
|
|
371
|
+
once, read by everything — over budget, containing arrays, and not per-draw data. They stay. Same for
|
|
372
|
+
anything reached through `GPUDatabase` or `GPUSingleTypeTable`.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## 8. What was built
|
|
377
|
+
|
|
378
|
+
Landed in one pass, all against a green suite (14,873 tests). Deviations from the plan above are
|
|
379
|
+
noted where they occurred.
|
|
380
|
+
|
|
381
|
+
**Mock device** — `SoftwareGPUImmediateData.js` holds WebGPU's `[[immediate_data]]` /
|
|
382
|
+
`[[immediate_slots_set]]`; `setImmediates` on both pass encoders, with content-timeline failures
|
|
383
|
+
throwing `OperationError` and device-timeline failures invalidating the encoder through a new
|
|
384
|
+
`SoftwareGPUCommandEncoder#notify_pass_validation_error`; `maxImmediateSize` raised 0 → 64;
|
|
385
|
+
`createPipelineLayout` validates `immediateSize`. Draws and dispatches now run WebGPU's *Validate
|
|
386
|
+
immediate data*, so a forgotten `setImmediates` is caught here rather than in a browser.
|
|
387
|
+
|
|
388
|
+
**Emulator** — `WGSLJavaScriptCompiler` routes `var<immediate>` into `globalBindings` instead of
|
|
389
|
+
letting it fall through to `privateGlobals`, where it was silently default-initialised. Pinned by a
|
|
390
|
+
spec that fails without the fix.
|
|
391
|
+
|
|
392
|
+
**Validator** — *no change needed.* The plan assumed `validate_wgsl_source` would have to learn about
|
|
393
|
+
`requires` and `var<immediate>`; it already accepts both. A regression test pins that so a future
|
|
394
|
+
rule cannot start false-positiving on them.
|
|
395
|
+
|
|
396
|
+
**Pipeline layout** — `PipelineLayoutDescriptor.immediateSize`, through `from`/`forStage`/`fromJSON`/
|
|
397
|
+
`hash`/`equals`/`copy`, and passed to `createPipelineLayout` by the manager.
|
|
398
|
+
|
|
399
|
+
**Codegen** — `CodeChunk#addLanguageExtension` as a separate channel from `addExtension`, emitted as
|
|
400
|
+
`requires` rather than `enable` and deliberately not folded into `#features` (a different namespace
|
|
401
|
+
from `GPUFeatureName`). `ShaderResourceSetDescriptor#setImmediate` validates size, arrays at any
|
|
402
|
+
depth, atomics and runtime sizing at declaration time.
|
|
403
|
+
|
|
404
|
+
**Binding** — `writeImmediate(map, buffer)` writes into a caller-owned `BinaryBuffer` at its
|
|
405
|
+
`position` via the already-public `write_wgsl_type_value`, allocating nothing.
|
|
406
|
+
`ShadeGPUCommandContext` owns one such buffer for the life of the context.
|
|
407
|
+
`GraphicsContext#setPipelineBindings` took an optional fourth parameter, so all four existing callers
|
|
408
|
+
are untouched — `generateBindings` kept its return shape and its 10 callers were not disturbed.
|
|
409
|
+
|
|
410
|
+
**FrameGraph** — the plan missed this entirely. `getResourceByName` only searches groups, so the
|
|
411
|
+
graph path threw for any immediate; and worse, `graph_prepare_pass_input_data` and
|
|
412
|
+
`graph_prepare_bindings` read a numeric input as a graph resource id, which would have silently
|
|
413
|
+
misread a bare `u32` immediate as a handle. `isImmediateName` now short-circuits all three helpers.
|
|
414
|
+
Pinned by a spec that fails without it.
|
|
415
|
+
|
|
416
|
+
**Detection** — `immediate_data_supported()` / `device_supports_immediate_size()`, plus a budget
|
|
417
|
+
check in `ShaderDescriptor.validate_against_device`.
|
|
418
|
+
|
|
419
|
+
### Not done
|
|
420
|
+
|
|
421
|
+
- **No production shader migrated.** The 19 candidates in §7 are unchanged; this is the machinery
|
|
422
|
+
only. `shader_read_out_bucket` remains the right first patient.
|
|
423
|
+
- **`shader_perfect_perspective`'s `bool` member** is still a `bool` (§7's note). Pre-existing, and
|
|
424
|
+
it blocks only that one struct.
|
|
425
|
+
- **The eight `setBindGroup` bypass paths** (§6, L3) are untouched. No candidate lives there.
|
|
426
|
+
|
|
427
|
+
## 9. Transition candidates, proved
|
|
428
|
+
|
|
429
|
+
Every candidate below was **actually transitioned** — declaration and call site edited in the working
|
|
430
|
+
tree — then proved, then reverted. This section records what the proof showed, not what inspection
|
|
431
|
+
suggested. The tier-1 entries were carried all the way through; the rest were assessed against the
|
|
432
|
+
same checks without being applied.
|
|
433
|
+
|
|
434
|
+
### What "proved" means here
|
|
435
|
+
|
|
436
|
+
The six checks each applied transition had to pass:
|
|
437
|
+
|
|
438
|
+
1. **Module imports.** These shaders build at module scope, so a rejected type, a broken declaration
|
|
439
|
+
or bad codegen throws at import.
|
|
440
|
+
2. **`immediateSize` is what the struct says.**
|
|
441
|
+
3. **Generated WGSL declares `var<immediate>` with no `@group`/`@binding`, and emits `requires`.**
|
|
442
|
+
4. **`validate_wgsl_source` returns no diagnostics** on the generated source.
|
|
443
|
+
5. **`pipeline.layout.immediateSize` matches**, so a device would accept the pipeline.
|
|
444
|
+
6. **A value round-trips** through `writeImmediate` into the expected words.
|
|
445
|
+
|
|
446
|
+
Plus, across the whole set: the settings name must no longer resolve through `getResourceByName` or
|
|
447
|
+
appear in any group — a stale uniform left beside a new immediate would bind twice.
|
|
448
|
+
|
|
449
|
+
**What the proof does not cover, and why the scores are not higher.** *None of these shaders has any
|
|
450
|
+
spec coverage* — `grep -rl <name> --include=*.spec.js` returns zero for every one, and for their call
|
|
451
|
+
sites too. The full suite passing with all five applied (14,887 tests) therefore says only that
|
|
452
|
+
nothing *else* broke. Nothing executes these passes outside a browser, so the call-site edit is
|
|
453
|
+
verified by inspection and by the diff being a verbatim move, not by running it.
|
|
454
|
+
|
|
455
|
+
**The one silent failure mode.** A misspelled *field* inside the value object is not an error:
|
|
456
|
+
`write_gpu_typed_buffer` writes 0 for a member it cannot find. Probed and confirmed —
|
|
457
|
+
`{bucket_ID: 11, frame: 2}` against `{bucket_id, frame}` yields words `[0, 2]`, no throw. This is
|
|
458
|
+
pre-existing behaviour that immediates inherit rather than introduce (the same is true of
|
|
459
|
+
`allocateTransientValueBuffer` today), but it is why every transition below moves the object literal
|
|
460
|
+
**verbatim** rather than retyping it, and why the diffs are worth reading rather than trusting.
|
|
461
|
+
|
|
462
|
+
### Tier 1 — transitioned and proved end to end
|
|
463
|
+
|
|
464
|
+
| Shader | Bytes | Call-site change | Group effect | Net lines | Confidence |
|
|
465
|
+
|---|---:|---|---|---:|---:|
|
|
466
|
+
| `shader_sharc_evict` | 8 | **none** — already passes a plain object | leads a group of 4; siblings shift down one | −0 | **95%** |
|
|
467
|
+
| `shader_shadow_upscale` | 4 | **none** — already passes a plain object | leads a group of 5; siblings shift down one | −0 | **95%** |
|
|
468
|
+
| `shader_read_out_bucket` | 4 | 2 sites, inline `{ bucket_id }` | **owns group 0 outright** — group disappears, later groups renumber | −8 | **90%** |
|
|
469
|
+
| `shader_generate_rays` | 4 | 1 site, inline `{ frame_index }` | **last in its group** — no sibling index moves | −4 | **92%** |
|
|
470
|
+
| `graph_lvp_atlas_to_sh` | 20 | 1 site, inline 5-field object | owns group 0 outright — group disappears | −5 | **85%** |
|
|
471
|
+
|
|
472
|
+
The two at 95% need *no call-site edit at all*: they already pass `settings: { … }` as a plain
|
|
473
|
+
object, because the immediate path accepts a value where the uniform path demands a bindable buffer.
|
|
474
|
+
Their entire diff is one line in the declaration. The residual 5% is that no test executes them.
|
|
475
|
+
|
|
476
|
+
`shader_read_out_bucket` scores lower than `shader_generate_rays` despite being smaller: removing its
|
|
477
|
+
uniform empties group 0, so every later group renumbers. That is consistent between the WGSL and the
|
|
478
|
+
layout because both regenerate from the same descriptor, and it is why it still scores 90 — but it is
|
|
479
|
+
a larger blast radius than a declaration that sits last in a shared group and moves nothing.
|
|
480
|
+
|
|
481
|
+
`graph_lvp_atlas_to_sh` is the lowest of the tier because its value object has five fields drawn from
|
|
482
|
+
four different sources, which is the most surface for the silent-misspelling failure above.
|
|
483
|
+
|
|
484
|
+
### Tier 2 — same shape, not yet applied
|
|
485
|
+
|
|
486
|
+
Assessed against the same checks by inspection. All are live (one importer each), all fit, none
|
|
487
|
+
contains an array.
|
|
488
|
+
|
|
489
|
+
| Shader | Bytes | Notes | Confidence |
|
|
490
|
+
|---|---:|---|---:|
|
|
491
|
+
| `graph_lpv_store_sh` | 16 | leads a group of 3 | 80% |
|
|
492
|
+
| `graph_reduce_atlas_to_sh` | 24 | owns its group | 80% |
|
|
493
|
+
| `shader_lpv_build_gbuffer` | 24 | ImageShader | 75% |
|
|
494
|
+
| `shader_lpv_accumulate_depth` | 20 | ImageShader, group of 5 | 75% |
|
|
495
|
+
| `graph_lpv_shade` | 32 | group of 3 | 75% |
|
|
496
|
+
| `graph_sample_light_probes` | 20 | ImageShader, group of 7 | 70% |
|
|
497
|
+
| `shader_build_scene_sdf_f32` | 32 | **two** resource sets (f32 and f16) share one struct; both must move together | 65% |
|
|
498
|
+
|
|
499
|
+
`LPV_ACCUMULATION_PUSH_CONSTANTS` (20 bytes, 5 importers) is deliberately absent. It is shared across
|
|
500
|
+
four shaders and supplied through `graph_import_typed_buffer`, so it is not a per-shader edit — it is
|
|
501
|
+
the `graph_import_typed_buffer` conversion described in §3, which changes how the consuming pass
|
|
502
|
+
declares its input. Higher payoff, different job.
|
|
503
|
+
|
|
504
|
+
### Not worth doing
|
|
505
|
+
|
|
506
|
+
Six candidates have **zero importers** — nothing in the tree constructs them:
|
|
507
|
+
`shader_lightmap_tile_composit`, `shader_lightmap_accummulate_tile`, `shader_update_clipmap_cascade`,
|
|
508
|
+
`shader_perfect_perspective`, `shader_scene_sdf_debug_viz`, `shader_scene_sdf_soft_shadows`.
|
|
509
|
+
|
|
510
|
+
They would transition trivially and change nothing that runs. `shader_perfect_perspective` also still
|
|
511
|
+
carries the `bool` member from §7. Migrating dead shaders would inflate the count and prove nothing;
|
|
512
|
+
they are listed so nobody spends a morning on them.
|
|
513
|
+
|
|
514
|
+
`LIGHT_CLUSTER_ASSIGNMENT_PUSH_CONSTANTS` remains excluded on both counts — 128 bytes and an array.
|
|
515
|
+
|
|
516
|
+
### Recommended order
|
|
517
|
+
|
|
518
|
+
`shader_sharc_evict` first, then `shader_shadow_upscale`: one line each, no call site, and they
|
|
519
|
+
establish the pattern with the smallest possible diff. `shader_generate_rays` next, as the first with
|
|
520
|
+
a call-site edit. Then `shader_read_out_bucket`, which is the first to renumber groups and therefore
|
|
521
|
+
the first worth looking at in a browser before moving on.
|
|
522
|
+
|
|
523
|
+
Before any of them lands in a build anyone plays: run one in Chrome 149+. Nothing in the suite
|
|
524
|
+
executes these passes, and that gap is the whole of the residual risk.
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## 10. If a fallback is ever wanted anyway
|
|
529
|
+
|
|
530
|
+
It belongs in a shader variant, not in the binding machinery — and Shade already supports that with
|
|
531
|
+
no new mechanism. `ShaderDescriptor.compile()` clones the body chunk and adds the resource set's
|
|
532
|
+
generated chunk as a dependency:
|
|
533
|
+
|
|
534
|
+
```js
|
|
535
|
+
const chunk = this.#body.clone();
|
|
536
|
+
chunk.addDependency(this.#resources.generateCodeChunk());
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
So one `CodeChunk` body pairs with two different `ShaderResourceSetDescriptor`s to produce two
|
|
540
|
+
`ShaderDescriptor`s — immediate and uniform — from a single source of shader text, selected by
|
|
541
|
+
whoever constructs them. The body never changes: `settings.probe_count` reads identically either way.
|
|
542
|
+
|
|
543
|
+
That is a per-shader, opt-in decision made by the shader's author, which is a very different thing
|
|
544
|
+
from a capability branch threaded through codegen, pipeline layout, binding resolution and the test
|
|
545
|
+
harness. Keeping the door open costs nothing; walking through it preemptively cost a great deal.
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## 11. Open questions
|
|
550
|
+
|
|
551
|
+
- **Which shader migrates first, and when?** Answered in §9: `shader_sharc_evict` and
|
|
552
|
+
`shader_shadow_upscale` need no call-site edit at all. The machinery is inert until one lands.
|
|
553
|
+
- **Do these passes get a spec before or after they migrate?** None of the twelve live candidates has
|
|
554
|
+
any test coverage, which is the entire residual risk in §9. A spec that drives one through the
|
|
555
|
+
software device would convert several 75s into 90s, and is probably worth more than the next three
|
|
556
|
+
migrations.
|
|
557
|
+
- **Is 64 bytes worth revisiting later?** `maxImmediateSize` is raisable, but D3D12's root signature
|
|
558
|
+
budget means no browser is likely to expose more. `IMMEDIATE_SIZE_FLOOR` refuses more than 64 at
|
|
559
|
+
declaration; if a project ever knows its device offers more, that constant is the single place to
|
|
560
|
+
relax.
|
|
561
|
+
- **Should `graph_import_typed_buffer`'s two-buffer dance be fixed independently?** §3 argues its 21
|
|
562
|
+
call sites are the strongest case for immediates, but the standing TODO there is a separate bug —
|
|
563
|
+
the 2025-09-13 note says the obvious fix produced garbage and was never chased down. Immediates
|
|
564
|
+
route around it for the ≤64-byte cases without explaining it.
|