@woosh/meep-engine 3.60.4 → 3.60.6

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.
Files changed (419) hide show
  1. package/CHANGELOG.md +2621 -0
  2. package/CONTRIBUTING.md +7 -0
  3. package/MIGRATION_2_TO_3.md +136 -0
  4. package/SECURITY.md +4 -0
  5. package/editor/EditorCameraNavigation.d.ts +2 -1
  6. package/editor/EditorCameraNavigation.d.ts.map +1 -1
  7. package/editor/EditorCameraNavigation.js +51 -57
  8. package/package.json +6 -2
  9. package/src/engine/EngineHarness.d.ts +3 -9
  10. package/src/engine/EngineHarness.d.ts.map +1 -1
  11. package/src/engine/EngineHarness.js +6 -8
  12. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts +9 -6
  13. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts.map +1 -1
  14. package/src/engine/graphics/camera/makeOrbitalCameraController.js +44 -71
  15. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts +64 -0
  16. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts.map +1 -0
  17. package/src/engine/graphics/camera/orbital/MomentumAxis.js +187 -0
  18. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts +86 -0
  19. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts.map +1 -0
  20. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.js +319 -0
  21. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +34 -10
  22. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
  23. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +116 -30
  24. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +1 -7
  25. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  26. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +1 -37
  27. package/src/engine/graphics/ecs/water/Water.d.ts +8 -0
  28. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  29. package/src/engine/graphics/ecs/water/Water.js +8 -0
  30. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  31. package/src/engine/graphics3/WaterSystem.js +731 -734
  32. package/src/engine/graphics3/decal/DecalTileShader.d.ts +61 -0
  33. package/src/engine/graphics3/decal/DecalTileShader.d.ts.map +1 -0
  34. package/src/engine/graphics3/decal/DecalTileShader.js +138 -0
  35. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts +10 -4
  36. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
  37. package/src/engine/graphics3/decal/GPUDecalRenderer.js +149 -44
  38. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts +25 -0
  39. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts.map +1 -0
  40. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.js +55 -0
  41. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts +22 -0
  42. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts.map +1 -0
  43. package/src/engine/graphics3/decal/graph_gather_decal_tiles.js +70 -0
  44. package/src/engine/graphics3/decal/shader_decal_composite.d.ts +7 -5
  45. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  46. package/src/engine/graphics3/decal/shader_decal_composite.js +48 -44
  47. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts +12 -0
  48. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts.map +1 -0
  49. package/src/engine/graphics3/decal/shader_decal_tile_copy.js +78 -0
  50. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts +11 -0
  51. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts.map +1 -0
  52. package/src/engine/graphics3/decal/shader_gather_decal_tiles.js +80 -0
  53. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts +13 -0
  54. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts.map +1 -0
  55. package/src/engine/input/devices/mouse/MouseEventButtons.js +23 -0
  56. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts +4 -0
  57. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts.map +1 -1
  58. package/src/engine/input/ecs/controllers/KeyboardCameraController.js +9 -45
  59. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  60. package/src/shade/playground/water_refraction/README.md +5 -6
  61. package/src/shade/playground/water_refraction/index.html +1 -1
  62. package/src/shade/renderer/Renderer.d.ts +11 -0
  63. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  64. package/src/shade/renderer/Renderer.js +50 -6
  65. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
  66. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
  67. package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
  68. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  69. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  70. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  71. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  72. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  73. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +28 -45
  74. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts.map +1 -1
  75. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +111 -119
  76. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +22 -3
  77. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
  78. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +84 -3
  79. package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.d.ts +19 -0
  80. package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.d.ts.map +1 -0
  81. package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.js +25 -0
  82. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
  83. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
  84. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
  85. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +15 -0
  86. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -1
  87. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +38 -0
  88. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
  89. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
  90. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
  91. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
  92. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
  93. package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.d.ts +28 -59
  94. package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.d.ts.map +1 -1
  95. package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.js +84 -789
  96. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -8
  98. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +7 -6
  99. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
  100. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +12 -11
  101. package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.d.ts +2 -2
  102. package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.js +2 -2
  103. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +1 -4
  104. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  105. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +1 -10
  106. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
  107. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
  108. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
  109. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
  110. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +1 -7
  111. package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
  112. package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
  113. package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
  114. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
  115. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
  116. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
  117. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
  118. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
  119. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
  120. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
  121. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  122. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
  123. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
  124. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
  125. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
  126. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
  127. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
  128. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
  129. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
  130. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
  131. package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
  132. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
  133. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  134. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
  135. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
  136. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
  137. package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
  138. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  139. package/src/shade/renderer/material/GPUMaterialManager.d.ts +11 -1
  140. package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
  141. package/src/shade/renderer/material/GPUMaterialManager.js +16 -8
  142. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
  143. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  144. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
  145. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +7 -3
  146. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
  147. package/src/shade/renderer/postprocess/gtao/GTAO.js +26 -13
  148. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts +14 -0
  149. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
  150. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +196 -68
  151. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
  152. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +42 -28
  153. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
  154. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +429 -414
  155. package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.d.ts +16 -0
  156. package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.d.ts.map +1 -0
  157. package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.js +156 -0
  158. package/src/shade/renderer/scene/GPUSceneContext.d.ts +2 -2
  159. package/src/shade/renderer/scene/GPUSceneContext.js +5 -5
  160. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  161. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
  162. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
  163. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  164. package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
  165. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
  166. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
  167. package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
  168. package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
  169. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  170. package/src/shade/renderer/water/shader_water.js +22 -13
  171. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
  172. package/src/shade/renderer/water/shader_water_avboit.js +7 -7
  173. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
  174. package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
  175. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
  176. package/editor/REWORK_PLAN_2026_08.md +0 -692
  177. package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
  178. package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
  179. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
  180. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
  181. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
  182. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
  183. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
  184. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
  185. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
  186. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
  187. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
  188. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
  189. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
  190. package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
  191. package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
  192. package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
  193. package/src/REVIEW_2026_08_06/adv-core.md +0 -511
  194. package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
  195. package/src/REVIEW_2026_08_06/adv-view.md +0 -446
  196. package/src/REVIEW_2026_08_06/api-core.md +0 -296
  197. package/src/REVIEW_2026_08_06/api-engine.md +0 -195
  198. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
  199. package/src/REVIEW_2026_08_06/core-binary.md +0 -348
  200. package/src/REVIEW_2026_08_06/core-collection.md +0 -376
  201. package/src/REVIEW_2026_08_06/core-math.md +0 -221
  202. package/src/REVIEW_2026_08_06/core-process.md +0 -266
  203. package/src/REVIEW_2026_08_06/core-science.md +0 -223
  204. package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
  205. package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
  206. package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
  207. package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
  208. package/src/REVIEW_2026_08_06/engine-input.md +0 -197
  209. package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
  210. package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
  211. package/src/REVIEW_2026_08_06/generation.md +0 -176
  212. package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
  213. package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
  214. package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
  215. package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
  216. package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
  217. package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
  218. package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
  219. package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
  220. package/src/REVIEW_2026_08_06/view.md +0 -667
  221. package/src/REVIEW_2026_08_06.md +0 -610
  222. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
  223. package/src/core/bvh2/bvh3/NOTES.md +0 -2
  224. package/src/core/bvh8/NOTES.md +0 -63
  225. package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
  226. package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
  227. package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
  228. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
  229. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
  230. package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
  231. package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
  232. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
  233. package/src/core/geom/3d/gjk/NOTES.md +0 -8
  234. package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
  235. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
  236. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
  237. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
  238. package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
  239. package/src/engine/REVIEW_2026_07_03.md +0 -211
  240. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
  241. package/src/engine/control/first-person/DESIGN.md +0 -658
  242. package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
  243. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
  244. package/src/engine/control/first-person/TODO.md +0 -160
  245. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
  246. package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
  247. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
  248. package/src/engine/ecs/ik/PLAN.md +0 -179
  249. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
  250. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
  251. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
  252. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
  253. package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
  254. package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
  255. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
  256. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
  257. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
  258. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
  259. package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
  260. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
  261. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
  262. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
  263. package/src/engine/network/CONGESTION_CONTROL.md +0 -87
  264. package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
  265. package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
  266. package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
  267. package/src/engine/physics/cloth/PLAN.md +0 -3056
  268. package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
  269. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
  270. package/src/engine/physics/fluid/solver/NOTES.md +0 -46
  271. package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
  272. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
  273. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
  274. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
  275. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
  276. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
  277. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
  278. package/src/format/image/avif/DECISIONS.md +0 -545
  279. package/src/format/image/jpeg/NOTES.md +0 -3
  280. package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
  281. package/src/generation/COORDINATES.md +0 -54
  282. package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
  283. package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
  284. package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
  285. package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
  286. package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
  287. package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
  288. package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
  289. package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
  290. package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
  291. package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
  292. package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
  293. package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
  294. package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
  295. package/src/shade/RENDERER_CONTRACT.md +0 -697
  296. package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
  297. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
  298. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
  299. package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
  300. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
  301. package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
  302. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
  303. package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
  304. package/src/shade/renderer/NOTES.md +0 -78
  305. package/src/shade/renderer/PLAN.md +0 -8
  306. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
  307. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
  308. package/src/shade/renderer/atmosphere/NOTES.md +0 -6
  309. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
  310. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
  311. package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
  312. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
  313. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
  314. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
  315. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
  316. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
  317. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
  318. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
  319. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
  320. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
  321. package/src/shade/renderer/camera/orbital/README.md +0 -81
  322. package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
  323. package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
  324. package/src/shade/renderer/camera/orbital/index.js +0 -9
  325. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
  326. package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
  327. package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
  328. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
  329. package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
  330. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
  331. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
  332. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
  333. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
  334. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -481
  335. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
  336. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
  337. package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
  338. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
  339. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
  340. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
  341. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
  342. package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -529
  343. package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.d.ts +0 -47
  344. package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.d.ts.map +0 -1
  345. package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.js +0 -80
  346. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.d.ts +0 -16
  347. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.d.ts.map +0 -1
  348. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.js +0 -67
  349. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.d.ts +0 -17
  350. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.d.ts.map +0 -1
  351. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.js +0 -145
  352. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.d.ts +0 -12
  353. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.d.ts.map +0 -1
  354. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.js +0 -105
  355. package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
  356. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4306
  357. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
  358. package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
  359. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
  360. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
  361. package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
  362. package/src/shade/renderer/hiz/NOTES.md +0 -3
  363. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
  364. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
  365. package/src/shade/renderer/light/cluster/NOTES.md +0 -11
  366. package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
  367. package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
  368. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
  369. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
  370. package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
  371. package/src/shade/renderer/particles/DESIGN.md +0 -1011
  372. package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -280
  373. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
  374. package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
  375. package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
  376. package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
  377. package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
  378. package/src/shade/renderer/postprocess/nss/NOTES.md +0 -332
  379. package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
  380. package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
  381. package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
  382. package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
  383. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
  384. package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
  385. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
  386. package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
  387. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
  388. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
  389. package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
  390. package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
  391. package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -391
  392. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -153
  393. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
  394. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
  395. package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
  396. package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
  397. package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
  398. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
  399. package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
  400. package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
  401. package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
  402. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -927
  403. package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
  404. package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
  405. package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
  406. package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
  407. package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
  408. package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
  409. package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
  410. package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
  411. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
  412. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
  413. package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -3
  414. package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
  415. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
  416. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
  417. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
  418. package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
  419. package/src/shade/wgsl/NOTES.md +0 -4
@@ -1,927 +0,0 @@
1
- # Shader switches: a proposal (2026-10-16, revision c; stages 1 and 2 landed)
2
-
3
- **Status: stages 1 and 2 are on master (2026-10-16). Stage 3, declared bindings, is built in a worktree, uncommitted
4
- (§12.3).**
5
-
6
- §0 records your decisions, §4–§6 the design and its stages, and §12 what each stage contains and how it was checked. Revisions (a) and (b) are superseded by your answers.
7
-
8
- ## 0. Decided so far
9
-
10
- From your answers of 2026-10-16:
11
-
12
- - **D1 — a switch is an integer.** A boolean is a switch of two values. A shader depends on several switches. A
13
- switch is a value, not a chunk, and one switch may be decided at several places in the graph. (Confirmed.)
14
- - **D2 — a switched shader specializes to a static graph.** A switched shader and an immutable set of switch values
15
- become an ordinary, fully static `CodeChunk` graph. The shader object caches its variants by the set.
16
- `GraphicsContext` knows nothing of switches.
17
- - **D3 — every switch defaults to 0.**
18
- - **D4 — code first, the kludges fixed after.** In the first stage, alternatives differ in code only. The long-term
19
- design still has to remove the existing kludges, resources included, and a refactor is fine.
20
-
21
- From your answers to revision (b):
22
-
23
- - **D5 — resources: one interface per definition (§5.2).** It is the union of its alternatives' bindings, bound by
24
- name, with defaults for what a mode lacks. Stage 3's declared bindings are wanted later; they will change the shape
25
- of a shader class a little.
26
- - **D6 — no flip machinery.** Pipelines made on first use compile synchronously, as they do today. A flip compiles the
27
- new variants then, and nothing extra is built for it.
28
- - **D7 — a variant's label carries its set:** `Shade From G-Buffer [shadow directional technique=Virtual]`.
29
- - **D8 — no interface declaration.** A choice never inspects its alternatives, and the parent never white-boxes what
30
- sits behind a switch. The per-value specs are what link every alternative into its real shaders.
31
- - **D9 — the API is `ShaderSwitch.enum(label, Enum)` and `ShaderSwitch.bool(label)`,** one mechanism underneath. A
32
- switch's values are its own: the enum's members, or `false`/`true`. Sets, choices, labels and errors use them, and
33
- errors and labels name the member.
34
- - *My call, inside D3:* an enum must have a member equal to 0, the default, and one without is refused at
35
- `enum()`. A bool's default is `false`. If you would rather the first-declared member be the default, it is a
36
- one-line change.
37
-
38
- What follows from those:
39
-
40
- - **Pins are gone.** Their only use was a pair of exports with different bindings. §5 makes one definition serve every
41
- value, and a pipeline that wants another value passes another set.
42
- - **The linker is untouched.** `compile()` links static graphs only, and refuses a graph that still holds a choice.
43
- Revision (a) threaded the set through `compile()`; this revision does not.
44
-
45
- ## 1. Summary
46
-
47
- ```js
48
- // a switch over an engine enum, 0 by default; or ShaderSwitch.bool(label), false by default
49
- export const ShadowDirectionalTechnique = { Cascades: 0, Virtual: 1 };
50
- export const SHADOW_DIRECTIONAL_TECHNIQUE = ShaderSwitch.enum('shadow directional technique', ShadowDirectionalTechnique);
51
-
52
- // where a graph decides it: the export every importer already uses
53
- export const chunk_shadowmap_sample_directional = CodeChunkChoice.from(SHADOW_DIRECTIONAL_TECHNIQUE);
54
- chunk_shadowmap_sample_directional.add(ShadowDirectionalTechnique.Cascades, chunk_shadowmap_sample_directional_cascades);
55
- chunk_shadowmap_sample_directional.add(ShadowDirectionalTechnique.Virtual, chunk_shadowmap_sample_directional_virtual);
56
-
57
- // an immutable, interned set of values; a switch it does not name is at its default
58
- const switches = ShaderSwitchSet.EMPTY.with(SHADOW_DIRECTIONAL_TECHNIQUE, ShadowDirectionalTechnique.Virtual);
59
-
60
- // the graph, specialized: every choice replaced by its selected alternative, so no choice is left
61
- const static_body = body.specialize(switches);
62
-
63
- // the shader caches its variants by the set; a variant is an ordinary static shader
64
- shader_deferred_main.variant(switches).draw({ /* … */ });
65
- shader_deferred_main.descriptor.switches; // [SHADOW_DIRECTIONAL_TECHNIQUE]
66
- ```
67
-
68
- For the case that prompted this, `chunk_shadowmap_sample_directional` becomes a choice between `_cascades` and
69
- `_virtual`. None of the chunks above it changes, and none of the nine shaders that reach it changes its chunk code.
70
-
71
- Measured on the real `Shade From G-Buffer` module (§7):
72
-
73
- - **Cascades** drops exactly the 14 VSM functions and 4 VSM declarations: 96 → 82 functions, 69,917 → 59,892
74
- characters.
75
- - **Virtual** drops the 2-function cascade walk: 94 functions.
76
- - In both, only `shadowmap_sample_directional` changes body.
77
- - **Tint** (Chrome, RTX 4090) compiles all three with no messages.
78
- - **Pipeline creation** in Chrome takes 0.81–0.83× (cascades) and 0.66–0.70× (virtual) of the shipped shader's.
79
- - **Cost of a variant:** specializing plus linking takes 0.075–0.083 ms across runs, the same as linking the shipped
80
- shader today.
81
- A repeat `variant(set)` lookup takes 0.1 µs and allocates nothing.
82
-
83
- The deferred GPU time measured earlier on the same variants (as hand-made twins) was 0.936× for cascades and 0.957×
84
- for virtual, with byte-identical images.
85
-
86
- **The long-term shape (§5–§6).** Each switched shader is one definition. It declares every binding any of its
87
- alternatives reads, and a binding a mode does not have falls back to a declared default. Owners bind by name and
88
- hand the renderer's set to `variant()`. That removes, in three stages:
89
-
90
- - the two IBL/Brick4 forward-fragment twins;
91
- - the paired AVBOIT/MBOIT/water exports and the `use_brick4 ? […] : […]` bind-entry arrays;
92
- - the `build_deferred_main_shader` and `build_ssr_resolve_shader` factories;
93
- - the per-mode froxel GI shaders;
94
- - water's hidden GI dependency;
95
- - the five owners' branches on `indirect_lighting_mode`.
96
-
97
- Open questions are in §11.
98
-
99
- ## 2. What the engine does today
100
-
101
- There are four ways a shader chooses an implementation today. None of them can be seen in the chunk graph.
102
-
103
- 1. **A runtime branch on a global choice.** `shadowmap_sample_directional` branches per light on
104
- `shadowmap.virtual_map != NULL`, though the technique is per renderer (`Renderer.feature_virtual_shadow_maps` →
105
- `GPUSceneShadowmapContext.use_virtual_shadow_maps`). Both paths are linked into nine shader definitions:
106
- - deferred heavy and deferred ReSTIR (`shader_deferred_main.js`);
107
- - AVBOIT draw IBL and Brick4 (`TemplatePassDescriptor`, one pipeline per material template);
108
- - MBOIT resolve IBL and Brick4;
109
- - volumetrics build-lighting and build-irradiance;
110
- - water finalize.
111
-
112
- The chain is `chunk_shadowmap_sample_directional` → `chunk_shadowmap_get_light_visibility` →
113
- `chunk_shade_directional_lights` → `chunk_shade_standard_material_direct` (and `_restir`) → the shader bodies.
114
- Volumetrics and water optics reach it from `chunk_shadowmap_get_light_visibility` directly.
115
- 2. **A factory parameter at the root.** `build_deferred_main_shader({ label, shading_chunk, with_restir })` builds heavy
116
- and ReSTIR, and the Renderer picks the export by `feature_restir_di_enabled`. This is the `make_shader({ flag })`
117
- shape that `no-nested-shader-codegen` rules out. ReSTIR also adds a binding, `tRestirRadiance`.
118
- 3. **A twin of an intermediate chunk.** `chunk_forward_shade_standard_fragment_ibl` and `_brick4` are 178 and 185 lines
119
- that differ in 46 lines: the indirect-lighting block, plus comments that have already drifted apart. They feed
120
- `shader_avboit_draw_ibl` / `_brick4` and `shader_oit_resolve_moments_ibl` / `shader_oit_resolve_moments`.
121
- The owners pick by `indirect_lighting_mode` (`graph_rasterize_meshes_transparent_avboit.js:538`,
122
- `graph_rasterize_meshes_transparent_oit.js:150`) and build different bind groups, because Brick4 adds a `brick4`
123
- storage buffer to the view group.
124
- 4. **A function filled in at the root.** Water's `finalize_body` calls `water_gi_specular` and `water_gi_irradiance`
125
- without listing a dependency that defines them. `water_finalize_shader_ibl` / `_brick4` add `chunk_water_gi_ibl` or
126
- `_brick4` beside it (`shader_water.js:593-597`). Nothing needs a twin, but the dependency is invisible.
127
-
128
- A switch with its choices gives all four one declared form: pattern 1 in stage 1 (shadows), and patterns 2, 3 and 4
129
- in stage 2, once one definition can carry every alternative's bindings (§5).
130
-
131
- The engine already has an *open* interface of the same shape. A material template supplies `material_surface` and
132
- `material_opacity` to every pass by function name (`ShadeMaterialTemplate.surface` / `.opacity`), as Unreal's materials
133
- and vertex factories do. Switches are the *closed* counterpart: an engine-owned choice whose values the engine knows.
134
- Neither replaces the other.
135
-
136
- ## 3. What other engines do
137
-
138
- Read from primary docs and references. The rest comes from the research brief
139
- `shader-switch-prior-art.md` in the side-notes folder of this session group, which has the URLs and identifiers; its
140
- Bevy and gpuweb claims were re-checked against the pages.
141
-
142
- **Unreal: permutation domains.** A dimension is a C++ type (bool, int, range, sparse int, or enum), each carrying a preprocessor define name. Their product is a permutation domain whose permutation id is mixed-radix over the dimensions. A shader class lists its dimensions (for example, the deferred light pixel shader has 14 of them, including the virtual shadow map mask).
143
-
144
- - Unused combinations are culled before compilation (e.g., a directional light × the VSM mask is never compiled).
145
- - Redundant combinations are remapped together (e.g., visualize-culling forces the others off).
146
- - Conditional defines (such as VSM defines) are added to the compilation environment only when that dimension is on.
147
- - The draw site sets every dimension by hand, then asks the shader map for
148
- `(shader type, permutation id)`. **The owner knows every dimension.** Nothing derives the domain: the shader class lists it
149
- and the draw site sets it.
150
- - Dimensions select `#if` blocks. Dependencies are `#include`s, which can sit inside an `#if`, but nothing declares
151
- what each value pulls in.
152
-
153
- **Unreal: vertex factories and materials.** These are the other half of Unreal's answer, and they are an *interface
154
- slot*. The vertex factory compilation environment maps a virtual include path to the concrete implementation's source file.
155
-
156
- Every shader that includes the path gets the implementation the compile supplies, and nothing in between is twinned.
157
- The shader map is keyed by material × vertex factory × shader type × permutation.
158
-
159
- Note that Unreal's deferred light shader does not choose its *shadow technique* this way. VSM is projected into a
160
- screen-space mask beforehand, and the light shader only chooses whether to read the mask.
161
-
162
- **Unity: keywords.** A pass declares its keyword sets by hand with `#pragma multi_compile` / `shader_feature`
163
- (`_local`, `_vertex`/`_fragment` suffixes). A plain `#include` drops the pragmas of the file it includes, so a library's
164
- switches do not reach the shader that uses it. `#include_with_pragmas` exists to let them, and URP's `Lit.shader` uses
165
- it to pull in whole keyword bundles. That is Unity working its way toward a domain derived from the includes.
166
-
167
- - Since 2021.2 every declared keyword is local to the shader's `LocalKeywordSpace`. A `GlobalKeyword` is mapped into
168
- each shader's space by name.
169
- - Setting a keyword the shader does not declare does nothing. This design does the same: values for other switches
170
- are ignored.
171
- - Variants are looked up by closest match, and a missing one falls back silently. We should not copy that.
172
-
173
- **Bevy and naga_oil.** naga_oil passes one global map of shader defs to every module. Each module's `effective_defs`
174
- are collected by syntax: every name in an `#if`/`#ifdef`, whether or not its branch is taken, plus the effective defs of
175
- every import. So a module's domain is derived and transitive. The module is cached by a key made only of those defs
176
- (`ModuleKey::from_members`). That is this design's `descriptor.switches` and its key, arrived at independently.
177
-
178
- The code itself is chosen by `#ifdef` inside the text, the conditional-template shape this engine rejects.
179
-
180
- naga_oil's other route is `virtual fn` in a base module and `override fn base::f` in the top-level shader. A
181
- `Redirector` then rewrites every call in the final module, so no intermediate module is twinned. But the redirect is
182
- global, so two call sites cannot get different implementations. That is candidate B (§10).
183
-
184
- **Bevy 0.20 (released 2026-10-08) has since moved all its shaders to WESL** and removed its own WGSL dialect (checked
185
- against the release post). The release says nothing about what replaces `virtual fn`/`override fn`.
186
-
187
- **WESL.** `@if`/`@elif`/`@else` attributes go on declarations, statements, struct members and imports. The conditions
188
- are names joined with `! && ||`; they carry no values. Features are one flat set for the whole link.
189
-
190
- - The spec makes an unset feature that is used a link error. wesl-js treats it as false. wesl-rs offers
191
- `Enable/Disable/Keep/Error` and defaults to `Disable`.
192
- - A removed branch need not be valid, and only declarations reachable from the root are emitted.
193
- - A module-parameters proposal (#222, unimplemented) would fork only the declarations that depend on a parameter.
194
-
195
- **gpuweb / WGSL.** WGSL itself has no conditional compilation, shipped or accepted. The only thread is gpuweb#5140,
196
- "@if for conditions", which is open in Milestone 3 with no decision (checked). Reviewers there asked for *declared*
197
- conditions, so that a library can say which conditions it reads. That is the same requirement as here.
198
-
199
- `override` is pipeline-overridable, but the spec defines a shader as the call closure from the entry point *whatever the
200
- constants' values*. A function reached only through a false `if` on an override is part of the shader, its bindings stay
201
- in the shader's interface, and nothing requires the dead branch to be removed. Chrome's Tint keeps it as `if (false)`
202
- (measured 2026-10-16), and DXC then drops it.
203
-
204
- This is a second reason an override is the wrong mechanism, beyond hiding the dependency. A Brick4-or-IBL override would
205
- still need the `brick4` buffer bound in IBL mode.
206
-
207
- **Slang: link-time `extern`/`export`.** A module declares `extern struct S : IFoo;` or `extern static const int k;`. A
208
- provider module declares `export struct S : IFoo = Impl;`. The program is composed from the modules chosen
209
- (`createCompositeComponentType`) and then `link()`ed. Unselected providers never enter the program, and intermediate
210
- modules compile once.
211
-
212
- This is the cleanest prior art for choosing deep in a graph without duplication, and it is open, like B. Whether any
213
- API lists a module's unresolved externs is unverified.
214
-
215
- **Filament** chooses the shadow technique per View at *run time*. Only the sampler type is a variant; PCF, VSM and
216
- EVSM are a uniform (`frameUniforms.shadowSamplingType`). That is today's meep, the runtime branch, which costs 4–6 % of
217
- the deferred pass here.
218
-
219
- **Godot** reads each specialization constant through an accessor that falls back to a push constant when built as an
220
- "ubershader". One source then serves both the specialized pipeline and a fallback compiled in the background. We do not
221
- need that, because `PipelineEnquiry` already compiles off the main thread.
222
-
223
- **What this design takes, and what it leaves.**
224
-
225
- - **From Unreal:** a dimension as an object with a closed value set, and a key per shader made of its values. But not
226
- the owner listing every dimension; here the graph lists them.
227
- - **From naga_oil:** deriving the domain transitively from the graph, and keying only on it.
228
- - **From Unity:** values a shader does not use are ignored.
229
- - **From WESL's spec:** an unset value is an error, not false.
230
- - **From Slang and vertex factories:** the implementation is linked in *beside* the code that calls it, so nothing in
231
- between is twinned or rebuilt.
232
- - **Left out:** conditional text inside a chunk (Unreal's and naga_oil's `#if`, WESL's `@if` on statements), which is
233
- what `CodeChunk` exists to avoid. Also left out: global redirects (naga_oil overrides) and closest-match fallback
234
- (Unity).
235
-
236
- An alternative here is a whole chunk with its own dependencies, which is WESL's `@if` on a declaration, raised to a
237
- declared, typed choice.
238
-
239
- ## 4. The design
240
-
241
- ### 4.1 Pieces
242
-
243
- | piece | is | API |
244
- |---|---|---|
245
- | `ShaderSwitch` | a named choice over an engine enum's members (default: the member equal to 0) or over `false`/`true` (default `false`) | `ShaderSwitch.enum(label, Enum)`, `ShaderSwitch.bool(label)`; `label`, `values`, `default_value`, `is_value(v)`, `name_of(v)` |
246
- | `ShaderSwitchSet` | immutable, **interned** values for some switches; a switch it does not name is at its default | `EMPTY`, `with(switch, value)`, `get(switch)`, `restricted_to(switches)` |
247
- | `CodeChunkChoice` | a chunk standing for one alternative per value of one switch | `CodeChunkChoice.from(switch)`, `add(value, chunk)`, `alternative_for(set)` |
248
- | `CodeChunk.specialize(set)` | the graph with every choice replaced by its selected alternative | returns a static `CodeChunk` |
249
- | `CodeChunk.collectSwitches()` | every switch the graph decides, through every alternative | `ShaderSwitch[]` |
250
- | `variant(set)` | on `ShaderDescriptor`, `ImageShader`, `ComputeShader`, and (designed) `TemplatePassDescriptor` | a static shader of the same class |
251
-
252
- **Interning** makes a set a value: two sets that give every switch the same value are one object, and a switch set to
253
- 0 is not stored. So:
254
-
255
- - `a.with(X, 1).with(Y, 2) === a.with(Y, 2).with(X, 1)`;
256
- - `EMPTY.with(X, X.default_value) === EMPTY`;
257
- - every cache keys by identity;
258
- - `toString()` names the members, `shadow directional technique=Virtual`, which is what labels carry (D7);
259
- - `with` returns `this` when the value is already that, so a renderer re-deriving its set every frame allocates
260
- nothing until a flag changes.
261
-
262
- Each set is made once, on first use, and kept for the process, so the table only grows by sets actually used.
263
-
264
- ### 4.2 Specialization: a switched graph becomes a static one
265
-
266
- `specialize(set)` walks the graph once, depth-first, with a memo from source chunk to result:
267
-
268
- - A **choice** becomes the specialization of the alternative `set` selects. An alternative may itself be a choice of
269
- another switch.
270
- - A **chunk whose dependencies all come back as themselves** is returned as it is. Everything not above a choice is
271
- shared with the source graph, and a graph with no choice specializes to itself.
272
- - **Any other chunk** becomes a new `CodeChunk`: the same text and extensions, with specialized dependencies. If two
273
- dependencies resolve to the same chunk, it is kept once.
274
- - **The memo** keeps what two chunks share in the source shared in the result, so a diamond stays a diamond.
275
-
276
- `compile()` is unchanged, except that it throws, naming the choice, if a choice reaches it. The linker only ever sees
277
- static graphs.
278
-
279
- `collectSwitches()` walks every alternative, selected or not, so a switch nested inside one alternative is still in
280
- the shader's domain.
281
-
282
- Errors:
283
-
284
- - a value with no alternative at a choice names the switch and the value;
285
- - a cycle through any alternative is refused at `add()`;
286
- - a choice equals only itself, since every choice has the same empty text.
287
-
288
- ### 4.3 Variants on the shader classes
289
-
290
- "The shader" caches its variants by the set, as you asked. Every class has the same shape, `variant(set)`:
291
-
292
- 1. Look `set` up by identity. A frame asking again stops here: 0.1 µs, no allocation.
293
- 2. Restrict it to the descriptor's `switches`. Sets that agree on this shader's switches get the same variant.
294
- 3. If the restricted set is `EMPTY`, return `this`. The object a module exports *is* the all-defaults variant, so
295
- `shader.pipeline` and `descriptor.source` keep meaning what they mean today.
296
- 4. Otherwise build, once, a static object of the same class from the specialized descriptor.
297
-
298
- | class | status | variant is |
299
- |---|---|---|
300
- | `ShaderDescriptor` | prototyped | `ShaderDescriptor.from({ label: '<label> [<set>]', resources: same object, body: body.specialize(set) })`. `switches` = `body.collectSwitches()`. `source` = the all-zeros variant. |
301
- | `ImageShader` | prototyped | `new ImageShader({ descriptor: descriptor.variant(set), targets, depthStencil })`, the same targets and depth state as the original. |
302
- | `ComputeShader` | prototyped | `new ComputeShader(descriptor.variant(set))`. Refused if its `@workgroup_size` differs from the original's: a switch changes what a pass computes, never how it is dispatched. |
303
- | `TemplatePassDescriptor` | designed | a sibling sharing `label`, `build` and the attachments. `build(template, set)` gets the set and returns `….variant(set).source`; existing builds that ignore the second argument keep working. Its own per-manager × template × cull-mode cache is unchanged. The reads of `.pipeline` for its layout (`graph_rasterize_meshes_transparent_avboit.js:287`, `:593`; `graph_rasterize_meshes_transparent_oit.js:205`, `:353`) becomes a `pipeline_layout` getter, so a sibling needs no pipeline of its own. |
304
- | water's finalize pipeline | designed | `create_water_finalize_pipeline(set)`, cached the same way. |
305
-
306
- A few things fall out of variants being whole static objects:
307
-
308
- - `features` (the `enable` list) and `validate_against_device` are per variant, with no extra work.
309
- - Labels carry the set, so profiler entries and validation errors name the variant they came from.
310
-
311
- ### 4.4 Where the set comes from
312
-
313
- - **Who builds it.** The Renderer, from its feature flags, at the start of `render()`:
314
-
315
- ```js
316
- this.#shader_switches = this.#shader_switches
317
- .with(SHADOW_DIRECTIONAL_TECHNIQUE, this.feature_virtual_shadow_maps ? ShadowDirectionalTechnique.Virtual : ShadowDirectionalTechnique.Cascades)
318
- .with(INDIRECT_LIGHTING, this.indirect_lighting_mode) // stage 2
319
- .with(LOCAL_LIGHT_SHADING, this.feature_restir_di_enabled ? LocalLightShading.ReSTIR : LocalLightShading.Clustered); // stage 2
320
- ```
321
-
322
- This allocates nothing while the flags are unchanged.
323
- - **How it reaches owners.** Each owner is handed it the way it is handed `indirect_lighting_mode` today: the deferred
324
- pass in the Renderer, the volumetrics and transparency graph functions through their options, water through its
325
- draw call. An owner passes the set opaquely to `variant()`. It never learns which switches its shaders contain, and
326
- in stage 2 the set replaces the `indirect_lighting_mode` parameter it carries today.
327
- - **A per-pipeline choice.** An owner asks for it explicitly, with `shader.variant(switches.with(X, v))`, for example a
328
- debug view that forces cascades.
329
- - **A forgotten set.** With D3 an owner that forgets to pass the set draws the all-defaults variant, silently. That is the price of
330
- defaults. The domain is enumerable, so a review can list every shader with non-empty `switches` and check that its
331
- owner calls `variant`.
332
-
333
- ### 4.5 Caching and keys
334
-
335
- - **Shader level:** by set identity, then by the restricted set (§4.3).
336
- - **GPU pipeline managers: unchanged.** `RenderPipelineManager` and `ComputePipelineManager` key by descriptor, which
337
- includes the WGSL text and the label. Distinct variants are distinct pipelines. Sets differing only in switches a
338
- shader does not have share one, because the variant is restricted to the shader's own switches first.
339
- - *Corrected in stage 2:* sets that produce the same text through different values do not share. LPV and IBL on
340
- the transparent paths and water link the same code, but D7 puts the set in the label and descriptor equality
341
- compares labels, so LPV gets its own pipeline: one extra compile when LPV is switched on.
342
- - **What affects what.** `descriptor.switches` lists a shader's switches, and `TemplatePassDescriptor.switches_of(template)`
343
- does the same per template, since a template's own chunks may hold a choice. "Which pipelines does flipping X
344
- recompile" is the set of shaders whose `switches` include X.
345
-
346
- ### 4.6 Preparation, and a flip
347
-
348
- - **Offers.** The Renderer offers `shader.variant(this.#shader_switches).pipeline` and hands the set to the `enquire_*`
349
- functions, so only the selected variants are prepared.
350
- - **When the set changes**, nothing extra happens (D6). The first frame that draws with the new set makes its
351
- variants through `obtain`, synchronously, as a feature turned on after `initialize()` does today. That is about 1 s
352
- cold for the deferred shader. The shadow records and the shaders flip in that same frame.
353
- - **Speculative offers.** The domain is enumerable, so a settings screen can offer the other value in the background to
354
- make flips instant.
355
-
356
- ### 4.7 Specs
357
-
358
- - **Existing specs keep passing.** With D3 they test the all-defaults variant, and the whole shade suite (877 files) passes on the
359
- prototype unchanged.
360
- - **New specs per switched shader:** "every value validates as WGSL", a plain `for` over `switch.values` inside one test,
361
- not `.each`.
362
- - **Chunk-level harnesses** that compile a graph containing a choice call `specialize(set)` first. The linker refuses
363
- one that does not.
364
-
365
- ## 5. Resources: the long-term half
366
-
367
- ### 5.1 What differs between alternatives today
368
-
369
- | fork | bindings only one mode has | does the resource exist in the other modes? |
370
- |---|---|---|
371
- | AVBOIT draw, MBOIT resolve | `brick4` (in the view group) | yes: `Brick4LightMap` creates its buffer in its constructor, per scene |
372
- | water finalize | `brick4`, `view` (an extra group) | yes |
373
- | SSR resolve (`build_ssr_resolve_shader` factory) | IBL `tEnvironment`; Brick4 `brick4`, `tSTBN_vec2`; LPV `tEnvironment` + 2 textures, 3 buffers + 1 uniform | Brick4, STBN and LPV's buffers yes; LPV's atlas textures only once LPV runs |
374
- | froxel GI (a module per mode) | IBL `environment_sh`; Brick4 `brick4`; LPV 3 buffers + 1 uniform | yes |
375
- | deferred heavy/ReSTIR (factory) | `tRestirRadiance` | only when ReSTIR runs |
376
-
377
- *Corrected in stage 2:* LPV's buffers exist in every mode. `GPUSceneContext`'s constructor makes the
378
- `GPULightProbeVolume` beside the `Brick4LightMap`, and its buffers are created there at a reserved minimum (one probe,
379
- one tetrahedron, a BVH root). Only the atlas textures wait: a `GPUTextureContext` allocates on first use, and nothing
380
- uses them outside LPV.
381
-
382
- Mode-specific bindings cannot simply get a group of their own. The raster passes already use groups 0–3 (material,
383
- scene, view, lights), which is all WebGPU guarantees (`maxBindGroups` defaults to 4, and the device floor does not
384
- raise it).
385
-
386
- ### 5.2 Recommended: one interface per definition, the union of its alternatives'
387
-
388
- A switched shader's resource set declares every binding any of its alternatives reads. Switches then change code only,
389
- never the interface (D4 holds forever, not just in stage 1). It follows that:
390
-
391
- - **Layouts don't depend on the set.** The shared groups (`SHADING_LIGHT_RESOURCE_GROUP`, `FORWARD_LIGHT_LAYOUT`) and the
392
- bind groups built from them stay shared across variants and passes. A layout may list a binding the selected variant
393
- does not read; WebGPU allows that.
394
- - **Owners bind by name.** `group.generateBindings(map, encoder)` already does this, and every pass becomes
395
- "pass what you have". The positional `use_brick4 ? [ … ] : [ … ]` arrays in the AVBOIT and MBOIT owners go.
396
- - **A binding a mode lacks falls back to its declared default.**
397
- - Textures already take one: `addTexture(…, BLACK_PIXEL_TEXTURE)` is in use. `tRestirRadiance` defaults to black.
398
- - Storage buffers have no default parameter today. One convention would be added: `addStorageBuffer(name, type,
399
- write, default_value)`, with the engine's zero buffer of the type's minimum size. LPV's three buffers need it.
400
- - *As built in stage 2, your option B on the frame graph path only:* `addStorageBuffer(name, type, write,
401
- transient_default)`. The declaration says only whether the convention applies. A frame graph pass given nothing
402
- for such a binding binds a transient storage buffer of the type's minimum binding size from the frame's pool, so
403
- there is no buffer to keep and no lifecycle. Its contents are whatever the pooled buffer last held, so it may
404
- only stand in for a binding the selected variant does not read, and an assert checks exactly that: the module
405
- names the binding nowhere but its declaration, comments aside. A pass that binds by hand (`generateBindings`)
406
- gets no default and is told so.
407
- - Owners pass only what the running mode has. The Renderer hands SSR and froxel GI Brick4's map, or the light
408
- probe volume, only while that mode runs. AVBOIT, MBOIT and water bind by hand and pass Brick4's buffer, which
409
- exists in every mode (§5.1).
410
- - Textures: `tRestirRadiance` and LPV's `lpv_atlas_depth` default to `BLACK_PIXEL_TEXTURE`, and
411
- `lpv_atlas_radiance`, a `uint` binding, to a new one-texel `r32uint` zero, `ZERO_UINT_PIXEL_TEXTURE`. LPV's
412
- `lpv_metadata` uniform defaults to a volume of no probes.
413
-
414
- **Measured in stage 2 (§12.2):** an unread binding in a layout costs nothing measurable at draw time. The AVBOIT draw
415
- and the SSR resolve take the same GPU time with the union of bindings as with their own mode's.
416
-
417
- ### 5.3 Later, and optional: chunks declare the bindings they read
418
-
419
- Today `chunk_shade_directional_lights` says in prose that it "relies on the `light_data` storage binding and the shadow
420
- atlas bindings being present". `chunk_water_gi_brick4` says "Expects `brick4` and `view`". These are implicit
421
- dependencies, of the same kind as water's hidden GI function.
422
-
423
- A chunk would declare them through a channel beside `addExtension`: `chunk.requireResource(RESOURCE_BRICK4)`, with the
424
- resource descriptor exported once. `compile()` collects them over the static graph as it collects extensions, and
425
- `ShaderDescriptor` checks each against its resource set by name and type. The error names both: "`chunk_brick4_sample_*`
426
- reads `brick4`, which `AVBOIT / draw` does not bind".
427
-
428
- With switches, a variant's required set is then known exactly. Migration is chunk by chunk, since an undeclared read
429
- works as it does today.
430
-
431
- ### 5.4 Considered and not recommended
432
-
433
- - **Resources chosen by switch, per variant.** A group, or entries within one, would change with the set, so each
434
- variant binds only what it reads. Shared groups would stop being shared whenever a variant read less than all of
435
- one, and owners would build a bind group per variant. It buys nothing measured.
436
- - **A group per mode.** It does not fit in 4 groups.
437
-
438
- ## 6. Migration, in three stages
439
-
440
- Each stage is a set of one-concern commits, red-then-green where it fixes something.
441
-
442
- ### Stage 1: the mechanism and shadows (code-only)
443
-
444
- 1. The compiler layer as prototyped: `ShaderSwitch`, `ShaderSwitchSet`, `CodeChunkChoice`, `specialize`,
445
- `collectSwitches`, and `variant` on `ShaderDescriptor`, `ImageShader` and `ComputeShader`.
446
- 2. `TemplatePassDescriptor.variant` and `build(template, set)`. `.pipeline` reads for layout become `pipeline_layout`.
447
- Water's finalize builder takes a set.
448
- 3. Shadows:
449
- - add `ShadowDirectionalTechnique { Cascades: 0, Virtual: 1 }` and
450
- `SHADOW_DIRECTIONAL_TECHNIQUE = ShaderSwitch.enum('shadow directional technique', ShadowDirectionalTechnique)`;
451
- - split `_cascades` (today's function without the virtual branch) from `_virtual` (record read; NULL → 1.0; else
452
- `shadowmap_sample_virtual`);
453
- - make `chunk_shadowmap_sample_directional` the choice. Nothing above it changes.
454
- 4. The Renderer builds `#shader_switches` and hands it to:
455
- - the deferred pass;
456
- - volumetrics build-lighting and build-irradiance;
457
- - AVBOIT and MBOIT;
458
- - water;
459
- - the `enquire_*` functions.
460
-
461
- 5. Specs: about a dozen that compile these shaders directly gain a loop over the technique.
462
-
463
- ### Stage 2: resources, and the forks that needed them
464
-
465
- Every step below is "one definition, one choice, bind by name".
466
-
467
- - **`INDIRECT_LIGHTING = ShaderSwitch.enum('indirect lighting', ShadeIndirectLightingMode)`**, the enum as it is, with IBL
468
- as 0.
469
- - **Forward fragment:** one `chunk_forward_shade_standard_fragment` calling `forward_sample_indirect(…)`. A choice
470
- provides it: IBL → environment, Brick4 → `brick4_sample_forward_indirect`, LPV → environment. This replaces the
471
- 178/185-line twins.
472
- - **AVBOIT and MBOIT:** one draw and one resolve, each with `brick4` in the view group. The `_ibl`/`_brick4` exports and
473
- the branch on `indirect_lighting_mode` in both owners go.
474
- - **Water:** one finalize shader. `chunk_water_gi` is a choice, and `finalize_body` lists it as a dependency, so the
475
- hidden dependency becomes explicit.
476
- - **SSR resolve:** one shader. `ssr_sample_radiance` (and `ssr_sample_hit_irradiance`) are choices, and
477
- `build_ssr_resolve_shader` goes.
478
- - **Froxel GI:** one shader whose per-froxel radiance lookup is a choice; the three modules become three alternatives.
479
- - **`LOCAL_LIGHT_SHADING = ShaderSwitch.enum('local light shading', LocalLightShading)`**, `{ Clustered: 0, ReSTIR: 1 }`. The deferred body depends on a choice between
480
- `chunk_shade_standard_material_direct` and `_restir`. `tRestirRadiance` defaults to black.
481
- `build_deferred_main_shader` and `shader_deferred_main_restir` go. The forward paths keep linking the clustered chunk
482
- directly, so they never depend on the switch.
483
- - **The storage-buffer default convention** (§5.2), for LPV's buffers. *Built on the frame graph path only (§5.2).*
484
- - **Measure:** the transparent pass and SSR before and after, by the paired method.
485
-
486
- ### Stage 3 (optional): declared bindings
487
-
488
- `requireResource` and the link-time check (§5.3), then chunk by chunk, starting with the ones that say "relies on" or
489
- "expects" in prose. *Built for every chunk the switched shaders link (§12.3).*
490
-
491
- ### What goes away, in total
492
-
493
- - **Duplicate code:**
494
- - the forward fragment twins;
495
- - the AVBOIT and MBOIT export pairs;
496
- - the water finalize pair;
497
- - two per-mode SSR wrappers and three froxel GI modules, which become alternatives;
498
- - `shader_deferred_main_restir`.
499
- - **Factories:** `build_deferred_main_shader` and `build_ssr_resolve_shader`.
500
- - **Owner branches:** five `switch`/`?:` on `indirect_lighting_mode`, two positional bind-entry pairs, and the
501
- `indirect_lighting_mode` parameter threaded through those owners.
502
- - **A runtime branch:** the per-light shadow technique branch, worth 4–6 % of the deferred pass.
503
-
504
- ## 7. Prototype and measurements
505
-
506
- ### Specs, 33 new tests
507
-
508
- - `compiler/ShaderSwitch.spec.js`, 4 tests: an enum's members in declaration order with default 0; a bool's
509
- `false`/`true` with default `false`; an enum without a 0 member refused; repeated values refused.
510
- - `compiler/CodeChunkChoice.spec.js`, 16 tests:
511
- - only the selected closure is linked;
512
- - an unnamed enum switch is 0, an unnamed bool `false`;
513
- - an enum with gaps between its values;
514
- - a choice three chunks deep needs one copy of each chunk above it;
515
- - a shared chunk stays one chunk;
516
- - topological order;
517
- - a static graph is its own specialization;
518
- - the linker refuses a choice;
519
- - discovery from above;
520
- - a switch nested in one alternative, and one with 3 values;
521
- - an alternative that is a choice;
522
- - one switch at two places;
523
- - a missing alternative names the switch and the member;
524
- - cycles;
525
- - identity.
526
- - `compiler/ShaderSwitchSet.spec.js`, 5 tests: interning regardless of order; a default is not stored; a changed
527
- value; restriction; `toString` names the members.
528
- - `ShaderDescriptor.spec.js`, 4 tests: domain found two chunks down; `source` is the all-defaults variant; a variant
529
- links one alternative, has no switches and is labelled `… technique=Virtual`; sets that agree share a variant, and
530
- the empty set gives the descriptor itself.
531
- - `ImageShader.spec.js`, 2 tests, and `ComputeShader.spec.js`, 2 tests: the variant's pipeline code and targets, caching,
532
- `this` for all-defaults, and a variant with another `@workgroup_size` refused.
533
-
534
- ### The real deferred shader
535
-
536
- A scratch probe (`scratchpad/probe/shadow_choice.probe.js`, not in the tree) rebuilds the four chunks above
537
- `chunk_shadowmap_sample_directional` from their real `.text` and dependency lists, with the choice in its place. In the
538
- engine they would not change at all.
539
-
540
- | | characters | functions | engine validator | Tint (Chrome, RTX 4090) | vs shipped |
541
- |---|---|---|---|---|---|
542
- | shipped (both paths) | 69,917 | 96 | pass | 0 messages | |
543
- | cascades | 59,892 | 82 | pass | 0 messages | −14 VSM functions, −4 VSM structs/consts; only `shadowmap_sample_directional` changed |
544
- | virtual | 62,682 | 94 | pass | 0 messages | −`shadowmap_csm_compute_cascade_blended` and its row read, −2 structs; only `shadowmap_sample_directional` changed |
545
-
546
- - **Link time**, median of 200 interleaved runs: today's `compile()` 0.079 / 0.083 ms in two runs; `specialize` +
547
- `compile()` of a variant 0.075 / 0.083 ms.
548
- - **`variant(set)` with a set already seen:** 0.1 µs.
549
- - **Pipeline creation** (`tint_check.mjs`):
550
- - **Method.** `createRenderPipelineAsync` with the deferred pass's vertex stage, target and depth state, and
551
- `layout: 'auto'`. One at a time, rotating order, 8 rounds. A fresh seed constant per compile, so neither Dawn's
552
- nor the driver's cache serves it.
553
- - **Results.** Two runs: shipped 1251 / 1225 ms, cascades 1009 / 1017 ms (0.81× / 0.83×), virtual 877 / 810 ms
554
- (0.70× / 0.66×).
555
- - **Caveat.** This is wall time on a busy box, not the trace's `InitializeImpl`, so it compares with the earlier
556
- twins (0.78× / 0.66×) by ratio only.
557
- - **GPU time was not re-measured.** The 0.936× / 0.957× were measured on twins built by the same removals. The cascades
558
- variant is that twin. The virtual variant is the twin's branch inverted (`== NULL → 1.0` first).
559
-
560
- ## 8. Costs
561
-
562
- | part | size |
563
- |---|---|
564
- | prototype as it stands | 3 classes (540 lines with docs); `CodeChunk` +135; `ShaderDescriptor` +67/−1; `ImageShader` +51; `ComputeShader` +49; 6 spec files (504 lines) |
565
- | rest of stage 1 (estimate) | `TemplatePassDescriptor` (~50), water builder, Renderer set + plumbing to the owners and the enquiries (~100), shadow split (1 file into 3), ~12 specs |
566
- | stage 2 (estimate) | the steps of §6 stage 2, each mostly deletion. Expected net negative in lines, not counted. |
567
-
568
- Runtime:
569
-
570
- - No per-frame allocation once the set is steady.
571
- - A lookup is 0.1 µs, and a variant costs 0.075–0.083 ms to specialize and link.
572
- - A flip compiles the affected variants on first use (D6). The old ones stay cached.
573
-
574
- Risks:
575
-
576
- - **A forgotten set draws the all-defaults variant silently (§4.4).**
577
- - **Errors move to first use.** A broken non-zero alternative only shows when a pipeline is made. The per-value specs
578
- catch it.
579
-
580
- ## 9. Reuse map
581
-
582
- | piece | reuses / generalises |
583
- |---|---|
584
- | specialization | `CodeChunk`'s own graph and private fields, with the same identity-based dedup the linker relies on. No second linker; `compile()` is untouched. |
585
- | values | engine enums as they are (`ShadeIndirectLightingMode` unchanged), or a bool. Like Unreal's enum/bool/sparse-int permutation dimensions, an enum need not be contiguous. |
586
- | variant objects | `ShaderDescriptor`, `ImageShader`, `ComputeShader` and `TemplatePassDescriptor` themselves. A variant is one of them, so draw, dispatch, validation and the profiler need nothing new. |
587
- | GPU pipeline cache | `RenderPipelineManager` / `ComputePipelineManager` unchanged. Text equality separates and shares variants. |
588
- | preparation | `PipelineEnquiry` and `prepare()` at start-up, offering the selected variants. A flip uses `obtain` as anything made on first use does. |
589
- | bind by name | `ShaderResourceGroupDescriptor.generateBindings`, and texture `default_value` (`BLACK_PIXEL_TEXTURE`). One new default convention, for storage buffers. |
590
- | declared bindings (stage 3) | the `addExtension` / `addLanguageExtension` channel: collected at link, the same way. |
591
- | `ProgrammableStage.constants` | left for what `override` is for: numbers. Not for choosing code. |
592
-
593
- ## 10. Candidates considered (revision a)
594
-
595
- - **A: a switch is the choice.** The value is an alternative chunk, and each place where the graph chooses is its own
596
- switch. It would make the renderer name every place a decision is made. Superseded by D1.
597
- - **B: open slots filled by whoever builds the shader.** This is Unreal's vertex-factory include, naga_oil's
598
- `override fn`, Slang's `extern`/`export`, and today's water GI. Nothing can enumerate a slot's implementations, so
599
- preparation and specs cannot loop over variants. Material templates already are the engine's open interface.
600
- - **C: a switch decided at choices in the graph.** This is the design above, now with enum and bool values (D1, D9), a
601
- static graph per variant (D2) and a default (D3).
602
- - **Rejected before this proposal:** WGSL `override` constants. They hide the dependency, and the spec keeps a function
603
- reached through a false override `if` in the shader, its bindings included.
604
-
605
- ## 11. Open questions for Alex
606
-
607
- The design questions are closed (D1–D9). An enum must have a member equal to 0, a rule Alex kept. Stage 1 landed as
608
- four one-concern commits:
609
-
610
- 1. the compiler layer;
611
- 2. the shader classes' `variant`;
612
- 3. `TemplatePassDescriptor`'s;
613
- 4. the shadow switch with the renderer's set and its plumbing.
614
-
615
- Stage 2 landed as seven, with the storage-buffer default as you chose it: option B, on the frame graph path only
616
- (§5.2).
617
-
618
- Stage 3 is built and checked (§12.3), uncommitted. Open: landing, proposed as five one-concern commits,
619
- master-direct:
620
-
621
- 1. a resource's declaration apart from its placement (`ShaderResourceDescriptor`'s factories, `group.add`,
622
- `isSatisfiedBy`);
623
- 2. chunks declare the bindings they read, and a shader's link checks them (`requireResource`);
624
- 3. six shaders bind what their chunks read: the SDF build, screen-space shadows, the four lightmap debug views;
625
- 4. the switched shaders' chunks declare their bindings, with the audit spec;
626
- 5. this document.
627
-
628
- Also open from stage 3: how the sky reaches water's optics chunks, so they can list the GI they call (§12.3).
629
-
630
- ## 12. As built
631
-
632
- ### 12.1 Stage 1, as landed (2026-10-16)
633
-
634
- #### What it contains
635
-
636
- - **The mechanism:**
637
- - `ShaderSwitch` (`enum` / `bool`), `ShaderSwitchSet` and `CodeChunkChoice`;
638
- - `CodeChunk.specialize` and `collectSwitches`;
639
- - `variant(set)` on `ShaderDescriptor`, `ImageShader`, `ComputeShader` and `TemplatePassDescriptor`.
640
-
641
- A `TemplatePassDescriptor` build is now `(template, switches)`. The four transparent passes that light fragments
642
- build `….variant(switches)`, and their pipeline labels carry the set. Owners read a template pass's layout through
643
- `pipeline_layout`, so a variant needs no pipeline of its own.
644
- - **The shadow switch:**
645
- - `ShadowDirectionalTechnique { Cascades: 0, Virtual: 1 }`;
646
- - `SHADOW_DIRECTIONAL_TECHNIQUE = ShaderSwitch.enum('shadow directional technique', ShadowDirectionalTechnique)`;
647
- - `chunk_shadowmap_sample_directional_cascades` and `chunk_shadowmap_sample_directional_virtual`.
648
-
649
- `chunk_shadowmap_sample_directional` is now the choice between those two alternatives. No chunk above it changed.
650
- - **The renderer:**
651
- - `Renderer.#shader_switches` follows `feature_virtual_shadow_maps`. It is updated in `#draw_to_target`, beside the
652
- shadow context's own flag, and in `enquirePipelines`, and readable as `renderer.shader_switches`.
653
- - It is handed to:
654
- - the deferred pass;
655
- - `GPUViewVolumetrics.graph_update`, which passes it to froxel irradiance and light scattering;
656
- - the AVBOIT orchestrator, which passes it to the draw pass and to each side channel's `avboit_prepare` (water);
657
- - the MBOIT orchestrator, for its resolve pass.
658
- - **Water:** `create_water_finalize_pipeline_ibl/brick4(switches)`, and `GPUWaterRenderer` caches the result per GI mode
659
- and set.
660
-
661
- #### Tests
662
-
663
- - **`shadow_directional_technique_consumers.spec.js`** covers all nine shaders under both techniques. In each, exactly
664
- the selected technique's sampling is linked, the other's is absent, and the module passes the WGSL checks. With every
665
- switch at its default, the result is cascades.
666
- - **`TemplatePassDescriptor.spec.js`** (3 tests): a variant builds each template for its set; one variant per set, and
667
- the pass itself for the empty set; a variant shares its original's attachments and layout.
668
- - **The AVBOIT orchestrator spec** gained a test on the software device: water's optics pipeline, as recorded on the
669
- device, links the technique of the set the orchestrator was handed. It fails if the orchestrator stops passing the set
670
- to side channels (checked).
671
- - **Five chunk-level harnesses now specialize before linking**, which the linker requires:
672
- - the cascades harness to Cascades;
673
- - the VSM harness to Virtual — 4 of its 6 tests fail if it is built for Cascades instead (checked);
674
- - the normal-decode harness and the two water-optics harnesses to the default.
675
- - **Gates:** the shade suite passes, 879 files and 6,744 tests. `check-types` reads 18 against the base's 18.
676
-
677
- #### Device check (one-off, headless Chrome, RTX 4090)
678
-
679
- The stage-1 tree and its base (`03d5816bd`) were served side by side.
680
-
681
- - **VSM playground (deferred lighting).**
682
- - Stage 1 makes two shadow-sampling pipelines: `Shade From G-Buffer`, cascades only, offered at start-up; and
683
- `Shade From G-Buffer [shadow directional technique=Virtual]`, virtual only. No pipeline links both.
684
- - The base makes one pipeline that links both.
685
- - **Images.** Stage 1 against the base differs in no more pixels than the base against itself, with the playground's
686
- movers animating and the filter jittered per frame:
687
-
688
- | technique | stage 1 vs base | base vs base |
689
- |---|---|---|
690
- | cascades | 6.3 % of pixels | 7.4 % |
691
- | virtual | 5.5 % of pixels | 7.8 % |
692
-
693
- - **The same playground with a fog volume added from the page (deferred + volumetric lighting).**
694
- - Stage 1 adds `Volumetrics / build lighting`, cascades only, and `Volumetrics / build lighting [shadow directional
695
- technique=Virtual]`, virtual only. The base has one, which links both.
696
- - The fog's per-frame jitter moves most pixels by a level or two, in both comparisons alike:
697
-
698
- | technique | stage 1 vs base: pixels off by > 8 levels (mean max-channel difference) | base vs base |
699
- |---|---|---|
700
- | cascades | 5.97 % (4.32) | 5.70 % (4.33) |
701
- | virtual | 6.00 % (4.38) | 5.81 % (4.44) |
702
- - **Console errors.** `vsm: feedback readback failed` appears only after a tab is closed, on the base as on stage 1.
703
- It is a readback cut short by the close.
704
-
705
- Not checked on the device:
706
-
707
- - **water and MBOIT,** whose playground does not expose its renderer for the flip. They are covered on the software
708
- device and by the consumer spec.
709
- - **GPU time of the variants.** The 0.936× / 0.957× of revision (a) were measured on equivalent twins.
710
-
711
- ### 12.2 Stage 2, as landed (2026-10-16, `6e263855d`..`e354def85`)
712
-
713
- #### What it contains
714
-
715
- - **Two switches,** set by the Renderer's `#update_shader_switches` beside the shadow technique:
716
- - `INDIRECT_LIGHTING = ShaderSwitch.enum('indirect lighting', ShadeIndirectLightingMode)`, from
717
- `indirect_lighting_mode`;
718
- - `LOCAL_LIGHT_SHADING = ShaderSwitch.enum('local light shading', LocalLightShading)`, `{ Clustered: 0, ReSTIR: 1 }`,
719
- from `feature_restir_di_enabled`.
720
- - **Six choices,** each where a graph asks for the light:
721
-
722
- | choice | IBL | Brick4 | LPV |
723
- |---|---|---|---|
724
- | `chunk_forward_sample_indirect` | environment | `brick4_sample_forward_indirect` | environment |
725
- | `chunk_water_gi` (now listed by `finalize_body`) | `chunk_water_gi_ibl` | `chunk_water_gi_brick4` | `chunk_water_gi_ibl` |
726
- | `chunk_ssr_sample_radiance` | environment | `brick4_sample_specular` | `lpv_sample_radiance` |
727
- | `chunk_ssr_add_hit_irradiance` | nothing | nothing | the LPV bounce at the hit |
728
- | `chunk_volumetrics_gi_sample` | `environment_sh` | `brick4_sample_sh` | the LPV mesh |
729
-
730
- and `chunk_shade_standard_material_direct_deferred` on `LOCAL_LIGHT_SHADING`: the clustered chunk, or `_restir`.
731
- - **One definition each:** `chunk_forward_shade_standard_fragment`, `shader_avboit_draw`, `shader_oit_resolve_moments`,
732
- `water_finalize_shader` (`create_water_finalize_pipeline(switches)`), `ssr_resolve_shader`, `shader_volumetrics_gi`,
733
- `shader_deferred_main`. Each declares the union of its alternatives' bindings.
734
- - **Gone:** the forward twins; `shader_avboit_draw_ibl`/`_brick4`; `shader_oit_resolve_moments_ibl`; water's `_ibl`/
735
- `_brick4` shaders, pipelines and resource sets; `build_ssr_resolve_shader` and the three per-mode resolves; the
736
- three froxel GI modules; `build_deferred_main_shader` and `shader_deferred_main_restir`.
737
- - **Owners:** no pass below the Renderer takes `indirect_lighting_mode` any more. The AVBOIT orchestrator, its side
738
- channels' `avboit_prepare`, the MBOIT orchestrator, `GPUViewVolumetrics.graph_update` and `SSR.graph_pass` take the
739
- set alone.
740
- - AVBOIT and MBOIT bind their view group by name (`AVBOIT_DRAW_VIEW_GROUP`, `OIT_RESOLVE_VIEW_GROUP`, through
741
- `generateBindings`). The positional pairs are gone.
742
- - SSR and froxel GI take Brick4's map and the light probe volume as optional sources, and the Renderer hands them
743
- the running mode's alone. Another mode's bindings are the shader's defaults (§5.2).
744
- - Water binds Brick4's buffer and the view in every mode, by hand: both exist in every mode.
745
- - SSR restarts its history when its resolve variant changes, where it used to compare modes.
746
- - The Renderer's own branches on the mode stay: they pick passes, not shaders (the diffuse/specular pair, Brick4's
747
- fused pass, the enquiry, the LPV update).
748
- - **Defaults** (§5.2): storage buffers with a transient default (`brick4` in SSR and froxel GI, and the LPV's three
749
- buffers through `LPV_SHADER_RESOURCE_GROUP_WITH_DEFAULTS`, a twin of the shared group that the passes which always
750
- bind the volume keep using); `lpv_metadata` a volume of no probes; `tRestirRadiance` and `lpv_atlas_depth`
751
- `BLACK_PIXEL_TEXTURE`; `lpv_atlas_radiance` the new `ZERO_UINT_PIXEL_TEXTURE`.
752
-
753
- #### What changed in the code
754
-
755
- Every variant was diffed against the shader it replaces, declaration by declaration, with the order-dependent
756
- `Struct_N` names normalised:
757
-
758
- - **Same code:** water's optics under Brick4, the AVBOIT Brick4 vertex stage, and deferred ReSTIR.
759
- - **One wrapper function more** (`forward_sample_indirect`, `volumetrics_gi_sample`, `ssr_add_hit_irradiance`), which
760
- the caller now calls in place of the code it wrapped: the transparent passes, SSR and froxel GI.
761
- - **Declarations only:** the union's bindings, and binding numbers shifted to make room for them. Deferred
762
- (clustered) gains `tRestirRadiance`.
763
-
764
- #### Tests
765
-
766
- - **`indirect_lighting_consumers.spec.js`** (5 tests): the AVBOIT draw and MBOIT resolve for the standard template,
767
- water's optics, SSR's resolve and froxel GI, under every mode. Each variant links only its mode's sampling, passes
768
- the WGSL checks, and has the same layout or resources as every other mode's; with every switch at its default the
769
- result is IBL.
770
- - **`shader_deferred_main.spec.js`** (3 tests): each way of shading local lights links its own code, and the two bind
771
- alike with `tRestirRadiance` black by default.
772
- - **`storage_buffer_transient_default.spec.js`** (4 tests, software device): a pass given nothing binds a transient
773
- buffer of the minimum binding size; a pass given the buffer binds it; a shader that reads a binding left to its
774
- default is refused; binding by hand, the default is no default. Each fails with its piece of the mechanism
775
- disabled (checked).
776
- - **Owners pass the running mode's source alone:** `SSR.spec.js` and `graph_build_froxel_gi.spec.js` check, per mode,
777
- that the pass binds that mode's source and no other's, and that the frame then submits on the software device.
778
- An owner binding another mode's source, as option A would have, fails them (checked).
779
- - **Water's dependency is pinned:** taking `chunk_water_gi` back out of `finalize_body` fails the consumer spec's water
780
- test and both water optics rows of `water_shaders_wgsl_validity.spec.js` (checked).
781
- - **The AVBOIT orchestrator spec** gained a test on the software device: water's optics are drawn with the mode of the
782
- set the orchestrator is handed, and bind every mode's resources by name without an error.
783
- - **`shadow_directional_technique_consumers.spec.js`** now crosses the shadow technique with every indirect lighting
784
- mode and both ways of shading local lights. Fifteen other specs follow the renames.
785
- - **Gates:** the shade suite passes, 894 files and 6,787 tests. `check-types` reads 18, the same as the base.
786
-
787
- #### Device checks (one-off, headless Chrome, RTX 4090)
788
-
789
- - **One mode per pipeline.** A probe recorded every pipeline's WGSL while the avboit, water_refraction and
790
- particle_ecs playgrounds were switched through IBL, Brick4 and LPV, AVBOIT and MBOIT, with SSR and then ReSTIR on.
791
- Every pipeline of a switched shader links exactly one mode's sampling:
792
-
793
- | shader | pipelines |
794
- |---|---|
795
- | AVBOIT draw, MBOIT resolve, water optics | the default (environment), `[indirect lighting=Brick4]` (Brick4), `[indirect lighting=LPV]` (environment) |
796
- | SSR resolve | one per mode, each linking only its own |
797
- | Volumetrics / GI | one per mode, each linking only its own |
798
- | Shade From G-Buffer | the default (clustered), `[local light shading=ReSTIR]` (ReSTIR) |
799
-
800
- No compilation or validation errors came from these shaders, with option B's defaults standing in for the other
801
- modes' sources in SSR and froxel GI (run again after B).
802
- - **GPU time, before and after.** A bench copy of the tree held both paths, the old per-mode shader with its own
803
- bindings and the new one with the union, and its owners picked one per frame. Arms alternated in blocks of four
804
- frames inside one capture, the first frame of each block dropped, with four captures per setting in alternating
805
- order (`ab_drive`). Frames were checked to have drawn the arm's own pass. Spans are quantised to 1.024 µs, so the
806
- medians are what can be read:
807
-
808
- | pass | setting | median, old / new | captures |
809
- |---|---|---|---|
810
- | AVBOIT draw | IBL, avboit playground | 1.000 | 4 of 4 |
811
- | AVBOIT draw | Brick4 | 1.000 | 4 of 4 |
812
- | SSR resolve | IBL, ssr_reprojection, lateral and still, 1280 | 1.000 | 7 of 8; the eighth 0.976, one quantum |
813
- | SSR resolve | Brick4, lateral | 1.000 in one capture, 1.018 (one quantum) in three | 4 |
814
-
815
- Pass times were about 59 µs (AVBOIT IBL), 66–69 µs (AVBOIT Brick4), 42 µs (SSR IBL) and 56–57 µs (SSR Brick4).
816
- The avboit playground found no environment map on this machine and lit the scene by its own lights alone.
817
- These were taken before option B, with the scene's buffers in the entries the variant does not read; B puts
818
- transient buffers there instead, which changes nothing the shader reads.
819
-
820
- #### Found along the way, not changed
821
-
822
- - **ReSTIR DI's resample and spatial passes do not compile in Chrome.** `WEIGHT_LIMIT = 3.4028235e38` in
823
- `restir/reservoir/shader.js` rounds above the largest f32, and Tint refuses it. This predates stage 2 (`57e2f80b2`).
824
- The deferred pass's ReSTIR variant itself compiles.
825
- - **`chunk_water_inscatter` and `chunk_water_receiver_downwelling`** still call `water_gi_irradiance` without listing
826
- a dependency. Their spec harnesses supply a stub. `finalize_body` lists `chunk_water_gi`, so the shader's graph
827
- is explicit.
828
- - **LPV's transparent and water variants** are separate pipelines with the same code as IBL's (§4.5's correction).
829
-
830
- ### 12.3 Stage 3, as built (2026-10-16, uncommitted)
831
-
832
- #### What it contains
833
-
834
- - **A declaration apart from its placement.** `ShaderResourceDescriptor.uniform`, `.storageBuffer`, `.texture`,
835
- `.storageTexture` and `.sampler` make a declaration alone: name, kind, type, default. The group's `addX` methods call
836
- them, and `group.add(declaration)` places a copy (`clone_declaration`), so one declaration sits in any number of
837
- groups at their own binding indices and visibilities.
838
- - **What satisfies a declaration** (`isSatisfiedBy`): a binding of the same name and WGSL type with at least the
839
- declared access. A read-only storage buffer is satisfied by a read-write one, a `float` texture by an
840
- `unfilterable-float` one; `depth` is a type of its own; a sampler is a comparison sampler or not; a storage texture
841
- keeps its format and dimension, with its access a subset. Placement, visibility and defaults do not count.
842
- - **The channel,** beside `addExtension`: `CodeChunk.requireResource(declaration)`, read back through
843
- `required_resources`. `specialize` and `copy` carry it, and a choice refuses one, since it has no text to read
844
- with. `compile()` returns `resources`: each declaration once, with the first chunk in link order that declared it.
845
- - **The check.** `ShaderDescriptor.compile()` looks each declaration up in its resource set by name:
846
- - unbound: `'light_count' reads 'light_data', which 'test unbound' does not bind`;
847
- - mistyped: `… reads 'light_data' as a read-only-storage buffer of array< u32 >, which 'test mistyped' binds as a
848
- 2d unfilterable-float texture`.
849
-
850
- Image, compute and template shaders all compile through it, so a variant checks only what its selected
851
- alternatives declare. It throws rather than asserts: it runs once per variant, at link.
852
- - **`ShaderDescriptor.body`,** the graph a shader links, choices still in it.
853
- - **Declarations, exported where the data lives:**
854
-
855
- | file | declarations |
856
- |---|---|
857
- | `camera/CAMERA_RESOURCE.js`, `view/VIEW_RESOURCE.js` | `camera`, `view` |
858
- | `light/LIGHT_DATA_RESOURCE.js`, `light/cluster/LIGHT_CLUSTER_RESOURCES.js` | `light_data`; `cluster_parameters`, `cluster_lookup`, `cluster_data` |
859
- | `light/environment/ENVIRONMENT_MAP_RESOURCE.js`, `ENVIRONMENT_SH_RESOURCE.js` | `tEnvironment`, `environment_sh` |
860
- | `shadow/map/SHADOWMAP_RESOURCES.js` | `shadowmapAtlas`, `sCompareLinear` |
861
- | `global_illumination/brick4/gpu/BRICK4_RESOURCE.js`, `…/octahedral/LPV_SHADER_RESOURCE_GROUP.js` | `brick4`; the light probe volume's six |
862
- | `STATIC_GRAPHICS_ENGINE_ASSET_RESOURCES.js`, `texture/SAMPLER_LINEAR_RESOURCE.js` | `tSTBN_vec2`, `tSTBN_vec3`, `tSplitSum`; `sLinear` |
863
- | `deferred/GBUFFER_RESOURCES.js`, `restir/di/RESTIR_RADIANCE_RESOURCE.js` | `gBufferPBR`, `gBufferAlbedo`, `gBufferEmissive`, `tAlbedoAO`; `tRestirRadiance` |
864
- | `volumetrics/VOLUMETRICS_RESOURCES.js`, `…/gi/volumetrics_gi_resources.js`, `chunk_volumetrics_sun_optical_depth.js` | `volumetrics_metadata`, `tVolumetrics`, `tParticipatingMedia1`; `tGIY`, `tGICoCg`; the sun's cascades |
865
- | `shader/chunk/screen_space/SCREEN_SPACE_RESOURCES.js` | `tDepth`, `tHZB`, `tNormal` |
866
- | `geometry/GEOMETRY_ATTRIBUTE_DATA_RESOURCE.js`, `geometry/meshlet/MESHLET_METADATA_RESOURCE.js`, `scene/SCENE_DATABASE_RESOURCE.js` | `meshlet_data`, `meshlet_metadata`, `scene_database` |
867
- | `material/standard/SHADING_MATERIAL_RESOURCE_GROUP.js`, `water/surface/WATER_SURFACE_RESOURCES.js`, `water/shader_water.js` | the standard material's nine; the surface interface's five; water's trace and LOD |
868
-
869
- - **Migrated:** every chunk the eight switched shaders link in any variant declares what its own functions read,
870
- and so do the sibling chunks in the same files: 56 chunks in 52 files. The prose lists of bindings they replace are gone ("relies on the `light_data` storage
871
- binding…", "Bindings it reads: …").
872
- - **Groups built from the declarations:** `SHADING_LIGHT_RESOURCE_GROUP`, `LPV_SHADER_RESOURCE_GROUP`,
873
- `SHADING_MATERIAL_RESOURCE_GROUP`, the froxel GI group, water's resources and the sun's cascades in the lighting pass.
874
- Every other group keeps its `addX` calls, which satisfy the declarations by name and type.
875
-
876
- #### What changed in the code
877
-
878
- Every exported shader and every variant of the switched ones, 406 modules, was linked on the base and on stage 3
879
- and compared with the `Struct_N` names normalised. 399 are identical. The rest are the six shaders below, which gain
880
- the bindings their chunks read, and one script's error message (a path).
881
-
882
- #### Found and fixed: six shaders that could not compile
883
-
884
- A migrated chunk is checked wherever it is linked, and six shaders link one that reads a binding they never bound.
885
- Tint (headless Chrome, RTX 4090) refuses master's sources for all six: `unresolved value 'meshlet_data'` in the
886
- geometry SDF build and the four lightmap debug views (`density`, `uv_checker`, `view_flat`, `view_sh`), and
887
- `unresolved value 'tHZB'` in screen-space shadows. Until now they failed at `createShaderModule`; the check fails
888
- them at link, and their modules threw on import. None is reached in-tree: `GPUGeometryManager.sdf` throws "Not
889
- implemented", and nothing imports the other five. Each now binds what its chunks read, appended to its group:
890
-
891
- - **Screen-space shadows:** `tHZB`. Compiles on Tint.
892
- - **Geometry SDF build:** `meshlet_metadata` and `meshlet_data`, and `GPUGeometrySDFManager` passes
893
- `geometries.meshlets`' buffers as every other dispatch does. Compiles on Tint.
894
- - **The lightmap debug views:** the same two buffers, so they import again. On Tint they now stop at `struct member
895
- lightmap_uvs not found`: the mesh struct no longer has the field. Not changed.
896
-
897
- #### Tests
898
-
899
- - **`ShaderResourceDescriptor.spec.js`** (6 tests): what satisfies a declaration, per kind.
900
- - **`ShaderResourceGroupDescriptor.spec.js`** (+1): one declaration in two groups sits at each group's index, and
901
- visibility is the group's.
902
- - **`CodeChunk.spec.js`** (+1) and **`CodeChunkChoice.spec.js`** (+1): collection at link; a choice refuses a
903
- declaration.
904
- - **`ShaderDescriptor.spec.js`** (+4): unbound and mistyped name the chunk, the binding and the shader; a binding read
905
- as declared, or read-write, links the same source as no declaration; a variant checks only its selected alternative.
906
- - **`switched_shaders_declare_bindings.spec.js`** (7 tests), the audit: every chunk of the eight switched shaders,
907
- in every variant, declares exactly the module-scope bindings its own functions read, parsed, with parameters and
908
- locals excluded. The shader's body is where its bindings are written, and declares nothing. It failed with 106
909
- findings over 50 chunks before the migration.
910
- - **`GPUGeometrySDFManager.spec.js`:** its geometry manager stand-in gains the meshlet buffers.
911
- - **Gates:** the shade suite passes, 901 files and 6,833 tests. `check-types` reads 18, the same as the base.
912
-
913
- #### Device check (one-off, in-app browser)
914
-
915
- The modules are the same text, so the check is that the renderer's pipelines still link and run. The avboit
916
- playground compiled its nine AVBOIT modules and drew AVBOIT and MBOIT, under IBL and Brick4 each, for 2,038 frames;
917
- the vsm playground drew cascades and then virtual shadow maps. No uncaptured device error came from either.
918
-
919
- #### Found along the way, not changed
920
-
921
- - **Water's optics still call `water_gi_irradiance` without a dependency.** Making `chunk_water_gi` their dependency
922
- was tried: the shader is unchanged, but the in-scatter oracle and the downwelling spec harnesses supply their own
923
- stub through that name, and would then link two definitions, which the emulator tolerates and WGSL does not.
924
- Fixing it means a choice of how the sky reaches these chunks: an argument, or a stub chunk the harness specializes in.
925
- - **The lightmap debug views' `lightmap_uvs`,** above.
926
- - **Placement derived from the declarations,** the larger change to the shader class this stage anticipated (§5.3):
927
- not built. Groups still place their bindings by hand, and declarations only check them.