@woosh/meep-engine 3.17.2 → 3.18.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/editor/actions/concrete/ComponentAddAction.d.ts +1 -0
- package/editor/actions/concrete/ComponentAddAction.d.ts.map +1 -1
- package/editor/actions/concrete/ComponentAddAction.js +4 -0
- package/editor/actions/concrete/ComponentRemoveAction.d.ts +1 -0
- package/editor/actions/concrete/ComponentRemoveAction.d.ts.map +1 -1
- package/editor/actions/concrete/ComponentRemoveAction.js +4 -0
- package/editor/actions/concrete/EntityCreateAction.d.ts +1 -0
- package/editor/actions/concrete/EntityCreateAction.d.ts.map +1 -1
- package/editor/actions/concrete/EntityCreateAction.js +7 -0
- package/editor/actions/concrete/EntityRemoveAction.d.ts +1 -0
- package/editor/actions/concrete/EntityRemoveAction.d.ts.map +1 -1
- package/editor/actions/concrete/EntityRemoveAction.js +4 -0
- package/editor/actions/concrete/PatchTerrainHeightAction.d.ts +10 -0
- package/editor/actions/concrete/PatchTerrainHeightAction.d.ts.map +1 -1
- package/editor/actions/concrete/PatchTerrainHeightAction.js +107 -85
- package/editor/actions/concrete/TransformModifyAction.d.ts +1 -0
- package/editor/actions/concrete/TransformModifyAction.d.ts.map +1 -1
- package/editor/actions/concrete/TransformModifyAction.js +4 -0
- package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
- package/editor/particles/effect/ParticleEffectDocument.js +10 -6
- package/editor/particles/effect/ParticleReferenceSimulation.d.ts.map +1 -1
- package/editor/particles/effect/ParticleReferenceSimulation.js +98 -4
- package/editor/particles/effect/make_starter_particle_effect.d.ts.map +1 -1
- package/editor/particles/effect/make_starter_particle_effect.js +0 -2
- package/editor/tools/engine/ToolEngine.d.ts +30 -0
- package/editor/tools/engine/ToolEngine.d.ts.map +1 -1
- package/editor/tools/engine/ToolEngine.js +257 -188
- package/editor/view/ecs/EntityList.d.ts.map +1 -1
- package/editor/view/ecs/EntityList.js +6 -5
- package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleEmitterInspectorView.js +17 -9
- package/package.json +1 -1
- package/src/CODEC_LAYOUT_VERIFICATION.md +1 -2
- package/src/core/binary/meshopt/MESHOPT_VERTEX_BLOCK.d.ts +56 -0
- package/src/core/binary/meshopt/MESHOPT_VERTEX_BLOCK.d.ts.map +1 -0
- package/src/core/binary/meshopt/MESHOPT_VERTEX_BLOCK.js +73 -0
- package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.d.ts.map +1 -1
- package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +18 -63
- package/src/core/binary/meshopt/meshopt_encode_byte_group.d.ts +53 -0
- package/src/core/binary/meshopt/meshopt_encode_byte_group.d.ts.map +1 -0
- package/src/core/binary/meshopt/meshopt_encode_byte_group.js +140 -0
- package/src/core/binary/meshopt/meshopt_encode_data_block.d.ts +32 -0
- package/src/core/binary/meshopt/meshopt_encode_data_block.d.ts.map +1 -0
- package/src/core/binary/meshopt/meshopt_encode_data_block.js +77 -0
- package/src/core/binary/meshopt/meshopt_encode_index_sequence.d.ts +46 -0
- package/src/core/binary/meshopt/meshopt_encode_index_sequence.d.ts.map +1 -0
- package/src/core/binary/meshopt/meshopt_encode_index_sequence.js +146 -0
- package/src/core/binary/meshopt/meshopt_encode_vertex_buffer.d.ts +40 -0
- package/src/core/binary/meshopt/meshopt_encode_vertex_buffer.d.ts.map +1 -0
- package/src/core/binary/meshopt/meshopt_encode_vertex_buffer.js +700 -0
- package/src/core/binary/meshopt/meshopt_write_uint_var.d.ts +24 -0
- package/src/core/binary/meshopt/meshopt_write_uint_var.d.ts.map +1 -0
- package/src/core/binary/meshopt/meshopt_write_uint_var.js +39 -0
- package/src/core/math/random/seededRandom_Mulberry32.d.ts.map +1 -1
- package/src/core/math/random/seededRandom_Mulberry32.js +4 -2
- package/src/core/process/undo/Action.d.ts +19 -0
- package/src/core/process/undo/Action.d.ts.map +1 -1
- package/src/core/process/undo/Action.js +17 -0
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +78 -0
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -0
- package/src/engine/graphics3/GPUParticleEmitterSystem.js +310 -0
- package/src/engine/graphics3/GraphicsEngine.d.ts +17 -0
- package/src/engine/graphics3/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics3/GraphicsEngine.js +30 -10
- package/src/engine/graphics3/particles/particle_gpu_records.d.ts.map +1 -1
- package/src/engine/graphics3/particles/particle_gpu_records.js +0 -2
- package/src/engine/interpolation/InterpolationSystem.d.ts +7 -0
- package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
- package/src/engine/interpolation/InterpolationSystem.js +10 -0
- package/src/engine/network/NetworkSession.d.ts.map +1 -1
- package/src/engine/network/NetworkSession.js +14 -34
- package/src/engine/network/time/RenderPlayout.d.ts +76 -0
- package/src/engine/network/time/RenderPlayout.d.ts.map +1 -0
- package/src/engine/network/time/RenderPlayout.js +163 -0
- package/src/engine/network/transport/adapters/WebSocketTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/WebSocketTransport.js +11 -4
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +4 -12
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +39 -13
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/engine/sound/simulation/core/VolumeField.d.ts.map +1 -1
- package/src/engine/sound/simulation/core/VolumeField.js +4 -1
- package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +23 -2
- package/src/shade/playground/particle_system/README.md +139 -139
- package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
- package/src/shade/playground/particle_system/particle_prototype.js +0 -2
- package/src/shade/playground/particle_system/particle_scene.d.ts +6 -9
- package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -1
- package/src/shade/playground/particle_system/particle_scene.js +6 -13
- package/src/shade/playground/ssr_reprojection/README.md +103 -0
- package/src/shade/playground/ssr_reprojection/SpatialFilterProbe.d.ts +17 -0
- package/src/shade/playground/ssr_reprojection/SpatialFilterProbe.d.ts.map +1 -0
- package/src/shade/playground/ssr_reprojection/SpatialFilterProbe.js +68 -0
- package/src/shade/playground/ssr_reprojection/index.html +79 -0
- package/src/shade/playground/ssr_reprojection/main.d.ts +2 -0
- package/src/shade/playground/ssr_reprojection/main.d.ts.map +1 -0
- package/src/shade/playground/ssr_reprojection/main.js +245 -0
- package/src/shade/playground/ssr_reprojection/scene.d.ts +18 -0
- package/src/shade/playground/ssr_reprojection/scene.d.ts.map +1 -0
- package/src/shade/playground/ssr_reprojection/scene.js +51 -0
- package/src/shade/renderer/Renderer.d.ts +2 -23
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +185 -218
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts +5 -8
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
- package/src/shade/renderer/animation/GPUAnimationManager.js +12 -12
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUTypedTable.js +6 -0
- package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.js +7 -0
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/chunk_brick4_sample_specular.d.ts +4 -0
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/chunk_brick4_sample_specular.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/chunk_brick4_sample_specular.js +51 -0
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/shader_brick4_specular.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/shader_brick4_specular.js +5 -35
- package/src/shade/renderer/particles/DESIGN.md +703 -514
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts +16 -4
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +1189 -991
- package/src/shade/renderer/particles/ParticleConstants.d.ts +64 -0
- package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
- package/src/shade/renderer/particles/ParticleConstants.js +71 -0
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds.d.ts +57 -0
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds.d.ts.map +1 -0
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds.js +181 -0
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds_unknown.d.ts +7 -0
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds_unknown.d.ts.map +1 -0
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds_unknown.js +12 -0
- package/src/shade/renderer/particles/bounds/graph_particle_bounds.d.ts +33 -0
- package/src/shade/renderer/particles/bounds/graph_particle_bounds.d.ts.map +1 -0
- package/src/shade/renderer/particles/bounds/graph_particle_bounds.js +67 -0
- package/src/shade/renderer/particles/bounds/shader_particle_bounds.d.ts +40 -0
- package/src/shade/renderer/particles/bounds/shader_particle_bounds.d.ts.map +1 -0
- package/src/shade/renderer/particles/bounds/shader_particle_bounds.js +187 -0
- package/src/shade/renderer/particles/coherence/graph_particle_bucket.d.ts +15 -2
- package/src/shade/renderer/particles/coherence/graph_particle_bucket.d.ts.map +1 -1
- package/src/shade/renderer/particles/coherence/graph_particle_bucket.js +14 -8
- package/src/shade/renderer/particles/data/PARTICLE_COUNTERS.d.ts +58 -4
- package/src/shade/renderer/particles/data/PARTICLE_COUNTERS.d.ts.map +1 -1
- package/src/shade/renderer/particles/data/PARTICLE_COUNTERS.js +79 -4
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STATE.d.ts +23 -1
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STATE.d.ts.map +1 -1
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STATE.js +35 -2
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts +4 -2
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +5 -8
- package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -1
- package/src/shade/renderer/particles/data/particle_emitter_record.js +0 -6
- package/src/shade/renderer/particles/graph_particles.d.ts +25 -3
- package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles.js +108 -31
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts +6 -4
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles_avboit.js +5 -5
- package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +5 -0
- package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/EmitterRegistry.js +27 -6
- package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +4 -0
- package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +6 -1
- package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +35 -27
- package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/ParticleEmitter.js +424 -425
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_warmup.d.ts +20 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_warmup.d.ts.map +1 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts +4 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +38 -2
- package/src/shade/renderer/particles/shaders/chunk_particle_spawn_budget.d.ts +22 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_spawn_budget.d.ts.map +1 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_spawn_budget.js +42 -0
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_occupancy.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_occupancy.js +53 -22
- package/src/shade/renderer/particles/shaders/shader_particle_emitter_tick.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emitter_tick.js +213 -216
- package/src/shade/renderer/particles/shaders/shader_particle_reclaim.d.ts +50 -0
- package/src/shade/renderer/particles/shaders/shader_particle_reclaim.d.ts.map +1 -0
- package/src/shade/renderer/particles/shaders/shader_particle_render.js +1 -1
- package/src/shade/renderer/particles/warmup/chunk_particle_warmup_step.d.ts +36 -0
- package/src/shade/renderer/particles/warmup/chunk_particle_warmup_step.d.ts.map +1 -0
- package/src/shade/renderer/particles/warmup/chunk_particle_warmup_step.js +102 -0
- package/src/shade/renderer/particles/warmup/graph_particle_warmup.d.ts +102 -0
- package/src/shade/renderer/particles/warmup/graph_particle_warmup.d.ts.map +1 -0
- package/src/shade/renderer/particles/warmup/graph_particle_warmup.js +329 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup.d.ts +112 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup.d.ts.map +1 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup.js +318 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.d.ts +35 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.d.ts.map +1 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.js +108 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.d.ts +3 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.d.ts.map +1 -0
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.js +70 -0
- package/src/shade/renderer/particles/warmup/warmup_schedule.d.ts +62 -0
- package/src/shade/renderer/particles/warmup/warmup_schedule.d.ts.map +1 -0
- package/src/shade/renderer/particles/warmup/warmup_schedule.js +55 -0
- package/src/shade/renderer/postprocess/PostProcess.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/PostProcess.js +2 -1
- package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +286 -0
- package/src/shade/renderer/postprocess/ssr/SSR.d.ts +57 -21
- package/src/shade/renderer/postprocess/ssr/SSR.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/SSR.js +230 -129
- package/src/shade/renderer/postprocess/ssr/SSRReprojectionMode.d.ts +11 -0
- package/src/shade/renderer/postprocess/ssr/SSRReprojectionMode.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/SSRReprojectionMode.js +6 -0
- package/src/shade/renderer/postprocess/ssr/chunk_ssr_metadata.d.ts +4 -0
- package/src/shade/renderer/postprocess/ssr/chunk_ssr_metadata.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/chunk_ssr_metadata.js +32 -0
- package/src/shade/renderer/postprocess/ssr/chunk_ssr_spatial_variance.d.ts +4 -0
- package/src/shade/renderer/postprocess/ssr/chunk_ssr_spatial_variance.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/chunk_ssr_spatial_variance.js +41 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.d.ts +3 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.js +30 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.d.ts +4 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.js +70 -0
- package/src/shade/renderer/postprocess/ssr/resolve/build_ssr_resolve_shader.d.ts +21 -0
- package/src/shade/renderer/postprocess/ssr/resolve/build_ssr_resolve_shader.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/resolve/build_ssr_resolve_shader.js +363 -0
- package/src/shade/renderer/postprocess/ssr/resolve/chunk_get_neighbour_weight.js +1 -1
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_Brick4.d.ts +2 -0
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_Brick4.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_Brick4.js +24 -0
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_IBL.d.ts +1 -6
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_IBL.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_IBL.js +16 -332
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_LPV.d.ts +1 -6
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_LPV.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_LPV.js +25 -358
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts +0 -5
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.js +201 -276
- package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts +1 -0
- package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.js +167 -143
- package/src/shade/renderer/shader/graph/graph_create_buffer.d.ts +15 -0
- package/src/shade/renderer/shader/graph/graph_create_buffer.d.ts.map +1 -0
- package/src/shade/renderer/shader/graph/graph_create_buffer.js +20 -0
- package/src/shade/renderer/view/GPUViewContext.d.ts +3 -4
- package/src/shade/renderer/view/GPUViewContext.d.ts.map +1 -1
- package/src/shade/renderer/view/GPUViewContext.js +5 -5
- package/src/core/binary/meshopt/__meshopt_test_streams.d.ts +0 -24
- package/src/core/binary/meshopt/__meshopt_test_streams.d.ts.map +0 -1
- package/src/core/binary/meshopt/__meshopt_test_streams.js +0 -237
- package/src/core/geom/3d/atlas/atlas_test_fixtures.d.ts +0 -84
- package/src/core/geom/3d/atlas/atlas_test_fixtures.d.ts.map +0 -1
- package/src/core/geom/3d/atlas/atlas_test_fixtures.js +0 -266
- package/src/engine/asset/loaders/gltf_test_fixtures.d.ts +0 -97
- package/src/engine/asset/loaders/gltf_test_fixtures.d.ts.map +0 -1
- package/src/engine/asset/loaders/gltf_test_fixtures.js +0 -359
- package/src/shade/renderer/animation/skin_test_fixtures.d.ts +0 -86
- package/src/shade/renderer/animation/skin_test_fixtures.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_test_fixtures.js +0 -250
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.d.ts +0 -16
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +0 -27
- package/src/shade/renderer/particles/particle_test_fixtures.d.ts +0 -151
- package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +0 -1
- package/src/shade/renderer/particles/particle_test_fixtures.js +0 -229
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_world_sphere.js +0 -23
- package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.js +0 -457
|
@@ -1,991 +1,1189 @@
|
|
|
1
|
-
import { assert } from "../../../core/assert.js";
|
|
2
|
-
import { align_4 } from "../../../core/binary/align_4.js";
|
|
3
|
-
import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
|
|
4
|
-
import { warn_limited } from "../../util/warn_limited.js";
|
|
5
|
-
import { PREFIX_SCAN_CSDLDF_HEADER_WORDS } from "../gpu_primitive/prefix_sum/v1/shader_prefix_scan_csdldf.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
*
|
|
149
|
-
* @type {
|
|
150
|
-
*/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* @type {
|
|
182
|
-
*/
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
*
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
#
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
*
|
|
245
|
-
* @
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
*
|
|
258
|
-
* @
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
*
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
*
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
*
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
this
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
* @
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
* @param {
|
|
567
|
-
* @returns {{
|
|
568
|
-
*/
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
buffers.
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
buffers.
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
/**
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1
|
+
import { assert } from "../../../core/assert.js";
|
|
2
|
+
import { align_4 } from "../../../core/binary/align_4.js";
|
|
3
|
+
import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
|
|
4
|
+
import { warn_limited } from "../../util/warn_limited.js";
|
|
5
|
+
import { PREFIX_SCAN_CSDLDF_HEADER_WORDS } from "../gpu_primitive/prefix_sum/v1/shader_prefix_scan_csdldf.js";
|
|
6
|
+
import { hash_mix2 } from "../../../core/math/hash/hash_mix2.js";
|
|
7
|
+
import {
|
|
8
|
+
PARTICLE_EMITTER_TICK_WORKGROUP_SIZE,
|
|
9
|
+
PARTICLE_RECORD_WORD_COUNT,
|
|
10
|
+
PARTICLE_SIMULATE_WORKGROUP_SIZE,
|
|
11
|
+
PARTICLE_SORT_BUCKET_COUNT,
|
|
12
|
+
PARTICLE_SPAWN_COMMAND_WORDS,
|
|
13
|
+
PARTICLE_VM_FAST_REGISTER_SLOTS,
|
|
14
|
+
PARTICLE_WARMUP_EMITTER_JITTER,
|
|
15
|
+
PARTICLE_WARMUP_QUEUE_WORDS,
|
|
16
|
+
} from "./ParticleConstants.js";
|
|
17
|
+
import {
|
|
18
|
+
PARTICLE_BOUNDS_WORDS,
|
|
19
|
+
PARTICLE_COUNTER,
|
|
20
|
+
PARTICLE_COUNTER_COUNT,
|
|
21
|
+
particle_bounds_empty_words,
|
|
22
|
+
particle_counters_word_count,
|
|
23
|
+
} from "./data/PARTICLE_COUNTERS.js";
|
|
24
|
+
import { graph_particles_simulate } from "./graph_particles.js";
|
|
25
|
+
import {
|
|
26
|
+
graph_particle_avboit_draw,
|
|
27
|
+
graph_particle_avboit_occupancy,
|
|
28
|
+
graph_particle_avboit_splat,
|
|
29
|
+
} from "./graph_particles_avboit.js";
|
|
30
|
+
import { graph_particles_billboard } from "./graph_particles_billboard.js";
|
|
31
|
+
import { ParticleRenderMode } from "./ParticleRenderMode.js";
|
|
32
|
+
import { EmitterRegistry } from "./runtime/EmitterRegistry.js";
|
|
33
|
+
import { create_particle_billboard_pipeline } from "./shaders/shader_particle_render.js";
|
|
34
|
+
import { graph_particle_sort } from "./sort/graph_particle_sort.js";
|
|
35
|
+
import { WARMUP_QUEUE } from "./warmup/shader_particle_warmup.js";
|
|
36
|
+
import { warmup_schedule } from "./warmup/warmup_schedule.js";
|
|
37
|
+
import { GPUTextureContext } from "../texture/GPUTextureContext.js";
|
|
38
|
+
|
|
39
|
+
const WORD = Uint32Array.BYTES_PER_ELEMENT;
|
|
40
|
+
|
|
41
|
+
const STORAGE = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
|
|
42
|
+
const INDIRECT = GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The GPU particle system: a feature of a scene, in the mould of `ReSTIRDI` — construct once
|
|
46
|
+
* against a {@link GPUSceneContext}, and contribute the frame's passes through
|
|
47
|
+
* {@link execute}.
|
|
48
|
+
*
|
|
49
|
+
* ## What it owns
|
|
50
|
+
*
|
|
51
|
+
* Every piece of persistent GPU state: the particle pool, the ping-pong alive lists, the dead
|
|
52
|
+
* free-list, the counters, both indirect-args buffers, the simulation-order (coherence) scratch,
|
|
53
|
+
* the emitter database (the emitter table and the GPU-owned state table, one buffer), the packed
|
|
54
|
+
* program buffer, and the spawn-command ring. Buffers that depend on the emitter set — the
|
|
55
|
+
* coherence scratch on the number of distinct programs, the program buffer on the heap, the
|
|
56
|
+
* database on its own pages, the counters on the emitter table's element capacity (they carry the
|
|
57
|
+
* per-emitter bounds accumulator, see `bounds/`) — are grown inside
|
|
58
|
+
* {@link execute}, the way ReSTIR's reservoirs follow the resolution. The render-order (sort)
|
|
59
|
+
* scratch is not in that list because it is not unconditional: see below.
|
|
60
|
+
*
|
|
61
|
+
* Every pass stays within the eight storage buffers a stage gets by default: the emitter record
|
|
62
|
+
* and the GPU-owned state are two tables of one database buffer, and the VM's constants and code
|
|
63
|
+
* are one packed buffer. The one exception is the wide-register fallback (below), whose emit and
|
|
64
|
+
* simulate passes bind a ninth for the register file — inside the ten the engine already demands of
|
|
65
|
+
* an adapter, but past the default.
|
|
66
|
+
*
|
|
67
|
+
* ## Two interpreters
|
|
68
|
+
*
|
|
69
|
+
* The VM's register file normally lives in shader registers, and is therefore small — see
|
|
70
|
+
* {@link ./ParticleConstants.js PARTICLE_VM_FAST_REGISTER_SLOTS}. An effect whose compiled program
|
|
71
|
+
* needs more than it holds does not fail: the whole system swaps emit and simulate for variants
|
|
72
|
+
* that keep the file in a storage buffer, which is far slower and effectively unbounded. The choice
|
|
73
|
+
* is per-frame, whole-system, and made here — the registry reports `max_register_count` over the
|
|
74
|
+
* effects it holds and this is where that number meets the VM's; nothing about authoring a graph has
|
|
75
|
+
* a register ceiling.
|
|
76
|
+
*
|
|
77
|
+
* ## Emitters are scene nodes
|
|
78
|
+
*
|
|
79
|
+
* There is no `addEmitter`. A {@link ParticleEmitter} is a {@link Node3D}: put it in the scene —
|
|
80
|
+
* on its own or under a joint — and the next {@link execute} finds it in the scene's node list,
|
|
81
|
+
* gives it a row in the emitter table pointed at the `transforms` row the scene context gave the
|
|
82
|
+
* node, and it starts spawning. Take it out of the scene and its row is retired and its particles
|
|
83
|
+
* freed. The sweep runs only when the scene's membership version moves, which is how per-frame
|
|
84
|
+
* upkeep stays proportional to what changed rather than to how many emitters there are.
|
|
85
|
+
*
|
|
86
|
+
* ## Spawning is the GPU's decision
|
|
87
|
+
*
|
|
88
|
+
* The CPU never counts particles or walks emitters per frame. The emitter tick pass integrates
|
|
89
|
+
* every emitter's rate on the GPU, and the emit dispatch is sized by a prefix scan of what it
|
|
90
|
+
* decided. The host's one way to ask for particles is {@link spawn}: a burst queued into a small
|
|
91
|
+
* command list that a pass adds to the emitter's pending spawns, on the same path as everything
|
|
92
|
+
* else. See `graph_particles.js` for the frame.
|
|
93
|
+
*
|
|
94
|
+
* ## Drawing, and the one thing the mode changes
|
|
95
|
+
*
|
|
96
|
+
* {@link execute} is the whole per-frame call, once per frame. It always records everything that
|
|
97
|
+
* moves particles and leaves the alive list and the indirect draw args as graph handles; what it
|
|
98
|
+
* records after that is the {@link ParticleRenderMode} it was given.
|
|
99
|
+
*
|
|
100
|
+
* `BILLBOARD` sorts the alive list back-to-front and draws it over a colour target, because
|
|
101
|
+
* premultiplied over-blending is order-dependent. `AVBOIT` records neither: under the renderer the
|
|
102
|
+
* system is an `AVBOITSideChannel`, and the transparency orchestrator asks it for its occupancy, its
|
|
103
|
+
* extinction and its draw at the three points where the transparent meshes have contributed theirs
|
|
104
|
+
* ({@link avboit_occupancy}, {@link avboit_splat}, {@link avboit_draw}). Those read the same handles
|
|
105
|
+
* and composite into a volume and a set of accumulators that are sums, so an ordering would buy
|
|
106
|
+
* nothing — and the sort is four passes and a scan over every live particle, plus two lists a word
|
|
107
|
+
* per particle each, which is why the mode also decides whether those buffers exist at all.
|
|
108
|
+
*
|
|
109
|
+
* ## Allocation
|
|
110
|
+
*
|
|
111
|
+
* The per-frame path allocates the frame graph's records and nothing else: emitter rows are
|
|
112
|
+
* staged through one shared record, spawn commands go into a preallocated ring, and the
|
|
113
|
+
* per-dispatch settings are reused objects. A command context is opened only on frames that have
|
|
114
|
+
* something to upload.
|
|
115
|
+
*/
|
|
116
|
+
export class GPUParticleSystem {
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Engine GraphicsContext.
|
|
120
|
+
* @type {object}
|
|
121
|
+
*/
|
|
122
|
+
graphics;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The scene whose nodes the emitters are, and whose database their world matrices come from.
|
|
126
|
+
* @type {import("../scene/GPUSceneContext.js").GPUSceneContext}
|
|
127
|
+
*/
|
|
128
|
+
scene_context;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Maximum number of simultaneously live particles.
|
|
132
|
+
* @type {number}
|
|
133
|
+
*/
|
|
134
|
+
capacity;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @type {EmitterRegistry}
|
|
138
|
+
*/
|
|
139
|
+
registry;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Frames elapsed. Drives the alive-list ping-pong and the VM's frame builtin.
|
|
143
|
+
* @type {number}
|
|
144
|
+
*/
|
|
145
|
+
frame = 0;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Seconds since start, the VM's `TIME` builtin. Advanced by `dt` each {@link execute}.
|
|
149
|
+
* @type {number}
|
|
150
|
+
*/
|
|
151
|
+
time = 0;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Cap on how much missed time a culled emitter makes up for when it comes back into view.
|
|
155
|
+
* @type {number}
|
|
156
|
+
*/
|
|
157
|
+
max_catchup_seconds = 1;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* View-depth range the render sort quantises: particles nearer than `[0]` share the front bin,
|
|
161
|
+
* farther than `[1]` the back one. Read only by {@link ParticleRenderMode.BILLBOARD}.
|
|
162
|
+
* @type {Float32Array}
|
|
163
|
+
*/
|
|
164
|
+
sort_depth_range = new Float32Array([0.1, 100]);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Sprite atlas the billboards sample, and the sampler they sample it with. A 1x1 white texel by
|
|
168
|
+
* default, so an emitter with no art draws as a plain quad rather than nothing.
|
|
169
|
+
* @type {GPUTextureView}
|
|
170
|
+
*/
|
|
171
|
+
atlas;
|
|
172
|
+
|
|
173
|
+
/** @type {GPUSampler} */
|
|
174
|
+
atlas_sampler;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The persistent GPU buffers, by the names the graph functions bind them under. Exposed for
|
|
178
|
+
* inspection — a harness copying the counters out to display them — never for the frame loop,
|
|
179
|
+
* which reads nothing back. Entries that grow with the emitter set are replaced in place.
|
|
180
|
+
*
|
|
181
|
+
* @type {object}
|
|
182
|
+
*/
|
|
183
|
+
buffers = {};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Distinct programs the coherence scratch is currently sized for.
|
|
187
|
+
* @type {number}
|
|
188
|
+
*/
|
|
189
|
+
#sim_bucket_capacity = 0;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The spawn-command ring: `[row, count]` pairs, filled by {@link spawn}, merged and uploaded on
|
|
193
|
+
* the next {@link execute}.
|
|
194
|
+
* @type {Uint32Array}
|
|
195
|
+
*/
|
|
196
|
+
#spawn_commands;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The emitter each ring entry was issued for, so an entry whose emitter has since left the
|
|
200
|
+
* scene — or whose row has gone to somebody else — is dropped at upload rather than delivered
|
|
201
|
+
* to the row's next tenant.
|
|
202
|
+
* @type {import("./runtime/ParticleEmitter.js").ParticleEmitter[]}
|
|
203
|
+
*/
|
|
204
|
+
#spawn_command_emitters;
|
|
205
|
+
|
|
206
|
+
/** @type {number} */
|
|
207
|
+
#spawn_command_count = 0;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Scratch for merging the ring: pending count by row. Grown with the emitter table, never per
|
|
211
|
+
* frame.
|
|
212
|
+
* @type {Uint32Array}
|
|
213
|
+
*/
|
|
214
|
+
#spawn_by_row = new Uint32Array(0);
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Emitters registered since the last frame with a pre-warm to run — the input of the warm-up
|
|
218
|
+
* pipeline (`warmup/`), consumed by the frame that records it. Filled where the emitter is
|
|
219
|
+
* registered ({@link sync_membership}), which is the one moment the host knows an emitter is
|
|
220
|
+
* new: the GPU decides everything else about spawning, but "this one has just arrived and asks
|
|
221
|
+
* to arrive warm" is a host-side fact.
|
|
222
|
+
*
|
|
223
|
+
* @type {import("./runtime/ParticleEmitter.js").ParticleEmitter[]}
|
|
224
|
+
*/
|
|
225
|
+
#warmup_queue = [];
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The queue as the GPU reads it, `PARTICLE_WARMUP_QUEUE_WORDS` per entry — see
|
|
229
|
+
* `warmup/shader_particle_warmup.js`. Grown to fit, never per frame.
|
|
230
|
+
* @type {Uint32Array}
|
|
231
|
+
*/
|
|
232
|
+
#warmup_words = new Uint32Array(0);
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Emitter node -> the sweep that last saw it in the scene.
|
|
236
|
+
* @type {Map<import("./runtime/ParticleEmitter.js").ParticleEmitter, number>}
|
|
237
|
+
*/
|
|
238
|
+
#swept = new Map();
|
|
239
|
+
|
|
240
|
+
/** @type {number} */
|
|
241
|
+
#sweep = 0;
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The 1x1 white texture {@link atlas} points at until a caller replaces it. Held so
|
|
245
|
+
* {@link destroy} can free it; a caller that assigns its own atlas owns that one.
|
|
246
|
+
* @type {GPUTextureContext|null}
|
|
247
|
+
*/
|
|
248
|
+
#default_atlas = null;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The scene membership version the emitter set was last reconciled against.
|
|
252
|
+
* @type {number}
|
|
253
|
+
*/
|
|
254
|
+
#instances_version = -1;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Program assignments used to build the previous frame's coherence list.
|
|
258
|
+
* @type {number}
|
|
259
|
+
*/
|
|
260
|
+
#program_assignment_version = 0;
|
|
261
|
+
|
|
262
|
+
/** @type {object|null} */
|
|
263
|
+
#pipeline = null;
|
|
264
|
+
|
|
265
|
+
/** @type {string} */
|
|
266
|
+
#pipeline_key = "";
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Per-frame scalars handed to {@link graph_particles_simulate}, reused rather than rebuilt.
|
|
270
|
+
* @type {object}
|
|
271
|
+
*/
|
|
272
|
+
#frame = {
|
|
273
|
+
dt: 0, time: 0, index: 0, max_catchup: 1, spawn_command_count: 0,
|
|
274
|
+
warmup_count: 0, warmup_capacity: 0, warmup_schedule: null,
|
|
275
|
+
rebucket: false,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/** @type {{capacity: number, group_count: number}} */
|
|
279
|
+
#emitters = { capacity: 0, group_count: 0 };
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* This frame's simulation, for the draw that follows it: the graph it was recorded into, the
|
|
283
|
+
* scene database handle it read, and the handles it produced. Reused, never reallocated.
|
|
284
|
+
* @type {{graph: object|null, scene_database: number, emitter_group_count: number, handles: object|null}}
|
|
285
|
+
*/
|
|
286
|
+
#simulated = { graph: null, scene_database: -1, emitter_group_count: 0, handles: null };
|
|
287
|
+
|
|
288
|
+
/** @type {object} */
|
|
289
|
+
#render = {
|
|
290
|
+
atlas: null,
|
|
291
|
+
atlas_sampler: null,
|
|
292
|
+
obtain_pipeline: (color_format, depth_format) => this.#obtain_pipeline(color_format, depth_format),
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* @param {object} graphics engine GraphicsContext (has `.device`)
|
|
297
|
+
* @param {import("../scene/GPUSceneContext.js").GPUSceneContext} scene_context
|
|
298
|
+
* @param {object} [options]
|
|
299
|
+
* @param {number} [options.capacity] max simultaneous particles
|
|
300
|
+
* @param {number} [options.spawn_command_capacity] most {@link spawn} calls one frame keeps
|
|
301
|
+
*/
|
|
302
|
+
constructor(graphics, scene_context, { capacity = 1 << 16, spawn_command_capacity = 256 } = {}) {
|
|
303
|
+
assert.defined(graphics, "graphics");
|
|
304
|
+
assert.defined(scene_context, "scene_context");
|
|
305
|
+
assert.equal(scene_context.isGPUSceneContext, true, "scene_context.isGPUSceneContext !== true");
|
|
306
|
+
assert.isPositiveInteger(capacity, "capacity");
|
|
307
|
+
assert.isPositiveInteger(spawn_command_capacity, "spawn_command_capacity");
|
|
308
|
+
|
|
309
|
+
this.graphics = graphics;
|
|
310
|
+
this.scene_context = scene_context;
|
|
311
|
+
this.capacity = capacity;
|
|
312
|
+
|
|
313
|
+
this.registry = new EmitterRegistry(graphics.device);
|
|
314
|
+
|
|
315
|
+
this.#spawn_commands = new Uint32Array(spawn_command_capacity * PARTICLE_SPAWN_COMMAND_WORDS);
|
|
316
|
+
this.#spawn_command_emitters = new Array(spawn_command_capacity).fill(null);
|
|
317
|
+
|
|
318
|
+
this.#create_buffers();
|
|
319
|
+
this.#create_default_atlas();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The buffer the emitter table lives in — what every emitter-reading pass binds.
|
|
324
|
+
* @returns {GPUBuffer}
|
|
325
|
+
*/
|
|
326
|
+
get emitter_database() {
|
|
327
|
+
return this.registry.database.buffer;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* The live emitters, in no particular order.
|
|
332
|
+
* @returns {import("./runtime/ParticleEmitter.js").ParticleEmitter[]}
|
|
333
|
+
*/
|
|
334
|
+
get emitters() {
|
|
335
|
+
return this.registry.emitters;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Number of simulation-order buckets — one per program id the registry can hand out, which is
|
|
340
|
+
* its peak number of distinct compiled programs rather than its current one.
|
|
341
|
+
*
|
|
342
|
+
* This is the coherence pass's only host-provided number, and it is static topology rather than
|
|
343
|
+
* per-frame state: it changes when an effect the set has never held is registered, not as
|
|
344
|
+
* particles are born and die. Everything the pass measures per frame stays on the GPU. Sizing it
|
|
345
|
+
* from the peak rather than the live count is what keeps it from moving every time an effect is
|
|
346
|
+
* unregistered — the bins of released ids are empty, and an empty bin rounds up to zero
|
|
347
|
+
* workgroups and shifts nothing after it.
|
|
348
|
+
*
|
|
349
|
+
* @returns {number}
|
|
350
|
+
*/
|
|
351
|
+
bucketCount() {
|
|
352
|
+
return Math.max(1, this.registry.program_id_capacity);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Index of the alive list this frame reads from / writes to (ping-pong by frame parity).
|
|
357
|
+
* @returns {number}
|
|
358
|
+
*/
|
|
359
|
+
get alive_write_index() {
|
|
360
|
+
return 1 - (this.frame % 2);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Ask for `count` particles from an emitter, on top of its rate, on the next frame. Goes
|
|
365
|
+
* through the GPU path — the tick pass folds it into the emitter's accumulator, the budget and
|
|
366
|
+
* the culling apply — so it is a request, not a guarantee.
|
|
367
|
+
*
|
|
368
|
+
* @param {import("./runtime/ParticleEmitter.js").ParticleEmitter} emitter a registered emitter
|
|
369
|
+
* @param {number} count
|
|
370
|
+
*/
|
|
371
|
+
spawn(emitter, count) {
|
|
372
|
+
assert.defined(emitter, "emitter");
|
|
373
|
+
assert.isNonNegativeInteger(count, "count");
|
|
374
|
+
|
|
375
|
+
const context = this.registry.context(emitter);
|
|
376
|
+
|
|
377
|
+
assert.defined(context, "emitter is not in the scene");
|
|
378
|
+
|
|
379
|
+
const ring = this.#spawn_commands;
|
|
380
|
+
const base = this.#spawn_command_count * PARTICLE_SPAWN_COMMAND_WORDS;
|
|
381
|
+
|
|
382
|
+
if (base >= ring.length) {
|
|
383
|
+
// the text is the key warn_limited counts on, so it carries no burst size: one message
|
|
384
|
+
// per ring size, not one per number a caller happens to ask for
|
|
385
|
+
warn_limited(`GPUParticleSystem: spawn command ring is full (${ring.length / PARTICLE_SPAWN_COMMAND_WORDS} per frame); dropping this frame's remaining bursts`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
ring[base] = context.row;
|
|
390
|
+
ring[base + 1] = count;
|
|
391
|
+
this.#spawn_command_emitters[this.#spawn_command_count] = emitter;
|
|
392
|
+
this.#spawn_command_count++;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The simulation half of the frame: reconcile the emitter set with the scene, push what changed
|
|
397
|
+
* to the GPU, and record every pass that moves particles — through to the indirect draw args.
|
|
398
|
+
* What it leaves as graph handles is what a draw consumes, whichever draw that turns out to be.
|
|
399
|
+
*
|
|
400
|
+
* @param {object} params see {@link execute}
|
|
401
|
+
* @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} params.graph
|
|
402
|
+
* @param {number} params.camera
|
|
403
|
+
* @param {number} params.scene_database
|
|
404
|
+
* @param {number} params.dt
|
|
405
|
+
* @returns {import("./graph_particles.js").ParticleSimulationHandles}
|
|
406
|
+
*/
|
|
407
|
+
#simulate({ graph, camera, scene_database, dt }) {
|
|
408
|
+
this.sync_membership();
|
|
409
|
+
this.#upload();
|
|
410
|
+
|
|
411
|
+
const frame = this.#frame;
|
|
412
|
+
frame.dt = dt;
|
|
413
|
+
frame.time = this.time;
|
|
414
|
+
frame.index = this.frame;
|
|
415
|
+
frame.max_catchup = this.max_catchup_seconds;
|
|
416
|
+
frame.spawn_command_count = this.#spawn_command_count;
|
|
417
|
+
frame.rebucket = this.#program_assignment_version !== this.registry.program_assignment_version;
|
|
418
|
+
this.#program_assignment_version = this.registry.program_assignment_version;
|
|
419
|
+
|
|
420
|
+
const table = this.registry.table;
|
|
421
|
+
const emitters = this.#emitters;
|
|
422
|
+
emitters.capacity = table.element_capacity;
|
|
423
|
+
emitters.group_count = table.dispatch_group_count(PARTICLE_EMITTER_TICK_WORKGROUP_SIZE);
|
|
424
|
+
|
|
425
|
+
const buffers = this.buffers;
|
|
426
|
+
buffers.alive_write = buffers.alive[this.alive_write_index];
|
|
427
|
+
buffers.alive_read = buffers.alive[1 - this.alive_write_index];
|
|
428
|
+
// The table's database owns and grows its own buffer; it is re-read every frame for that
|
|
429
|
+
// reason.
|
|
430
|
+
buffers.emitter_database = this.registry.database.buffer;
|
|
431
|
+
|
|
432
|
+
const simulated = graph_particles_simulate({
|
|
433
|
+
graph,
|
|
434
|
+
camera,
|
|
435
|
+
scene_database,
|
|
436
|
+
gpu: buffers,
|
|
437
|
+
emitters,
|
|
438
|
+
capacity: this.capacity,
|
|
439
|
+
bucket_count: this.bucketCount(),
|
|
440
|
+
// Whose register file the VM runs on is decided here, once, for the whole system: the
|
|
441
|
+
// registry reports what the hungriest registered effect needs and this compares it
|
|
442
|
+
// against what the fast file holds. Re-read every frame because registering an emitter —
|
|
443
|
+
// or unregistering the one hungry effect — can flip it either way.
|
|
444
|
+
wide_registers: this.registry.max_register_count > PARTICLE_VM_FAST_REGISTER_SLOTS,
|
|
445
|
+
frame,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Kept for the draw that follows, whichever it is, and for nothing past this frame.
|
|
449
|
+
const current = this.#simulated;
|
|
450
|
+
current.graph = graph;
|
|
451
|
+
current.scene_database = scene_database;
|
|
452
|
+
current.emitter_group_count = emitters.group_count;
|
|
453
|
+
current.handles = simulated;
|
|
454
|
+
|
|
455
|
+
// Consumed: the counts were recorded into the graph, the words were uploaded.
|
|
456
|
+
this.#spawn_command_count = 0;
|
|
457
|
+
this.#warmup_queue.length = 0;
|
|
458
|
+
|
|
459
|
+
this.frame++;
|
|
460
|
+
this.time += dt;
|
|
461
|
+
|
|
462
|
+
return simulated;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* The system's whole contribution to the frame: the simulation, then whatever `mode` says draws
|
|
467
|
+
* it.
|
|
468
|
+
*
|
|
469
|
+
* {@link ParticleRenderMode.BILLBOARD} adds the depth sort and one billboard draw over `color`
|
|
470
|
+
* with fixed-function blending, for a page with no transparency pipeline of its own.
|
|
471
|
+
* {@link ParticleRenderMode.AVBOIT} adds nothing: the orchestrator draws the particles later in
|
|
472
|
+
* the frame through {@link avboit_occupancy}, {@link avboit_splat} and {@link avboit_draw}, off
|
|
473
|
+
* the handles this just produced, and its accumulators are order-independent — so the sort is
|
|
474
|
+
* skipped and its buffers are never allocated. `color` comes straight back either way, so a
|
|
475
|
+
* caller can assign the result unconditionally and let the mode be the only difference.
|
|
476
|
+
*
|
|
477
|
+
* Call after the scene context has built for the frame (`GPUSceneContext#update`), so every
|
|
478
|
+
* emitter node has its `transforms` row, and after the hierarchy pass has run, so the rows'
|
|
479
|
+
* `global` matrices are this frame's.
|
|
480
|
+
*
|
|
481
|
+
* **Once per frame, not once per view.** This advances the simulation by `dt` and hands out
|
|
482
|
+
* handles into the graph it recorded into; a second view of the same scene records a second
|
|
483
|
+
* graph and would step the simulation twice. The renderer has one view per `render` call today,
|
|
484
|
+
* so this holds by construction; a second view of one scene needs the step and the handles
|
|
485
|
+
* separated — record the passes for the first graph, import the same buffers into the rest.
|
|
486
|
+
*
|
|
487
|
+
* @param {object} params
|
|
488
|
+
* @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} params.graph
|
|
489
|
+
* @param {number} params.camera graph handle of the camera uniform buffer
|
|
490
|
+
* @param {number} params.scene_database graph handle of the scene database — the one the
|
|
491
|
+
* hierarchy pass returned, so this reads the matrices it composed
|
|
492
|
+
* @param {number} params.dt seconds since the previous frame
|
|
493
|
+
* @param {ParticleRenderMode} [params.mode] who draws, and so whether there is a sort
|
|
494
|
+
* @param {number} [params.color] graph handle of the colour target to draw over. Required by
|
|
495
|
+
* `BILLBOARD`; returned untouched by `AVBOIT`
|
|
496
|
+
* @param {number} [params.depth] graph handle of the scene depth (tested, never written).
|
|
497
|
+
* Required by `BILLBOARD`
|
|
498
|
+
* @returns {number|undefined} the colour handle after the particle draw, or `color` unchanged
|
|
499
|
+
*/
|
|
500
|
+
execute({ graph, camera, scene_database, color, depth, dt, mode = ParticleRenderMode.BILLBOARD }) {
|
|
501
|
+
assert.defined(graph, "graph");
|
|
502
|
+
assert.isNumber(dt, "dt");
|
|
503
|
+
assert.enum(mode, ParticleRenderMode, "mode");
|
|
504
|
+
|
|
505
|
+
const billboard = mode === ParticleRenderMode.BILLBOARD;
|
|
506
|
+
|
|
507
|
+
// Checked before the simulation rather than at the draw: the simulation advances `frame` and
|
|
508
|
+
// `time` and writes the pool, and a throw after that leaves a frame half-stepped.
|
|
509
|
+
if (billboard) {
|
|
510
|
+
assert.isNonNegativeInteger(color, "color");
|
|
511
|
+
assert.isNonNegativeInteger(depth, "depth");
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const simulated = this.#simulate({ graph, camera, scene_database, dt });
|
|
515
|
+
|
|
516
|
+
if (!billboard) {
|
|
517
|
+
// AVBOIT: the side channels draw these handles later in the frame, and the colour target
|
|
518
|
+
// is not this call's to touch.
|
|
519
|
+
return color;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Allocated here rather than in the constructor: they are two words per particle plus the
|
|
523
|
+
// bins, and only this mode ever reads them. A system the renderer drives never makes them.
|
|
524
|
+
this.#ensure_sort_buffers();
|
|
525
|
+
|
|
526
|
+
// The render order: back-to-front by view depth, so premultiplied transparency composites
|
|
527
|
+
// correctly. Its two per-particle passes take the dispatch the coherence reset wrote from
|
|
528
|
+
// the GPU's own alive count.
|
|
529
|
+
const sorted = graph_particle_sort({
|
|
530
|
+
graph,
|
|
531
|
+
gpu: this.buffers,
|
|
532
|
+
camera,
|
|
533
|
+
emitter_database: simulated.emitter_database,
|
|
534
|
+
pool: simulated.pool,
|
|
535
|
+
alive: simulated.alive,
|
|
536
|
+
counters: simulated.counters,
|
|
537
|
+
dispatch_args: simulated.sim_dispatch_args,
|
|
538
|
+
bucket_count: PARTICLE_SORT_BUCKET_COUNT,
|
|
539
|
+
near: this.sort_depth_range[0],
|
|
540
|
+
far: this.sort_depth_range[1],
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const render = this.#render;
|
|
544
|
+
render.atlas = this.atlas;
|
|
545
|
+
render.atlas_sampler = this.atlas_sampler;
|
|
546
|
+
|
|
547
|
+
// One instanced billboard draw over the sorted list, sized by the draw args this frame's
|
|
548
|
+
// build-indirect wrote.
|
|
549
|
+
return graph_particles_billboard({
|
|
550
|
+
graph,
|
|
551
|
+
camera,
|
|
552
|
+
color,
|
|
553
|
+
depth,
|
|
554
|
+
pool: simulated.pool,
|
|
555
|
+
draw_list: sorted,
|
|
556
|
+
emitter_database: simulated.emitter_database,
|
|
557
|
+
draw_args: simulated.draw_args,
|
|
558
|
+
render,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* What this frame's {@link simulate} left, checked against the graph a side-channel call names:
|
|
564
|
+
* the AVBOIT orchestrator records its passes into the same graph, later in the same frame.
|
|
565
|
+
*
|
|
566
|
+
* @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} graph
|
|
567
|
+
* @returns {{graph: object, scene_database: number, emitter_group_count: number, handles: import("./graph_particles.js").ParticleSimulationHandles}}
|
|
568
|
+
*/
|
|
569
|
+
#simulated_for(graph) {
|
|
570
|
+
const current = this.#simulated;
|
|
571
|
+
|
|
572
|
+
assert.equal(current.graph, graph, "the particles were not simulated into this graph this frame — call execute() before the transparency pipeline records");
|
|
573
|
+
|
|
574
|
+
return current;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* `AVBOITSideChannel#avboit_occupancy`: the emitters' measured bounding boxes, as depth slices.
|
|
579
|
+
*
|
|
580
|
+
* @param {object} args see `AVBOITSideChannel`
|
|
581
|
+
* @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} args.graph
|
|
582
|
+
* @param {number} args.camera graph handle of the camera uniform buffer
|
|
583
|
+
* @param {ArrayLike<number>} args.cluster_parameters the frame's depth curve
|
|
584
|
+
* @param {number} args.occupancy graph handle of the occupancy bits
|
|
585
|
+
* @returns {number} the occupancy handle after the write
|
|
586
|
+
*/
|
|
587
|
+
avboit_occupancy({ graph, camera, cluster_parameters, occupancy }) {
|
|
588
|
+
const current = this.#simulated_for(graph);
|
|
589
|
+
|
|
590
|
+
return graph_particle_avboit_occupancy({
|
|
591
|
+
graph,
|
|
592
|
+
camera,
|
|
593
|
+
cluster_parameters,
|
|
594
|
+
occupancy,
|
|
595
|
+
emitter_database: current.handles.emitter_database,
|
|
596
|
+
emitter_group_count: current.emitter_group_count,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* `AVBOITSideChannel#avboit_splat`: the live particles' extinction, into the volume.
|
|
602
|
+
*
|
|
603
|
+
* @param {object} args see `AVBOITSideChannel`
|
|
604
|
+
* @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} args.graph
|
|
605
|
+
* @param {number} args.camera graph handle of the camera uniform buffer
|
|
606
|
+
* @param {number} args.warp graph handle of the warp LUT
|
|
607
|
+
* @param {number} args.depth_texture graph handle of the scene depth
|
|
608
|
+
* @param {{extinction: number, overflow: number, dummy: number}} args.volume the voxel volume
|
|
609
|
+
* @returns {{extinction: number, overflow: number, dummy: number}} the volume after the write
|
|
610
|
+
*/
|
|
611
|
+
avboit_splat({ graph, camera, warp, depth_texture, volume }) {
|
|
612
|
+
const { handles } = this.#simulated_for(graph);
|
|
613
|
+
|
|
614
|
+
return graph_particle_avboit_splat({
|
|
615
|
+
graph,
|
|
616
|
+
camera,
|
|
617
|
+
warp,
|
|
618
|
+
depth_texture,
|
|
619
|
+
volume,
|
|
620
|
+
atlas: this.atlas,
|
|
621
|
+
atlas_sampler: this.atlas_sampler,
|
|
622
|
+
pool: handles.pool,
|
|
623
|
+
alive: handles.alive,
|
|
624
|
+
emitter_database: handles.emitter_database,
|
|
625
|
+
draw_args: handles.draw_args,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* `AVBOITSideChannel#avboit_draw`: the live particles, into the accumulators.
|
|
631
|
+
*
|
|
632
|
+
* @param {object} args see `AVBOITSideChannel`
|
|
633
|
+
* @param {import("../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} args.graph
|
|
634
|
+
* @param {import("../view/GPUViewContext.js").GPUViewContext} args.view_ctx
|
|
635
|
+
* @param {number} args.camera graph handle of the camera uniform buffer
|
|
636
|
+
* @param {number} args.depth_texture graph handle of the scene depth
|
|
637
|
+
* @param {number} args.warp graph handle of the warp LUT
|
|
638
|
+
* @param {number} args.integral graph handle of the integral texture
|
|
639
|
+
* @param {number} args.volumetrics_lut graph handle
|
|
640
|
+
* @param {number} args.volumetrics_metadata graph handle
|
|
641
|
+
* @param {{color: number, norm: number, extinction: number, emission: number}} args.accumulators
|
|
642
|
+
* @returns {{color: number, norm: number, extinction: number, emission: number}} after the draw
|
|
643
|
+
*/
|
|
644
|
+
avboit_draw({ graph, view_ctx, camera, depth_texture, warp, integral, volumetrics_lut, volumetrics_metadata, accumulators }) {
|
|
645
|
+
const { handles } = this.#simulated_for(graph);
|
|
646
|
+
|
|
647
|
+
return graph_particle_avboit_draw({
|
|
648
|
+
graph,
|
|
649
|
+
view_ctx,
|
|
650
|
+
camera,
|
|
651
|
+
depth_texture,
|
|
652
|
+
warp,
|
|
653
|
+
integral,
|
|
654
|
+
volumetrics_lut,
|
|
655
|
+
volumetrics_metadata,
|
|
656
|
+
accumulators,
|
|
657
|
+
atlas: this.atlas,
|
|
658
|
+
atlas_sampler: this.atlas_sampler,
|
|
659
|
+
pool: handles.pool,
|
|
660
|
+
alive: handles.alive,
|
|
661
|
+
emitter_database: handles.emitter_database,
|
|
662
|
+
draw_args: handles.draw_args,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Bring the emitter set in line with the scene: emitter nodes that arrived get rows, nodes that
|
|
668
|
+
* left give theirs back. Runs only when the scene's membership version has moved.
|
|
669
|
+
*
|
|
670
|
+
* A node the scene context has not placed yet (no `transforms` row) is left for the next frame,
|
|
671
|
+
* and the version is not recorded, so the sweep runs again until every emitter is bound.
|
|
672
|
+
* An atlas owner may call this after the scene context builds, before setting row regions.
|
|
673
|
+
* execute() calls it as well; unchanged membership costs one version comparison.
|
|
674
|
+
* @returns {void}
|
|
675
|
+
*/
|
|
676
|
+
sync_membership() {
|
|
677
|
+
const scene = this.scene_context.scene;
|
|
678
|
+
const batch = scene.instances;
|
|
679
|
+
|
|
680
|
+
if (batch.version === this.#instances_version) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const sweep = ++this.#sweep;
|
|
685
|
+
const nodes = batch.nodes;
|
|
686
|
+
const node_count = nodes.length;
|
|
687
|
+
const mapping = this.scene_context.id_mapping;
|
|
688
|
+
const registry = this.registry;
|
|
689
|
+
const swept = this.#swept;
|
|
690
|
+
|
|
691
|
+
let complete = true;
|
|
692
|
+
|
|
693
|
+
for (let i = 0; i < node_count; i++) {
|
|
694
|
+
const node = nodes[i];
|
|
695
|
+
|
|
696
|
+
if (node.isParticleEmitter !== true) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const transform_row = mapping.get(node.id);
|
|
701
|
+
|
|
702
|
+
if (transform_row === undefined) {
|
|
703
|
+
complete = false;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (registry.context(node) === undefined) {
|
|
708
|
+
registry.add(node, transform_row);
|
|
709
|
+
|
|
710
|
+
// Just arrived, and asked to arrive warm: the warm-up pipeline runs its simulation
|
|
711
|
+
// forward this frame, before it is first drawn. A reused row is a new emitter as far
|
|
712
|
+
// as this is concerned — it is the emitter that asks, not the row.
|
|
713
|
+
if (node.prewarm > 0) {
|
|
714
|
+
this.#warmup_queue.push(node);
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
registry.set_transform_row(node, transform_row);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
swept.set(node, sweep);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
for (const [emitter, seen] of swept) {
|
|
724
|
+
if (seen !== sweep) {
|
|
725
|
+
registry.remove(emitter);
|
|
726
|
+
swept.delete(emitter);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (complete) {
|
|
731
|
+
this.#instances_version = batch.version;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Push everything the CPU changed to the GPU: dirty emitter rows, the state buffer's growth,
|
|
737
|
+
* rebuilt program buffers, the spawn-command ring, and the coherence scratch's growth. A
|
|
738
|
+
* command context is opened only if something needs one.
|
|
739
|
+
*/
|
|
740
|
+
#upload() {
|
|
741
|
+
const registry = this.registry;
|
|
742
|
+
const device = this.graphics.device;
|
|
743
|
+
|
|
744
|
+
registry.flush();
|
|
745
|
+
|
|
746
|
+
// Both tables stage into the one database: the emitter rows the author changed, and the
|
|
747
|
+
// state rows claimed or released with them.
|
|
748
|
+
const rows_staged = registry.table.element_upload_buffer.position > 0
|
|
749
|
+
|| registry.state_table.element_upload_buffer.position > 0;
|
|
750
|
+
|
|
751
|
+
if (rows_staged) {
|
|
752
|
+
const cmd = ShadeGPUCommandContext.create(this.graphics, "particles/upload");
|
|
753
|
+
|
|
754
|
+
registry.database.update(cmd);
|
|
755
|
+
|
|
756
|
+
cmd.finish();
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (registry.programs_dirty) {
|
|
760
|
+
this.buffers.program = replace_data_buffer(device, this.buffers.program, "particles/program", registry.build_program());
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
this.#merge_spawn_commands();
|
|
764
|
+
|
|
765
|
+
if (this.#spawn_command_count > 0) {
|
|
766
|
+
device.queue.writeBuffer(
|
|
767
|
+
this.buffers.spawn_commands, 0,
|
|
768
|
+
this.#spawn_commands.buffer, 0,
|
|
769
|
+
this.#spawn_command_count * PARTICLE_SPAWN_COMMAND_WORDS * WORD,
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
this.#upload_warmup_queue();
|
|
774
|
+
|
|
775
|
+
this.#ensure_sim_buffers(this.bucketCount());
|
|
776
|
+
this.#ensure_counters(registry.table.element_capacity);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Turn the emitters queued for a warm-up into the words the warm-up tick reads, and the numbers
|
|
781
|
+
* the frame graph sizes the loop by; upload the words. Nothing when nothing is queued, which is
|
|
782
|
+
* nearly every frame.
|
|
783
|
+
*
|
|
784
|
+
* Two per-emitter numbers are hashed here rather than left at zero, both to keep emitters warmed
|
|
785
|
+
* in one batch off each other's beat: the accumulator's starting fraction, so their first whole
|
|
786
|
+
* particles fall on different ticks, and the clock each one's tick runs at. See
|
|
787
|
+
* `warmup/shader_particle_warmup.js`.
|
|
788
|
+
*
|
|
789
|
+
* The transient pool is sized by what the batch can spawn in all — every emitter's rate over its
|
|
790
|
+
* own pre-warm, plus one each for the rounding — and no larger than the main pool it is bound
|
|
791
|
+
* for: what would not fit there is dropped at migration anyway.
|
|
792
|
+
*/
|
|
793
|
+
#upload_warmup_queue() {
|
|
794
|
+
const queue = this.#warmup_queue;
|
|
795
|
+
const frame = this.#frame;
|
|
796
|
+
|
|
797
|
+
// A queued emitter that has since left the scene has no row to warm; drop it here.
|
|
798
|
+
let count = 0;
|
|
799
|
+
|
|
800
|
+
for (let i = 0; i < queue.length; i++) {
|
|
801
|
+
const emitter = queue[i];
|
|
802
|
+
|
|
803
|
+
if (this.registry.context(emitter) !== undefined) {
|
|
804
|
+
queue[count++] = emitter;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
queue.length = count;
|
|
809
|
+
|
|
810
|
+
if (count === 0) {
|
|
811
|
+
frame.warmup_count = 0;
|
|
812
|
+
frame.warmup_capacity = 0;
|
|
813
|
+
frame.warmup_schedule = null;
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
let words = this.#warmup_words;
|
|
818
|
+
const needed = count * PARTICLE_WARMUP_QUEUE_WORDS;
|
|
819
|
+
|
|
820
|
+
if (words.length < needed) {
|
|
821
|
+
words = new Uint32Array(Math.max(needed, words.length * 2));
|
|
822
|
+
this.#warmup_words = words;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const floats = new Float32Array(words.buffer, words.byteOffset, words.length);
|
|
826
|
+
|
|
827
|
+
let max_prewarm = 0;
|
|
828
|
+
let spawned = 0;
|
|
829
|
+
|
|
830
|
+
for (let i = 0; i < count; i++) {
|
|
831
|
+
const emitter = queue[i];
|
|
832
|
+
const context = this.registry.context(emitter);
|
|
833
|
+
const base = i * PARTICLE_WARMUP_QUEUE_WORDS;
|
|
834
|
+
|
|
835
|
+
// Two unit fractions from one hash of the row and the emitter's seed: the same emitter
|
|
836
|
+
// in the same row warms the same way twice, and two emitters do not.
|
|
837
|
+
const hash = hash_mix2(context.row, context.generation) >>> 0;
|
|
838
|
+
const phase = (hash >>> 8) / 16777216;
|
|
839
|
+
const beat = ((hash & 0xff) / 255) - 0.5;
|
|
840
|
+
|
|
841
|
+
words[base + WARMUP_QUEUE.ROW] = context.row;
|
|
842
|
+
floats[base + WARMUP_QUEUE.PREWARM] = emitter.prewarm;
|
|
843
|
+
floats[base + WARMUP_QUEUE.ACCUMULATOR] = phase;
|
|
844
|
+
floats[base + WARMUP_QUEUE.CLOCK] = 1 + PARTICLE_WARMUP_EMITTER_JITTER * beat;
|
|
845
|
+
|
|
846
|
+
max_prewarm = Math.max(max_prewarm, emitter.prewarm);
|
|
847
|
+
spawned += Math.ceil(emitter.spawn_rate * emitter.prewarm) + 1;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const device = this.graphics.device;
|
|
851
|
+
const bytes = needed * WORD;
|
|
852
|
+
|
|
853
|
+
if (this.buffers.warmup_queue.size < bytes) {
|
|
854
|
+
this.buffers.warmup_queue.destroy();
|
|
855
|
+
this.buffers.warmup_queue = device.createBuffer({ label: "particles/warmup_queue", size: bytes, usage: STORAGE });
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
device.queue.writeBuffer(this.buffers.warmup_queue, 0, words.buffer, words.byteOffset, bytes);
|
|
859
|
+
|
|
860
|
+
frame.warmup_count = count;
|
|
861
|
+
frame.warmup_capacity = Math.max(1, Math.min(this.capacity, spawned));
|
|
862
|
+
frame.warmup_schedule = warmup_schedule(max_prewarm);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Fold the ring down to one entry per row, dropping entries whose emitter no longer holds the
|
|
867
|
+
* row it was issued against.
|
|
868
|
+
*
|
|
869
|
+
* The spawn-command pass adds to a state row with a plain read-modify-write, so two entries
|
|
870
|
+
* naming one row would lose one; and a burst issued to an emitter that left the scene this
|
|
871
|
+
* frame must not land on the row's next tenant. Both are settled here, in place, over a few
|
|
872
|
+
* dozen entries at most.
|
|
873
|
+
*/
|
|
874
|
+
#merge_spawn_commands() {
|
|
875
|
+
const count = this.#spawn_command_count;
|
|
876
|
+
|
|
877
|
+
if (count === 0) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const ring = this.#spawn_commands;
|
|
882
|
+
const emitters = this.#spawn_command_emitters;
|
|
883
|
+
|
|
884
|
+
let rows = this.#spawn_by_row;
|
|
885
|
+
const needed = Math.max(1, this.registry.table.element_capacity);
|
|
886
|
+
|
|
887
|
+
if (rows.length < needed) {
|
|
888
|
+
rows = new Uint32Array(needed);
|
|
889
|
+
this.#spawn_by_row = rows;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
for (let i = 0; i < count; i++) {
|
|
893
|
+
const emitter = emitters[i];
|
|
894
|
+
const row = ring[i * PARTICLE_SPAWN_COMMAND_WORDS];
|
|
895
|
+
const context = this.registry.context(emitter);
|
|
896
|
+
|
|
897
|
+
if (context !== undefined && context.row === row) {
|
|
898
|
+
rows[row] += ring[i * PARTICLE_SPAWN_COMMAND_WORDS + 1];
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
emitters[i] = null;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
let merged = 0;
|
|
905
|
+
|
|
906
|
+
for (let i = 0; i < count; i++) {
|
|
907
|
+
const row = ring[i * PARTICLE_SPAWN_COMMAND_WORDS];
|
|
908
|
+
const pending = rows[row];
|
|
909
|
+
|
|
910
|
+
if (pending === 0) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
ring[merged * PARTICLE_SPAWN_COMMAND_WORDS] = row;
|
|
915
|
+
ring[merged * PARTICLE_SPAWN_COMMAND_WORDS + 1] = pending;
|
|
916
|
+
rows[row] = 0;
|
|
917
|
+
merged++;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
this.#spawn_command_count = merged;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* The counters buffer holds the per-emitter bounds accumulators after the counters themselves
|
|
925
|
+
* (`data/PARTICLE_COUNTERS.js`), so it follows the emitter table's element capacity.
|
|
926
|
+
*
|
|
927
|
+
* Grown by copy, not by reallocation: the counters at the head of it are live GPU state — the
|
|
928
|
+
* free-list length above all, which no CPU value can reconstruct — and the accumulators are this
|
|
929
|
+
* frame's measurements of emitters that have not moved. A fresh buffer would zero the free list
|
|
930
|
+
* and strand the whole pool.
|
|
931
|
+
*
|
|
932
|
+
* The rows past the old end are the only ones the copy does not answer for, and zero is the
|
|
933
|
+
* wrong answer for them: a zero accumulator reads back as a degenerate box at the world origin
|
|
934
|
+
* rather than as an absence, and the first box measured for such a row would be stretched to
|
|
935
|
+
* include the origin. They get the empty interval written over them instead — the same two words
|
|
936
|
+
* the publish pass resets a row to.
|
|
937
|
+
*
|
|
938
|
+
* @param {number} emitter_capacity element capacity of the emitter table
|
|
939
|
+
*/
|
|
940
|
+
#ensure_counters(emitter_capacity) {
|
|
941
|
+
const size = particle_counters_word_count(emitter_capacity) * WORD;
|
|
942
|
+
const existing = this.buffers.counters;
|
|
943
|
+
|
|
944
|
+
if (existing.size >= size) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const device = this.graphics.device;
|
|
949
|
+
const grown = device.createBuffer({ label: "particles/counters", size, usage: STORAGE });
|
|
950
|
+
|
|
951
|
+
const cmd = ShadeGPUCommandContext.create(this.graphics, "particles/counters grow");
|
|
952
|
+
|
|
953
|
+
cmd.gpu_encoder.copyBufferToBuffer(existing, 0, grown, 0, existing.size);
|
|
954
|
+
|
|
955
|
+
cmd.finish();
|
|
956
|
+
|
|
957
|
+
// The new region is a whole number of accumulators: the counters are the head of the buffer
|
|
958
|
+
// and every growth adds rows behind them.
|
|
959
|
+
const added_rows = (size - existing.size) / (PARTICLE_BOUNDS_WORDS * WORD);
|
|
960
|
+
|
|
961
|
+
device.queue.writeBuffer(grown, existing.size, particle_bounds_empty_words(added_rows));
|
|
962
|
+
|
|
963
|
+
existing.destroy();
|
|
964
|
+
|
|
965
|
+
this.buffers.counters = grown;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Scratch follows the peak number of program ids. The coherence list itself is persistent:
|
|
970
|
+
* the next simulate consumes its entries and padding with the previous frame's saved count.
|
|
971
|
+
*
|
|
972
|
+
* @param {number} buckets
|
|
973
|
+
*/
|
|
974
|
+
#ensure_sim_buffers(buckets) {
|
|
975
|
+
if (buckets <= this.#sim_bucket_capacity) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const device = this.graphics.device;
|
|
980
|
+
const buffers = this.buffers;
|
|
981
|
+
const capacity = this.capacity;
|
|
982
|
+
|
|
983
|
+
for (const name of ["sim_keys", "sim_histogram", "sim_bucket_counts", "sim_cursor"]) {
|
|
984
|
+
buffers[name]?.destroy();
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Padding rounds each bucket up to a whole workgroup, so it can add at most `workgroup - 1`
|
|
988
|
+
// entries per bucket; one whole workgroup per bucket is the bound.
|
|
989
|
+
const previous = buffers.sim_list;
|
|
990
|
+
buffers.sim_list = device.createBuffer({ label: "particles/sim_list", size: (capacity + buckets * PARTICLE_SIMULATE_WORKGROUP_SIZE) * WORD, usage: STORAGE });
|
|
991
|
+
if (previous !== undefined) {
|
|
992
|
+
const cmd = ShadeGPUCommandContext.create(this.graphics, "particles/coherence grow");
|
|
993
|
+
cmd.gpu_encoder.copyBufferToBuffer(previous, 0, buffers.sim_list, 0, previous.size);
|
|
994
|
+
cmd.finish();
|
|
995
|
+
previous.destroy();
|
|
996
|
+
}
|
|
997
|
+
buffers.sim_keys = device.createBuffer({ label: "particles/sim_keys", size: capacity * WORD, usage: STORAGE });
|
|
998
|
+
// `{count, elements}` for the CSDLDF scan; the scan reads whole vec4s, so the bins are
|
|
999
|
+
// rounded up to a multiple of four.
|
|
1000
|
+
buffers.sim_histogram = device.createBuffer({ label: "particles/sim_histogram", size: (PREFIX_SCAN_CSDLDF_HEADER_WORDS + align_4(buckets)) * WORD, usage: STORAGE });
|
|
1001
|
+
buffers.sim_bucket_counts = device.createBuffer({ label: "particles/sim_bucket_counts", size: buckets * WORD, usage: STORAGE });
|
|
1002
|
+
buffers.sim_cursor = device.createBuffer({ label: "particles/sim_cursor", size: buckets * WORD, usage: STORAGE });
|
|
1003
|
+
|
|
1004
|
+
this.#sim_bucket_capacity = buckets;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* The render-order scratch, made on the first frame that actually sorts.
|
|
1009
|
+
*
|
|
1010
|
+
* Only {@link ParticleRenderMode.BILLBOARD} reads any of it, and the two per-particle lists are
|
|
1011
|
+
* a word per particle each — at the renderer's default capacity, half a megabyte that AVBOIT
|
|
1012
|
+
* would never touch. Nothing in them outlives a frame, so there is nothing to carry over and the
|
|
1013
|
+
* first sorted frame is as correct as any later one.
|
|
1014
|
+
*/
|
|
1015
|
+
#ensure_sort_buffers() {
|
|
1016
|
+
const buffers = this.buffers;
|
|
1017
|
+
|
|
1018
|
+
if (buffers.sorted !== undefined) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const device = this.graphics.device;
|
|
1023
|
+
const capacity = this.capacity;
|
|
1024
|
+
|
|
1025
|
+
buffers.sorted = device.createBuffer({ label: "particles/sorted", size: capacity * WORD, usage: STORAGE });
|
|
1026
|
+
buffers.sort_keys = device.createBuffer({ label: "particles/sort_keys", size: capacity * WORD, usage: STORAGE });
|
|
1027
|
+
buffers.sort_histogram = device.createBuffer({ label: "particles/sort_histogram", size: (PREFIX_SCAN_CSDLDF_HEADER_WORDS + align_4(PARTICLE_SORT_BUCKET_COUNT)) * WORD, usage: STORAGE });
|
|
1028
|
+
buffers.sort_cursor = device.createBuffer({ label: "particles/sort_cursor", size: PARTICLE_SORT_BUCKET_COUNT * WORD, usage: STORAGE });
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* The buffers whose size is fixed by the pool capacity, and the free list's initial contents.
|
|
1033
|
+
*/
|
|
1034
|
+
#create_buffers() {
|
|
1035
|
+
const device = this.graphics.device;
|
|
1036
|
+
const capacity = this.capacity;
|
|
1037
|
+
const buffers = this.buffers;
|
|
1038
|
+
|
|
1039
|
+
buffers.pool = device.createBuffer({ label: "particles/pool", size: capacity * PARTICLE_RECORD_WORD_COUNT * WORD, usage: STORAGE });
|
|
1040
|
+
buffers.alive = [
|
|
1041
|
+
device.createBuffer({ label: "particles/alive_a", size: capacity * WORD, usage: STORAGE }),
|
|
1042
|
+
device.createBuffer({ label: "particles/alive_b", size: capacity * WORD, usage: STORAGE }),
|
|
1043
|
+
];
|
|
1044
|
+
buffers.dead = device.createBuffer({ label: "particles/dead", size: capacity * WORD, usage: STORAGE });
|
|
1045
|
+
// Just the counters to begin with. The per-emitter bounds accumulator that shares this
|
|
1046
|
+
// buffer follows the emitter table's capacity, and there is no table yet.
|
|
1047
|
+
buffers.counters = device.createBuffer({ label: "particles/counters", size: PARTICLE_COUNTER_COUNT * WORD, usage: STORAGE });
|
|
1048
|
+
|
|
1049
|
+
// Indirect args, written on-GPU. Zero-initialised (WebGPU spec), so the first frame's
|
|
1050
|
+
// indirect dispatch/draw are no-ops until the first build-indirect has run.
|
|
1051
|
+
buffers.dispatch_args = device.createBuffer({ label: "particles/dispatch_args", size: 3 * WORD, usage: INDIRECT });
|
|
1052
|
+
buffers.draw_args = device.createBuffer({ label: "particles/draw_args", size: 4 * WORD, usage: INDIRECT });
|
|
1053
|
+
buffers.sim_dispatch_args = device.createBuffer({ label: "particles/sim_dispatch_args", size: 3 * WORD, usage: INDIRECT });
|
|
1054
|
+
|
|
1055
|
+
buffers.spawn_commands = device.createBuffer({ label: "particles/spawn_commands", size: this.#spawn_commands.byteLength, usage: STORAGE });
|
|
1056
|
+
|
|
1057
|
+
// The warm-up queue: one entry to start with, grown on upload to what a frame registers.
|
|
1058
|
+
buffers.warmup_queue = device.createBuffer({ label: "particles/warmup_queue", size: PARTICLE_WARMUP_QUEUE_WORDS * WORD, usage: STORAGE });
|
|
1059
|
+
|
|
1060
|
+
// Every slot free, and the counters saying so.
|
|
1061
|
+
const dead = new Uint32Array(capacity);
|
|
1062
|
+
for (let i = 0; i < capacity; i++) {
|
|
1063
|
+
dead[i] = i;
|
|
1064
|
+
}
|
|
1065
|
+
const counters = new Uint32Array(PARTICLE_COUNTER_COUNT);
|
|
1066
|
+
counters[PARTICLE_COUNTER.DEAD] = capacity;
|
|
1067
|
+
|
|
1068
|
+
device.queue.writeBuffer(buffers.dead, 0, dead);
|
|
1069
|
+
device.queue.writeBuffer(buffers.counters, 0, counters);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* A 1x1 white atlas and a linear sampler, so the system draws before anyone hands it art.
|
|
1074
|
+
* @returns {void}
|
|
1075
|
+
*/
|
|
1076
|
+
#create_default_atlas() {
|
|
1077
|
+
const device = this.graphics.device;
|
|
1078
|
+
|
|
1079
|
+
const texture = new GPUTextureContext(device);
|
|
1080
|
+
texture.descriptor.label = "particles/default_atlas";
|
|
1081
|
+
texture.descriptor.size = [1, 1];
|
|
1082
|
+
texture.descriptor.format = "rgba8unorm";
|
|
1083
|
+
texture.descriptor.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST;
|
|
1084
|
+
|
|
1085
|
+
device.queue.writeTexture({ texture: texture.gpu_texture }, new Uint8Array([255, 255, 255, 255]), { bytesPerRow: 4 }, [1, 1]);
|
|
1086
|
+
texture.incrementVersion();
|
|
1087
|
+
|
|
1088
|
+
// kept, not just viewed: a texture nothing holds is a texture nothing can free
|
|
1089
|
+
this.#default_atlas = texture;
|
|
1090
|
+
this.atlas = texture.obtainView();
|
|
1091
|
+
this.atlas_sampler = device.createSampler({
|
|
1092
|
+
label: "particles/atlas_sampler",
|
|
1093
|
+
magFilter: "linear",
|
|
1094
|
+
minFilter: "linear",
|
|
1095
|
+
addressModeU: "clamp-to-edge",
|
|
1096
|
+
addressModeV: "clamp-to-edge",
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* @param {string} color_format
|
|
1102
|
+
* @param {string} depth_format
|
|
1103
|
+
* @returns {object} the billboard pipeline descriptor for these attachment formats
|
|
1104
|
+
*/
|
|
1105
|
+
#obtain_pipeline(color_format, depth_format) {
|
|
1106
|
+
const key = `${color_format}/${depth_format}`;
|
|
1107
|
+
|
|
1108
|
+
if (this.#pipeline === null || this.#pipeline_key !== key) {
|
|
1109
|
+
this.#pipeline = create_particle_billboard_pipeline({
|
|
1110
|
+
color_format,
|
|
1111
|
+
depth_format,
|
|
1112
|
+
// reverse-Z, the engine convention
|
|
1113
|
+
depth_compare: "greater",
|
|
1114
|
+
});
|
|
1115
|
+
this.#pipeline_key = key;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return this.#pipeline;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Sizes of the persistent buffers, in bytes, as they stand. Exposed for tests.
|
|
1123
|
+
* @returns {Object<string, number>}
|
|
1124
|
+
*/
|
|
1125
|
+
bufferSizes() {
|
|
1126
|
+
const sizes = {};
|
|
1127
|
+
|
|
1128
|
+
for (const [name, buffer] of Object.entries(this.buffers)) {
|
|
1129
|
+
if (Array.isArray(buffer)) {
|
|
1130
|
+
sizes[name] = buffer[0].size;
|
|
1131
|
+
} else if (buffer !== undefined && buffer !== null) {
|
|
1132
|
+
sizes[name] = buffer.size;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
return sizes;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
destroy() {
|
|
1140
|
+
/*
|
|
1141
|
+
The emitters go first, and they matter more than the buffers: they are the scene's nodes,
|
|
1142
|
+
they outlive this system, and each is holding a row of the table about to be freed. A node
|
|
1143
|
+
left holding one is stranded — the next system's membership sweep reads `row !== -1` as
|
|
1144
|
+
"already mine", takes the moved branch rather than the arrived one, and never registers it.
|
|
1145
|
+
Retiring them here puts every emitter back where it was before this system saw it.
|
|
1146
|
+
*/
|
|
1147
|
+
for (const emitter of this.registry.emitters.slice()) {
|
|
1148
|
+
this.registry.remove(emitter);
|
|
1149
|
+
}
|
|
1150
|
+
this.#swept.clear();
|
|
1151
|
+
this.#instances_version = -1;
|
|
1152
|
+
|
|
1153
|
+
for (const buffer of Object.values(this.buffers)) {
|
|
1154
|
+
if (Array.isArray(buffer)) {
|
|
1155
|
+
buffer.forEach((b) => b.destroy());
|
|
1156
|
+
} else if (buffer !== undefined && buffer !== null) {
|
|
1157
|
+
buffer.destroy();
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
this.buffers = {};
|
|
1162
|
+
this.registry.database.destroy();
|
|
1163
|
+
|
|
1164
|
+
// the default atlas is this system's own; a page that replaced it owns what it put there
|
|
1165
|
+
this.#default_atlas?.destroy();
|
|
1166
|
+
this.#default_atlas = null;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* A storage buffer holding `data`, replacing `previous` (destroyed) if it exists. The program
|
|
1172
|
+
* buffer is monolithic — a program is variable-length, so there is no table to page it into — and
|
|
1173
|
+
* is rebuilt whole on the rare frame the program heap changes.
|
|
1174
|
+
*
|
|
1175
|
+
* @param {GPUDevice} device
|
|
1176
|
+
* @param {GPUBuffer|undefined} previous
|
|
1177
|
+
* @param {string} label
|
|
1178
|
+
* @param {Uint32Array} data
|
|
1179
|
+
* @returns {GPUBuffer}
|
|
1180
|
+
*/
|
|
1181
|
+
function replace_data_buffer(device, previous, label, data) {
|
|
1182
|
+
previous?.destroy();
|
|
1183
|
+
|
|
1184
|
+
const buffer = device.createBuffer({ label, size: Math.max(WORD, data.byteLength), usage: STORAGE });
|
|
1185
|
+
|
|
1186
|
+
device.queue.writeBuffer(buffer, 0, data);
|
|
1187
|
+
|
|
1188
|
+
return buffer;
|
|
1189
|
+
}
|