@woosh/meep-engine 3.29.0 → 3.30.0

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 (226) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/bundle-worker-terrain.js +1 -1
  3. package/package.json +1 -1
  4. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  5. package/src/core/binary/BinaryBuffer.js +14 -2
  6. package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
  7. package/src/core/bvh2/bvh3/BVH.js +14 -1
  8. package/src/core/cache/FrequencySketch.d.ts +3 -1
  9. package/src/core/cache/FrequencySketch.d.ts.map +1 -1
  10. package/src/core/cache/FrequencySketch.js +10 -4
  11. package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -1
  12. package/src/core/cache/wtinylfu/CacheWTinylfu.js +24 -2
  13. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts +20 -0
  14. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
  15. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +69 -4
  16. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +48 -18
  17. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
  18. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +262 -70
  19. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts +25 -0
  20. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts.map +1 -0
  21. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.js +228 -0
  22. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts +13 -0
  23. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts.map +1 -1
  24. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.js +10 -128
  25. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.d.ts.map +1 -1
  26. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.js +11 -1
  27. package/src/core/graph/mn_graph_collapse_weighted_edge.js +6 -6
  28. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
  29. package/src/core/math/lookup/ParameterLookupTable.js +10 -5
  30. package/src/core/math/noise/sdnoise.d.ts.map +1 -1
  31. package/src/core/math/noise/sdnoise.js +16 -10
  32. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  33. package/src/core/model/object/ImmutableObjectPool.js +17 -7
  34. package/src/core/process/executor/ConcurrentExecutor.d.ts +1 -5
  35. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  36. package/src/core/process/executor/ConcurrentExecutor.js +95 -1
  37. package/src/core/process/task/task_find_dependency_cycle.d.ts +17 -0
  38. package/src/core/process/task/task_find_dependency_cycle.d.ts.map +1 -0
  39. package/src/core/process/task/task_find_dependency_cycle.js +81 -0
  40. package/src/core/wfc/WFCCellChoice.d.ts +17 -0
  41. package/src/core/wfc/WFCCellChoice.d.ts.map +1 -0
  42. package/src/core/wfc/WFCCellChoice.js +36 -0
  43. package/src/core/wfc/WFCLattice.d.ts +105 -0
  44. package/src/core/wfc/WFCLattice.d.ts.map +1 -0
  45. package/src/core/wfc/WFCLattice.js +235 -0
  46. package/src/core/wfc/WFCRuleTable.d.ts +172 -0
  47. package/src/core/wfc/WFCRuleTable.d.ts.map +1 -0
  48. package/src/core/wfc/WFCRuleTable.js +334 -0
  49. package/src/core/wfc/WFCState.d.ts +273 -0
  50. package/src/core/wfc/WFCState.d.ts.map +1 -0
  51. package/src/core/wfc/WFCState.js +552 -0
  52. package/src/core/wfc/WFCStatus.d.ts +11 -0
  53. package/src/core/wfc/WFCStatus.d.ts.map +1 -0
  54. package/src/core/wfc/WFCStatus.js +28 -0
  55. package/src/core/wfc/WFCTileWeights.d.ts +48 -0
  56. package/src/core/wfc/WFCTileWeights.d.ts.map +1 -0
  57. package/src/core/wfc/WFCTileWeights.js +86 -0
  58. package/src/core/wfc/WFC_UNRESOLVED.d.ts +12 -0
  59. package/src/core/wfc/WFC_UNRESOLVED.d.ts.map +1 -0
  60. package/src/core/wfc/WFC_UNRESOLVED.js +11 -0
  61. package/src/core/wfc/wfc_estimate_bytes.d.ts +24 -0
  62. package/src/core/wfc/wfc_estimate_bytes.d.ts.map +1 -0
  63. package/src/core/wfc/wfc_estimate_bytes.js +47 -0
  64. package/src/core/wfc/wfc_rule_table_tile_components.d.ts +27 -0
  65. package/src/core/wfc/wfc_rule_table_tile_components.d.ts.map +1 -0
  66. package/src/core/wfc/wfc_rule_table_tile_components.js +80 -0
  67. package/src/core/wfc/wfc_solve.d.ts +21 -0
  68. package/src/core/wfc/wfc_solve.d.ts.map +1 -0
  69. package/src/core/wfc/wfc_solve.js +154 -0
  70. package/src/core/wfc/wfc_state_apply_boundary.d.ts +18 -0
  71. package/src/core/wfc/wfc_state_apply_boundary.d.ts.map +1 -0
  72. package/src/core/wfc/wfc_state_apply_boundary.js +61 -0
  73. package/src/core/wfc/wfc_state_assign.d.ts +11 -0
  74. package/src/core/wfc/wfc_state_assign.d.ts.map +1 -0
  75. package/src/core/wfc/wfc_state_assign.js +36 -0
  76. package/src/core/wfc/wfc_state_ban_bits.d.ts +17 -0
  77. package/src/core/wfc/wfc_state_ban_bits.d.ts.map +1 -0
  78. package/src/core/wfc/wfc_state_ban_bits.js +81 -0
  79. package/src/core/wfc/wfc_state_collapse.d.ts +12 -0
  80. package/src/core/wfc/wfc_state_collapse.d.ts.map +1 -0
  81. package/src/core/wfc/wfc_state_collapse.js +73 -0
  82. package/src/core/wfc/wfc_state_flush_dirty.d.ts +12 -0
  83. package/src/core/wfc/wfc_state_flush_dirty.d.ts.map +1 -0
  84. package/src/core/wfc/wfc_state_flush_dirty.js +39 -0
  85. package/src/core/wfc/wfc_state_propagate.d.ts +14 -0
  86. package/src/core/wfc/wfc_state_propagate.d.ts.map +1 -0
  87. package/src/core/wfc/wfc_state_propagate.js +155 -0
  88. package/src/core/wfc/wfc_state_read.d.ts +11 -0
  89. package/src/core/wfc/wfc_state_read.d.ts.map +1 -0
  90. package/src/core/wfc/wfc_state_read.js +38 -0
  91. package/src/core/wfc/wfc_state_restrict.d.ts +18 -0
  92. package/src/core/wfc/wfc_state_restrict.d.ts.map +1 -0
  93. package/src/core/wfc/wfc_state_restrict.js +39 -0
  94. package/src/core/wfc/wfc_state_set_eligible.d.ts +24 -0
  95. package/src/core/wfc/wfc_state_set_eligible.d.ts.map +1 -0
  96. package/src/core/wfc/wfc_state_set_eligible.js +49 -0
  97. package/src/core/wfc/wfc_state_undo_to.d.ts +13 -0
  98. package/src/core/wfc/wfc_state_undo_to.d.ts.map +1 -0
  99. package/src/core/wfc/wfc_state_undo_to.js +84 -0
  100. package/src/core/wfc/wfc_words_per_cell.d.ts +11 -0
  101. package/src/core/wfc/wfc_words_per_cell.d.ts.map +1 -0
  102. package/src/core/wfc/wfc_words_per_cell.js +16 -0
  103. package/src/engine/ecs/EntityComponentDataset.d.ts +8 -0
  104. package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
  105. package/src/engine/ecs/EntityComponentDataset.js +37 -14
  106. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +8 -1
  107. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
  108. package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +37 -2
  109. package/src/engine/ecs/guid/UUID.d.ts.map +1 -1
  110. package/src/engine/ecs/guid/UUID.js +45 -17
  111. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
  112. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +3 -1
  113. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  114. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +366 -360
  115. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.d.ts.map +1 -1
  116. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.js +24 -10
  117. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  118. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +24 -5
  119. package/src/engine/interpolation/Interpoland.d.ts +7 -1
  120. package/src/engine/interpolation/Interpoland.d.ts.map +1 -1
  121. package/src/engine/interpolation/Interpoland.js +8 -1
  122. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
  123. package/src/engine/interpolation/InterpolationSystem.js +3 -0
  124. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +5 -3
  125. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
  126. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +14 -3
  127. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -1
  128. package/src/engine/interpolation/pose_interpoland.js +2 -0
  129. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  130. package/src/engine/network/orchestrator/NetworkPeer.js +15 -10
  131. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  132. package/src/engine/physics/cloth/ecs/ClothWorld.js +17 -0
  133. package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
  134. package/src/engine/physics/mls-mpm/MLS_MPM.js +14 -0
  135. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +2 -1
  136. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -1
  137. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +1 -1
  138. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +1 -0
  139. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
  140. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +1 -0
  141. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
  142. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +2 -1
  143. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
  144. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +12 -3
  145. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +1 -0
  146. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
  147. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -0
  148. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  149. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  150. package/src/engine/sound/sopra/runtime/EventInstance.js +4 -1
  151. package/src/format/image/avif/av1/decode/decode_coefficients.d.ts.map +1 -1
  152. package/src/format/image/avif/av1/decode/decode_coefficients.js +248 -259
  153. package/src/format/image/avif/av1/decode/read_golomb.d.ts +21 -0
  154. package/src/format/image/avif/av1/decode/read_golomb.d.ts.map +1 -0
  155. package/src/format/image/avif/av1/decode/read_golomb.js +49 -0
  156. package/src/format/image/avif/av1/filter/loop_restoration.js +461 -451
  157. package/src/format/image/avif/av1/util/round2.d.ts +19 -2
  158. package/src/format/image/avif/av1/util/round2.d.ts.map +1 -1
  159. package/src/format/image/avif/av1/util/round2.js +51 -27
  160. package/src/format/image/avif/heif/parse_avif_file.js +24 -0
  161. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  162. package/src/shade/renderer/buffer/table/GPUTypedTable.js +14 -0
  163. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +46 -3
  164. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +91 -1
  165. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +0 -13
  166. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  167. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +0 -15
  168. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +5 -5
  169. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts +11 -1
  170. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts.map +1 -1
  171. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js +11 -1
  172. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +18 -9
  173. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -1
  174. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js +20 -26
  175. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +1 -3
  176. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -1
  177. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.js +2 -5
  178. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -1
  179. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
  180. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +14 -1
  181. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.d.ts.map +1 -1
  182. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +12 -3
  183. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.d.ts.map +1 -1
  184. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.js +7 -1
  185. package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
  186. package/src/shade/renderer/lightmap/GPULightMap.js +349 -336
  187. package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
  188. package/src/shade/renderer/loader/gltf/load_gltf.js +1137 -1128
  189. package/src/shade/renderer/particles/DESIGN.md +14 -5
  190. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +25 -10
  191. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  192. package/src/shade/renderer/particles/GPUParticleSystem.js +182 -38
  193. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  194. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +16 -10
  195. package/src/shade/renderer/particles/graph_particles.d.ts +4 -2
  196. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  197. package/src/shade/renderer/particles/graph_particles.js +8 -2
  198. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  199. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +8 -2
  200. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +10 -4
  202. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +31 -0
  203. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +1 -0
  204. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.js +62 -0
  205. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +13 -0
  206. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  207. package/src/shade/renderer/particles/vm/ParticleVMReference.js +95 -17
  208. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +21 -0
  209. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -0
  210. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +28 -0
  211. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  212. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +19 -2
  213. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  214. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +19 -2
  215. package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
  216. package/src/shade/renderer/shader/type/WebGPUStruct.js +12 -0
  217. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts +8 -2
  218. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts.map +1 -1
  219. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +41 -6
  220. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2383 -2356
  221. package/src/view/tooltip/DomTooltipManager.d.ts +7 -0
  222. package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
  223. package/src/view/tooltip/DomTooltipManager.js +22 -0
  224. package/src/view/tooltip/DomTooltipObserver.d.ts.map +1 -1
  225. package/src/view/tooltip/DomTooltipObserver.js +13 -3
  226. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.js +0 -45
@@ -284,7 +284,12 @@ export function evaluate_particle_operation(op, a, b, c) {
284
284
  // the whole of what the option trades; anything an executor could disagree about by more
285
285
  // than a rounding is refused below rather than folded.
286
286
  case VM_OP.MAD: return Math.fround(a * b + c);
287
- case VM_OP.MIX: return Math.fround(a + (b - a) * c);
287
+ case VM_OP.MIX: {
288
+ const from = Math.fround(a * Math.fround(1 - c));
289
+ const to = Math.fround(b * c);
290
+
291
+ return Math.fround(from + to);
292
+ }
288
293
  case VM_OP.SIN: return Math.fround(Math.sin(a));
289
294
  case VM_OP.COS: return Math.fround(Math.cos(a));
290
295
  case VM_OP.TAN: return Math.fround(Math.tan(a));
@@ -294,7 +299,7 @@ export function evaluate_particle_operation(op, a, b, c) {
294
299
  case VM_OP.LOG: return Math.fround(Math.log(a));
295
300
  // `1 / sqrt(a)` for `a <= 0` is an infinity or a `NaN`, and `finite` drops both — the same
296
301
  // rule that keeps `SQRT` next door out of trouble, so there is nothing to say here.
297
- case VM_OP.RSQRT: return Math.fround(1 / Math.sqrt(a));
302
+ case VM_OP.RSQRT: return Math.fround(1 / Math.fround(Math.sqrt(a)));
298
303
  // WGSL's `pow` is `exp2(e2 * log2(e1))`, which is a `NaN` for every base at or below zero
299
304
  // — including `pow(0, 0)`, where JS says 1. A fold that turned the hardware's `NaN` into a
300
305
  // number would not be a rounding, so the non-positive bases are not folded at all.
@@ -306,9 +311,10 @@ export function evaluate_particle_operation(op, a, b, c) {
306
311
  return NaN;
307
312
  }
308
313
 
309
- const t = Math.min(Math.max((c - a) / (b - a), 0), 1);
314
+ const span = Math.fround(b - a);
315
+ const t = Math.min(Math.max(Math.fround(Math.fround(c - a) / span), 0), 1);
310
316
 
311
- return Math.fround(t * t * (3 - 2 * t));
317
+ return Math.fround(Math.fround(t * t) * Math.fround(3 - Math.fround(2 * t)));
312
318
  }
313
319
  // `atan2(0, 0)` is 0 in JS and left to the implementation in WGSL. Every other pair is an
314
320
  // angle both agree on to a rounding.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The free list as a fresh pool leaves it: every slot free, `dead[i] = i`, bottom to top.
3
+ *
4
+ * It is a stack of slot indices — the emit pass pops `dead[--DEAD]` — so the order within it is
5
+ * arbitrary and identity is simply the cheapest arrangement to write. What matters is that every
6
+ * slot appears exactly once: a repeat hands two particles one record, and a gap strands a slot for
7
+ * the lifetime of the system.
8
+ *
9
+ * ## Why this is a pass and not a host write
10
+ *
11
+ * Zero is not a valid free list — `dead[i] = 0` for every `i` is the same slot half a million times
12
+ * over — so the buffer WebGPU hands back has to be written before the first emit. Writing it from
13
+ * the host means a typed array the size of the list (2 MiB at the default capacity), a loop over
14
+ * every element of it, and a staged upload, all on the frame the system is constructed. Here it is
15
+ * one store per slot with no host memory involved at all, and it leaves the free list `STORAGE`
16
+ * only: nothing else ever writes it from the host, so it needs no `COPY_DST` and stays off the
17
+ * driver's upload-friendly — and slower — path.
18
+ *
19
+ * ## Bounded launch, strided loop
20
+ *
21
+ * The pool capacity belongs to whoever constructs the system, and `capacity / workgroup_size` can
22
+ * pass `maxComputeWorkgroupsPerDimension` — a validation error for a direct dispatch, and worse
23
+ * than that for the indirect ones elsewhere in the engine, which simply do not run. So the caller
24
+ * clamps the launch and every lane strides through the list by the whole grid: the fill covers any
25
+ * capacity, and a grid that happens to cover it in one pass costs one comparison to find out.
26
+ */
27
+ /** Lanes per workgroup. One store each, so this is sized for occupancy and nothing else. */
28
+ export const PARTICLE_INIT_FREE_LIST_WORKGROUP_SIZE: 256;
29
+ export const shader_particle_init_free_list: ComputeShader;
30
+ import { ComputeShader } from "../../shader/ComputeShader.js";
31
+ //# sourceMappingURL=shader_particle_init_free_list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shader_particle_init_free_list.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/shaders/shader_particle_init_free_list.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,4FAA4F;AAC5F,yDAA0D;AAQ1D,2DAmBG;8BA3D2B,+BAA+B"}
@@ -0,0 +1,62 @@
1
+ import { u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
2
+ import { WebGPUArray } from "../../shader/type/WebGPUArray.js";
3
+ import { ComputeShader } from "../../shader/ComputeShader.js";
4
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
5
+ import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
6
+
7
+ /**
8
+ * The free list as a fresh pool leaves it: every slot free, `dead[i] = i`, bottom to top.
9
+ *
10
+ * It is a stack of slot indices — the emit pass pops `dead[--DEAD]` — so the order within it is
11
+ * arbitrary and identity is simply the cheapest arrangement to write. What matters is that every
12
+ * slot appears exactly once: a repeat hands two particles one record, and a gap strands a slot for
13
+ * the lifetime of the system.
14
+ *
15
+ * ## Why this is a pass and not a host write
16
+ *
17
+ * Zero is not a valid free list — `dead[i] = 0` for every `i` is the same slot half a million times
18
+ * over — so the buffer WebGPU hands back has to be written before the first emit. Writing it from
19
+ * the host means a typed array the size of the list (2 MiB at the default capacity), a loop over
20
+ * every element of it, and a staged upload, all on the frame the system is constructed. Here it is
21
+ * one store per slot with no host memory involved at all, and it leaves the free list `STORAGE`
22
+ * only: nothing else ever writes it from the host, so it needs no `COPY_DST` and stays off the
23
+ * driver's upload-friendly — and slower — path.
24
+ *
25
+ * ## Bounded launch, strided loop
26
+ *
27
+ * The pool capacity belongs to whoever constructs the system, and `capacity / workgroup_size` can
28
+ * pass `maxComputeWorkgroupsPerDimension` — a validation error for a direct dispatch, and worse
29
+ * than that for the indirect ones elsewhere in the engine, which simply do not run. So the caller
30
+ * clamps the launch and every lane strides through the list by the whole grid: the fill covers any
31
+ * capacity, and a grid that happens to cover it in one pass costs one comparison to find out.
32
+ */
33
+
34
+ /** Lanes per workgroup. One store each, so this is sized for occupancy and nothing else. */
35
+ export const PARTICLE_INIT_FREE_LIST_WORKGROUP_SIZE = 256;
36
+
37
+ const resources = new ShaderResourceSetDescriptor();
38
+
39
+ // per-dispatch, four bytes, read-only: immediate data rather than a uniform buffer holding one u32
40
+ resources.setImmediate("u_capacity", u32);
41
+ resources.createGroup().addStorageBuffer("dead", WebGPUArray.u32, true);
42
+
43
+ export const shader_particle_init_free_list = ComputeShader.from({
44
+ label: "Particle Init Free List",
45
+ resources,
46
+ body: CodeChunk.from(
47
+ /* language=wgsl */
48
+ `
49
+ @compute @workgroup_size(${PARTICLE_INIT_FREE_LIST_WORKGROUP_SIZE}, 1, 1)
50
+ fn main(
51
+ @builtin(global_invocation_id) global_id : vec3<u32>,
52
+ @builtin(num_workgroups) group_count : vec3<u32>,
53
+ ) {
54
+ // The grid may be smaller than the list; every lane keeps taking the next slot a whole grid on.
55
+ let stride = group_count.x * ${PARTICLE_INIT_FREE_LIST_WORKGROUP_SIZE}u;
56
+
57
+ for (var slot = global_id.x; slot < u_capacity; slot += stride) {
58
+ dead[slot] = slot;
59
+ }
60
+ }
61
+ `),
62
+ });
@@ -29,6 +29,19 @@ export function u32_to_unit_f32(u: number): number;
29
29
  * so a source may overlap the destination in any way and still mean what it held before the
30
30
  * instruction.
31
31
  *
32
+ * ## Every step is an f32 step
33
+ *
34
+ * The register file is `f32`, and so is the shader's. An opcode that takes more than one arithmetic
35
+ * step to reach its result therefore rounds at every one of them, through `Math.fround`, rather
36
+ * than computing wide and rounding on the way out. Rounding only at the end gives f32 RESULTS
37
+ * without f32 RANGE, and the range is where the two executors part company: a product that is
38
+ * `inf` on the device is an ordinary number in `f64`, a difference that saturates there is finite
39
+ * here, and neither is a rounding apart. `CROSS` answered 0 where the device answers NaN, and
40
+ * `SMOOTHSTEP` answered the midpoint where the device answers the near edge.
41
+ *
42
+ * Two opcodes are deliberately outside that rule, and each says why where it is written: `MAD`,
43
+ * which models the fused product, and `FRACT`, whose subtraction cannot round twice.
44
+ *
32
45
  * @param {object} params
33
46
  * @param {Uint32Array} params.words instruction words for the section (SPAWN or UPDATE)
34
47
  * @param {Float32Array} params.constants constant pool, one float per word
@@ -1 +1 @@
1
- {"version":3,"file":"ParticleVMReference.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/vm/ParticleVMReference.js"],"names":[],"mappings":"AA0BA;;;;;;;GAOG;AACH,gCAHW,MAAM,GACJ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAW5C;AAED;;;;;GAKG;AACH,mCAHW,MAAM,GACJ,MAAM,CAIlB;AA8DD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;IAR+B,KAAK,EAAzB,WAAW;IACU,SAAS,EAA9B,YAAY;IACG,cAAc,EAA7B,MAAM;IACc,MAAM,EAA1B,WAAW;IACS,QAAQ,GAA5B,MAAM,EAAE,EAAE;IACM,SAAS,GAAzB,MAAM;IACJ;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAuRlD"}
1
+ {"version":3,"file":"ParticleVMReference.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/vm/ParticleVMReference.js"],"names":[],"mappings":"AA0BA;;;;;;;GAOG;AACH,gCAHW,MAAM,GACJ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAW5C;AAED;;;;;GAKG;AACH,mCAHW,MAAM,GACJ,MAAM,CAIlB;AA8DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH;IAR+B,KAAK,EAAzB,WAAW;IACU,SAAS,EAA9B,YAAY;IACG,cAAc,EAA7B,MAAM;IACc,MAAM,EAA1B,WAAW;IACS,QAAQ,GAA5B,MAAM,EAAE,EAAE;IACM,SAAS,GAAzB,MAAM;IACJ;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAyUlD"}
@@ -125,6 +125,19 @@ const LITERAL_U32 = new Uint32Array(LITERAL_F32.buffer);
125
125
  * so a source may overlap the destination in any way and still mean what it held before the
126
126
  * instruction.
127
127
  *
128
+ * ## Every step is an f32 step
129
+ *
130
+ * The register file is `f32`, and so is the shader's. An opcode that takes more than one arithmetic
131
+ * step to reach its result therefore rounds at every one of them, through `Math.fround`, rather
132
+ * than computing wide and rounding on the way out. Rounding only at the end gives f32 RESULTS
133
+ * without f32 RANGE, and the range is where the two executors part company: a product that is
134
+ * `inf` on the device is an ordinary number in `f64`, a difference that saturates there is finite
135
+ * here, and neither is a rounding apart. `CROSS` answered 0 where the device answers NaN, and
136
+ * `SMOOTHSTEP` answered the midpoint where the device answers the near edge.
137
+ *
138
+ * Two opcodes are deliberately outside that rule, and each says why where it is written: `MAD`,
139
+ * which models the fused product, and `FRACT`, whose subtraction cannot round twice.
140
+ *
128
141
  * @param {object} params
129
142
  * @param {Uint32Array} params.words instruction words for the section (SPAWN or UPDATE)
130
143
  * @param {Float32Array} params.constants constant pool, one float per word
@@ -263,6 +276,18 @@ export function execute_particle_vm({
263
276
  case DIV:
264
277
  for (let i = 0; i < w; i++) d[i] = a[i] / b[i];
265
278
  break;
279
+ // The exception to the rule above, and the only one that is a choice. `MAD` models the
280
+ // FUSED form: the product reaches the addition unrounded, which is what makes it more
281
+ // than `MUL` then `ADD`. {@link ../optimizer/pass_peephole.js} refuses to fuse that
282
+ // pair into a `MAD` by default for exactly this reason, and
283
+ // {@link ../optimizer/particle_integer_bounds.js} exists only to prove the cases where
284
+ // the product needs no rounding and the two forms therefore agree; rounding it here
285
+ // would make both of those dead weight.
286
+ //
287
+ // The shader spells this `a * b + c`, which a driver is free to contract into an FMA
288
+ // or not, so it is a reading rather than a certainty — `VM_OP.MAD` is INEXACT in
289
+ // {@link ../optimizer/particle_vm_semantics.js} to say so. Spelling the shader
290
+ // `fma(a, b, c)` would settle it in this reading's favour on every device.
266
291
  case MAD:
267
292
  for (let i = 0; i < w; i++) d[i] = a[i] * b[i] + c[i];
268
293
  break;
@@ -272,8 +297,21 @@ export function execute_particle_vm({
272
297
  case MAX:
273
298
  for (let i = 0; i < w; i++) d[i] = Math.max(a[i], b[i]);
274
299
  break;
300
+ // WGSL defines `mix(e1, e2, e3)` as `e1 * (1 - e3) + e2 * e3`, and this used to spell
301
+ // it as the lerp `a + (b - a) * c`. The two are the same number only where the span
302
+ // `b - a` loses nothing, and at the ends of the f32 range it loses everything:
303
+ // `mix(1e20, 1, 1)` is 1 on the device and was 0 here, because `1 - 1e20` IS `-1e20`
304
+ // in f32. Rounding the lerp's steps without changing its shape would have made it
305
+ // worse — the span of (3e38, -3e38) is not an f32 at all, so a blend both of whose
306
+ // ends are finite would have come out infinite. Scaling each end on its own is what
307
+ // the shader does and it cannot overflow where the result does not.
275
308
  case MIX:
276
- for (let i = 0; i < w; i++) d[i] = a[i] + (b[i] - a[i]) * c[i];
309
+ for (let i = 0; i < w; i++) {
310
+ const from = Math.fround(a[i] * Math.fround(1 - c[i]));
311
+ const to = Math.fround(b[i] * c[i]);
312
+
313
+ d[i] = Math.fround(from + to);
314
+ }
277
315
  break;
278
316
  case CLAMP:
279
317
  for (let i = 0; i < w; i++) d[i] = Math.min(Math.max(a[i], b[i]), c[i]);
@@ -288,6 +326,12 @@ export function execute_particle_vm({
288
326
  case FLOOR:
289
327
  for (let i = 0; i < w; i++) d[i] = Math.floor(a[i]);
290
328
  break;
329
+ // The other exception, and this one is free: `a - floor(a)` cannot round twice. For
330
+ // |a| >= 1 the fractional part is a run of at most 23 of a's own mantissa bits and is
331
+ // an f32 exactly; for |a| < 1 it is a itself, or `1 + a`, which only becomes inexact
332
+ // in f64 once |a| falls below about 2^-29 — by which point it is far inside the last
333
+ // f32 step below 1 and rounds to 1.0 either way. The two conditions a double rounding
334
+ // would need cannot hold at once, which the spec checks rather than assumes.
291
335
  case FRACT:
292
336
  for (let i = 0; i < w; i++) d[i] = a[i] - Math.floor(a[i]);
293
337
  break;
@@ -316,9 +360,13 @@ export function execute_particle_vm({
316
360
  for (let i = 0; i < w; i++) d[i] = smoothstep(a[i], b[i], c[i]);
317
361
  break;
318
362
  // WGSL's `inverseSqrt`. Spelled as the reciprocal of a root rather than approximated,
319
- // because the reference is the definition the hardware's estimate is measured against.
363
+ // because the reference is the definition the hardware's estimate is measured against
364
+ // — which is also why the root is rounded before the division reads it. A definition
365
+ // carried out in f64 is not the thing `inverseSqrt` is allowed to be 2 ULP away from:
366
+ // 1 / sqrt(0.0009) is 33.33333206176758 taken wide and 33.333335876464844 taken the
367
+ // way the device takes it, and the slack is measured from the second.
320
368
  case RSQRT:
321
- for (let i = 0; i < w; i++) d[i] = 1 / Math.sqrt(a[i]);
369
+ for (let i = 0; i < w; i++) d[i] = Math.fround(1 / Math.fround(Math.sqrt(a[i])));
322
370
  break;
323
371
  case TAN:
324
372
  for (let i = 0; i < w; i++) d[i] = Math.tan(a[i]);
@@ -336,40 +384,55 @@ export function execute_particle_vm({
336
384
 
337
385
  // The reductions write a single lane; whatever wants the result wider reads it through
338
386
  // an `.xxxx` swizzle.
387
+ //
388
+ // Every step goes through `Math.fround`, because a plain `let sum = 0` is an f64
389
+ // accumulator and only the store into the register file rounds — which gives f32
390
+ // RESULTS without f32 RANGE, and the range is not recovered at the end. The shader
391
+ // computes `dot(a, a)` entirely in f32, where 1e20 squared is already `inf` and
392
+ // 1e-25 squared is already zero, so the two sides answered `inf` against 1.00000002e20
393
+ // and, through NORMALIZE's `len > 0`, the zero vector against a unit one.
339
394
  case DOT: {
340
395
  let dp = 0;
341
- for (let i = 0; i < w; i++) dp += a[i] * b[i];
396
+ for (let i = 0; i < w; i++) dp = Math.fround(dp + Math.fround(a[i] * b[i]));
342
397
  d[0] = dp;
343
398
  break;
344
399
  }
400
+ // Six products and three differences, every one of them in f32. Taken wide, a lane
401
+ // whose two products both overflow cancelled to exactly 0 — the mathematically right
402
+ // answer for a vector crossed with itself — where the device subtracts `inf - inf` and
403
+ // gets NaN. a = b = (0, 1e20, 1e20) is the shortest witness, and a NaN that exists
404
+ // only on the GPU is the worst shape this takes: invisible in the editor preview, and
405
+ // loose in a position attribute from the first frame it appears.
345
406
  case CROSS:
346
- d[0] = a[1] * b[2] - a[2] * b[1];
347
- d[1] = a[2] * b[0] - a[0] * b[2];
348
- d[2] = a[0] * b[1] - a[1] * b[0];
407
+ d[0] = Math.fround(Math.fround(a[1] * b[2]) - Math.fround(a[2] * b[1]));
408
+ d[1] = Math.fround(Math.fround(a[2] * b[0]) - Math.fround(a[0] * b[2]));
409
+ d[2] = Math.fround(Math.fround(a[0] * b[1]) - Math.fround(a[1] * b[0]));
349
410
  // A fourth lane, if asked for, is the zero the opcode used to append.
350
411
  d[3] = 0;
351
412
  break;
352
413
  case LENGTH: {
353
414
  let sum = 0;
354
- for (let i = 0; i < w; i++) sum += a[i] * a[i];
355
- d[0] = Math.sqrt(sum);
415
+ for (let i = 0; i < w; i++) sum = Math.fround(sum + Math.fround(a[i] * a[i]));
416
+ d[0] = Math.fround(Math.sqrt(sum));
356
417
  break;
357
418
  }
358
419
  case NORMALIZE: {
359
420
  let sum = 0;
360
- for (let i = 0; i < w; i++) sum += a[i] * a[i];
361
- const len = Math.sqrt(sum);
362
- const inv = len > 0 ? 1 / len : 0;
421
+ for (let i = 0; i < w; i++) sum = Math.fround(sum + Math.fround(a[i] * a[i]));
422
+ const len = Math.fround(Math.sqrt(sum));
423
+ // the shader's `select(0.0, 1.0 / len, len > 0.0)` — the same test, over a length
424
+ // that has now underflowed on the same inputs
425
+ const inv = len > 0 ? Math.fround(1 / len) : 0;
363
426
  for (let i = 0; i < w; i++) d[i] = a[i] * inv;
364
427
  break;
365
428
  }
366
429
  case DISTANCE: {
367
430
  let sum = 0;
368
431
  for (let i = 0; i < w; i++) {
369
- const e = a[i] - b[i];
370
- sum += e * e;
432
+ const e = Math.fround(a[i] - b[i]);
433
+ sum = Math.fround(sum + Math.fround(e * e));
371
434
  }
372
- d[0] = Math.sqrt(sum);
435
+ d[0] = Math.fround(Math.sqrt(sum));
373
436
  break;
374
437
  }
375
438
 
@@ -414,6 +477,18 @@ export function execute_particle_vm({
414
477
  }
415
478
 
416
479
  /**
480
+ * WGSL's `smoothstep`, which is `t * t * (3 - 2t)` over `t = clamp((x - low) / (high - low), 0, 1)`.
481
+ *
482
+ * Every step is an f32 step, for the reason the executor's docblock gives, and here it is the SPAN
483
+ * that carries it: with the edges at ±3e38 the divisor is 6e38, which is `inf` in f32, so the
484
+ * device divides by an infinity, gets 0, and returns the near edge. Taken wide the divisor is
485
+ * finite and the answer was the midpoint — a ramp authored across a range wider than f32 reaches
486
+ * read as half-way in the editor preview and as not started at all in the game.
487
+ *
488
+ * The equal-edges guard stands in for a division by zero the device would do instead. It is the one
489
+ * place this function is not the shader's arithmetic, and it only differs from it at `x === edge0`,
490
+ * where the device's `0 / 0` is a NaN that WGSL does not define the clamp of.
491
+ *
417
492
  * @param {number} edge0
418
493
  * @param {number} edge1
419
494
  * @param {number} x
@@ -421,6 +496,9 @@ export function execute_particle_vm({
421
496
  */
422
497
  function smoothstep(edge0, edge1, x) {
423
498
  if (edge0 === edge1) return x < edge0 ? 0 : 1;
424
- const t = Math.min(Math.max((x - edge0) / (edge1 - edge0), 0), 1);
425
- return t * t * (3 - 2 * t);
499
+
500
+ const span = Math.fround(edge1 - edge0);
501
+ const t = Math.min(Math.max(Math.fround(Math.fround(x - edge0) / span), 0), 1);
502
+
503
+ return Math.fround(Math.fround(t * t) * Math.fround(3 - Math.fround(2 * t)));
426
504
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Whether the scene has anything in an opaque rasterization bucket.
3
+ *
4
+ * The opaque path clears the visibility-buffer colour attachments and the depth attachment on its
5
+ * first draw, and it is the only thing that does — every later pass is recorded with `load`. So a
6
+ * scene whose materials are all alpha-tested or transparent, which is an ordinary thing (a
7
+ * foliage-only vegetation view, a glass preview, a point light whose only casters are billboards),
8
+ * used to draw over the previous frame's contents: geometry depth-tested against stale occluders,
9
+ * and every pixel the alpha-tested pass did not cover keeping last frame's mesh and triangle ids for
10
+ * the deferred shading to resolve as real surfaces.
11
+ *
12
+ * This is what lets the composer hand the clear to the first pass that will actually run.
13
+ *
14
+ * @param {Set<number>} active_buckets bucket ids any material in the scene asks for
15
+ * @returns {boolean}
16
+ *
17
+ * @author Alex Goldring
18
+ * @copyright Company Named Limited (c) 2026
19
+ */
20
+ export function rasterization_has_opaque_bucket(active_buckets: Set<number>): boolean;
21
+ //# sourceMappingURL=rasterization_has_opaque_bucket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rasterization_has_opaque_bucket.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,gEANW,IAAI,MAAM,CAAC,GACT,OAAO,CAWnB"}
@@ -0,0 +1,28 @@
1
+ import { rasterization_opaque_buckets } from "./rasterization_opaque_buckets.js";
2
+
3
+ /**
4
+ * Whether the scene has anything in an opaque rasterization bucket.
5
+ *
6
+ * The opaque path clears the visibility-buffer colour attachments and the depth attachment on its
7
+ * first draw, and it is the only thing that does — every later pass is recorded with `load`. So a
8
+ * scene whose materials are all alpha-tested or transparent, which is an ordinary thing (a
9
+ * foliage-only vegetation view, a glass preview, a point light whose only casters are billboards),
10
+ * used to draw over the previous frame's contents: geometry depth-tested against stale occluders,
11
+ * and every pixel the alpha-tested pass did not cover keeping last frame's mesh and triangle ids for
12
+ * the deferred shading to resolve as real surfaces.
13
+ *
14
+ * This is what lets the composer hand the clear to the first pass that will actually run.
15
+ *
16
+ * @param {Set<number>} active_buckets bucket ids any material in the scene asks for
17
+ * @returns {boolean}
18
+ *
19
+ * @author Alex Goldring
20
+ * @copyright Company Named Limited (c) 2026
21
+ */
22
+ export function rasterization_has_opaque_bucket(active_buckets) {
23
+ for (const _bucket of rasterization_opaque_buckets(active_buckets)) {
24
+ return true;
25
+ }
26
+
27
+ return false;
28
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"graph_rasterize_scene_fast.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;GAUG;AACH;;;;;;;IAFa;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAC,CA0I3D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IAJ+B,KAAK;IACC,GAAG;IACA,MAAM;SA8C7C"}
1
+ {"version":3,"file":"graph_rasterize_scene_fast.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js"],"names":[],"mappings":"AAoBA;;;;;;;;;;GAUG;AACH;;;;;;;IAFa;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAC,CA0J3D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IAJ+B,KAAK;IACC,GAAG;IACA,MAAM;SA8C7C"}
@@ -13,6 +13,7 @@ import { graph_vgeo_expand_if_enabled } from "../../geometry/virtual/runtime/gra
13
13
  import { RasterizationCollectionType } from "../expand/RasterizationCollectionType.js";
14
14
  import { construct_primitive_state } from "../bucket/construct_primitive_state.js";
15
15
  import { rasterization_opaque_buckets } from "../bucket/rasterization_opaque_buckets.js";
16
+ import { rasterization_has_opaque_bucket } from "../bucket/rasterization_has_opaque_bucket.js";
16
17
  import { graph_mesh_frustum_cull } from "../compute/graph_mesh_frustum_cull.js";
17
18
  import { graph_rasterize_meshes_alpha_tested } from "../native/graph_rasterize_meshes_alpha_tested.js";
18
19
  import { graph_extract_meshes_from_bucket } from "./graph_extract_meshes_from_bucket.js";
@@ -123,13 +124,27 @@ export function graph_rasterize_scene_fast_raw({
123
124
 
124
125
  // alpha-tested: reuse the existing native pass but skip HZB filtering by passing hzb=-1
125
126
  if (job.alpha_tested_pass !== undefined) {
127
+ /**
128
+ * The opaque path's first draw is the only thing that clears the visibility-buffer colour
129
+ * attachments and the depth attachment — the clear values live on the opaque pass descriptor
130
+ * and reach the targets through its `loadOp`, and every pass after it is recorded with
131
+ * `load`. When the scene has no opaque bucket that draw never happens, so the first pass that
132
+ * does run owns the clear. The alpha-tested descriptor carries the same clear values
133
+ * (`[0,0,0,0]`, VIZ_BUFFER_INVALID_MESH_VALUE, DEPTH_BACKGROUND_VALUE), so handing it over
134
+ * changes nothing about what the targets are cleared to.
135
+ *
136
+ * Wherever an opaque bucket exists — nearly every scene — this starts true and every call
137
+ * below asks for `false`, exactly as before.
138
+ */
139
+ let targets_cleared = rasterization_has_opaque_bucket(active_buckets);
140
+
126
141
 
127
142
  const alpha_tested_front = graph_rasterize_meshes_alpha_tested({
128
143
  graph,
129
144
  buckets: gr_render_buckets,
130
145
  view_ctx: view_ctx,
131
146
  render_pass_descriptor: job.alpha_tested_pass,
132
- clear_target: false,
147
+ clear_target: !targets_cleared,
133
148
  side: ShadeDrawSide.Front,
134
149
  cull_policy: job.cull_policy,
135
150
  limits,
@@ -142,13 +157,14 @@ export function graph_rasterize_scene_fast_raw({
142
157
 
143
158
  target_depth = alpha_tested_front.depth;
144
159
  targets_color = alpha_tested_front.color_targets;
160
+ targets_cleared = targets_cleared || alpha_tested_front.executed;
145
161
 
146
162
  const alpha_tested_double = graph_rasterize_meshes_alpha_tested({
147
163
  graph,
148
164
  buckets: gr_render_buckets,
149
165
  view_ctx: view_ctx,
150
166
  render_pass_descriptor: job.alpha_tested_pass,
151
- clear_target: false,
167
+ clear_target: !targets_cleared,
152
168
  side: ShadeDrawSide.Double,
153
169
  cull_policy: job.cull_policy,
154
170
  limits,
@@ -161,6 +177,7 @@ export function graph_rasterize_scene_fast_raw({
161
177
 
162
178
  target_depth = alpha_tested_double.depth;
163
179
  targets_color = alpha_tested_double.color_targets;
180
+ targets_cleared = targets_cleared || alpha_tested_double.executed;
164
181
  }
165
182
 
166
183
  return {target_depth, targets_color};
@@ -1 +1 @@
1
- {"version":3,"file":"graph_rasterize_scene.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/rasterize/standard/graph_rasterize_scene.js"],"names":[],"mappings":"AAcA;;;;;;;;;;;GAWG;AACH;IAN+B,KAAK;IACC,GAAG;IACA,MAAM;IACjC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAC,CA0MrE"}
1
+ {"version":3,"file":"graph_rasterize_scene.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/rasterize/standard/graph_rasterize_scene.js"],"names":[],"mappings":"AAeA;;;;;;;;;;;GAWG;AACH;IAN+B,KAAK;IACC,GAAG;IACA,MAAM;IACjC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAC,CA0NrE"}
@@ -11,6 +11,7 @@ import { RasterizationCollectionType } from "../expand/RasterizationCollectionTy
11
11
  import { graph_rasterize_meshes_alpha_tested } from "../native/graph_rasterize_meshes_alpha_tested.js";
12
12
  import { graph_rasterize_partial_opaque } from "./graph_rasterize_partial_opaque.js";
13
13
  import { graph_rasterize_remaining_opaque } from "./graph_rasterize_remaining_opaque.js";
14
+ import { rasterization_has_opaque_bucket } from "../bucket/rasterization_has_opaque_bucket.js";
14
15
 
15
16
  /**
16
17
  * TODO:
@@ -155,13 +156,27 @@ export function graph_rasterize_scene({
155
156
 
156
157
 
157
158
  if (job.alpha_tested_pass !== undefined) {
159
+ /**
160
+ * The opaque path's first draw is the only thing that clears the visibility-buffer colour
161
+ * attachments and the depth attachment — the clear values live on the opaque pass descriptor
162
+ * and reach the targets through its `loadOp`, and every pass after it is recorded with
163
+ * `load`. When the scene has no opaque bucket that draw never happens, so the first pass that
164
+ * does run owns the clear. The alpha-tested descriptor carries the same clear values
165
+ * (`[0,0,0,0]`, VIZ_BUFFER_INVALID_MESH_VALUE, DEPTH_BACKGROUND_VALUE), so handing it over
166
+ * changes nothing about what the targets are cleared to.
167
+ *
168
+ * Wherever an opaque bucket exists — nearly every scene — this starts true and every call
169
+ * below asks for `false`, exactly as before.
170
+ */
171
+ let targets_cleared = rasterization_has_opaque_bucket(active_buckets);
172
+
158
173
 
159
174
  const alpha_tested_front = graph_rasterize_meshes_alpha_tested({
160
175
  graph,
161
176
  buckets: gr_render_buckets,
162
177
  view_ctx,
163
178
  render_pass_descriptor: job.alpha_tested_pass,
164
- clear_target: false,
179
+ clear_target: !targets_cleared,
165
180
  side: ShadeDrawSide.Front,
166
181
  cull_policy: job.cull_policy,
167
182
  limits,
@@ -174,13 +189,14 @@ export function graph_rasterize_scene({
174
189
 
175
190
  gr_texture_depth = alpha_tested_front.depth;
176
191
  raster_color_targets = alpha_tested_front.color_targets;
192
+ targets_cleared = targets_cleared || alpha_tested_front.executed;
177
193
 
178
194
  const alpha_tested_double = graph_rasterize_meshes_alpha_tested({
179
195
  graph,
180
196
  buckets: gr_render_buckets,
181
197
  view_ctx,
182
198
  render_pass_descriptor: job.alpha_tested_pass,
183
- clear_target: false,
199
+ clear_target: !targets_cleared,
184
200
  side: ShadeDrawSide.Double,
185
201
  cull_policy: job.cull_policy,
186
202
  limits,
@@ -193,6 +209,7 @@ export function graph_rasterize_scene({
193
209
 
194
210
  gr_texture_depth = alpha_tested_double.depth;
195
211
  raster_color_targets = alpha_tested_double.color_targets;
212
+ targets_cleared = targets_cleared || alpha_tested_double.executed;
196
213
 
197
214
  if (alpha_tested_front.executed || alpha_tested_double.executed) {
198
215
 
@@ -1 +1 @@
1
- {"version":3,"file":"WebGPUStruct.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/shader/type/WebGPUStruct.js"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH;IAqMI;;;;;;;;;;;;;;OAcG;IACH,8BAHW,MAAM,GACL,YAAY,CAiCvB;IAED;;;;OAIG;IACH,6BAHc,eAAe,KACjB,YAAY,CAiBvB;IA9PD;;;OAGG;IACH,uBAEC;IAVD,kBAEC;IAUD;;;OAGG;IACH,iBAFU,eAAe,EAAE,CAEf;IAmBZ;;;;OAIG;IACH,YAHW,YAAY,GACV,IAAI,CAYhB;IAED;;;OAGG;IACH,SAFY,YAAY,CAUvB;IAED;;;;;;;;OAQG;IACH,kBAJa,MAAM,CA+BlB;IAED;;;;;;OAMG;IACH,QAFa,IAAI,CAoChB;IAED,4BAYC;IAqGD;;;;OAIG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAGD;;;;;OAKG;IACH,gBAJW,MAAM,GACL,eAAe,CAa1B;IA8GL;;;OAGG;IACH,mBAFU,OAAO,CAEc;;CAN9B;2BAza0B,iBAAiB;gCAEZ,sBAAsB"}
1
+ {"version":3,"file":"WebGPUStruct.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/shader/type/WebGPUStruct.js"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH;IAiNI;;;;;;;;;;;;;;OAcG;IACH,8BAHW,MAAM,GACL,YAAY,CAiCvB;IAED;;;;OAIG;IACH,6BAHc,eAAe,KACjB,YAAY,CAiBvB;IA1QD;;;OAGG;IACH,uBAEC;IAVD,kBAEC;IAUD;;;OAGG;IACH,iBAFU,eAAe,EAAE,CAEf;IAmBZ;;;;OAIG;IACH,YAHW,YAAY,GACV,IAAI,CAwBhB;IAED;;;OAGG;IACH,SAFY,YAAY,CAUvB;IAED;;;;;;;;OAQG;IACH,kBAJa,MAAM,CA+BlB;IAED;;;;;;OAMG;IACH,QAFa,IAAI,CAoChB;IAED,4BAYC;IAqGD;;;;OAIG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAGD;;;;;OAKG;IACH,gBAJW,MAAM,GACL,eAAe,CAa1B;IA8GL;;;OAGG;IACH,mBAFU,OAAO,CAEc;;CAN9B;2BArb0B,iBAAiB;gCAEZ,sBAAsB"}
@@ -83,6 +83,18 @@ export class WebGPUStruct extends WebGPUType {
83
83
  this.#size = other.#size;
84
84
  this.#align = other.#align;
85
85
 
86
+ /**
87
+ * Carried with the layout it describes. Without it a clone claimed a size and per-field
88
+ * offsets while reading as unbuilt, and `pack()` — which reorders the fields and then
89
+ * rebuilds only `if (this.#is_built)` — left the declaration in the packed order and the
90
+ * offsets in the unpacked one. WGSL would lay a field out at one address and
91
+ * `write_struct_value` seek to another, with no error anywhere.
92
+ */
93
+ this.#is_built = other.#is_built;
94
+
95
+ //the cached WGSL text belongs to the struct that built it
96
+ this.#declaration_chunk = undefined;
97
+
86
98
  this.fields = other.fields.map(f => f.clone());
87
99
 
88
100
  return this;
@@ -47,10 +47,16 @@ export class WebGPUMipmapGenerator {
47
47
  * @param {GPUTexture} texture - Texture to generate mipmaps for.
48
48
  * @param {GPUTextureDescriptor} texture_descriptor - GPUTextureDescriptor the texture was created with.
49
49
  * @param {TextureFilterType} [filter]
50
- * @param {GPUCommandEncoder} [commandEncoder]
50
+ * @param {GPUCommandEncoder} [commandEncoder] when supplied, the commands are recorded rather
51
+ * than submitted, so the scratch resources outlive this call and `release_after_submit` is
52
+ * required
53
+ * @param {{destroy: function():void}[]} [release_after_submit] where the scratch buffer and
54
+ * textures go when the encoder is the caller's; destroy them once it has submitted
51
55
  * @returns {GPUTexture} - The originally passed texture
52
56
  */
53
- generateMipmap(texture: GPUTexture, texture_descriptor: GPUTextureDescriptor, filter?: TextureFilterType, commandEncoder?: GPUCommandEncoder): GPUTexture;
57
+ generateMipmap(texture: GPUTexture, texture_descriptor: GPUTextureDescriptor, filter?: TextureFilterType, commandEncoder?: GPUCommandEncoder, release_after_submit?: {
58
+ destroy: () => void;
59
+ }[]): GPUTexture;
54
60
  #private;
55
61
  }
56
62
  import { HashMap } from "../../../../core/collection/map/HashMap.js";
@@ -1 +1 @@
1
- {"version":3,"file":"WebGPUMipmapGenerator.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js"],"names":[],"mappings":"AAkMA;;;GAGG;AACH;IAqDI;;;OAGG;IACH,+BAWC;IAnED;;OAEG;IACH,kBAAM;IAEN;;;;OAIG;IACH,oBAFU,QAAQ,eAAe,oBAAoB,CAE3B;IAE1B;;OAEG;IACH,oBAAQ;IAgCR,gBAGC;IA6DD;;;;;OAKG;IACH,yEAFW,iBAAiB,QAY3B;IAED;;;OAGG;IACH,wBAFW,MAAM,QAyBhB;IAED;;OAEG;IACH,cAEC;IAmGG,qBAiBE;IAEF,oBAGE;IAGN;;;;;;;;OAQG;IACH,uFAJW,iBAAiB,kDA0X3B;;CACJ;wBAl2BuB,4CAA4C;AA2CpE;IAEI;;OAEG;IACH,yBAAO;IAEP;;OAEG;IACH;;;;QA1BA;;WAEG;;QAEH;;WAEG;;QAEH;;WAEG;;;;MAgBI;IAEP;;;;OAIG;IACH,cAHW,eAAe,GACd,OAAO,CAMlB;IAED;;;OAGG;IACH,QAFY,MAAM,CAOjB;CAEJ;kCAjEiC,yBAAyB"}
1
+ {"version":3,"file":"WebGPUMipmapGenerator.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js"],"names":[],"mappings":"AAkMA;;;GAGG;AACH;IAqDI;;;OAGG;IACH,+BAWC;IAnED;;OAEG;IACH,kBAAM;IAEN;;;;OAIG;IACH,oBAFU,QAAQ,eAAe,oBAAoB,CAE3B;IAE1B;;OAEG;IACH,oBAAQ;IAgCR,gBAGC;IA6DD;;;;;OAKG;IACH,yEAFW,iBAAiB,QAY3B;IAED;;;OAGG;IACH,wBAFW,MAAM,QAyBhB;IAED;;OAEG;IACH,cAEC;IAmGG,qBAiBE;IAEF,oBAGE;IAGN;;;;;;;;;;;;OAYG;IACH,uFARW,iBAAiB,6DAIjB;QAAC,OAAO,QAAa,IAAI,CAAA;KAAC,EAAE,cAyZtC;;CACJ;wBAr4BuB,4CAA4C;AA2CpE;IAEI;;OAEG;IACH,yBAAO;IAEP;;OAEG;IACH;;;;QA1BA;;WAEG;;QAEH;;WAEG;;QAEH;;WAEG;;;;MAgBI;IAEP;;;;OAIG;IACH,cAHW,eAAe,GACd,OAAO,CAMlB;IAED;;;OAGG;IACH,QAFY,MAAM,CAOjB;CAEJ;kCAjEiC,yBAAyB"}