@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.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
- package/src/core/binary/BinaryBuffer.js +14 -2
- package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/BVH.js +14 -1
- package/src/core/cache/FrequencySketch.d.ts +3 -1
- package/src/core/cache/FrequencySketch.d.ts.map +1 -1
- package/src/core/cache/FrequencySketch.js +10 -4
- package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -1
- package/src/core/cache/wtinylfu/CacheWTinylfu.js +24 -2
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts +20 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +69 -4
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +48 -18
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +262 -70
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts +25 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.js +228 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts +13 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.js +10 -128
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.js +11 -1
- package/src/core/graph/mn_graph_collapse_weighted_edge.js +6 -6
- package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
- package/src/core/math/lookup/ParameterLookupTable.js +10 -5
- package/src/core/math/noise/sdnoise.d.ts.map +1 -1
- package/src/core/math/noise/sdnoise.js +16 -10
- package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
- package/src/core/model/object/ImmutableObjectPool.js +17 -7
- package/src/core/process/executor/ConcurrentExecutor.d.ts +1 -5
- package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
- package/src/core/process/executor/ConcurrentExecutor.js +95 -1
- package/src/core/process/task/task_find_dependency_cycle.d.ts +17 -0
- package/src/core/process/task/task_find_dependency_cycle.d.ts.map +1 -0
- package/src/core/process/task/task_find_dependency_cycle.js +81 -0
- package/src/core/wfc/WFCCellChoice.d.ts +17 -0
- package/src/core/wfc/WFCCellChoice.d.ts.map +1 -0
- package/src/core/wfc/WFCCellChoice.js +36 -0
- package/src/core/wfc/WFCLattice.d.ts +105 -0
- package/src/core/wfc/WFCLattice.d.ts.map +1 -0
- package/src/core/wfc/WFCLattice.js +235 -0
- package/src/core/wfc/WFCRuleTable.d.ts +172 -0
- package/src/core/wfc/WFCRuleTable.d.ts.map +1 -0
- package/src/core/wfc/WFCRuleTable.js +334 -0
- package/src/core/wfc/WFCState.d.ts +273 -0
- package/src/core/wfc/WFCState.d.ts.map +1 -0
- package/src/core/wfc/WFCState.js +552 -0
- package/src/core/wfc/WFCStatus.d.ts +11 -0
- package/src/core/wfc/WFCStatus.d.ts.map +1 -0
- package/src/core/wfc/WFCStatus.js +28 -0
- package/src/core/wfc/WFCTileWeights.d.ts +48 -0
- package/src/core/wfc/WFCTileWeights.d.ts.map +1 -0
- package/src/core/wfc/WFCTileWeights.js +86 -0
- package/src/core/wfc/WFC_UNRESOLVED.d.ts +12 -0
- package/src/core/wfc/WFC_UNRESOLVED.d.ts.map +1 -0
- package/src/core/wfc/WFC_UNRESOLVED.js +11 -0
- package/src/core/wfc/wfc_estimate_bytes.d.ts +24 -0
- package/src/core/wfc/wfc_estimate_bytes.d.ts.map +1 -0
- package/src/core/wfc/wfc_estimate_bytes.js +47 -0
- package/src/core/wfc/wfc_rule_table_tile_components.d.ts +27 -0
- package/src/core/wfc/wfc_rule_table_tile_components.d.ts.map +1 -0
- package/src/core/wfc/wfc_rule_table_tile_components.js +80 -0
- package/src/core/wfc/wfc_solve.d.ts +21 -0
- package/src/core/wfc/wfc_solve.d.ts.map +1 -0
- package/src/core/wfc/wfc_solve.js +154 -0
- package/src/core/wfc/wfc_state_apply_boundary.d.ts +18 -0
- package/src/core/wfc/wfc_state_apply_boundary.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_apply_boundary.js +61 -0
- package/src/core/wfc/wfc_state_assign.d.ts +11 -0
- package/src/core/wfc/wfc_state_assign.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_assign.js +36 -0
- package/src/core/wfc/wfc_state_ban_bits.d.ts +17 -0
- package/src/core/wfc/wfc_state_ban_bits.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_ban_bits.js +81 -0
- package/src/core/wfc/wfc_state_collapse.d.ts +12 -0
- package/src/core/wfc/wfc_state_collapse.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_collapse.js +73 -0
- package/src/core/wfc/wfc_state_flush_dirty.d.ts +12 -0
- package/src/core/wfc/wfc_state_flush_dirty.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_flush_dirty.js +39 -0
- package/src/core/wfc/wfc_state_propagate.d.ts +14 -0
- package/src/core/wfc/wfc_state_propagate.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_propagate.js +155 -0
- package/src/core/wfc/wfc_state_read.d.ts +11 -0
- package/src/core/wfc/wfc_state_read.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_read.js +38 -0
- package/src/core/wfc/wfc_state_restrict.d.ts +18 -0
- package/src/core/wfc/wfc_state_restrict.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_restrict.js +39 -0
- package/src/core/wfc/wfc_state_set_eligible.d.ts +24 -0
- package/src/core/wfc/wfc_state_set_eligible.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_set_eligible.js +49 -0
- package/src/core/wfc/wfc_state_undo_to.d.ts +13 -0
- package/src/core/wfc/wfc_state_undo_to.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_undo_to.js +84 -0
- package/src/core/wfc/wfc_words_per_cell.d.ts +11 -0
- package/src/core/wfc/wfc_words_per_cell.d.ts.map +1 -0
- package/src/core/wfc/wfc_words_per_cell.js +16 -0
- package/src/engine/ecs/EntityComponentDataset.d.ts +8 -0
- package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
- package/src/engine/ecs/EntityComponentDataset.js +37 -14
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +8 -1
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +37 -2
- package/src/engine/ecs/guid/UUID.d.ts.map +1 -1
- package/src/engine/ecs/guid/UUID.js +45 -17
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +3 -1
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +366 -360
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.js +24 -10
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +24 -5
- package/src/engine/interpolation/Interpoland.d.ts +7 -1
- package/src/engine/interpolation/Interpoland.d.ts.map +1 -1
- package/src/engine/interpolation/Interpoland.js +8 -1
- package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
- package/src/engine/interpolation/InterpolationSystem.js +3 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +5 -3
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +14 -3
- package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -1
- package/src/engine/interpolation/pose_interpoland.js +2 -0
- package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/NetworkPeer.js +15 -10
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothWorld.js +17 -0
- package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
- package/src/engine/physics/mls-mpm/MLS_MPM.js +14 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +2 -1
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +1 -1
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +2 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +12 -3
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/EventInstance.js +4 -1
- package/src/format/image/avif/av1/decode/decode_coefficients.d.ts.map +1 -1
- package/src/format/image/avif/av1/decode/decode_coefficients.js +248 -259
- package/src/format/image/avif/av1/decode/read_golomb.d.ts +21 -0
- package/src/format/image/avif/av1/decode/read_golomb.d.ts.map +1 -0
- package/src/format/image/avif/av1/decode/read_golomb.js +49 -0
- package/src/format/image/avif/av1/filter/loop_restoration.js +461 -451
- package/src/format/image/avif/av1/util/round2.d.ts +19 -2
- package/src/format/image/avif/av1/util/round2.d.ts.map +1 -1
- package/src/format/image/avif/av1/util/round2.js +51 -27
- package/src/format/image/avif/heif/parse_avif_file.js +24 -0
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUTypedTable.js +14 -0
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +46 -3
- package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +91 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +0 -13
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +0 -15
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +5 -5
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts +11 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js +11 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +18 -9
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js +20 -26
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +1 -3
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.js +2 -5
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +14 -1
- package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +12 -3
- package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.d.ts.map +1 -1
- package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.js +7 -1
- package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
- package/src/shade/renderer/lightmap/GPULightMap.js +349 -336
- package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
- package/src/shade/renderer/loader/gltf/load_gltf.js +1137 -1128
- package/src/shade/renderer/particles/DESIGN.md +14 -5
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts +25 -10
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +182 -38
- package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/groups/simplex_reference.js +16 -10
- package/src/shade/renderer/particles/graph_particles.d.ts +4 -2
- package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles.js +8 -2
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +8 -2
- package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +10 -4
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +31 -0
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +1 -0
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.js +62 -0
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +13 -0
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/ParticleVMReference.js +95 -17
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +21 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +28 -0
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +19 -2
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +19 -2
- package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
- package/src/shade/renderer/shader/type/WebGPUStruct.js +12 -0
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts +8 -2
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts.map +1 -1
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +41 -6
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2383 -2356
- package/src/view/tooltip/DomTooltipManager.d.ts +7 -0
- package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
- package/src/view/tooltip/DomTooltipManager.js +22 -0
- package/src/view/tooltip/DomTooltipObserver.d.ts.map +1 -1
- package/src/view/tooltip/DomTooltipObserver.js +13 -3
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.js +0 -45
|
@@ -62,14 +62,23 @@ words = 128 B). Word 0 is a reserved header holding the owning **emitter row** a
|
|
|
62
62
|
pool + single alive/dead lists means **one** emit dispatch and **one** simulate dispatch cover every
|
|
63
63
|
emitter — the Destiny single-shader property.
|
|
64
64
|
|
|
65
|
-
Lifecycle uses the canonical GPU dead-list / alive-list
|
|
66
|
-
- `dead_list`: stack of free slot indices (+ atomic count).
|
|
67
|
-
|
|
65
|
+
Lifecycle uses the canonical GPU dead-list / alive-list pair:
|
|
66
|
+
- `dead_list`: stack of free slot indices (+ atomic count). Filled `dead[i] = i` at construction by
|
|
67
|
+
`shaders/shader_particle_init_free_list.js` — a dispatch, not a host upload, which is why the
|
|
68
|
+
buffer carries no `COPY_DST`.
|
|
69
|
+
- `alive_list`: a compact list of live slot indices (+ count), rebuilt from scratch every frame.
|
|
68
70
|
- `counters`: atomics {alive_count, dead_count, spawn budget, …} + derived indirect dispatch/draw args.
|
|
69
71
|
|
|
70
72
|
Emit: pop dead → run INIT → append `alive_new`. Simulate: read the **coherence list** last frame's
|
|
71
73
|
bucketing pass built from `alive_current` → run UPDATE → kill pushes to `dead`, survival appends to
|
|
72
|
-
`alive_new`.
|
|
74
|
+
`alive_new`.
|
|
75
|
+
|
|
76
|
+
There is no ping-pong pair, although the shape invites one. The frame appends to the new list from a
|
|
77
|
+
counter its first pass zeroes and the draws walk that same new list, so the previous frame's list has
|
|
78
|
+
exactly one reader in the whole design: the bucketing rebuild on a frame whose program assignments
|
|
79
|
+
changed. So the list is taken from the graphics context's buffer allocator and given back every
|
|
80
|
+
frame, before the next is asked for — the pool answers with the same memory, the reserved footprint
|
|
81
|
+
is one list, and only a rebuild frame holds two. See `GPUParticleSystem#swap_alive_list`.
|
|
73
82
|
|
|
74
83
|
**The loop is strictly GPU-driven.** The build-indirect pass snapshots the padded length of the
|
|
75
84
|
coherence list into `counters[ALIVE_IN]` and writes the indirect dispatch args next frame's simulate
|
|
@@ -644,7 +653,7 @@ through `GPUTextureContext`.
|
|
|
644
653
|
|
|
645
654
|
Constructed once against a `GPUSceneContext`, in the mould of `ReSTIRDI`. It owns persistent buffers
|
|
646
655
|
and only those — a buffer is reserved here if and only if its contents cross the frame boundary:
|
|
647
|
-
pool, alive
|
|
656
|
+
pool, alive list, dead list, counters, the coherence list, simulate's indirect dispatch args, the
|
|
648
657
|
emitter database (both tables), the packed program buffer, the spawn-command ring and the warm-up
|
|
649
658
|
queue — and grows the ones that depend on the emitter set inside `execute`. Everything else a frame
|
|
650
659
|
touches is the frame graph's, declared by the pass that first writes it: the bucketing scratch, the
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* ## What it owns
|
|
21
21
|
*
|
|
22
22
|
* Persistent GPU state, and only that: a buffer is reserved here if and only if its contents have
|
|
23
|
-
* to survive to the next frame. That is the particle pool, the
|
|
23
|
+
* to survive to the next frame. That is the particle pool, the alive list, the dead
|
|
24
24
|
* free-list, the counters, the simulation-order (coherence) list, simulate's indirect dispatch args
|
|
25
25
|
* (written at the end of one frame, read by the next), the emitter database (the emitter table and
|
|
26
26
|
* the GPU-owned state table, one buffer), the packed program buffer, and the two CPU-uploaded
|
|
@@ -37,6 +37,22 @@
|
|
|
37
37
|
* Reserving them here instead would be megabytes at the default capacity that no other feature
|
|
38
38
|
* could ever borrow, and most of them are cleared by a reset pass every frame regardless.
|
|
39
39
|
*
|
|
40
|
+
* ## The alive list is borrowed, not reserved
|
|
41
|
+
*
|
|
42
|
+
* The alive list crosses the frame boundary and so cannot be the graph's — but it does not have to
|
|
43
|
+
* be the feature's either. It is taken from the graphics context's buffer allocator and given back
|
|
44
|
+
* every frame ({@link #swap_alive_list}), which is the difference between one list and two.
|
|
45
|
+
*
|
|
46
|
+
* Two was the obvious reading of the ping-pong: the frame builds a new list while the previous one
|
|
47
|
+
* is still there to be read. But nothing reads the previous one. Emit and simulate append to the
|
|
48
|
+
* new list from a counter the frame resets to zero, the draws walk that same new list, and the only
|
|
49
|
+
* pass that ever looks at the one before it is the bucketing rebuild on a frame whose program
|
|
50
|
+
* assignments changed — a live program edit, not a frame. So on an ordinary frame the previous list
|
|
51
|
+
* goes back to the pool BEFORE the new one is asked for, the pool answers with the same memory, and
|
|
52
|
+
* the feature's reserved footprint is one list rather than two. On the rare frame that does rebuild,
|
|
53
|
+
* the old list is held one frame longer — until the graph recorded around it has been submitted —
|
|
54
|
+
* and the feature briefly costs what it used to cost always.
|
|
55
|
+
*
|
|
40
56
|
* Every pass stays within the eight storage buffers a stage gets by default: the emitter record
|
|
41
57
|
* and the GPU-owned state are two tables of one database buffer, and the VM's constants and code
|
|
42
58
|
* are one packed buffer. The one exception is the wide-register fallback (below), whose emit and
|
|
@@ -123,7 +139,7 @@ export class GPUParticleSystem {
|
|
|
123
139
|
*/
|
|
124
140
|
registry: EmitterRegistry;
|
|
125
141
|
/**
|
|
126
|
-
* Frames elapsed.
|
|
142
|
+
* Frames elapsed. The VM's frame builtin.
|
|
127
143
|
* @type {number}
|
|
128
144
|
*/
|
|
129
145
|
frame: number;
|
|
@@ -151,6 +167,10 @@ export class GPUParticleSystem {
|
|
|
151
167
|
* them — never for the frame loop, which reads nothing back. Entries that grow with the
|
|
152
168
|
* emitter set are replaced in place.
|
|
153
169
|
*
|
|
170
|
+
* `alive` and `alive_write` are one buffer under two names — what the feature holds, and what
|
|
171
|
+
* the frame writes — and `alive_read` is the previous frame's list, present only on the frames
|
|
172
|
+
* that read one. The list itself belongs to the allocator, not here; see {@link #swap_alive_list}.
|
|
173
|
+
*
|
|
154
174
|
* @type {object}
|
|
155
175
|
*/
|
|
156
176
|
buffers: object;
|
|
@@ -178,11 +198,6 @@ export class GPUParticleSystem {
|
|
|
178
198
|
* @returns {number}
|
|
179
199
|
*/
|
|
180
200
|
bucketCount(): number;
|
|
181
|
-
/**
|
|
182
|
-
* Index of the alive list this frame reads from / writes to (ping-pong by frame parity).
|
|
183
|
-
* @returns {number}
|
|
184
|
-
*/
|
|
185
|
-
get alive_write_index(): number;
|
|
186
201
|
/**
|
|
187
202
|
* Ask for `count` particles from an emitter, on top of its rate, on the next frame. Goes
|
|
188
203
|
* through the GPU path — the tick pass folds it into the emitter's accumulator, the budget and
|
|
@@ -330,9 +345,9 @@ export class GPUParticleSystem {
|
|
|
330
345
|
set_transform_row(emitter: import("./runtime/ParticleEmitter.js").ParticleEmitter, transform_row: number): void;
|
|
331
346
|
/**
|
|
332
347
|
* Sizes of the persistent buffers, in bytes, as they stand — one entry per distinct buffer, so
|
|
333
|
-
* this is the feature's
|
|
334
|
-
* `
|
|
335
|
-
* and
|
|
348
|
+
* this is the feature's footprint and not a list of the names it binds them under.
|
|
349
|
+
* `alive_write` is `alive` under the frame's name for it and would otherwise be counted twice;
|
|
350
|
+
* `alive_read` is a second list, and appears only on the frames that hold one. Exposed for tests.
|
|
336
351
|
*
|
|
337
352
|
* @returns {Object<string, number>}
|
|
338
353
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GPUParticleSystem.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/particles/GPUParticleSystem.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GPUParticleSystem.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/particles/GPUParticleSystem.js"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assert } from "../../../core/assert.js";
|
|
2
2
|
import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
|
|
3
|
+
import { BufferResourceDescriptor } from "../../device/graph/BufferResourceDescriptor.js";
|
|
3
4
|
import { warn_limited } from "../../util/warn_limited.js";
|
|
4
5
|
import { hash_mix2 } from "../../../core/math/hash/hash_mix2.js";
|
|
5
6
|
import {
|
|
@@ -19,6 +20,10 @@ import {
|
|
|
19
20
|
particle_counters_word_count,
|
|
20
21
|
} from "./data/PARTICLE_COUNTERS.js";
|
|
21
22
|
import { graph_particles_simulate } from "./graph_particles.js";
|
|
23
|
+
import {
|
|
24
|
+
PARTICLE_INIT_FREE_LIST_WORKGROUP_SIZE,
|
|
25
|
+
shader_particle_init_free_list,
|
|
26
|
+
} from "./shaders/shader_particle_init_free_list.js";
|
|
22
27
|
import {
|
|
23
28
|
graph_particle_avboit_draw,
|
|
24
29
|
graph_particle_avboit_occupancy,
|
|
@@ -38,7 +43,7 @@ const WORD = Uint32Array.BYTES_PER_ELEMENT;
|
|
|
38
43
|
big ones and are touched by nothing but shaders. Adding a flag "just in case" moves 64 MiB onto a
|
|
39
44
|
slower path for a copy nobody makes — see `BufferResourceDescriptor`.
|
|
40
45
|
*/
|
|
41
|
-
/** Written only by shaders. */
|
|
46
|
+
/** Written only by shaders. The free list is one of these: `init_free_list` fills it on-device. */
|
|
42
47
|
const STORAGE = GPUBufferUsage.STORAGE;
|
|
43
48
|
/** Seeded or refilled from the host with `writeBuffer`. */
|
|
44
49
|
const STORAGE_UPLOAD = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST;
|
|
@@ -76,7 +81,7 @@ const DEFAULT_CAPACITY = 524288;
|
|
|
76
81
|
* ## What it owns
|
|
77
82
|
*
|
|
78
83
|
* Persistent GPU state, and only that: a buffer is reserved here if and only if its contents have
|
|
79
|
-
* to survive to the next frame. That is the particle pool, the
|
|
84
|
+
* to survive to the next frame. That is the particle pool, the alive list, the dead
|
|
80
85
|
* free-list, the counters, the simulation-order (coherence) list, simulate's indirect dispatch args
|
|
81
86
|
* (written at the end of one frame, read by the next), the emitter database (the emitter table and
|
|
82
87
|
* the GPU-owned state table, one buffer), the packed program buffer, and the two CPU-uploaded
|
|
@@ -93,6 +98,22 @@ const DEFAULT_CAPACITY = 524288;
|
|
|
93
98
|
* Reserving them here instead would be megabytes at the default capacity that no other feature
|
|
94
99
|
* could ever borrow, and most of them are cleared by a reset pass every frame regardless.
|
|
95
100
|
*
|
|
101
|
+
* ## The alive list is borrowed, not reserved
|
|
102
|
+
*
|
|
103
|
+
* The alive list crosses the frame boundary and so cannot be the graph's — but it does not have to
|
|
104
|
+
* be the feature's either. It is taken from the graphics context's buffer allocator and given back
|
|
105
|
+
* every frame ({@link #swap_alive_list}), which is the difference between one list and two.
|
|
106
|
+
*
|
|
107
|
+
* Two was the obvious reading of the ping-pong: the frame builds a new list while the previous one
|
|
108
|
+
* is still there to be read. But nothing reads the previous one. Emit and simulate append to the
|
|
109
|
+
* new list from a counter the frame resets to zero, the draws walk that same new list, and the only
|
|
110
|
+
* pass that ever looks at the one before it is the bucketing rebuild on a frame whose program
|
|
111
|
+
* assignments changed — a live program edit, not a frame. So on an ordinary frame the previous list
|
|
112
|
+
* goes back to the pool BEFORE the new one is asked for, the pool answers with the same memory, and
|
|
113
|
+
* the feature's reserved footprint is one list rather than two. On the rare frame that does rebuild,
|
|
114
|
+
* the old list is held one frame longer — until the graph recorded around it has been submitted —
|
|
115
|
+
* and the feature briefly costs what it used to cost always.
|
|
116
|
+
*
|
|
96
117
|
* Every pass stays within the eight storage buffers a stage gets by default: the emitter record
|
|
97
118
|
* and the GPU-owned state are two tables of one database buffer, and the VM's constants and code
|
|
98
119
|
* are one packed buffer. The one exception is the wide-register fallback (below), whose emit and
|
|
@@ -173,7 +194,7 @@ export class GPUParticleSystem {
|
|
|
173
194
|
registry;
|
|
174
195
|
|
|
175
196
|
/**
|
|
176
|
-
* Frames elapsed.
|
|
197
|
+
* Frames elapsed. The VM's frame builtin.
|
|
177
198
|
* @type {number}
|
|
178
199
|
*/
|
|
179
200
|
frame = 0;
|
|
@@ -206,6 +227,10 @@ export class GPUParticleSystem {
|
|
|
206
227
|
* them — never for the frame loop, which reads nothing back. Entries that grow with the
|
|
207
228
|
* emitter set are replaced in place.
|
|
208
229
|
*
|
|
230
|
+
* `alive` and `alive_write` are one buffer under two names — what the feature holds, and what
|
|
231
|
+
* the frame writes — and `alive_read` is the previous frame's list, present only on the frames
|
|
232
|
+
* that read one. The list itself belongs to the allocator, not here; see {@link #swap_alive_list}.
|
|
233
|
+
*
|
|
209
234
|
* @type {object}
|
|
210
235
|
*/
|
|
211
236
|
buffers = {};
|
|
@@ -216,6 +241,32 @@ export class GPUParticleSystem {
|
|
|
216
241
|
*/
|
|
217
242
|
#sim_bucket_capacity = 0;
|
|
218
243
|
|
|
244
|
+
/**
|
|
245
|
+
* What the alive list is asked for under: one `u32` per pool slot, touched by nothing but
|
|
246
|
+
* shaders, and deliberately not cleared — every entry a reader sees was appended this frame,
|
|
247
|
+
* from a counter the frame's first pass resets to zero, so whatever the last borrower left in
|
|
248
|
+
* it is unreadable by construction. One instance, reused: the pool reads a descriptor and does
|
|
249
|
+
* not keep it.
|
|
250
|
+
*
|
|
251
|
+
* @type {BufferResourceDescriptor}
|
|
252
|
+
*/
|
|
253
|
+
#alive_descriptor;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The alive list the last recorded frame built — the next frame's `alive_read`. Borrowed from
|
|
257
|
+
* `graphics.buffer_allocator_main`, so it is released rather than destroyed.
|
|
258
|
+
* @type {GPUBuffer|null}
|
|
259
|
+
*/
|
|
260
|
+
#alive = null;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* An alive list a recorded frame still reads: the bucketing rebuild's input on a frame whose
|
|
264
|
+
* program assignments changed. Handed back on the next {@link execute}, by which point that
|
|
265
|
+
* frame's graph has been executed and submitted.
|
|
266
|
+
* @type {GPUBuffer|null}
|
|
267
|
+
*/
|
|
268
|
+
#alive_retired = null;
|
|
269
|
+
|
|
219
270
|
/**
|
|
220
271
|
* The spawn-command ring: `[row, count]` pairs, filled by {@link spawn}, merged and uploaded on
|
|
221
272
|
* the next {@link execute}.
|
|
@@ -355,14 +406,6 @@ export class GPUParticleSystem {
|
|
|
355
406
|
return Math.max(1, this.registry.program_id_capacity);
|
|
356
407
|
}
|
|
357
408
|
|
|
358
|
-
/**
|
|
359
|
-
* Index of the alive list this frame reads from / writes to (ping-pong by frame parity).
|
|
360
|
-
* @returns {number}
|
|
361
|
-
*/
|
|
362
|
-
get alive_write_index() {
|
|
363
|
-
return 1 - (this.frame % 2);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
409
|
/**
|
|
367
410
|
* Ask for `count` particles from an emitter, on top of its rate, on the next frame. Goes
|
|
368
411
|
* through the GPU path — the tick pass folds it into the emitter's accumulator, the budget and
|
|
@@ -444,8 +487,9 @@ export class GPUParticleSystem {
|
|
|
444
487
|
emitters.group_count = table.dispatch_group_count(PARTICLE_EMITTER_TICK_WORKGROUP_SIZE);
|
|
445
488
|
|
|
446
489
|
const buffers = this.buffers;
|
|
447
|
-
|
|
448
|
-
|
|
490
|
+
|
|
491
|
+
this.#swap_alive_list(frame.rebucket);
|
|
492
|
+
|
|
449
493
|
// The table's database owns and grows its own buffer; it is re-read every frame for that
|
|
450
494
|
// reason.
|
|
451
495
|
buffers.emitter_database = this.registry.database.buffer;
|
|
@@ -907,6 +951,107 @@ export class GPUParticleSystem {
|
|
|
907
951
|
this.#sim_bucket_capacity = buckets;
|
|
908
952
|
}
|
|
909
953
|
|
|
954
|
+
/**
|
|
955
|
+
* Give this frame its alive list, and hand back the ones nothing reads any more.
|
|
956
|
+
*
|
|
957
|
+
* The list is the allocator's memory on loan rather than a pair of buffers reserved here — see
|
|
958
|
+
* the class doc. Three things happen, in this order:
|
|
959
|
+
*
|
|
960
|
+
* 1. A list an earlier frame retired goes back. That frame's graph has been recorded, executed
|
|
961
|
+
* and submitted since, so the bucketing pass that was reading it is done.
|
|
962
|
+
* 2. The previous frame's list goes back too — BEFORE the new one is asked for, so that the
|
|
963
|
+
* pool can answer with the same memory and the steady-state footprint is one list. Skipped
|
|
964
|
+
* when this frame rebuilds the buckets, because that pass reads it; it is retired instead
|
|
965
|
+
* and goes back on the next call, one step behind.
|
|
966
|
+
* 3. The new list is taken. Uncleared: the frame appends to it from a counter its first pass
|
|
967
|
+
* zeroes, so no reader can reach what the last borrower left.
|
|
968
|
+
*
|
|
969
|
+
* The pool may answer with a larger buffer than asked for, which changes nothing — every
|
|
970
|
+
* reader is bounded by a count, never by the buffer's length.
|
|
971
|
+
*
|
|
972
|
+
* @param {boolean} rebucket whether this frame's bucketing rebuild reads the previous list
|
|
973
|
+
*/
|
|
974
|
+
#swap_alive_list(rebucket) {
|
|
975
|
+
const allocator = this.graphics.buffer_allocator_main;
|
|
976
|
+
const buffers = this.buffers;
|
|
977
|
+
const previous = this.#alive;
|
|
978
|
+
|
|
979
|
+
// One is taken at construction and none is ever let go without another taking its place, so
|
|
980
|
+
// there is always a previous list — including on the first frame, which can already be a
|
|
981
|
+
// rebucket if a program was edited before it.
|
|
982
|
+
assert.notNull(previous, "the previous alive list");
|
|
983
|
+
|
|
984
|
+
if (this.#alive_retired !== null) {
|
|
985
|
+
allocator.release(this.#alive_retired);
|
|
986
|
+
this.#alive_retired = null;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (rebucket) {
|
|
990
|
+
this.#alive_retired = previous;
|
|
991
|
+
} else {
|
|
992
|
+
allocator.release(previous);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const alive = allocator.get(this.#alive_descriptor);
|
|
996
|
+
|
|
997
|
+
this.#alive = alive;
|
|
998
|
+
|
|
999
|
+
buffers.alive = alive;
|
|
1000
|
+
buffers.alive_write = alive;
|
|
1001
|
+
// Named only on the frames that read one: on every other frame `previous` has already gone
|
|
1002
|
+
// back to the pool, and may be somebody else's by the time the graph runs.
|
|
1003
|
+
buffers.alive_read = rebucket ? previous : undefined;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Hand every alive list back to the allocator and stop naming them.
|
|
1008
|
+
*
|
|
1009
|
+
* {@link destroy} cannot simply destroy them with the rest: the memory is the pool's, and a
|
|
1010
|
+
* pooled buffer destroyed behind the pool's back is a handle it will hand to the next caller.
|
|
1011
|
+
*/
|
|
1012
|
+
#release_alive_lists() {
|
|
1013
|
+
const allocator = this.graphics.buffer_allocator_main;
|
|
1014
|
+
|
|
1015
|
+
if (this.#alive !== null) {
|
|
1016
|
+
allocator.release(this.#alive);
|
|
1017
|
+
this.#alive = null;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if (this.#alive_retired !== null) {
|
|
1021
|
+
allocator.release(this.#alive_retired);
|
|
1022
|
+
this.#alive_retired = null;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
delete this.buffers.alive;
|
|
1026
|
+
delete this.buffers.alive_write;
|
|
1027
|
+
delete this.buffers.alive_read;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Fill the free list with every slot, on the GPU — see `shaders/shader_particle_init_free_list.js`
|
|
1032
|
+
* for why this is a dispatch and not a host write.
|
|
1033
|
+
*
|
|
1034
|
+
* The launch is clamped to what a dispatch dimension holds and the shader strides through the
|
|
1035
|
+
* rest: at the default capacity this is a couple of thousand workgroups, but the capacity is
|
|
1036
|
+
* the caller's to choose and a dispatch past `maxComputeWorkgroupsPerDimension` is refused.
|
|
1037
|
+
*/
|
|
1038
|
+
#init_free_list() {
|
|
1039
|
+
const device = this.graphics.device;
|
|
1040
|
+
|
|
1041
|
+
const cmd = ShadeGPUCommandContext.create(this.graphics, "particles/free list init");
|
|
1042
|
+
|
|
1043
|
+
shader_particle_init_free_list.dispatch({
|
|
1044
|
+
encoder: cmd,
|
|
1045
|
+
group_count_x: Math.min(
|
|
1046
|
+
Math.ceil(this.capacity / PARTICLE_INIT_FREE_LIST_WORKGROUP_SIZE),
|
|
1047
|
+
device.limits.maxComputeWorkgroupsPerDimension,
|
|
1048
|
+
),
|
|
1049
|
+
bindings: { dead: this.buffers.dead, u_capacity: this.capacity },
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
cmd.finish();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
910
1055
|
/**
|
|
911
1056
|
* The buffers whose size is fixed by the pool capacity, and the free list's initial contents.
|
|
912
1057
|
*/
|
|
@@ -916,11 +1061,12 @@ export class GPUParticleSystem {
|
|
|
916
1061
|
const buffers = this.buffers;
|
|
917
1062
|
|
|
918
1063
|
buffers.pool = device.createBuffer({ label: "particles/pool", size: capacity * PARTICLE_RECORD_WORD_COUNT * WORD, usage: STORAGE });
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1064
|
+
|
|
1065
|
+
this.#alive_descriptor = BufferResourceDescriptor.from(capacity * WORD, STORAGE);
|
|
1066
|
+
this.#alive = this.graphics.buffer_allocator_main.get(this.#alive_descriptor);
|
|
1067
|
+
buffers.alive = this.#alive;
|
|
1068
|
+
|
|
1069
|
+
buffers.dead = device.createBuffer({ label: "particles/dead", size: capacity * WORD, usage: STORAGE });
|
|
924
1070
|
// Just the counters to begin with. The per-emitter bounds accumulator that shares this
|
|
925
1071
|
// buffer follows the emitter table's capacity, and there is no table yet.
|
|
926
1072
|
buffers.counters = device.createBuffer({ label: "particles/counters", size: PARTICLE_COUNTER_COUNT * WORD, usage: STORAGE_GROWABLE });
|
|
@@ -936,16 +1082,14 @@ export class GPUParticleSystem {
|
|
|
936
1082
|
// The warm-up queue: one entry to start with, grown on upload to what a frame registers.
|
|
937
1083
|
buffers.warmup_queue = device.createBuffer({ label: "particles/warmup_queue", size: PARTICLE_WARMUP_QUEUE_WORDS * WORD, usage: STORAGE_UPLOAD });
|
|
938
1084
|
|
|
939
|
-
//
|
|
940
|
-
|
|
941
|
-
for (let i = 0; i < capacity; i++) {
|
|
942
|
-
dead[i] = i;
|
|
943
|
-
}
|
|
1085
|
+
// The counters say every slot is free; the pass below makes it so. A handful of words is
|
|
1086
|
+
// the whole of what the host ever writes into either — the list itself is the GPU's.
|
|
944
1087
|
const counters = new Uint32Array(PARTICLE_COUNTER_COUNT);
|
|
945
1088
|
counters[PARTICLE_COUNTER.DEAD] = capacity;
|
|
946
1089
|
|
|
947
|
-
device.queue.writeBuffer(buffers.dead, 0, dead);
|
|
948
1090
|
device.queue.writeBuffer(buffers.counters, 0, counters);
|
|
1091
|
+
|
|
1092
|
+
this.#init_free_list();
|
|
949
1093
|
}
|
|
950
1094
|
|
|
951
1095
|
/**
|
|
@@ -978,9 +1122,9 @@ export class GPUParticleSystem {
|
|
|
978
1122
|
|
|
979
1123
|
/**
|
|
980
1124
|
* Sizes of the persistent buffers, in bytes, as they stand — one entry per distinct buffer, so
|
|
981
|
-
* this is the feature's
|
|
982
|
-
* `
|
|
983
|
-
* and
|
|
1125
|
+
* this is the feature's footprint and not a list of the names it binds them under.
|
|
1126
|
+
* `alive_write` is `alive` under the frame's name for it and would otherwise be counted twice;
|
|
1127
|
+
* `alive_read` is a second list, and appears only on the frames that hold one. Exposed for tests.
|
|
984
1128
|
*
|
|
985
1129
|
* @returns {Object<string, number>}
|
|
986
1130
|
*/
|
|
@@ -988,15 +1132,13 @@ export class GPUParticleSystem {
|
|
|
988
1132
|
const sizes = {};
|
|
989
1133
|
const counted = new Set();
|
|
990
1134
|
|
|
991
|
-
for (const [name,
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
continue;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
counted.add(buffer);
|
|
998
|
-
sizes[name] = buffer.size;
|
|
1135
|
+
for (const [name, buffer] of Object.entries(this.buffers)) {
|
|
1136
|
+
if (buffer === undefined || buffer === null || counted.has(buffer)) {
|
|
1137
|
+
continue;
|
|
999
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
counted.add(buffer);
|
|
1141
|
+
sizes[name] = buffer.size;
|
|
1000
1142
|
}
|
|
1001
1143
|
|
|
1002
1144
|
return sizes;
|
|
@@ -1061,10 +1203,12 @@ export class GPUParticleSystem {
|
|
|
1061
1203
|
this.registry.remove(emitter);
|
|
1062
1204
|
}
|
|
1063
1205
|
|
|
1206
|
+
// Before the loop below, and not in it: the alive lists are the allocator's and go back to
|
|
1207
|
+
// it. Destroying one would leave the pool holding a handle it will hand to the next caller.
|
|
1208
|
+
this.#release_alive_lists();
|
|
1209
|
+
|
|
1064
1210
|
for (const buffer of Object.values(this.buffers)) {
|
|
1065
|
-
if (
|
|
1066
|
-
buffer.forEach((b) => b.destroy());
|
|
1067
|
-
} else if (buffer !== undefined && buffer !== null) {
|
|
1211
|
+
if (buffer !== undefined && buffer !== null) {
|
|
1068
1212
|
buffer.destroy();
|
|
1069
1213
|
}
|
|
1070
1214
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simplex_reference.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/particles/graph/groups/simplex_reference.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"simplex_reference.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/particles/graph/groups/simplex_reference.js"],"names":[],"mappings":"AA+GA;;;;;GAKG;AACH,0CAHW,MAAM,EAAE,GACN,MAAM,CA2ElB"}
|
|
@@ -18,8 +18,14 @@
|
|
|
18
18
|
* double-precision oracle takes the other branch there and disagrees with the VM about a third of the
|
|
19
19
|
* time, and it would be the one that was wrong: a GPU is `f32` too.
|
|
20
20
|
*
|
|
21
|
-
* So `fr` is applied wherever the VM would have stored a register
|
|
22
|
-
*
|
|
21
|
+
* So `fr` is applied wherever the VM would have stored a register — and inside `DOT` and
|
|
22
|
+
* `NORMALIZE` as well, because the shader computes those entirely in f32 and the reference executor
|
|
23
|
+
* now does the same. This file used to say the sum was accumulated wide and rounded once, which was
|
|
24
|
+
* a faithful transcription of a reference VM that summed in f64: f32 results without f32 range.
|
|
25
|
+
*
|
|
26
|
+
* The one place `fr` is deliberately absent from an intermediate is a product the group spells as a
|
|
27
|
+
* `MAD` — `row * ns_x + ns_y`, and the two reflections below it. `MAD` models the fused form, so
|
|
28
|
+
* its product is not rounded on either executor; see the opcode in {@link ../../vm/ParticleVMReference.js}.
|
|
23
29
|
*/
|
|
24
30
|
|
|
25
31
|
/** @type {function(number):number} */
|
|
@@ -34,16 +40,16 @@ const G3 = 1 / 6;
|
|
|
34
40
|
/**
|
|
35
41
|
* @param {number[]} a
|
|
36
42
|
* @param {number[]} b
|
|
37
|
-
* @returns {number} the dot product,
|
|
43
|
+
* @returns {number} the dot product, every step of it in f32, as `DOT` does
|
|
38
44
|
*/
|
|
39
45
|
function dot(a, b) {
|
|
40
46
|
let sum = 0;
|
|
41
47
|
|
|
42
48
|
for (let i = 0; i < a.length; i++) {
|
|
43
|
-
sum
|
|
49
|
+
sum = fr(sum + fr(a[i] * b[i]));
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
return
|
|
52
|
+
return sum;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/**
|
|
@@ -58,8 +64,8 @@ function step(edge, x) {
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
/**
|
|
61
|
-
* `NORMALIZE`, as the opcode does it: the length
|
|
62
|
-
*
|
|
67
|
+
* `NORMALIZE`, as the opcode does it: every step of the length in f32, and a zero vector normalises
|
|
68
|
+
* to zero rather than to nothing.
|
|
63
69
|
*
|
|
64
70
|
* The group used to spell this as Gustavson's `taylorInvSqrt` — a line through the band the gradient
|
|
65
71
|
* table's squared lengths fall in — and now calls the opcode, which is one instruction rather than
|
|
@@ -73,12 +79,12 @@ function normalize(v) {
|
|
|
73
79
|
let sum = 0;
|
|
74
80
|
|
|
75
81
|
for (const component of v) {
|
|
76
|
-
sum
|
|
82
|
+
sum = fr(sum + fr(component * component));
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
const length = Math.sqrt(sum);
|
|
85
|
+
const length = fr(Math.sqrt(sum));
|
|
80
86
|
|
|
81
|
-
const inverse = length > 0 ? 1 / length : 0;
|
|
87
|
+
const inverse = length > 0 ? fr(1 / length) : 0;
|
|
82
88
|
|
|
83
89
|
return v.map(component => fr(component * inverse));
|
|
84
90
|
}
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
* ## What is imported, and what the graph owns
|
|
60
60
|
*
|
|
61
61
|
* A buffer is imported only if its contents cross the frame boundary: the pool, the free list, the
|
|
62
|
-
* counters, the alive
|
|
62
|
+
* counters, the alive list, `sim_list` (written here, read by next frame's simulate),
|
|
63
63
|
* `dispatch_args` (the same), the program blob and the two CPU-uploaded queues. Everything else is
|
|
64
64
|
* produced and consumed inside one graph, so the pass that first writes it declares it with a
|
|
65
65
|
* {@link BufferResourceDescriptor} in place of a handle and the graph allocates it just in time and
|
|
@@ -85,7 +85,9 @@
|
|
|
85
85
|
* @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} params.graph
|
|
86
86
|
* @param {number} params.camera graph handle of the view's camera uniform (`CAMERA_UNIFORM_STRUCT`)
|
|
87
87
|
* @param {number} params.scene_database graph handle of the scene database — the emitters' nodes
|
|
88
|
-
* @param {object} params.gpu the persistent buffers, see {@link GPUParticleSystem}
|
|
88
|
+
* @param {object} params.gpu the persistent buffers, see {@link GPUParticleSystem}. `gpu.alive_read`
|
|
89
|
+
* is the exception: it is the previous frame's alive list and is present only when
|
|
90
|
+
* `frame.rebucket` says this frame reads one
|
|
89
91
|
* @param {object} params.emitters the emitter table's shape this frame
|
|
90
92
|
* @param {number} params.emitters.capacity the table's element capacity — the length of the count
|
|
91
93
|
* list the scan consumes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph_particles.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/particles/graph_particles.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH;;;;;;;GAOG;AAEH
|
|
1
|
+
{"version":3,"file":"graph_particles.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/particles/graph_particles.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH;;;;;;;GAOG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH;IA/BkG,KAAK,EAA5F,OAAO,2DAA2D,EAAE,UAAU;IAC/D,MAAM,EAArB,MAAM;IACS,cAAc,EAA7B,MAAM;IACS,GAAG,EAAlB,MAAM;IAGS,QAAQ;QACC,QAAQ,EAAhC,MAAM;QAEkB,WAAW,EAAnC,MAAM;;IACS,QAAQ,EAAvB,MAAM;IACS,YAAY,EAA3B,MAAM;IAEU,cAAc,EAA9B,OAAO;IAGQ,KAAK;QACC,EAAE,EAAvB,MAAM;QACe,IAAI,EAAzB,MAAM;QACe,KAAK,EAA1B,MAAM;QACiB,QAAQ,GAA/B,OAAO;QACc,WAAW,EAAhC,MAAM;QACe,mBAAmB,EAAxC,MAAM;QACe,YAAY,EAAjC,MAAM;QAGe,eAAe,EAApC,MAAM;QAC6D,eAAe,EAAlF,OAAO,6BAA6B,EAAE,cAAc;;IAElD,yBAAyB,CAgUrC;;;;;UAxWa,MAAM;;;;WACN,MAAM;;;;eACN,MAAM;;;;;sBACN,MAAM"}
|
|
@@ -98,7 +98,7 @@ import { graph_particle_warmup } from "./warmup/graph_particle_warmup.js";
|
|
|
98
98
|
* ## What is imported, and what the graph owns
|
|
99
99
|
*
|
|
100
100
|
* A buffer is imported only if its contents cross the frame boundary: the pool, the free list, the
|
|
101
|
-
* counters, the alive
|
|
101
|
+
* counters, the alive list, `sim_list` (written here, read by next frame's simulate),
|
|
102
102
|
* `dispatch_args` (the same), the program blob and the two CPU-uploaded queues. Everything else is
|
|
103
103
|
* produced and consumed inside one graph, so the pass that first writes it declares it with a
|
|
104
104
|
* {@link BufferResourceDescriptor} in place of a handle and the graph allocates it just in time and
|
|
@@ -126,7 +126,9 @@ import { graph_particle_warmup } from "./warmup/graph_particle_warmup.js";
|
|
|
126
126
|
* @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} params.graph
|
|
127
127
|
* @param {number} params.camera graph handle of the view's camera uniform (`CAMERA_UNIFORM_STRUCT`)
|
|
128
128
|
* @param {number} params.scene_database graph handle of the scene database — the emitters' nodes
|
|
129
|
-
* @param {object} params.gpu the persistent buffers, see {@link GPUParticleSystem}
|
|
129
|
+
* @param {object} params.gpu the persistent buffers, see {@link GPUParticleSystem}. `gpu.alive_read`
|
|
130
|
+
* is the exception: it is the previous frame's alive list and is present only when
|
|
131
|
+
* `frame.rebucket` says this frame reads one
|
|
130
132
|
* @param {object} params.emitters the emitter table's shape this frame
|
|
131
133
|
* @param {number} params.emitters.capacity the table's element capacity — the length of the count
|
|
132
134
|
* list the scan consumes
|
|
@@ -197,6 +199,10 @@ export function graph_particles_simulate({
|
|
|
197
199
|
if (frame.rebucket) {
|
|
198
200
|
// Live program edits can split a previously uniform wave. Rebuild from the previous
|
|
199
201
|
// compact alive list BEFORE reset discards its count and before simulate broadcasts code.
|
|
202
|
+
//
|
|
203
|
+
// `gpu.alive_read` exists only here. It is the one reader the previous frame's list ever
|
|
204
|
+
// has, which is why the feature holds one list rather than a ping-pong pair and keeps the
|
|
205
|
+
// old one only across a frame that rebuckets — see `GPUParticleSystem#swap_alive_list`.
|
|
200
206
|
const bucketed = graph_particle_bucket({
|
|
201
207
|
graph, emitter_database, pool, counters, capacity, bucket_count,
|
|
202
208
|
alive: graph_import_buffer(graph, gpu.alive_read, "particles/alive_read"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"particle_vm_semantics.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/optimizer/particle_vm_semantics.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"particle_vm_semantics.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/optimizer/particle_vm_semantics.js"],"names":[],"mappings":"AAkJA;;;;;;GAMG;AACH,oCAHW,MAAM,GACJ,mBAAmB,CAW/B;AAhKD;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,+CAA+C;AAC/C,2BADW,IAAI,MAAM,EAAE,mBAAmB,CAAC,CACL;;;;;;;kBA1BxB,OAAO;;;;;cAGP,OAAO;;;;;;gBAEP,OAAO;;;;;;eAGP,OAAO;;;;;;;;cAGP,OAAO;;;;;;;;;;sBAKP,OAAO"}
|
|
@@ -89,11 +89,17 @@ semantics(VM_OP.ADD, EXACT);
|
|
|
89
89
|
semantics(VM_OP.SUB, EXACT);
|
|
90
90
|
semantics(VM_OP.MUL, EXACT);
|
|
91
91
|
semantics(VM_OP.DIV, EXACT);
|
|
92
|
-
//
|
|
92
|
+
// The reference leaves the product unrounded — it models the fused form — and the shader spells
|
|
93
|
+
// `a * b + c`, which a driver may contract into an FMA or may round in between. The two executors
|
|
94
|
+
// agree exactly on the first reading and not on the second, and nothing in WGSL says which one a
|
|
95
|
+
// device takes.
|
|
93
96
|
semantics(VM_OP.MAD, INEXACT);
|
|
94
97
|
semantics(VM_OP.MIN, EXACT);
|
|
95
98
|
semantics(VM_OP.MAX, EXACT);
|
|
96
|
-
// The reference spells it `a
|
|
99
|
+
// The reference now spells it as WGSL defines it, `a * (1 - t) + b * t`, rather than as the lerp
|
|
100
|
+
// `a + (b - a) * t` — those are different numbers, not different roundings, wherever the span
|
|
101
|
+
// `b - a` overflows or swallows an end. What is left is that a driver may contract either product
|
|
102
|
+
// with the sum, which is the same slack `MAD` has.
|
|
97
103
|
semantics(VM_OP.MIX, INEXACT);
|
|
98
104
|
semantics(VM_OP.CLAMP, EXACT);
|
|
99
105
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pass_fold_constants.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/optimizer/pass_fold_constants.js"],"names":[],"mappings":"AAyF4B,sJAEwD;AAuJpF;;;;;;;;;;GAUG;AACH,gDANW,MAAM,KACN,MAAM,KACN,MAAM,KACN,MAAM,GACJ,MAAM,
|
|
1
|
+
{"version":3,"file":"pass_fold_constants.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/optimizer/pass_fold_constants.js"],"names":[],"mappings":"AAyF4B,sJAEwD;AAuJpF;;;;;;;;;;GAUG;AACH,gDANW,MAAM,KACN,MAAM,KACN,MAAM,KACN,MAAM,GACJ,MAAM,CAyElB;AApTD,qBAAqB;AACrB,2CADW,MAAM,CAC4C;AAE7D;;;;;GAKG;AACH,0CAFU,IAAI,MAAM,CAAC,CAE2C"}
|