@woosh/meep-engine 3.45.0 → 3.47.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-terrain.js +1 -1
- package/editor/actions/concrete/TransformModifyAction.d.ts.map +1 -1
- package/editor/actions/concrete/TransformModifyAction.js +10 -2
- package/editor/ecs/component/FieldDescriptor.js +15 -0
- package/editor/ecs/component/createObjectEditor.js +41 -12
- package/editor/ecs/component/editors/ecs/Transform64Editor.d.ts.map +1 -1
- package/editor/ecs/component/editors/ecs/Transform64Editor.js +77 -70
- package/editor/tools/TransformToolV2.d.ts.map +1 -1
- package/editor/tools/TransformToolV2.js +4 -0
- package/editor/tools/v2/TransformControls.d.ts.map +1 -1
- package/editor/tools/v2/TransformControls.js +7 -0
- package/editor/view/ecs/EntityEditor.d.ts.map +1 -1
- package/editor/view/ecs/EntityEditor.js +4 -2
- package/package.json +1 -1
- package/src/core/assert.d.ts.map +1 -1
- package/src/core/assert.js +5 -1
- package/src/core/binary/hash/XXH128Hash.d.ts +1 -37
- package/src/core/binary/hash/XXH128Hash.d.ts.map +1 -1
- package/src/core/binary/hash/XXH128Hash.js +4 -55
- package/src/core/binary/hash/XXH3_SECRET.d.ts +1 -12
- package/src/core/binary/hash/XXH3_SECRET.d.ts.map +1 -1
- package/src/core/binary/hash/XXH3_SECRET.js +4 -25
- package/src/core/binary/hash/crc32.d.ts +1 -15
- package/src/core/binary/hash/crc32.d.ts.map +1 -1
- package/src/core/binary/hash/crc32.js +4 -96
- package/src/core/binary/hash/xxh3_128.d.ts +1 -9
- package/src/core/binary/hash/xxh3_128.d.ts.map +1 -1
- package/src/core/binary/hash/xxh3_128.js +4 -341
- package/src/core/binary/hash/xxh3_64.d.ts +1 -8
- package/src/core/binary/hash/xxh3_64.d.ts.map +1 -1
- package/src/core/binary/hash/xxh3_64.js +4 -241
- package/src/core/binary/hash/xxh3_common.d.ts +1 -223
- package/src/core/binary/hash/xxh3_common.d.ts.map +1 -1
- package/src/core/binary/hash/xxh3_common.js +90 -476
- package/src/core/collection/array/array_insert_at.d.ts +16 -0
- package/src/core/collection/array/array_insert_at.d.ts.map +1 -0
- package/src/core/collection/array/array_insert_at.js +32 -0
- package/src/core/collection/array/array_remove_at.d.ts +15 -0
- package/src/core/collection/array/array_remove_at.d.ts.map +1 -0
- package/src/core/collection/array/array_remove_at.js +30 -0
- package/src/core/collection/array/combine_hash.d.ts +6 -0
- package/src/core/collection/array/combine_hash.d.ts.map +1 -1
- package/src/core/collection/array/combine_hash.js +6 -0
- package/src/core/function/FunctionCompiler.js +2 -2
- package/src/core/geom/3d/mat4/m4_multiply.d.ts +3 -0
- package/src/core/geom/3d/mat4/m4_multiply.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/m4_multiply.js +3 -0
- package/src/core/geom/3d/mat4/m4_multiply_f64.d.ts +30 -0
- package/src/core/geom/3d/mat4/m4_multiply_f64.d.ts.map +1 -0
- package/src/core/geom/3d/mat4/m4_multiply_f64.js +106 -0
- package/src/core/geom/3d/quaternion/quat3_look_rotation.d.ts +37 -0
- package/src/core/geom/3d/quaternion/quat3_look_rotation.d.ts.map +1 -0
- package/src/core/geom/3d/quaternion/quat3_look_rotation.js +172 -0
- package/src/core/geom/ConicRay.js +2 -2
- package/src/core/geom/Quaternion.d.ts +1 -0
- package/src/core/geom/Quaternion.d.ts.map +1 -1
- package/src/core/geom/Quaternion.js +12 -48
- package/src/core/geom/Vector4.d.ts.map +1 -1
- package/src/core/geom/Vector4.js +8 -5
- package/src/core/math/hash/XXH128Hash.d.ts +38 -0
- package/src/core/math/hash/XXH128Hash.d.ts.map +1 -0
- package/src/core/math/hash/XXH128Hash.js +55 -0
- package/src/core/math/hash/XXH3_SECRET.d.ts +13 -0
- package/src/core/math/hash/XXH3_SECRET.d.ts.map +1 -0
- package/src/core/math/hash/XXH3_SECRET.js +25 -0
- package/src/core/math/hash/crc32.d.ts +16 -0
- package/src/core/math/hash/crc32.d.ts.map +1 -0
- package/src/core/math/hash/crc32.js +96 -0
- package/src/core/math/hash/xxh3_128.d.ts +10 -0
- package/src/core/math/hash/xxh3_128.d.ts.map +1 -0
- package/src/core/math/hash/xxh3_128.js +341 -0
- package/src/core/math/hash/xxh3_64.d.ts +9 -0
- package/src/core/math/hash/xxh3_64.d.ts.map +1 -0
- package/src/core/math/hash/xxh3_64.js +241 -0
- package/src/core/math/hash/xxh3_common.d.ts +224 -0
- package/src/core/math/hash/xxh3_common.d.ts.map +1 -0
- package/src/core/math/hash/xxh3_common.js +476 -0
- package/src/core/math/lookup/ParameterLookupTable.js +2 -2
- package/src/core/model/node-graph/node/NodeInstancePortReference.js +2 -2
- package/src/core/model/reactive/model/ReactiveBinaryExpression.js +2 -2
- package/src/core/model/reactive/model/terminal/ReactiveReference.js +2 -2
- package/src/engine/Engine.d.ts.map +1 -1
- package/src/engine/Engine.js +9 -4
- package/src/engine/asset/AssetDescription.js +2 -2
- package/src/engine/ecs/animation/Animation.js +2 -2
- package/src/engine/ecs/animation/AnimationClip.d.ts.map +1 -1
- package/src/engine/ecs/animation/AnimationClip.js +7 -5
- package/src/engine/ecs/gui/GUIElement.d.ts.map +1 -1
- package/src/engine/ecs/gui/GUIElement.js +7 -5
- package/src/engine/ecs/gui/hud/HeadsUpDisplay.js +2 -2
- package/src/engine/ecs/gui/position/ViewportPosition.d.ts.map +1 -1
- package/src/engine/ecs/gui/position/ViewportPosition.js +11 -7
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +10 -0
- package/src/engine/ecs/transform/Transform64.d.ts +1 -1
- package/src/engine/ecs/transform/Transform64.d.ts.map +1 -1
- package/src/engine/ecs/transform/Transform64.js +5 -4
- package/src/engine/ecs/transform/t64_look_rotation.d.ts +4 -4
- package/src/engine/ecs/transform/t64_look_rotation.d.ts.map +1 -1
- package/src/engine/ecs/transform/t64_look_rotation.js +7 -18
- package/src/engine/graphics/ecs/animation/animator/AnimationClip.d.ts.map +1 -1
- package/src/engine/graphics/ecs/animation/animator/AnimationClip.js +8 -5
- package/src/engine/graphics/ecs/animation/animator/AnimationClipDefinition.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/AnimationNotification.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/AnimationNotificationDefinition.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/graph/definition/AnimationGraphDefinition.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/graph/definition/AnimationStateDefinition.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/graph/definition/AnimationTransitionDefinition.d.ts.map +1 -1
- package/src/engine/graphics/ecs/animation/animator/graph/definition/AnimationTransitionDefinition.js +8 -5
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +30 -1
- package/src/engine/graphics/ecs/decal/v2/Decal.d.ts.map +1 -1
- package/src/engine/graphics/ecs/decal/v2/Decal.js +9 -8
- package/src/engine/graphics/render/frame_graph/resource/TextureResourceDescriptor.d.ts.map +1 -1
- package/src/engine/graphics/render/frame_graph/resource/TextureResourceDescriptor.js +9 -6
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +6 -0
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/GPUParticleEmitterSystem.js +72 -29
- package/src/engine/graphics3/ParticipatingMedia.js +204 -204
- package/src/engine/graphics3/animation/AnimationClips.d.ts +5 -0
- package/src/engine/graphics3/animation/AnimationClips.d.ts.map +1 -1
- package/src/engine/graphics3/animation/AnimationClips.js +5 -0
- package/src/engine/grid/obstacle/GridObstacle.js +2 -2
- package/src/engine/input/devices/KeyboardDevice.d.ts +1 -0
- package/src/engine/input/devices/KeyboardDevice.d.ts.map +1 -1
- package/src/engine/input/devices/KeyboardDevice.js +7 -6
- package/src/engine/intelligence/behavior/selector/WeightedElement.js +2 -2
- package/src/engine/interpolation/Interpolated.js +2 -2
- package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts.map +1 -1
- package/src/engine/navigation/ecs/path_following/PathFollowingSystem.js +15 -2
- package/src/engine/network/CONGESTION_CONTROL.md +9 -9
- package/src/engine/network/MESSAGE_TRANSPORT.md +364 -0
- package/src/engine/network/NetworkSession.d.ts +31 -11
- package/src/engine/network/NetworkSession.d.ts.map +1 -1
- package/src/engine/network/NetworkSession.js +453 -286
- package/src/engine/network/README.md +77 -27
- package/src/engine/network/SMOOTHNESS_DESIGN.md +13 -5
- package/src/engine/network/diagnostics/BandwidthMeter.d.ts +1 -1
- package/src/engine/network/diagnostics/BandwidthMeter.js +1 -1
- package/src/engine/network/orchestrator/HostJoin.d.ts +111 -0
- package/src/engine/network/orchestrator/HostJoin.d.ts.map +1 -0
- package/src/engine/network/orchestrator/HostJoin.js +271 -0
- package/src/engine/network/orchestrator/NetworkPeer.d.ts +151 -112
- package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/NetworkPeer.js +450 -499
- package/src/engine/network/orchestrator/PendingSlices.d.ts +30 -0
- package/src/engine/network/orchestrator/PendingSlices.d.ts.map +1 -0
- package/src/engine/network/orchestrator/PendingSlices.js +83 -0
- package/src/engine/network/orchestrator/ReliableFrames.d.ts +34 -0
- package/src/engine/network/orchestrator/ReliableFrames.d.ts.map +1 -0
- package/src/engine/network/orchestrator/ReliableFrames.js +96 -0
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +77 -10
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +236 -61
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +20 -6
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +33 -8
- package/src/engine/network/replication/Replicator.d.ts.map +1 -1
- package/src/engine/network/replication/Replicator.js +7 -1
- package/src/engine/network/sim/ActionLog.d.ts +32 -5
- package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
- package/src/engine/network/sim/ActionLog.js +47 -5
- package/src/engine/network/sim/SimActionExecutor.d.ts +42 -2
- package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
- package/src/engine/network/sim/SimActionExecutor.js +100 -4
- package/src/engine/network/sim/Snapshotter.d.ts +12 -0
- package/src/engine/network/sim/Snapshotter.d.ts.map +1 -1
- package/src/engine/network/sim/Snapshotter.js +43 -0
- package/src/engine/network/state/Baseline.d.ts +1 -1
- package/src/engine/network/state/Baseline.js +1 -1
- package/src/engine/network/state/FrameBufferRing.d.ts +25 -6
- package/src/engine/network/state/FrameBufferRing.d.ts.map +1 -1
- package/src/engine/network/state/FrameBufferRing.js +51 -6
- package/src/engine/network/state/InputRing.d.ts +4 -0
- package/src/engine/network/state/InputRing.d.ts.map +1 -1
- package/src/engine/network/state/InputRing.js +7 -0
- package/src/engine/network/transport/DeferredTransport.d.ts.map +1 -1
- package/src/engine/network/transport/DeferredTransport.js +1 -0
- package/src/engine/network/transport/LoopbackTransport.d.ts.map +1 -1
- package/src/engine/network/transport/LoopbackTransport.js +4 -2
- package/src/engine/network/transport/Transport.d.ts +7 -0
- package/src/engine/network/transport/Transport.d.ts.map +1 -1
- package/src/engine/network/transport/Transport.js +8 -0
- package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/NodeUDPTransport.js +20 -5
- package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +8 -0
- package/src/engine/network/transport/adapters/WebTransportTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/WebTransportTransport.js +6 -0
- package/src/engine/network/transport/connection/Connection.d.ts +191 -0
- package/src/engine/network/transport/connection/Connection.d.ts.map +1 -0
- package/src/engine/network/transport/connection/Connection.js +1262 -0
- package/src/engine/network/transport/connection/RangeSet.d.ts +94 -0
- package/src/engine/network/transport/connection/RangeSet.d.ts.map +1 -0
- package/src/engine/network/transport/connection/RangeSet.js +260 -0
- package/src/engine/network/transport/connection/RxStream.d.ts +48 -0
- package/src/engine/network/transport/connection/RxStream.d.ts.map +1 -0
- package/src/engine/network/transport/connection/RxStream.js +211 -0
- package/src/engine/network/transport/connection/TxStream.d.ts +90 -0
- package/src/engine/network/transport/connection/TxStream.d.ts.map +1 -0
- package/src/engine/network/transport/connection/TxStream.js +243 -0
- package/src/engine/network/transport/connection/bytes_copy.d.ts +17 -0
- package/src/engine/network/transport/connection/bytes_copy.d.ts.map +1 -0
- package/src/engine/network/transport/connection/bytes_copy.js +34 -0
- package/src/engine/network/transport/connection/varint.d.ts +46 -0
- package/src/engine/network/transport/connection/varint.d.ts.map +1 -0
- package/src/engine/network/transport/connection/varint.js +107 -0
- package/src/engine/physics/cloth/ecs/Cloth.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/Cloth.js +7 -5
- package/src/engine/physics/cloth/ecs/ClothCollider.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothCollider.js +7 -5
- package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothDynamics.js +20 -11
- package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
- package/src/engine/physics/fluid/FluidField.js +7 -5
- package/src/engine/physics/fluid/ecs/FluidComponent.d.ts.map +1 -1
- package/src/engine/physics/fluid/ecs/FluidComponent.js +19 -11
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/engine/physics/fluid/effector/AbstractFluidEffector.d.ts +2 -2
- package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +2 -2
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +11 -7
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +11 -7
- package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/WakeFluidEffector.js +16 -9
- package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +38 -7
- package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -1
- package/src/engine/sound/ecs/audio/AudioEmitter.js +47 -14
- package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.d.ts.map +1 -1
- package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.js +5 -1
- package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.d.ts +18 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.js +48 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +12 -2
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -1
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +173 -9
- package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +9 -3
- package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -1
- package/src/engine/sound/ecs/audio/LiveEmitterSet.js +28 -8
- package/src/engine/sound/ecs/trigger/AudioEventTrigger.d.ts +8 -0
- package/src/engine/sound/ecs/trigger/AudioEventTrigger.d.ts.map +1 -1
- package/src/engine/sound/ecs/trigger/AudioEventTrigger.js +192 -191
- package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationAdapter.d.ts.map +1 -1
- package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationAdapter.js +5 -1
- package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.d.ts +18 -0
- package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.d.ts.map +1 -0
- package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.js +51 -0
- package/src/engine/sound/ecs/trigger/AudioEventTriggerSystem.d.ts.map +1 -1
- package/src/engine/sound/ecs/trigger/AudioEventTriggerSystem.js +15 -1
- package/src/engine/sound/simulation/core/AcousticMedium.js +2 -2
- package/src/engine/sound/simulation/definition/AcousticDirectivity.js +2 -2
- package/src/engine/sound/simulation/definition/AcousticMaterial.d.ts.map +1 -1
- package/src/engine/sound/simulation/definition/AcousticMaterial.js +11 -7
- package/src/engine/sound/sopra/README.md +768 -644
- package/src/engine/sound/sopra/SopraEngine.d.ts +104 -8
- package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -1
- package/src/engine/sound/sopra/SopraEngine.js +327 -12
- package/src/engine/sound/sopra/definition/BusDefinition.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/BusDefinition.js +5 -4
- package/src/engine/sound/sopra/definition/DuckingRule.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/DuckingRule.js +7 -5
- package/src/engine/sound/sopra/definition/EventDescription.d.ts +28 -2
- package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/EventDescription.js +77 -17
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +10 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +77 -55
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.d.ts +31 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.js +75 -0
- package/src/engine/sound/sopra/definition/MixerSnapshot.js +2 -2
- package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/ParameterDefinition.js +7 -5
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +2 -2
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +4 -3
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +2 -2
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +3 -3
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +16 -9
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +2 -2
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +2 -2
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +38 -9
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +34 -5
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +7 -2
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +137 -120
- package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +3 -1
- package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/effect/EqEffect.js +146 -128
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +1 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +8 -3
- package/src/engine/sound/sopra/legacy/SoundEmitter.d.ts.map +1 -1
- package/src/engine/sound/sopra/legacy/SoundEmitter.js +357 -352
- package/src/engine/sound/sopra/legacy/SoundTrack.d.ts.map +1 -1
- package/src/engine/sound/sopra/legacy/SoundTrack.js +281 -278
- package/src/engine/sound/sopra/runtime/BusGraph.d.ts +44 -2
- package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/BusGraph.js +83 -43
- package/src/engine/sound/sopra/runtime/EffectChain.d.ts +96 -0
- package/src/engine/sound/sopra/runtime/EffectChain.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/EffectChain.js +286 -0
- package/src/engine/sound/sopra/runtime/EffectParamOverrides.d.ts +123 -0
- package/src/engine/sound/sopra/runtime/EffectParamOverrides.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/EffectParamOverrides.js +320 -0
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts +55 -5
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/EventInstance.js +201 -26
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +2 -1
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -1
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +6 -1
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +15 -0
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -1
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +32 -0
- package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -1
- package/src/engine/sound/sopra/util/MockAudioContext.js +3 -0
- package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +13 -3
- package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -1
- package/src/engine/sound/sopra/util/fadeOutAndStop.js +25 -13
- package/src/format/image/png/PNGReader.js +644 -644
- package/src/format/image/png/crc32.d.ts +1 -1
- package/src/format/image/png/crc32.js +7 -8
- package/src/shade/RENDERER_CONTRACT.md +6 -3
- package/src/shade/descriptor/binding/BindGroupLayoutDescriptor.js +2 -2
- package/src/shade/descriptor/binding/BindGroupLayoutEntry.d.ts.map +1 -1
- package/src/shade/descriptor/binding/BindGroupLayoutEntry.js +12 -8
- package/src/shade/descriptor/binding/StorageTextureBindingLayout.js +2 -2
- package/src/shade/descriptor/binding/TextureBindingLayout.js +2 -2
- package/src/shade/descriptor/blend/BlendComponent.js +2 -2
- package/src/shade/descriptor/pipeline/PipelineDescriptorBase.js +2 -2
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +2 -2
- package/src/shade/descriptor/pipeline/ProgrammableStage.js +2 -2
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.js +2 -2
- package/src/shade/descriptor/pipeline/render/ColorTargetState.js +2 -2
- package/src/shade/descriptor/pipeline/render/FragmentState.js +2 -2
- package/src/shade/descriptor/pipeline/render/PrimitiveState.js +2 -2
- package/src/shade/descriptor/pipeline/render/RenderPipelineDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/render/RenderPipelineDescriptor.js +8 -5
- package/src/shade/descriptor/pipeline/render/VertexAttribute.js +2 -2
- package/src/shade/descriptor/texture/SamplerDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/texture/SamplerDescriptor.js +20 -12
- package/src/shade/descriptor/texture/TextureDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/texture/TextureDescriptor.js +11 -7
- package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts +24 -0
- package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts.map +1 -0
- package/src/shade/device/SHADE_DEVICE_FLOOR.js +34 -0
- package/src/shade/device/ShadeDeviceFailure.d.ts +12 -5
- package/src/shade/device/ShadeDeviceFailure.d.ts.map +1 -1
- package/src/shade/device/ShadeDeviceFailure.js +17 -8
- package/src/shade/device/ShadeDeviceFailureReason.d.ts +1 -1
- package/src/shade/device/ShadeDeviceFailureReason.js +2 -2
- package/src/shade/device/graph/BufferResourceDescriptor.d.ts.map +1 -1
- package/src/shade/device/graph/BufferResourceDescriptor.js +196 -193
- package/src/shade/device/graph/TextureResourceDescriptor.d.ts.map +1 -1
- package/src/shade/device/graph/TextureResourceDescriptor.js +178 -173
- package/src/shade/device/shade_device_floor_unmet.d.ts +13 -0
- package/src/shade/device/shade_device_floor_unmet.d.ts.map +1 -0
- package/src/shade/device/shade_device_floor_unmet.js +32 -0
- package/src/shade/device/timing/profile/sgpt_read_header.js +1 -1
- package/src/shade/device/timing/profile/sgpt_read_records.js +1 -1
- package/src/shade/device/timing/profile/sgpt_write_header.js +1 -1
- package/src/shade/device/timing/profile/sgpt_write_record.js +1 -1
- package/src/shade/renderer/Renderer.d.ts +2 -0
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +29 -55
- package/src/shade/renderer/binding/BufferBinding.js +2 -2
- package/src/shade/renderer/buffer/GPUBufferAllocator.d.ts.map +1 -1
- package/src/shade/renderer/buffer/GPUBufferAllocator.js +9 -0
- package/src/shade/renderer/buffer/buffer_pool_ops.d.ts +21 -12
- package/src/shade/renderer/buffer/buffer_pool_ops.d.ts.map +1 -1
- package/src/shade/renderer/buffer/buffer_pool_ops.js +18 -19
- package/src/shade/renderer/camera/Camera.js +2 -2
- package/src/shade/renderer/geometry/Attribute.js +2 -2
- package/src/shade/renderer/geometry/Geometry.js +2 -2
- package/src/shade/renderer/geometry/geometry_deduplicate_vertices.js +2 -2
- package/src/shade/renderer/geometry/meshlet/MeshletBatch.js +2 -2
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
- package/src/shade/renderer/gpu_primitive/test/gpu_test.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/test/gpu_test.js +8 -11
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/material/StandardShadeMaterial.d.ts.map +1 -1
- package/src/shade/renderer/material/StandardShadeMaterial.js +13 -8
- package/src/shade/renderer/pool/TransientResourcePool.d.ts +24 -0
- package/src/shade/renderer/pool/TransientResourcePool.d.ts.map +1 -1
- package/src/shade/renderer/pool/TransientResourcePool.js +41 -10
- package/src/shade/renderer/scene/optimization/geometry/internal/triangle_fingerprint_stream.d.ts.map +1 -1
- package/src/shade/renderer/scene/optimization/geometry/internal/triangle_fingerprint_stream.js +14 -5
- package/src/shade/renderer/scene/optimization/sampler2d_from_image_bitmap.js +1 -1
- package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
- package/src/shade/renderer/shader/ComputeShader.js +18 -2
- package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
- package/src/shade/renderer/shader/ImageShader.js +230 -218
- package/src/shade/renderer/shader/type/WGSLStructField.js +2 -2
- package/src/shade/renderer/shader/type/WebGPUArray.js +2 -2
- package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
- package/src/shade/renderer/shader/type/WebGPUStruct.js +9 -6
- package/src/shade/renderer/texture/ShadeTexture.d.ts.map +1 -1
- package/src/shade/renderer/texture/ShadeTexture.js +17 -10
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +2 -2
- package/src/shade/renderer/water/surface/chunk_water_surface_sample.d.ts +4 -4
- package/src/shade/renderer/water/surface/chunk_water_surface_sample.js +6 -6
- package/src/shade/renderer/water/surface/particles/GPUWaterParticles.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/GPUWaterParticles.js +46 -55
- package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.d.ts +3 -3
- package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.js +14 -4
- package/src/shade/renderer/water/surface/particles/chunk_water_particle.d.ts +10 -1
- package/src/shade/renderer/water/surface/particles/chunk_water_particle.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/chunk_water_particle.js +82 -40
- package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.d.ts +4 -1
- package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.js +8 -4
- package/src/shade/renderer/water/surface/particles/shader_water_particles.d.ts +98 -86
- package/src/shade/renderer/water/surface/particles/shader_water_particles.d.ts.map +1 -1
- package/src/shade/renderer/water/surface/particles/shader_water_particles.js +440 -298
- package/src/view/common/HTMLElementCacheKey.d.ts.map +1 -1
- package/src/view/common/HTMLElementCacheKey.js +4 -3
- package/src/view/graphics/GraphicsContextFailureView.d.ts +1 -2
- package/src/view/graphics/GraphicsContextFailureView.d.ts.map +1 -1
- package/src/view/graphics/GraphicsContextFailureView.js +1 -2
- package/src/view/minimap/MinimapMarker.js +2 -2
- package/src/engine/network/transport/Channel.js +0 -336
- package/src/engine/network/transport/ReliableCommandPipeline.js +0 -335
- package/src/engine/network/transport/fragments/FragmentAssembler.js +0 -350
- package/src/engine/network/transport/fragments/FragmentRetention.js +0 -191
- package/src/engine/network/transport/fragments/fragment_send.js +0 -157
- package/src/engine/network/transport/fragments/packet_size.js +0 -107
|
@@ -10,6 +10,7 @@ import { WGSLStructField } from "../../../shader/type/WGSLStructField.js";
|
|
|
10
10
|
import { atomic_u32, f32, u32 } from "../../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
|
|
11
11
|
import { COLLECTION_U32_STRUCT_READ } from "../../../gpu_primitive/COLLECTION_U32_STRUCT_READ.js";
|
|
12
12
|
import { STRUCT_DISPATCH_COMPUTE_COMMAND } from "../../../gpu_primitive/indirect/STRUCT_DISPATCH_COMPUTE_COMMAND.js";
|
|
13
|
+
import { WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION } from "../../../gpu_primitive/indirect/WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION.js";
|
|
13
14
|
import { GEOMETRY_METADATA_STRUCT } from "../../../geometry/GEOMETRY_METADATA_STRUCT.js";
|
|
14
15
|
import { MESHLET_METADATA_STRUCT } from "../../../geometry/meshlet/encoding/MESHLET_METADATA_STRUCT.js";
|
|
15
16
|
import { BVH2_NODE_STRUCT } from "../../../shader/chunk/bvh/BVH2_NODE_STRUCT.js";
|
|
@@ -52,52 +53,56 @@ export const WATER_PARTICLE_CORNERS = 4;
|
|
|
52
53
|
export const WATER_PARTICLE_RANK_LIMIT = 64;
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* elsewhere on the body does not count, so a splash far off, or a front moving to a coarser level, changes nothing here.
|
|
61
|
-
*
|
|
62
|
-
* A front's lot is its own for life and the same in every cell it reaches, so the fronts of the lowest lots are kept in
|
|
63
|
-
* every crowd they are in, whatever their size: the count at a point varies by fronts' worth around this, and a front
|
|
64
|
-
* the outline broke is drawn particle by particle, ~20 entries in one cell. Where a point would read more than the
|
|
65
|
-
* gather budget, its cells keep no more of it than their steepness's part, the lowest keys first
|
|
66
|
-
* ({@link WATER_PARTICLE_CROWD_BINS}), so no point reads past the budget. A whole front is drawn as the seven it was
|
|
67
|
-
* emitted as (water_particle_group), so three fresh splashes cross whole, whatever their steepness and levels; a fourth
|
|
68
|
-
* over them thins one.
|
|
69
|
-
*
|
|
70
|
-
* A cell only one front reaches is not crowded, however many of its packets it holds: one front puts at most ~40 in
|
|
71
|
-
* a cell, within the gather budget.
|
|
56
|
+
* How many places along the array either side a particle looks for its front's neighbours. A front keeps its
|
|
57
|
+
* particles in a row and in order — a ring is emitted whole, a split puts its three in its place, the dead leave no
|
|
58
|
+
* gap — and a mirror image sits right after the packet it left, so a neighbour is a place or two away, a few where
|
|
59
|
+
* images split beside their packets. A pair further apart, as a ring's last and first are, is drawn as two runs whose
|
|
60
|
+
* windows meet, the same surface.
|
|
72
61
|
*/
|
|
73
|
-
export const
|
|
62
|
+
export const WATER_PARTICLE_LINK_REACH = 8;
|
|
74
63
|
|
|
75
64
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
65
|
+
* Most particles one entry draws. A run ends where its neighbours' supports leave its cell. A cell is under four
|
|
66
|
+
* support radii s across, so the particles reaching it stand within a square of under 6s, and an arc of a circle inside a
|
|
67
|
+
* square of side L is at most about πL long; the step keeps a front's neighbours at least a third of their envelope's
|
|
68
|
+
* half-length h apart (they split at h), so s is at most ~3.2 of their spacing, and a cell holds at most ~62 of one
|
|
69
|
+
* front. A front closer set than the step leaves is drawn with a hole past this many.
|
|
80
70
|
*/
|
|
81
|
-
export const
|
|
71
|
+
export const WATER_PARTICLE_RUN_LIMIT = 96;
|
|
82
72
|
|
|
83
73
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
74
|
+
* Steepness classes a cell counts its runs in: half octaves of |a|/r below 0.1, the steepest a splash makes, the last
|
|
75
|
+
* everything flatter. A run's class is its front's, the same in every cell it reaches. The cut pass reads a cell's
|
|
76
|
+
* classes and total as vectors, written out for ten (WaterParticleCrowd).
|
|
86
77
|
*/
|
|
87
|
-
export const
|
|
78
|
+
export const WATER_PARTICLE_CROWD_BINS = 10;
|
|
79
|
+
|
|
80
|
+
/** Words a bucket's crowd takes, as the weigh pass counts it: its runs by steepness class, then all of them. */
|
|
81
|
+
export const WATER_PARTICLE_CROWD_WORDS = WATER_PARTICLE_CROWD_BINS + 1;
|
|
88
82
|
|
|
89
83
|
/**
|
|
90
|
-
* Words the nest
|
|
91
|
-
*
|
|
92
|
-
*
|
|
84
|
+
* Words the nest, cut and mark passes keep per bucket: the most runs a point under it reads at finer levels and its own
|
|
85
|
+
* and the coarser ones; how many steepness classes its points keep, the steepest first, within
|
|
86
|
+
* {@link WATER_PARTICLE_CROWD_KEEP}, and within the gather budget; and a slot of the frame's table of cut fronts.
|
|
93
87
|
*/
|
|
94
|
-
export const WATER_PARTICLE_NEST_WORDS =
|
|
88
|
+
export const WATER_PARTICLE_NEST_WORDS = 4;
|
|
95
89
|
|
|
96
|
-
/**
|
|
97
|
-
export const
|
|
90
|
+
/** Slots of the cut fronts' table a front's tag is looked for in, from its own: at most an eighth of them hold one. */
|
|
91
|
+
export const WATER_PARTICLE_FRONT_PROBES = 8;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Runs a crowded point keeps: the steepest, class by class, as many as fit. A run a crowd cannot keep at any of its
|
|
95
|
+
* cells dooms its particles (WATER_PARTICLE_STRUCT's `fading`): they fade and die as packets below the drawing threshold
|
|
96
|
+
* do, so the weakest go first and nothing doomed comes back. The surface sampler's gather budget above this holds the
|
|
97
|
+
* doomed while they fade; a run past the budget itself, as where fresh fronts arrive faster than doomed ones fade, is
|
|
98
|
+
* cut: it is not drawn and dies at its next step, and so does all of its front, which a cut would otherwise leave in
|
|
99
|
+
* pieces no cell counted. The steepest class is always kept: a fresh splash is drawn, though points where more of those
|
|
100
|
+
* cross than the budget holds then lose some.
|
|
101
|
+
*/
|
|
102
|
+
export const WATER_PARTICLE_CROWD_KEEP = 48;
|
|
98
103
|
|
|
99
|
-
/**
|
|
100
|
-
const
|
|
104
|
+
/** A doomed particle's `fading` that kills it at its next step: no steepness reaches it. */
|
|
105
|
+
const WATER_PARTICLE_CUT = 1e30;
|
|
101
106
|
|
|
102
107
|
const STRIDE = WATER_PARTICLE_BODY_BOUNDS_STRIDE;
|
|
103
108
|
const WORDS = WATER_PARTICLE_CROWD_WORDS;
|
|
@@ -303,7 +308,7 @@ fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) la
|
|
|
303
308
|
let ridge = select(0.0, height, base + count <= capacity);
|
|
304
309
|
for (var k = 0u; k < count; k++) {
|
|
305
310
|
if (base + k >= capacity) { break; }
|
|
306
|
-
next.elements[base + k] = ${WATER_PARTICLE_STRUCT.wgsl_ref}(impulse.xy * scale, f32(k) * dispersion, age, ridge, radius, dispersion, row, 0u);
|
|
311
|
+
next.elements[base + k] = ${WATER_PARTICLE_STRUCT.wgsl_ref}(impulse.xy * scale, f32(k) * dispersion, age, ridge, radius, dispersion, row, 0u, 0.0);
|
|
307
312
|
}
|
|
308
313
|
held += f32(count) * abs(ridge);
|
|
309
314
|
}
|
|
@@ -324,10 +329,16 @@ finalize_resources.createGroup()
|
|
|
324
329
|
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ, true)
|
|
325
330
|
.addStorageBuffer('table_command', STRUCT_DISPATCH_COMPUTE_COMMAND, true);
|
|
326
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Words the frame's clear writes per bucket of the table: its count, its largest |a|, its crowd and its nest, four to a
|
|
334
|
+
* thread (the table is a power of two of at least {@link WATER_PARTICLE_TABLE_MIN} buckets, so each array is whole vec4s).
|
|
335
|
+
*/
|
|
336
|
+
export const WATER_PARTICLE_CLEAR_WORDS = 2 + WATER_PARTICLE_CROWD_WORDS + WATER_PARTICLE_NEST_WORDS;
|
|
337
|
+
|
|
327
338
|
/**
|
|
328
339
|
* The live count, clamped to capacity (emitters append past it), and the frame's hash table size: twice the
|
|
329
340
|
* entries the particles can take, a power of two, at least ${WATER_PARTICLE_TABLE_MIN}; and the command that clears
|
|
330
|
-
* that much of the table, a thread
|
|
341
|
+
* that much of the table, a thread every four words, folded.
|
|
331
342
|
*/
|
|
332
343
|
export const shader_water_particles_finalize = ComputeShader.from({ label: 'Water / particles finalize', resources: finalize_resources,
|
|
333
344
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
@@ -338,8 +349,9 @@ fn main() {
|
|
|
338
349
|
let wanted = max(2u * ${WATER_PARTICLE_CORNERS}u * live, ${WATER_PARTICLE_TABLE_MIN}u);
|
|
339
350
|
let table = min(1u << (32u - countLeadingZeros(wanted - 1u)), limits.table_limit);
|
|
340
351
|
offsets.count = table;
|
|
341
|
-
|
|
342
|
-
table_command.
|
|
352
|
+
let groups = (table / 4u * ${WATER_PARTICLE_CLEAR_WORDS}u + 63u) / 64u;
|
|
353
|
+
table_command.workgroupCountX = min(groups, ${WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION}u);
|
|
354
|
+
table_command.workgroupCountY = (groups + ${WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION - 1}u) / ${WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION}u;
|
|
343
355
|
table_command.workgroupCountZ = 1u;
|
|
344
356
|
}
|
|
345
357
|
`, [WATER_PARTICLE_STRUCT.declaration_chunk]) });
|
|
@@ -347,77 +359,83 @@ fn main() {
|
|
|
347
359
|
const clear_resources = new ShaderResourceSetDescriptor();
|
|
348
360
|
clear_resources.createGroup()
|
|
349
361
|
.addStorageBuffer('buckets', WATER_PARTICLE_BUCKETS_STRUCT, true)
|
|
350
|
-
.addStorageBuffer('bucket_amplitude', WebGPUArray.
|
|
351
|
-
.addStorageBuffer('
|
|
352
|
-
.addStorageBuffer('bucket_nest', WebGPUArray.
|
|
362
|
+
.addStorageBuffer('bucket_amplitude', WebGPUArray.vec4u, true)
|
|
363
|
+
.addStorageBuffer('bucket_crowd', WebGPUArray.vec4u, true)
|
|
364
|
+
.addStorageBuffer('bucket_nest', WebGPUArray.vec4u, true);
|
|
353
365
|
|
|
354
366
|
/**
|
|
355
|
-
*
|
|
356
|
-
*
|
|
367
|
+
* Clear the frame's table, as far as finalize sized it: each bucket's count, largest |a| and crowd, its points let keep
|
|
368
|
+
* and draw every steepness class until the cut pass says otherwise, and its slot of the cut fronts' table. A thread
|
|
369
|
+
* every four words, the words of each array in a row, so neighbouring threads write neighbouring words; a bucket's nest
|
|
370
|
+
* is its four. A frame pays for the table its particles take, not for the pool's capacity; the buckets past it are
|
|
371
|
+
* never read.
|
|
357
372
|
*/
|
|
358
373
|
export const shader_water_particles_clear = ComputeShader.from({ label: 'Water / particles clear', resources: clear_resources,
|
|
359
374
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
360
375
|
@compute @workgroup_size(64)
|
|
361
376
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
362
|
-
let
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
377
|
+
let table = buckets.count;
|
|
378
|
+
let quarter = table / 4u;
|
|
379
|
+
var at = folded_workgroup_index(group) * 64u + lane;
|
|
380
|
+
if (at < quarter) {
|
|
381
|
+
for (var word = 4u * at; word < 4u * at + 4u; word++) { atomicStore(&buckets.elements[word], 0u); }
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
at -= quarter;
|
|
385
|
+
if (at < quarter) { bucket_amplitude[at] = vec4<u32>(0u); return; }
|
|
386
|
+
at -= quarter;
|
|
387
|
+
if (at < ${WORDS}u * quarter) { bucket_crowd[at] = vec4<u32>(0u); return; }
|
|
388
|
+
at -= ${WORDS}u * quarter;
|
|
389
|
+
if (at < table) { bucket_nest[at] = vec4<u32>(0u, ${BINS}u, ${BINS}u, 0u); }
|
|
368
390
|
}
|
|
369
391
|
`, [chunk_folded_workgroup_index]) });
|
|
370
392
|
|
|
371
393
|
/**
|
|
372
|
-
* A live particle's place in the hash this frame:
|
|
373
|
-
*
|
|
394
|
+
* A live particle's place in the hash this frame: the level and the cells its support overlaps (one or two a side, the
|
|
395
|
+
* level's cell being at least the support's diameter), and the entry it makes.
|
|
374
396
|
*/
|
|
375
397
|
const chunk_water_particle_cells = CodeChunk.from(/* language=WGSL */ `
|
|
376
|
-
struct
|
|
377
|
-
entry: ${WATER_PARTICLE_ENTRY_STRUCT.wgsl_ref},
|
|
398
|
+
struct WaterParticleReach {
|
|
378
399
|
level: u32,
|
|
379
400
|
first: vec2<i32>,
|
|
380
401
|
span: vec2<u32>,
|
|
381
402
|
}
|
|
382
403
|
|
|
383
|
-
|
|
384
|
-
|
|
404
|
+
// The cells a particle's support overlaps at its level.
|
|
405
|
+
fn water_particle_reach(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}) -> WaterParticleReach {
|
|
406
|
+
var reach: WaterParticleReach;
|
|
385
407
|
let position = water_particle_position(particle);
|
|
386
|
-
let width = water_particle_width(particle);
|
|
387
408
|
let distance = water_particle_distance(particle);
|
|
388
|
-
let
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
let size = water_particle_cell_size(
|
|
392
|
-
|
|
409
|
+
let support = water_particle_support(water_particle_half_length_at(particle.radius, distance), water_particle_width(particle),
|
|
410
|
+
particle.dispersion);
|
|
411
|
+
reach.level = water_particle_level(support);
|
|
412
|
+
let size = water_particle_cell_size(reach.level);
|
|
413
|
+
reach.first = vec2<i32>(floor((position - vec2<f32>(support)) / size));
|
|
393
414
|
let last = vec2<i32>(floor((position + vec2<f32>(support)) / size));
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
particle.dispersion, water_particle_phase(particle), water_particle_key(particle.body, cells.level), source, distance, half_length);
|
|
397
|
-
return cells;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
fn water_particle_corner_cell(cells: WaterParticleCells, corner: u32) -> vec2<i32> {
|
|
401
|
-
return cells.first + vec2<i32>(i32(corner & 1u), i32(corner >> 1u));
|
|
415
|
+
reach.span = vec2<u32>(clamp(last - reach.first, vec2<i32>(0), vec2<i32>(1))) + vec2<u32>(1u);
|
|
416
|
+
return reach;
|
|
402
417
|
}
|
|
403
418
|
|
|
404
|
-
|
|
405
|
-
|
|
419
|
+
// The entry a particle makes as a lone packet, for the first cell it reaches.
|
|
420
|
+
fn water_particle_entry(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}, source: u32, reach: WaterParticleReach) -> ${WATER_PARTICLE_ENTRY_STRUCT.wgsl_ref} {
|
|
421
|
+
let distance = water_particle_distance(particle);
|
|
422
|
+
return ${WATER_PARTICLE_ENTRY_STRUCT.wgsl_ref}(water_particle_position(particle), reach.first, particle.angle, particle.amplitude,
|
|
423
|
+
particle.radius, particle.dispersion, water_particle_phase(particle), water_particle_key(particle.body, reach.level), source,
|
|
424
|
+
distance, water_particle_half_length_at(particle.radius, distance), WATER_PARTICLE_LONE);
|
|
406
425
|
}
|
|
407
426
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return u32(min(abs(particle.amplitude) / max(particle.radius, 1e-6), 1.0) * ${WATER_PARTICLE_WEIGHT_SCALE}.0);
|
|
427
|
+
fn water_particle_corner_cell(reach: WaterParticleReach, corner: u32) -> vec2<i32> {
|
|
428
|
+
return reach.first + vec2<i32>(i32(corner & 1u), i32(corner >> 1u));
|
|
411
429
|
}
|
|
412
430
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
return pcg3d(vec3<u32>(bitcast<u32>(particle.origin.x), bitcast<u32>(particle.origin.y), particle.body)).x;
|
|
431
|
+
fn water_particle_has_corner(reach: WaterParticleReach, corner: u32) -> bool {
|
|
432
|
+
return all(vec2<u32>(corner & 1u, corner >> 1u) < reach.span);
|
|
416
433
|
}
|
|
417
434
|
|
|
418
|
-
//
|
|
419
|
-
fn
|
|
420
|
-
|
|
435
|
+
// Whether a particle's support overlaps a cell of its level.
|
|
436
|
+
fn water_particle_reaches(reach: WaterParticleReach, cell: vec2<i32>) -> bool {
|
|
437
|
+
let offset = cell - reach.first;
|
|
438
|
+
return all(offset >= vec2<i32>(0)) && all(vec2<u32>(offset) < reach.span);
|
|
421
439
|
}
|
|
422
440
|
|
|
423
441
|
// The bucket of the cell holding a cell's points at a level as coarse as its own or coarser, as the sampler finds it.
|
|
@@ -425,120 +443,111 @@ fn water_particle_bucket_over(body: u32, level: u32, cell: vec2<i32>, over: u32,
|
|
|
425
443
|
return water_particle_bucket(body, over, cell >> vec2<u32>(over - level), table_size);
|
|
426
444
|
}
|
|
427
445
|
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
fn
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
// the coarser ones: its most crowded point reads at least that, and the ramp draws a lot up to its share above it.
|
|
436
|
-
fn water_particle_crowd_reach(steepness: u32) -> f32 {
|
|
437
|
-
return ${1 + WATER_PARTICLE_THINNING_RAMP} * ${WATER_PARTICLE_CELL_SHARE}.0 / f32(steepness);
|
|
446
|
+
// What makes a front's particles alike (water_particle_alike) but their angle, hashed: a tag, never 0, and where in the
|
|
447
|
+
// cut fronts' table it is looked for.
|
|
448
|
+
fn water_particle_front_hash(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}) -> vec2<u32> {
|
|
449
|
+
let a = pcg3d(vec3<u32>(bitcast<u32>(particle.origin.x), bitcast<u32>(particle.origin.y), particle.body));
|
|
450
|
+
let b = pcg3d(vec3<u32>(bitcast<u32>(particle.age) ^ a.x, bitcast<u32>(particle.amplitude) ^ a.y, bitcast<u32>(particle.radius) ^ a.z));
|
|
451
|
+
let c = pcg3d(vec3<u32>(bitcast<u32>(particle.dispersion) ^ b.x, b.y, b.z));
|
|
452
|
+
return vec2<u32>(c.x | 1u, c.y);
|
|
438
453
|
}
|
|
439
454
|
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
fn
|
|
443
|
-
let
|
|
444
|
-
if (!(
|
|
445
|
-
return
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// How much of a packet its crowd leaves drawn, from its share of the most crowded of its cells: all of it from a share
|
|
449
|
-
// of one, and below that, all of it while its lot is under its share, fading to none a ramp of that share above, and
|
|
450
|
-
// none from a share of none. So a cell keeps about its share whatever its crowd.
|
|
451
|
-
fn water_particle_visibility(share: f32, lot: f32) -> f32 {
|
|
452
|
-
if (!(share > 0.0)) { return 0.0; }
|
|
453
|
-
return clamp((share - lot) / (${WATER_PARTICLE_THINNING_RAMP} * share) + 1.0, 0.0, 1.0);
|
|
455
|
+
// A run's steepness class (${BINS} half octaves of |a|/r below 0.1, the steepest first): its front's, alike at every
|
|
456
|
+
// particle of the run and in every cell it reaches.
|
|
457
|
+
fn water_particle_crowd_class(particle: ${WATER_PARTICLE_STRUCT.wgsl_ref}) -> u32 {
|
|
458
|
+
let steepness = abs(particle.amplitude) / max(particle.radius, 1e-30);
|
|
459
|
+
if (!(steepness > 0.0)) { return ${BINS - 1}u; }
|
|
460
|
+
return u32(clamp(floor(2.0 * log2(0.1 / steepness)), 0.0, ${BINS - 1}.0));
|
|
454
461
|
}
|
|
455
462
|
`, [chunk_water_particle]);
|
|
456
463
|
|
|
457
|
-
const fronts_resources = new ShaderResourceSetDescriptor();
|
|
458
|
-
fronts_resources.createGroup()
|
|
459
|
-
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT)
|
|
460
|
-
.addStorageBuffer('fronts', COLLECTION_U32_STRUCT_READ, true);
|
|
461
|
-
|
|
462
464
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
* gap — so the inclusive scan of these numbers each run from 1. A mirror image follows its packet in the row: it
|
|
466
|
-
* breaks its front's run there, as a death does, and is a front of its own.
|
|
465
|
+
* A particle's cells as the links pass wrote them for the frame: three words, its first cell and its level with its
|
|
466
|
+
* spans. Bindings: `reaches`.
|
|
467
467
|
*/
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (i >= next.count) { return; }
|
|
474
|
-
fronts.elements[i] = select(1u, 0u, i > 0u && water_particle_same_front(next.elements[i - 1u], next.elements[i]));
|
|
468
|
+
const chunk_water_particle_reach_read = CodeChunk.from(/* language=WGSL */ `
|
|
469
|
+
fn water_particle_reach_at(i: u32) -> WaterParticleReach {
|
|
470
|
+
let packed = reaches[3u * i + 2u];
|
|
471
|
+
return WaterParticleReach(packed & 0xffu, vec2<i32>(bitcast<i32>(reaches[3u * i]), bitcast<i32>(reaches[3u * i + 1u])),
|
|
472
|
+
vec2<u32>(((packed >> 8u) & 1u) + 1u, ((packed >> 9u) & 1u) + 1u));
|
|
475
473
|
}
|
|
476
|
-
`, [chunk_water_particle, chunk_folded_workgroup_index]) });
|
|
477
474
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
475
|
+
// No cells: what a missing neighbour reaches.
|
|
476
|
+
fn water_particle_reach_none() -> WaterParticleReach {
|
|
477
|
+
return WaterParticleReach(0u, vec2<i32>(0), vec2<u32>(0u));
|
|
478
|
+
}
|
|
479
|
+
`, [chunk_water_particle_cells]);
|
|
483
480
|
|
|
484
481
|
/**
|
|
485
|
-
*
|
|
482
|
+
* Whether a particle begins a run of its front in a cell its support overlaps: it has no neighbour before it round the
|
|
483
|
+
* front, or that neighbour's support does not overlap the cell. Each run is drawn in the cell by its first particle,
|
|
484
|
+
* once. Bindings: `links`, `reaches`.
|
|
486
485
|
*/
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
let run = fronts.elements[i];
|
|
494
|
-
if (i == 0u || fronts.elements[i - 1u] != run) {
|
|
495
|
-
runs[2u * (run - 1u)] = i;
|
|
496
|
-
runs[2u * (run - 1u) + 1u] = bitcast<u32>(1e30);
|
|
497
|
-
}
|
|
486
|
+
const chunk_water_particle_run_start = CodeChunk.from(/* language=WGSL */ `
|
|
487
|
+
// The cells the support of a particle's neighbour before it overlaps; none without one.
|
|
488
|
+
fn water_particle_before(i: u32) -> WaterParticleReach {
|
|
489
|
+
let before = links[2u * i];
|
|
490
|
+
if (before == ~0u) { return water_particle_reach_none(); }
|
|
491
|
+
return water_particle_reach_at(before);
|
|
498
492
|
}
|
|
499
|
-
`, [WATER_PARTICLE_STRUCT.declaration_chunk, chunk_folded_workgroup_index]) });
|
|
500
493
|
|
|
501
|
-
|
|
502
|
-
|
|
494
|
+
fn water_particle_starts_run(before: WaterParticleReach, cell: vec2<i32>) -> bool {
|
|
495
|
+
return !water_particle_reaches(before, cell);
|
|
496
|
+
}
|
|
497
|
+
`, [chunk_water_particle_reach_read]);
|
|
498
|
+
|
|
499
|
+
const links_resources = new ShaderResourceSetDescriptor();
|
|
500
|
+
links_resources.createGroup()
|
|
503
501
|
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT)
|
|
504
|
-
.addStorageBuffer('
|
|
505
|
-
.addStorageBuffer('
|
|
506
|
-
.addStorageBuffer('draw', WebGPUArray.u32, true);
|
|
502
|
+
.addStorageBuffer('links', WebGPUArray.u32, true)
|
|
503
|
+
.addStorageBuffer('reaches', WebGPUArray.u32, true);
|
|
507
504
|
|
|
508
505
|
/**
|
|
509
|
-
* Two words a particle: its
|
|
510
|
-
*
|
|
506
|
+
* Two words a particle: its front's neighbours β before and after it round the front (water_particle_linked), or none,
|
|
507
|
+
* from the particles {@link WATER_PARTICLE_LINK_REACH} places either side of it. A pair is linked from both ends or
|
|
508
|
+
* neither, so each draws the other's side of their meeting or neither does, and their windows sum to one. And three
|
|
509
|
+
* more: the cells its support overlaps, which every pass after reads rather than works out again.
|
|
511
510
|
*/
|
|
512
|
-
export const
|
|
511
|
+
export const shader_water_particles_links = ComputeShader.from({ label: 'Water / particles links', resources: links_resources,
|
|
513
512
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
514
513
|
@compute @workgroup_size(64)
|
|
515
514
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
516
515
|
let i = folded_workgroup_index(group) * 64u + lane;
|
|
517
516
|
let count = next.count;
|
|
518
517
|
if (i >= count) { return; }
|
|
519
|
-
let
|
|
520
|
-
|
|
521
|
-
var
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
518
|
+
let particle = next.elements[i];
|
|
519
|
+
var before = ~0u;
|
|
520
|
+
var after = ~0u;
|
|
521
|
+
let last = min(i + ${WATER_PARTICLE_LINK_REACH}u, count - 1u);
|
|
522
|
+
for (var j = max(i, ${WATER_PARTICLE_LINK_REACH}u) - ${WATER_PARTICLE_LINK_REACH}u; j <= last; j++) {
|
|
523
|
+
if (j == i) { continue; }
|
|
524
|
+
let other = next.elements[j];
|
|
525
|
+
if (water_particle_linked(other, particle)) { before = j; }
|
|
526
|
+
if (water_particle_linked(particle, other)) { after = j; }
|
|
527
|
+
}
|
|
528
|
+
links[2u * i] = before;
|
|
529
|
+
links[2u * i + 1u] = after;
|
|
530
|
+
let reach = water_particle_reach(particle);
|
|
531
|
+
reaches[3u * i] = bitcast<u32>(reach.first.x);
|
|
532
|
+
reaches[3u * i + 1u] = bitcast<u32>(reach.first.y);
|
|
533
|
+
reaches[3u * i + 2u] = reach.level | ((reach.span.x - 1u) << 8u) | ((reach.span.y - 1u) << 9u);
|
|
525
534
|
}
|
|
526
|
-
`, [
|
|
535
|
+
`, [chunk_water_particle_cells, chunk_folded_workgroup_index]) });
|
|
527
536
|
|
|
528
537
|
const weigh_resources = new ShaderResourceSetDescriptor();
|
|
529
538
|
weigh_resources.createGroup()
|
|
530
539
|
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT)
|
|
531
540
|
.addStorageBuffer('activity', WebGPUArray.u32)
|
|
532
541
|
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ)
|
|
533
|
-
.addStorageBuffer('
|
|
534
|
-
.addStorageBuffer('
|
|
542
|
+
.addStorageBuffer('links', WebGPUArray.u32)
|
|
543
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
544
|
+
.addStorageBuffer('bucket_crowd', WebGPUArray.from(atomic_u32), true)
|
|
535
545
|
.addStorageBuffer('body_bounds', WebGPUArray.from(atomic_u32), true);
|
|
536
546
|
|
|
537
547
|
/**
|
|
538
|
-
* Each
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
* entries. And its level to its body's: a point reads a cell of every level its body has.
|
|
548
|
+
* Each run of a body simulated this frame counted into every cell it is drawn in, by its steepness class
|
|
549
|
+
* ({@link WATER_PARTICLE_CROWD_WORDS}): how crowded the cell is, for the cut pass to keep its steepest by. And its level
|
|
550
|
+
* to its body's: a point reads a cell of every level its body has.
|
|
542
551
|
*/
|
|
543
552
|
export const shader_water_particles_weigh = ComputeShader.from({ label: 'Water / particles weigh', resources: weigh_resources,
|
|
544
553
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
@@ -546,40 +555,38 @@ export const shader_water_particles_weigh = ComputeShader.from({ label: 'Water /
|
|
|
546
555
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
547
556
|
let i = folded_workgroup_index(group) * 64u + lane;
|
|
548
557
|
if (i >= next.count) { return; }
|
|
549
|
-
let
|
|
550
|
-
if (drawn == 0u) { return; }
|
|
551
|
-
let particle = water_particle_drawn(next.elements[i], drawn);
|
|
558
|
+
let particle = next.elements[i];
|
|
552
559
|
if ((activity[particle.body] & ${WaterActivity.Active}u) == 0u) { return; }
|
|
553
|
-
let cells =
|
|
554
|
-
let
|
|
555
|
-
let
|
|
560
|
+
let cells = water_particle_reach_at(i);
|
|
561
|
+
let grade = water_particle_crowd_class(particle);
|
|
562
|
+
let before = water_particle_before(i);
|
|
556
563
|
atomicOr(&body_bounds[particle.body * ${STRIDE}u], 1u << cells.level);
|
|
557
564
|
for (var corner = 0u; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
558
565
|
if (!water_particle_has_corner(cells, corner)) { continue; }
|
|
559
|
-
let
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
atomicAdd(&
|
|
566
|
+
let cell = water_particle_corner_cell(cells, corner);
|
|
567
|
+
if (!water_particle_starts_run(before, cell)) { continue; }
|
|
568
|
+
let at = ${WORDS}u * water_particle_bucket(particle.body, cells.level, cell, offsets.count);
|
|
569
|
+
atomicAdd(&bucket_crowd[at + grade], 1u);
|
|
570
|
+
atomicAdd(&bucket_crowd[at + ${BINS}u], 1u);
|
|
564
571
|
}
|
|
565
572
|
}
|
|
566
|
-
`, [
|
|
573
|
+
`, [chunk_water_particle_run_start, chunk_folded_workgroup_index]) });
|
|
567
574
|
|
|
568
575
|
const nest_resources = new ShaderResourceSetDescriptor();
|
|
569
576
|
nest_resources.createGroup()
|
|
570
577
|
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT)
|
|
571
578
|
.addStorageBuffer('activity', WebGPUArray.u32)
|
|
572
579
|
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ)
|
|
573
|
-
.addStorageBuffer('
|
|
580
|
+
.addStorageBuffer('links', WebGPUArray.u32)
|
|
581
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
574
582
|
.addStorageBuffer('body_bounds', WebGPUArray.u32)
|
|
575
|
-
.addStorageBuffer('
|
|
583
|
+
.addStorageBuffer('bucket_crowd', WebGPUArray.u32)
|
|
576
584
|
.addStorageBuffer('bucket_nest', WebGPUArray.from(atomic_u32), true);
|
|
577
585
|
|
|
578
586
|
/**
|
|
579
|
-
* Each
|
|
580
|
-
* level and the coarser ones,
|
|
581
|
-
*
|
|
582
|
-
* among its cell's by its key (water_particle_crowd_bin), for the share pass to cap a crowd by.
|
|
587
|
+
* Each run of a body simulated this frame, its cells weighed: the runs a point of the cell it is drawn in reads at that
|
|
588
|
+
* level and the coarser ones, raised onto the cell over it at every coarser level its body has, so a cell knows the most
|
|
589
|
+
* its points read where finer levels hold runs under it.
|
|
583
590
|
*/
|
|
584
591
|
export const shader_water_particles_nest = ComputeShader.from({ label: 'Water / particles nest', resources: nest_resources,
|
|
585
592
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
@@ -587,115 +594,238 @@ export const shader_water_particles_nest = ComputeShader.from({ label: 'Water /
|
|
|
587
594
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
588
595
|
let i = folded_workgroup_index(group) * 64u + lane;
|
|
589
596
|
if (i >= next.count) { return; }
|
|
590
|
-
let
|
|
591
|
-
if (drawn == 0u) { return; }
|
|
592
|
-
let particle = water_particle_drawn(next.elements[i], drawn);
|
|
597
|
+
let particle = next.elements[i];
|
|
593
598
|
if ((activity[particle.body] & ${WaterActivity.Active}u) == 0u) { return; }
|
|
594
|
-
let cells =
|
|
595
|
-
let precedence = water_particle_precedence(particle);
|
|
599
|
+
let cells = water_particle_reach_at(i);
|
|
596
600
|
// its body's levels coarser than its own
|
|
597
601
|
let coarser = body_bounds[particle.body * ${STRIDE}u] & ~((2u << cells.level) - 1u);
|
|
602
|
+
let before = water_particle_before(i);
|
|
598
603
|
for (var corner = 0u; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
599
604
|
if (!water_particle_has_corner(cells, corner)) { continue; }
|
|
600
605
|
let cell = water_particle_corner_cell(cells, corner);
|
|
601
|
-
|
|
606
|
+
if (!water_particle_starts_run(before, cell)) { continue; }
|
|
602
607
|
// what a point of the cell reads at its level and the coarser ones
|
|
603
|
-
var
|
|
604
|
-
var steepness = bucket_weight[${WORDS}u * bucket];
|
|
608
|
+
var runs = bucket_crowd[${WORDS}u * water_particle_bucket(particle.body, cells.level, cell, offsets.count) + ${BINS}u];
|
|
605
609
|
var above = coarser;
|
|
606
610
|
for (var n = 0u; n < WATER_PARTICLE_LEVELS && above != 0u; n++) {
|
|
607
|
-
let over =
|
|
611
|
+
let over = water_particle_bucket_over(particle.body, cells.level, cell, firstTrailingBit(above), offsets.count);
|
|
608
612
|
above &= above - 1u;
|
|
609
|
-
|
|
610
|
-
steepness += bucket_weight[over];
|
|
613
|
+
runs += bucket_crowd[${WORDS}u * over + ${BINS}u];
|
|
611
614
|
}
|
|
612
615
|
// onto every cell over it
|
|
613
616
|
above = coarser;
|
|
614
617
|
for (var n = 0u; n < WATER_PARTICLE_LEVELS && above != 0u; n++) {
|
|
615
|
-
let over =
|
|
618
|
+
let over = water_particle_bucket_over(particle.body, cells.level, cell, firstTrailingBit(above), offsets.count);
|
|
616
619
|
above &= above - 1u;
|
|
617
|
-
atomicMax(&bucket_nest[over],
|
|
618
|
-
atomicMax(&bucket_nest[over + 1u], steepness);
|
|
620
|
+
atomicMax(&bucket_nest[${NEST}u * over], runs);
|
|
619
621
|
}
|
|
620
|
-
let octave = water_particle_crowd_bin(precedence, water_particle_crowd_reach(steepness));
|
|
621
|
-
if (octave < ${BINS}u) { atomicAdd(&bucket_nest[${NEST}u * bucket + 2u + octave], 1u); }
|
|
622
622
|
}
|
|
623
623
|
}
|
|
624
|
-
`, [
|
|
624
|
+
`, [chunk_water_particle_run_start, chunk_folded_workgroup_index]) });
|
|
625
625
|
|
|
626
|
-
const
|
|
627
|
-
|
|
626
|
+
const cut_resources = new ShaderResourceSetDescriptor();
|
|
627
|
+
cut_resources.createGroup()
|
|
628
628
|
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT)
|
|
629
629
|
.addStorageBuffer('activity', WebGPUArray.u32)
|
|
630
630
|
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ)
|
|
631
|
-
.addStorageBuffer('
|
|
632
|
-
.addStorageBuffer('
|
|
631
|
+
.addStorageBuffer('links', WebGPUArray.u32)
|
|
632
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
633
633
|
.addStorageBuffer('body_bounds', WebGPUArray.u32)
|
|
634
|
-
.addStorageBuffer('
|
|
635
|
-
.addStorageBuffer('
|
|
634
|
+
.addStorageBuffer('bucket_crowd', WebGPUArray.u32)
|
|
635
|
+
.addStorageBuffer('bucket_nest', WebGPUArray.from(atomic_u32), true);
|
|
636
636
|
|
|
637
637
|
/**
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
* than its own steepness's part: its keys' octaves dropped from the top until the rest fit
|
|
644
|
-
* ({@link WATER_PARTICLE_CROWD_BINS}). A cell only one front reaches does not count.
|
|
638
|
+
* How many steepness classes each cell's points keep, the steepest first: as many as keep its most crowded point within
|
|
639
|
+
* {@link WATER_PARTICLE_CROWD_KEEP} runs, over its own cell and the coarser ones over it, class by class, and what finer
|
|
640
|
+
* levels add under it (the nest pass's most, less what its own and coarser cells hold), counted as the steepest, since a
|
|
641
|
+
* run sits finer the younger it is; and as many as keep it within the gather budget. Never fewer than one. Each run
|
|
642
|
+
* lowers its cell's bucket to what it finds.
|
|
645
643
|
*/
|
|
646
|
-
export const
|
|
644
|
+
export const shader_water_particles_cut = ComputeShader.from({ label: 'Water / particles cut', resources: cut_resources,
|
|
647
645
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
646
|
+
// A cell's runs by steepness class, and in all: ${BINS} classes and the total, written out as vectors, so that a cell's
|
|
647
|
+
// words are read at once and a class is never a vector's component by a variable index (local memory).
|
|
648
|
+
struct WaterParticleCrowd {
|
|
649
|
+
low: vec4<u32>,
|
|
650
|
+
mid: vec4<u32>,
|
|
651
|
+
high: vec3<u32>,
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
fn water_particle_crowd_at(bucket: u32) -> WaterParticleCrowd {
|
|
655
|
+
let at = ${WORDS}u * bucket;
|
|
656
|
+
return WaterParticleCrowd(vec4<u32>(bucket_crowd[at], bucket_crowd[at + 1u], bucket_crowd[at + 2u], bucket_crowd[at + 3u]),
|
|
657
|
+
vec4<u32>(bucket_crowd[at + 4u], bucket_crowd[at + 5u], bucket_crowd[at + 6u], bucket_crowd[at + 7u]),
|
|
658
|
+
vec3<u32>(bucket_crowd[at + 8u], bucket_crowd[at + 9u], bucket_crowd[at + 10u]));
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
fn water_particle_crowd_add(a: WaterParticleCrowd, b: WaterParticleCrowd) -> WaterParticleCrowd {
|
|
662
|
+
return WaterParticleCrowd(a.low + b.low, a.mid + b.mid, a.high + b.high);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// The classes kept and drawn, the steepest first, once a point's reads take in a class: they only grow, so a class past
|
|
666
|
+
// the keep or the budget ends its count.
|
|
667
|
+
fn water_particle_crowd_step(reads: u32, grade: u32, classes: vec2<u32>) -> vec2<u32> {
|
|
668
|
+
return vec2<u32>(select(classes.x, grade + 1u, reads <= ${WATER_PARTICLE_CROWD_KEEP}u),
|
|
669
|
+
select(classes.y, grade + 1u, reads <= ${WATER_PARTICLE_GATHER_BUDGET}u));
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
fn water_particle_crowd_classes(finer: u32, crowd: WaterParticleCrowd) -> vec2<u32> {
|
|
673
|
+
var reads = finer;
|
|
674
|
+
var classes = vec2<u32>(1u);
|
|
675
|
+
reads += crowd.low.x; classes = water_particle_crowd_step(reads, 0u, classes);
|
|
676
|
+
reads += crowd.low.y; classes = water_particle_crowd_step(reads, 1u, classes);
|
|
677
|
+
reads += crowd.low.z; classes = water_particle_crowd_step(reads, 2u, classes);
|
|
678
|
+
reads += crowd.low.w; classes = water_particle_crowd_step(reads, 3u, classes);
|
|
679
|
+
reads += crowd.mid.x; classes = water_particle_crowd_step(reads, 4u, classes);
|
|
680
|
+
reads += crowd.mid.y; classes = water_particle_crowd_step(reads, 5u, classes);
|
|
681
|
+
reads += crowd.mid.z; classes = water_particle_crowd_step(reads, 6u, classes);
|
|
682
|
+
reads += crowd.mid.w; classes = water_particle_crowd_step(reads, 7u, classes);
|
|
683
|
+
reads += crowd.high.x; classes = water_particle_crowd_step(reads, 8u, classes);
|
|
684
|
+
reads += crowd.high.y; classes = water_particle_crowd_step(reads, 9u, classes);
|
|
685
|
+
return classes;
|
|
686
|
+
}
|
|
687
|
+
|
|
648
688
|
@compute @workgroup_size(64)
|
|
649
689
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
650
690
|
let i = folded_workgroup_index(group) * 64u + lane;
|
|
651
691
|
if (i >= next.count) { return; }
|
|
652
|
-
let
|
|
653
|
-
if (drawn == 0u) { return; }
|
|
654
|
-
let particle = water_particle_drawn(next.elements[i], drawn);
|
|
692
|
+
let particle = next.elements[i];
|
|
655
693
|
if ((activity[particle.body] & ${WaterActivity.Active}u) == 0u) { return; }
|
|
656
|
-
let cells =
|
|
657
|
-
let
|
|
658
|
-
let coarser =
|
|
659
|
-
|
|
694
|
+
let cells = water_particle_reach_at(i);
|
|
695
|
+
let levels = body_bounds[particle.body * ${STRIDE}u];
|
|
696
|
+
let coarser = levels & ~((2u << cells.level) - 1u);
|
|
697
|
+
let before = water_particle_before(i);
|
|
660
698
|
for (var corner = 0u; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
661
699
|
if (!water_particle_has_corner(cells, corner)) { continue; }
|
|
662
700
|
let cell = water_particle_corner_cell(cells, corner);
|
|
701
|
+
if (!water_particle_starts_run(before, cell)) { continue; }
|
|
663
702
|
let bucket = water_particle_bucket(particle.body, cells.level, cell, offsets.count);
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
if (bucket_weight[at + 1u] == ~bucket_weight[at + 2u]) { continue; }
|
|
667
|
-
// what a point of the cell reads at its level and the coarser ones
|
|
668
|
-
var entries = bucket_weight[at + 3u];
|
|
669
|
-
var steepness = bucket_weight[at];
|
|
703
|
+
// what its points read at its level and the coarser ones, by class and in all
|
|
704
|
+
var crowd = water_particle_crowd_at(bucket);
|
|
670
705
|
var above = coarser;
|
|
671
706
|
for (var n = 0u; n < WATER_PARTICLE_LEVELS && above != 0u; n++) {
|
|
672
|
-
let over =
|
|
707
|
+
let over = water_particle_bucket_over(particle.body, cells.level, cell, firstTrailingBit(above), offsets.count);
|
|
673
708
|
above &= above - 1u;
|
|
674
|
-
|
|
675
|
-
steepness += bucket_weight[over];
|
|
676
|
-
}
|
|
677
|
-
// and what its most crowded point reads
|
|
678
|
-
let crowd = max(entries, bucket_nest[nested]);
|
|
679
|
-
let total = max(steepness, bucket_nest[nested + 1u]);
|
|
680
|
-
if (crowd <= ${WATER_PARTICLE_CELL_SHARE}u || total == 0u) { continue; }
|
|
681
|
-
share = min(share, ${WATER_PARTICLE_CELL_SHARE}.0 * weight / f32(total));
|
|
682
|
-
if (crowd <= ${WATER_PARTICLE_GATHER_BUDGET}u) { continue; }
|
|
683
|
-
// no more kept than its part of the budget: the octaves of its keys dropped from the top until the rest fit
|
|
684
|
-
let quota = ${WATER_PARTICLE_GATHER_BUDGET}.0 * f32(bucket_weight[at]) / f32(total);
|
|
685
|
-
var below = 0u;
|
|
686
|
-
for (var octave = 0u; octave < ${BINS}u; octave++) { below += bucket_nest[nested + 2u + octave]; }
|
|
687
|
-
var reach = water_particle_crowd_reach(steepness);
|
|
688
|
-
for (var octave = 0u; octave < ${BINS}u && f32(below) > quota; octave++) {
|
|
689
|
-
below -= bucket_nest[nested + 2u + octave];
|
|
690
|
-
reach = select(0.0, 0.5 * reach, octave + 1u < ${BINS}u);
|
|
709
|
+
crowd = water_particle_crowd_add(crowd, water_particle_crowd_at(over));
|
|
691
710
|
}
|
|
692
|
-
//
|
|
693
|
-
|
|
711
|
+
// and, under the most crowded, what the finer ones add
|
|
712
|
+
let runs = crowd.high.z;
|
|
713
|
+
let most = max(atomicLoad(&bucket_nest[${NEST}u * bucket]), runs);
|
|
714
|
+
// within the crowd's keep, every class is kept and drawn: as the clear left it
|
|
715
|
+
if (most <= ${WATER_PARTICLE_CROWD_KEEP}u) { continue; }
|
|
716
|
+
let classes = water_particle_crowd_classes(most - runs, crowd);
|
|
717
|
+
atomicMin(&bucket_nest[${NEST}u * bucket + 1u], classes.x);
|
|
718
|
+
atomicMin(&bucket_nest[${NEST}u * bucket + 2u], classes.y);
|
|
694
719
|
}
|
|
695
|
-
// non-negative floats order as their bits do
|
|
696
|
-
atomicMin(&runs[2u * draw[2u * i] + 1u], bitcast<u32>(share));
|
|
697
720
|
}
|
|
698
|
-
`, [
|
|
721
|
+
`, [chunk_water_particle_run_start, chunk_folded_workgroup_index]) });
|
|
722
|
+
|
|
723
|
+
const mark_resources = new ShaderResourceSetDescriptor();
|
|
724
|
+
mark_resources.createGroup()
|
|
725
|
+
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT)
|
|
726
|
+
.addStorageBuffer('activity', WebGPUArray.u32)
|
|
727
|
+
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ)
|
|
728
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
729
|
+
.addStorageBuffer('bucket_nest', WebGPUArray.from(atomic_u32), true);
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Each particle of a body simulated this frame whose steepness class some cell it reaches cannot draw within the gather
|
|
733
|
+
* budget, or that an earlier frame cut, puts its front's tag in the frame's table of cut fronts: the first free slot of
|
|
734
|
+
* {@link WATER_PARTICLE_FRONT_PROBES} from its front's own, or the one already holding it. Its front's particles are
|
|
735
|
+
* alike and β apart, so every one of them then finds the tag, and the front is cut whole.
|
|
736
|
+
*/
|
|
737
|
+
export const shader_water_particles_mark = ComputeShader.from({ label: 'Water / particles mark', resources: mark_resources,
|
|
738
|
+
body: CodeChunk.from(/* language=WGSL */ `
|
|
739
|
+
@compute @workgroup_size(64)
|
|
740
|
+
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
741
|
+
let i = folded_workgroup_index(group) * 64u + lane;
|
|
742
|
+
if (i >= next.count) { return; }
|
|
743
|
+
let particle = next.elements[i];
|
|
744
|
+
if ((activity[particle.body] & ${WaterActivity.Active}u) == 0u) { return; }
|
|
745
|
+
let cells = water_particle_reach_at(i);
|
|
746
|
+
let grade = water_particle_crowd_class(particle);
|
|
747
|
+
var cut = particle.fading >= ${WATER_PARTICLE_CUT};
|
|
748
|
+
for (var corner = 0u; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
749
|
+
if (!water_particle_has_corner(cells, corner)) { continue; }
|
|
750
|
+
let at = ${NEST}u * water_particle_bucket(particle.body, cells.level, water_particle_corner_cell(cells, corner), offsets.count);
|
|
751
|
+
cut = cut || grade >= atomicLoad(&bucket_nest[at + 2u]);
|
|
752
|
+
}
|
|
753
|
+
if (!cut) { return; }
|
|
754
|
+
let hash = water_particle_front_hash(particle);
|
|
755
|
+
var probe = 0u;
|
|
756
|
+
// a weak exchange may fail with the slot still free: that slot again, twice the probes at most
|
|
757
|
+
for (var attempt = 0u; attempt < ${2 * WATER_PARTICLE_FRONT_PROBES}u && probe < ${WATER_PARTICLE_FRONT_PROBES}u; attempt++) {
|
|
758
|
+
let at = ${NEST}u * ((hash.y + probe) & (offsets.count - 1u)) + 3u;
|
|
759
|
+
let exchange = atomicCompareExchangeWeak(&bucket_nest[at], 0u, hash.x);
|
|
760
|
+
if (exchange.exchanged || exchange.old_value == hash.x) { break; }
|
|
761
|
+
if (exchange.old_value != 0u) { probe++; }
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
`, [chunk_water_particle_reach_read, chunk_folded_workgroup_index]) });
|
|
765
|
+
|
|
766
|
+
const keep_resources = new ShaderResourceSetDescriptor();
|
|
767
|
+
keep_resources.createGroup()
|
|
768
|
+
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT, true)
|
|
769
|
+
.addStorageBuffer('activity', WebGPUArray.u32)
|
|
770
|
+
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ)
|
|
771
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
772
|
+
.addStorageBuffer('bucket_nest', WebGPUArray.u32)
|
|
773
|
+
.addStorageBuffer('shown', WebGPUArray.u32, true);
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* What each crowd leaves of a body simulated this frame, particle by particle: a particle whose steepness class some
|
|
777
|
+
* cell it reaches does not keep is doomed, to fade from its next step and die (chunk_water_particle_step), and is drawn
|
|
778
|
+
* as it is until then; one whose front is in the frame's table of cut fronts (the mark pass) is not drawn, here or
|
|
779
|
+
* anywhere, and dies at its next step. So a crowd loses its flattest runs, and never by a lot or an order the particles
|
|
780
|
+
* arrive in. A frame that does not step decides the same again: nothing it reads has changed. One word a particle:
|
|
781
|
+
* whether it is drawn.
|
|
782
|
+
*/
|
|
783
|
+
export const shader_water_particles_keep = ComputeShader.from({ label: 'Water / particles keep', resources: keep_resources,
|
|
784
|
+
body: CodeChunk.from(/* language=WGSL */ `
|
|
785
|
+
@compute @workgroup_size(64)
|
|
786
|
+
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
787
|
+
let i = folded_workgroup_index(group) * 64u + lane;
|
|
788
|
+
if (i >= next.count) { return; }
|
|
789
|
+
let particle = next.elements[i];
|
|
790
|
+
if ((activity[particle.body] & ${WaterActivity.Active}u) == 0u) { shown[i] = 1u; return; }
|
|
791
|
+
let cells = water_particle_reach_at(i);
|
|
792
|
+
let grade = water_particle_crowd_class(particle);
|
|
793
|
+
var doomed = false;
|
|
794
|
+
var cut = particle.fading >= ${WATER_PARTICLE_CUT};
|
|
795
|
+
for (var corner = 0u; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
796
|
+
if (!water_particle_has_corner(cells, corner)) { continue; }
|
|
797
|
+
let at = ${NEST}u * water_particle_bucket(particle.body, cells.level, water_particle_corner_cell(cells, corner), offsets.count);
|
|
798
|
+
doomed = doomed || grade >= bucket_nest[at + 1u];
|
|
799
|
+
cut = cut || grade >= bucket_nest[at + 2u];
|
|
800
|
+
}
|
|
801
|
+
// its front cut where another of its particles is
|
|
802
|
+
let hash = water_particle_front_hash(particle);
|
|
803
|
+
for (var probe = 0u; probe < ${WATER_PARTICLE_FRONT_PROBES}u && !cut; probe++) {
|
|
804
|
+
let tag = bucket_nest[${NEST}u * ((hash.y + probe) & (offsets.count - 1u)) + 3u];
|
|
805
|
+
if (tag == 0u) { break; }
|
|
806
|
+
cut = tag == hash.x;
|
|
807
|
+
}
|
|
808
|
+
shown[i] = select(1u, 0u, cut);
|
|
809
|
+
if (cut) {
|
|
810
|
+
next.elements[i].fading = ${WATER_PARTICLE_CUT};
|
|
811
|
+
} else if (doomed && particle.fading == 0.0) {
|
|
812
|
+
// a tenth of its steepness now, where a fading packet dies
|
|
813
|
+
next.elements[i].fading = 0.1 * abs(particle.amplitude) / max(particle.radius, 1e-30);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
`, [chunk_water_particle_reach_read, chunk_folded_workgroup_index]) });
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* The cells the support of a drawn particle's drawn neighbour before it overlaps, or none: a drawn particle begins a
|
|
820
|
+
* run of its front in a cell its support overlaps where these do not. Bindings: `links`, `reaches`, `shown`.
|
|
821
|
+
*/
|
|
822
|
+
const chunk_water_particle_drawn_run_start = CodeChunk.from(/* language=WGSL */ `
|
|
823
|
+
fn water_particle_drawn_before(i: u32) -> WaterParticleReach {
|
|
824
|
+
let before = links[2u * i];
|
|
825
|
+
if (before == ~0u || shown[before] == 0u) { return water_particle_reach_none(); }
|
|
826
|
+
return water_particle_reach_at(before);
|
|
827
|
+
}
|
|
828
|
+
`, [chunk_water_particle_reach_read]);
|
|
699
829
|
|
|
700
830
|
const bin_resources = new ShaderResourceSetDescriptor();
|
|
701
831
|
bin_resources.createGroup()
|
|
@@ -705,19 +835,15 @@ bin_resources.createGroup()
|
|
|
705
835
|
.addStorageBuffer('slots', WebGPUArray.u32, true)
|
|
706
836
|
.addStorageBuffer('bucket_amplitude', WebGPUArray.from(atomic_u32), true)
|
|
707
837
|
.addStorageBuffer('body_bounds', WebGPUArray.from(atomic_u32), true)
|
|
708
|
-
.addStorageBuffer('
|
|
709
|
-
.addStorageBuffer('
|
|
710
|
-
.addStorageBuffer('
|
|
838
|
+
.addStorageBuffer('links', WebGPUArray.u32)
|
|
839
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
840
|
+
.addStorageBuffer('shown', WebGPUArray.u32);
|
|
711
841
|
|
|
712
842
|
/**
|
|
713
|
-
* Enter each
|
|
714
|
-
* cell's bucket, keep its slot there, raise the bucket's largest |a| to its own, and mark its level on its body. One
|
|
715
|
-
* atomic each: no compare-exchange loop a crowded bucket would serialize. A paused body's particles take no slot
|
|
716
|
-
* nothing draws them this frame.
|
|
717
|
-
*
|
|
718
|
-
* Where cells are crowded (the share pass), a run is drawn by the share its most crowded cell leaves it and its
|
|
719
|
-
* front's lot: one decision for all its packets and all their cells, so neither a front nor the surface across cells
|
|
720
|
-
* is holed, and it does not change with the order particles arrive in. One left out takes no slot.
|
|
843
|
+
* Enter each drawn run of a body simulated this frame in every cell it is drawn in, by its first particle: count it into
|
|
844
|
+
* the cell's bucket, keep its slot there, raise the bucket's largest |a| to its own, and mark its level on its body. One
|
|
845
|
+
* atomic each: no compare-exchange loop a crowded bucket would serialize. A paused body's particles take no slot, nor
|
|
846
|
+
* does one its crowd does not draw: nothing draws them this frame.
|
|
721
847
|
*/
|
|
722
848
|
export const shader_water_particles_bin = ComputeShader.from({ label: 'Water / particles bin', resources: bin_resources,
|
|
723
849
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
@@ -729,73 +855,93 @@ fn water_particle_unslotted(i: u32) {
|
|
|
729
855
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
730
856
|
let i = folded_workgroup_index(group) * 64u + lane;
|
|
731
857
|
if (i >= next.count) { return; }
|
|
732
|
-
let
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
let
|
|
736
|
-
let visibility = water_particle_visibility(bitcast<f32>(runs[2u * draw[2u * i] + 1u]), water_particle_lot(particle));
|
|
737
|
-
visible[i] = visibility;
|
|
738
|
-
if (!(visibility > 0.0)) { water_particle_unslotted(i); return; }
|
|
858
|
+
let particle = next.elements[i];
|
|
859
|
+
if ((activity[particle.body] & ${WaterActivity.Active}u) == 0u || shown[i] == 0u) { water_particle_unslotted(i); return; }
|
|
860
|
+
let cells = water_particle_reach_at(i);
|
|
861
|
+
let before = water_particle_drawn_before(i);
|
|
739
862
|
atomicOr(&body_bounds[particle.body * ${STRIDE}u], 1u << cells.level);
|
|
740
863
|
for (var corner = 0u; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
741
864
|
var slot = ~0u;
|
|
742
865
|
if (water_particle_has_corner(cells, corner)) {
|
|
743
|
-
let
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
866
|
+
let cell = water_particle_corner_cell(cells, corner);
|
|
867
|
+
if (!water_particle_reaches(before, cell)) {
|
|
868
|
+
let bucket = water_particle_bucket(particle.body, cells.level, cell, buckets.count);
|
|
869
|
+
slot = atomicAdd(&buckets.elements[bucket], 1u);
|
|
870
|
+
// non-negative floats order as their bits do
|
|
871
|
+
atomicMax(&bucket_amplitude[bucket], bitcast<u32>(abs(particle.amplitude)));
|
|
872
|
+
}
|
|
747
873
|
}
|
|
748
874
|
slots[i * ${WATER_PARTICLE_CORNERS}u + corner] = slot;
|
|
749
875
|
}
|
|
750
876
|
}
|
|
751
|
-
`, [
|
|
877
|
+
`, [chunk_water_particle_drawn_run_start, chunk_folded_workgroup_index]) });
|
|
752
878
|
|
|
753
879
|
const scatter_resources = new ShaderResourceSetDescriptor();
|
|
754
880
|
scatter_resources.createGroup()
|
|
755
881
|
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT)
|
|
756
882
|
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ)
|
|
757
883
|
.addStorageBuffer('slots', WebGPUArray.u32)
|
|
758
|
-
.addStorageBuffer('
|
|
759
|
-
.addStorageBuffer('
|
|
884
|
+
.addStorageBuffer('links', WebGPUArray.u32)
|
|
885
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
886
|
+
.addStorageBuffer('shown', WebGPUArray.u32)
|
|
760
887
|
.addStorageBuffer('staging', WebGPUArray.from(WATER_PARTICLE_ENTRY_STRUCT), true);
|
|
761
888
|
|
|
762
|
-
/**
|
|
889
|
+
/**
|
|
890
|
+
* Each run's entry to its bucket's range, in arrival order; the rank pass orders it. A run is its first particle and its
|
|
891
|
+
* front's drawn neighbours after it, one by one, while their supports overlap the cell, at most
|
|
892
|
+
* {@link WATER_PARTICLE_RUN_LIMIT}; an end is open where the front has no drawn neighbour beyond it.
|
|
893
|
+
*/
|
|
763
894
|
export const shader_water_particles_scatter = ComputeShader.from({ label: 'Water / particles scatter', resources: scatter_resources,
|
|
764
895
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
765
896
|
@compute @workgroup_size(64)
|
|
766
897
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
767
898
|
let i = folded_workgroup_index(group) * 64u + lane;
|
|
768
899
|
if (i >= next.count) { return; }
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
let
|
|
773
|
-
|
|
900
|
+
var first = 0u;
|
|
901
|
+
for (; first < ${WATER_PARTICLE_CORNERS}u && slots[i * ${WATER_PARTICLE_CORNERS}u + first] == ~0u; first++) {}
|
|
902
|
+
if (first == ${WATER_PARTICLE_CORNERS}u) { return; }
|
|
903
|
+
let particle = next.elements[i];
|
|
904
|
+
let cells = water_particle_reach_at(i);
|
|
905
|
+
let lone = water_particle_entry(particle, i, cells);
|
|
906
|
+
let before = links[2u * i];
|
|
907
|
+
let open_start = select(0u, WATER_PARTICLE_RUN_OPEN_START, before == ~0u || shown[before] == 0u);
|
|
908
|
+
for (var corner = first; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
774
909
|
let slot = slots[i * ${WATER_PARTICLE_CORNERS}u + corner];
|
|
775
910
|
if (slot == ~0u) { continue; }
|
|
776
911
|
let cell = water_particle_corner_cell(cells, corner);
|
|
777
912
|
let bucket = water_particle_bucket(particle.body, cells.level, cell, offsets.count);
|
|
778
913
|
var start = 0u;
|
|
779
914
|
if (bucket > 0u) { start = offsets.elements[bucket - 1u]; }
|
|
780
|
-
|
|
915
|
+
// the run, as far as its drawn neighbours reach the cell
|
|
916
|
+
var members = 1u;
|
|
917
|
+
var open_end = 0u;
|
|
918
|
+
var at = i;
|
|
919
|
+
for (; members < ${WATER_PARTICLE_RUN_LIMIT}u; members++) {
|
|
920
|
+
let after = links[2u * at + 1u];
|
|
921
|
+
if (after == ~0u) { open_end = WATER_PARTICLE_RUN_OPEN_END; break; }
|
|
922
|
+
if (shown[after] == 0u) { open_end = WATER_PARTICLE_RUN_OPEN_END; break; }
|
|
923
|
+
if (!water_particle_reaches(water_particle_reach_at(after), cell)) { break; }
|
|
924
|
+
at = after;
|
|
925
|
+
}
|
|
926
|
+
var entry = lone;
|
|
781
927
|
entry.cell = cell;
|
|
782
|
-
entry.
|
|
928
|
+
entry.run = members | open_start | open_end;
|
|
783
929
|
staging[start + slot] = entry;
|
|
784
930
|
}
|
|
785
931
|
}
|
|
786
|
-
`, [
|
|
932
|
+
`, [chunk_water_particle_reach_read, chunk_folded_workgroup_index]) });
|
|
787
933
|
|
|
788
934
|
const rank_resources = new ShaderResourceSetDescriptor();
|
|
789
935
|
rank_resources.createGroup()
|
|
790
936
|
.addStorageBuffer('next', WATER_PARTICLE_ARRAY_STRUCT)
|
|
791
937
|
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ)
|
|
792
938
|
.addStorageBuffer('slots', WebGPUArray.u32)
|
|
793
|
-
.addStorageBuffer('
|
|
939
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
794
940
|
.addStorageBuffer('staging', WebGPUArray.from(WATER_PARTICLE_ENTRY_STRUCT))
|
|
795
941
|
.addStorageBuffer('entries', WebGPUArray.from(WATER_PARTICLE_ENTRY_STRUCT), true);
|
|
796
942
|
|
|
797
943
|
/**
|
|
798
|
-
* Order every bucket by
|
|
944
|
+
* Order every bucket by its runs' first particles then cells, so a gather that runs out of budget drops the same
|
|
799
945
|
* entries every frame (the particle array keeps its order from frame to frame). A bucket of more than
|
|
800
946
|
* ${WATER_PARTICLE_RANK_LIMIT} keeps its arrival order: a fixed budget of comparisons per entry.
|
|
801
947
|
*/
|
|
@@ -811,14 +957,12 @@ fn water_entry_before(a: ${WATER_PARTICLE_ENTRY_STRUCT.wgsl_ref}, b: ${WATER_PAR
|
|
|
811
957
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
812
958
|
let i = folded_workgroup_index(group) * 64u + lane;
|
|
813
959
|
if (i >= next.count) { return; }
|
|
814
|
-
let
|
|
815
|
-
|
|
816
|
-
let particle = water_particle_drawn(next.elements[i], drawn);
|
|
817
|
-
let cells = water_particle_cells(particle, i);
|
|
960
|
+
let cells = water_particle_reach_at(i);
|
|
961
|
+
let body = next.elements[i].body;
|
|
818
962
|
for (var corner = 0u; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
819
963
|
let slot = slots[i * ${WATER_PARTICLE_CORNERS}u + corner];
|
|
820
964
|
if (slot == ~0u) { continue; }
|
|
821
|
-
let bucket = water_particle_bucket(
|
|
965
|
+
let bucket = water_particle_bucket(body, cells.level, water_particle_corner_cell(cells, corner), offsets.count);
|
|
822
966
|
var start = 0u;
|
|
823
967
|
if (bucket > 0u) { start = offsets.elements[bucket - 1u]; }
|
|
824
968
|
let end = offsets.elements[bucket];
|
|
@@ -831,7 +975,7 @@ fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) la
|
|
|
831
975
|
entries[start + rank] = entry;
|
|
832
976
|
}
|
|
833
977
|
}
|
|
834
|
-
`, [
|
|
978
|
+
`, [chunk_water_particle_reach_read, chunk_folded_workgroup_index]) });
|
|
835
979
|
|
|
836
980
|
const bounds_resources = new ShaderResourceSetDescriptor();
|
|
837
981
|
bounds_resources.createGroup()
|
|
@@ -839,14 +983,14 @@ bounds_resources.createGroup()
|
|
|
839
983
|
.addStorageBuffer('activity', WebGPUArray.u32)
|
|
840
984
|
.addStorageBuffer('offsets', COLLECTION_U32_STRUCT_READ)
|
|
841
985
|
.addStorageBuffer('bucket_amplitude', WebGPUArray.u32)
|
|
842
|
-
.addStorageBuffer('
|
|
986
|
+
.addStorageBuffer('slots', WebGPUArray.u32)
|
|
987
|
+
.addStorageBuffer('reaches', WebGPUArray.u32)
|
|
843
988
|
.addStorageBuffer('body_bounds', WebGPUArray.from(atomic_u32), true);
|
|
844
989
|
|
|
845
990
|
/**
|
|
846
|
-
* Each body's bound per level: the largest of count × max |a| over the buckets its
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
* adds to it.
|
|
991
|
+
* Each body's bound per level: the largest of count × max |a| over the buckets its runs are in, which is at least the
|
|
992
|
+
* bucket's Σ|a|. Every run that reaches a point is in that point's cell's bucket, and neither its height nor its
|
|
993
|
+
* horizontal displacement exceeds |a|, so the bound holds everywhere; a hash collision only adds to it.
|
|
850
994
|
*/
|
|
851
995
|
export const shader_water_particles_bounds = ComputeShader.from({ label: 'Water / particles bounds', resources: bounds_resources,
|
|
852
996
|
body: CodeChunk.from(/* language=WGSL */ `
|
|
@@ -854,15 +998,13 @@ export const shader_water_particles_bounds = ComputeShader.from({ label: 'Water
|
|
|
854
998
|
fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) lane: u32) {
|
|
855
999
|
let i = folded_workgroup_index(group) * 64u + lane;
|
|
856
1000
|
if (i >= next.count) { return; }
|
|
857
|
-
let
|
|
858
|
-
if (drawn == 0u) { return; }
|
|
859
|
-
let particle = water_particle_drawn(next.elements[i], drawn);
|
|
1001
|
+
let particle = next.elements[i];
|
|
860
1002
|
// a paused body keeps the bounds its particles last had
|
|
861
1003
|
if ((activity[particle.body] & ${WaterActivity.Active}u) == 0u) { return; }
|
|
862
|
-
let cells =
|
|
1004
|
+
let cells = water_particle_reach_at(i);
|
|
863
1005
|
var largest = 0.0;
|
|
864
1006
|
for (var corner = 0u; corner < ${WATER_PARTICLE_CORNERS}u; corner++) {
|
|
865
|
-
if (
|
|
1007
|
+
if (slots[i * ${WATER_PARTICLE_CORNERS}u + corner] == ~0u) { continue; }
|
|
866
1008
|
let bucket = water_particle_bucket(particle.body, cells.level, water_particle_corner_cell(cells, corner), offsets.count);
|
|
867
1009
|
var start = 0u;
|
|
868
1010
|
if (bucket > 0u) { start = offsets.elements[bucket - 1u]; }
|
|
@@ -871,4 +1013,4 @@ fn main(@builtin(workgroup_id) group: vec3u, @builtin(local_invocation_index) la
|
|
|
871
1013
|
// non-negative floats order as their bits do
|
|
872
1014
|
atomicMax(&body_bounds[particle.body * ${STRIDE}u + 1u + cells.level], bitcast<u32>(largest));
|
|
873
1015
|
}
|
|
874
|
-
`, [
|
|
1016
|
+
`, [chunk_water_particle_reach_read, chunk_folded_workgroup_index]) });
|