@woosh/meep-engine 3.60.9 → 3.61.1
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/CHANGELOG.md +90 -0
- package/package.json +1 -1
- package/src/engine/ecs/EntityManager.d.ts +3 -3
- package/src/engine/ecs/EntityManager.js +3 -3
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +4 -0
- package/src/engine/graphics/ecs/decal/v2/Decal.d.ts +46 -2
- package/src/engine/graphics/ecs/decal/v2/Decal.d.ts.map +1 -1
- package/src/engine/graphics/ecs/decal/v2/Decal.js +74 -5
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.d.ts.map +1 -1
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +13 -1
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_1_2.d.ts +9 -0
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_1_2.d.ts.map +1 -0
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_1_2.js +37 -0
- package/src/engine/graphics3/DecalSystem.d.ts.map +1 -1
- package/src/engine/graphics3/DecalSystem.js +462 -447
- package/src/engine/graphics3/decal/DECAL_GPU_RECORD.d.ts +14 -0
- package/src/engine/graphics3/decal/DECAL_GPU_RECORD.d.ts.map +1 -1
- package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +53 -13
- package/src/engine/graphics3/decal/chunk_decal_record.d.ts +6 -1
- package/src/engine/graphics3/decal/chunk_decal_record.d.ts.map +1 -1
- package/src/engine/graphics3/decal/chunk_decal_record.js +28 -9
- package/src/engine/graphics3/decal/decal_gpu_records.d.ts +2 -1
- package/src/engine/graphics3/decal/decal_gpu_records.d.ts.map +1 -1
- package/src/engine/graphics3/decal/decal_gpu_records.js +26 -5
- package/src/engine/graphics3/decal/decal_material_samplers.d.ts +6 -0
- package/src/engine/graphics3/decal/decal_material_samplers.d.ts.map +1 -1
- package/src/engine/graphics3/decal/decal_material_samplers.js +13 -0
- package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +1 -1
- package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +1 -1
- package/src/engine/graphics3/decal/shader_decal_composite.d.ts +5 -0
- package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
- package/src/engine/graphics3/decal/shader_decal_composite.js +69 -28
- package/src/engine/network/NetworkSession.d.ts +3 -0
- package/src/engine/network/NetworkSession.d.ts.map +1 -1
- package/src/engine/network/NetworkSession.js +68 -10
- package/src/engine/network/README.md +6 -3
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +51 -5
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +308 -34
- package/src/engine/network/replication/Replicator.d.ts +8 -2
- package/src/engine/network/replication/Replicator.d.ts.map +1 -1
- package/src/engine/network/replication/Replicator.js +13 -4
- package/src/engine/network/sim/ActionLog.d.ts +16 -2
- package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
- package/src/engine/network/sim/ActionLog.js +33 -2
- package/src/engine/network/sim/RewindEngine.d.ts +9 -0
- package/src/engine/network/sim/RewindEngine.d.ts.map +1 -1
- package/src/engine/network/sim/RewindEngine.js +15 -1
- package/src/engine/network/state/FrameBufferRing.d.ts +12 -5
- package/src/engine/network/state/FrameBufferRing.d.ts.map +1 -1
- package/src/engine/network/state/FrameBufferRing.js +32 -6
- package/src/engine/network/time/TimeDilation.d.ts +30 -14
- package/src/engine/network/time/TimeDilation.d.ts.map +1 -1
- package/src/engine/network/time/TimeDilation.js +47 -17
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/generation/foliage/skeleton/ShootWoodHash.d.ts +3 -2
- package/src/generation/foliage/skeleton/ShootWoodHash.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/ShootWoodHash.js +25 -2
- package/src/shade/renderer/buffer/graph_read_buffer.d.ts +3 -1
- package/src/shade/renderer/buffer/graph_read_buffer.d.ts.map +1 -1
- package/src/shade/renderer/buffer/graph_read_buffer.js +9 -6
- package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.d.ts +5 -0
- package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.d.ts.map +1 -1
- package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.js +48 -43
- package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
- package/src/shade/renderer/material/GPUMaterialManager.js +117 -35
- package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts +2 -2
- package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts.map +1 -1
- package/src/shade/renderer/material/standard/fragment_gbuffer.js +10 -3
- package/src/shade/renderer/material/standard/material_gbuffer_vertex.d.ts +15 -3
- package/src/shade/renderer/material/standard/material_gbuffer_vertex.d.ts.map +1 -1
- package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +25 -4
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts +23 -5
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +294 -20
- package/src/shade/renderer/particles/resize/shader_particle_shrink_alive_list.d.ts +21 -0
- package/src/shade/renderer/particles/resize/shader_particle_shrink_alive_list.d.ts.map +1 -0
- package/src/shade/renderer/particles/resize/shader_particle_shrink_alive_list.js +83 -0
- package/src/shade/renderer/particles/resize/shader_particle_shrink_free_list.d.ts +21 -0
- package/src/shade/renderer/particles/resize/shader_particle_shrink_free_list.d.ts.map +1 -0
- package/src/shade/renderer/particles/resize/shader_particle_shrink_free_list.js +64 -0
- package/src/shade/renderer/restir/di/chunk_restir_resolve.d.ts.map +1 -1
- package/src/shade/renderer/restir/di/chunk_restir_resolve.js +91 -94
- package/src/shade/renderer/restir/reservoir/chunk_rtxdi_stream_sample.js +1 -1
- package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_decode.d.ts +14 -0
- package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_decode.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_decode.js +36 -21
- package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_sample_gather.d.ts +22 -0
- package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_sample_gather.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/color/rgbe9995/chunk_rgbe9995_sample_gather.js +64 -0
- package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_vertex_normal.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_vertex_normal.js +13 -18
- package/src/shade/renderer/shader/chunk/geometry/normal/chunk_encode_vertex_normal.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/normal/chunk_encode_vertex_normal.js +28 -30
- package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_u32_to_vec2.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_u32_to_vec2.js +15 -16
- package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_vec2_to_u32.d.ts +0 -1
- package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_vec2_to_u32.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/vec2/chunk_unit_vec2_to_u32.js +15 -18
- package/src/shade/renderer/shader/chunk/subgroup/chunk_subgroup_bitset_or.d.ts +32 -0
- package/src/shade/renderer/shader/chunk/subgroup/chunk_subgroup_bitset_or.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/subgroup/chunk_subgroup_bitset_or.js +73 -0
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +8 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +7 -3
- package/src/shade/renderer/global_illumination/brick4/cpu/brick4_sh3_color_encode.d.ts +0 -2
- package/src/shade/renderer/global_illumination/brick4/cpu/brick4_sh3_color_encode.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/brick4/cpu/brick4_sh3_color_encode.js +0 -6
|
@@ -35,6 +35,17 @@ import { WARMUP_QUEUE } from "./warmup/shader_particle_warmup.js";
|
|
|
35
35
|
import { warmup_schedule } from "./warmup/warmup_schedule.js";
|
|
36
36
|
import { GPUTextureContext } from "../texture/GPUTextureContext.js";
|
|
37
37
|
import { inspect_gpu_buffer } from "../buffer/inspect_gpu_buffer.js";
|
|
38
|
+
import { shader_buffer_copy_storage_to_storage } from "../buffer/util/shader_buffer_copy_storage_to_storage.js";
|
|
39
|
+
import { COMPUTE_SHADER_DEFAULT_GROUP_SIZE } from "../shader/COMPUTE_SHADER_DEFAULT_GROUP_SIZE.js";
|
|
40
|
+
import {
|
|
41
|
+
SLOT_STACK_PUSH_RANGE_WORKGROUP_SIZE,
|
|
42
|
+
shader_slot_stack_push_range,
|
|
43
|
+
} from "../gpu_primitive/slot_stack/shader_slot_stack_push_range.js";
|
|
44
|
+
import {
|
|
45
|
+
PARTICLE_SHRINK_WORKGROUP_SIZE,
|
|
46
|
+
shader_particle_shrink_free_list,
|
|
47
|
+
} from "./resize/shader_particle_shrink_free_list.js";
|
|
48
|
+
import { shader_particle_shrink_alive_list } from "./resize/shader_particle_shrink_alive_list.js";
|
|
38
49
|
|
|
39
50
|
const WORD = Uint32Array.BYTES_PER_ELEMENT;
|
|
40
51
|
|
|
@@ -162,6 +173,17 @@ const DEFAULT_CAPACITY = 65536;
|
|
|
162
173
|
* and a set of accumulators that are sums — so the particles need no depth ordering at all, and
|
|
163
174
|
* none is ever built.
|
|
164
175
|
*
|
|
176
|
+
* ## Capacity
|
|
177
|
+
*
|
|
178
|
+
* {@link capacity} may be set at any time and takes effect on the next {@link execute}, which is
|
|
179
|
+
* where every buffer it sizes is replaced — never while a recorded graph may still read the old
|
|
180
|
+
* ones. Growing keeps every live particle: the pool and the free list are copied into the larger
|
|
181
|
+
* buffers and the new slots pushed onto the free list. Shrinking keeps every live particle the
|
|
182
|
+
* smaller pool can hold: a particle in a slot past the new capacity moves into a free slot below
|
|
183
|
+
* it, and when there are more particles than slots, the excess is dropped. A particle that moves
|
|
184
|
+
* reads a new `PARTICLE_INDEX`, and the frame that applies a shrink rebuilds the simulation order
|
|
185
|
+
* from the moved list before it simulates.
|
|
186
|
+
*
|
|
165
187
|
* ## Allocation
|
|
166
188
|
*
|
|
167
189
|
* The per-frame path allocates the frame graph's records and nothing else: emitter rows are
|
|
@@ -185,10 +207,31 @@ export class GPUParticleSystem {
|
|
|
185
207
|
scene_context;
|
|
186
208
|
|
|
187
209
|
/**
|
|
188
|
-
* Maximum number of simultaneously live particles.
|
|
210
|
+
* Maximum number of simultaneously live particles, as last set. See {@link capacity}.
|
|
211
|
+
* @type {number}
|
|
212
|
+
*/
|
|
213
|
+
#capacity;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The capacity the pool's buffers are built for: {@link #capacity} as of the last
|
|
217
|
+
* {@link execute}, which is where a new one is applied.
|
|
189
218
|
* @type {number}
|
|
190
219
|
*/
|
|
191
|
-
|
|
220
|
+
#pool_capacity;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Set by a shrink, which moves particles and so leaves the simulation order naming slots that
|
|
224
|
+
* are gone: the frame it is applied in rebuilds the order from the moved list, as for a program
|
|
225
|
+
* edit.
|
|
226
|
+
* @type {boolean}
|
|
227
|
+
*/
|
|
228
|
+
#rebucket_pending = false;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The new slots a grow pushes onto the free list. Reused.
|
|
232
|
+
* @type {{first: number, count: number, counter: number}}
|
|
233
|
+
*/
|
|
234
|
+
#push_settings = { first: 0, count: 0, counter: PARTICLE_COUNTER.DEAD };
|
|
192
235
|
|
|
193
236
|
/**
|
|
194
237
|
* @type {EmitterRegistry}
|
|
@@ -367,7 +410,8 @@ export class GPUParticleSystem {
|
|
|
367
410
|
|
|
368
411
|
this.graphics = graphics;
|
|
369
412
|
this.scene_context = scene_context;
|
|
370
|
-
this
|
|
413
|
+
this.#capacity = capacity;
|
|
414
|
+
this.#pool_capacity = capacity;
|
|
371
415
|
|
|
372
416
|
this.registry = new EmitterRegistry(graphics.device);
|
|
373
417
|
|
|
@@ -378,6 +422,26 @@ export class GPUParticleSystem {
|
|
|
378
422
|
this.#create_default_atlas();
|
|
379
423
|
}
|
|
380
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Maximum number of simultaneously live particles.
|
|
427
|
+
* @returns {number}
|
|
428
|
+
*/
|
|
429
|
+
get capacity() {
|
|
430
|
+
return this.#capacity;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Raise or lower the number of particles the pool holds. Applied by the next {@link execute};
|
|
435
|
+
* see the class doc for what happens to the particles already live.
|
|
436
|
+
*
|
|
437
|
+
* @param {number} value
|
|
438
|
+
*/
|
|
439
|
+
set capacity(value) {
|
|
440
|
+
assert.isPositiveInteger(value, "capacity");
|
|
441
|
+
|
|
442
|
+
this.#capacity = value;
|
|
443
|
+
}
|
|
444
|
+
|
|
381
445
|
/**
|
|
382
446
|
* The buffer the emitter table lives in — what every emitter-reading pass binds.
|
|
383
447
|
* @returns {GPUBuffer}
|
|
@@ -493,8 +557,10 @@ export class GPUParticleSystem {
|
|
|
493
557
|
frame.index = this.frame;
|
|
494
558
|
frame.max_catchup = this.max_catchup_seconds;
|
|
495
559
|
frame.spawn_command_count = this.#spawn_command_count;
|
|
496
|
-
frame.rebucket = this.#
|
|
560
|
+
frame.rebucket = this.#rebucket_pending
|
|
561
|
+
|| this.#program_assignment_version !== this.registry.program_assignment_version;
|
|
497
562
|
this.#program_assignment_version = this.registry.program_assignment_version;
|
|
563
|
+
this.#rebucket_pending = false;
|
|
498
564
|
|
|
499
565
|
const table = this.registry.table;
|
|
500
566
|
const emitters = this.#emitters;
|
|
@@ -524,7 +590,7 @@ export class GPUParticleSystem {
|
|
|
524
590
|
wind: this.scene_context.wind,
|
|
525
591
|
gpu: buffers,
|
|
526
592
|
emitters,
|
|
527
|
-
capacity: this
|
|
593
|
+
capacity: this.#pool_capacity,
|
|
528
594
|
bucket_count: this.bucketCount(),
|
|
529
595
|
// Whose register file the VM runs on is decided here, once, for the whole system: the
|
|
530
596
|
// registry reports what the hungriest registered effect needs and this compares it
|
|
@@ -709,14 +775,17 @@ export class GPUParticleSystem {
|
|
|
709
775
|
}
|
|
710
776
|
|
|
711
777
|
/**
|
|
712
|
-
* Push everything the CPU changed to the GPU: dirty emitter and binding
|
|
713
|
-
* growth, the program buffer rebuilt or its changed field span, the
|
|
714
|
-
* coherence scratch's growth. A command context is opened only if
|
|
778
|
+
* Push everything the CPU changed to the GPU: a new pool capacity, dirty emitter and binding
|
|
779
|
+
* rows, the state buffer's growth, the program buffer rebuilt or its changed field span, the
|
|
780
|
+
* spawn-command ring, and the coherence scratch's growth. A command context is opened only if
|
|
781
|
+
* something needs one.
|
|
715
782
|
*/
|
|
716
783
|
#upload() {
|
|
717
784
|
const registry = this.registry;
|
|
718
785
|
const device = this.graphics.device;
|
|
719
786
|
|
|
787
|
+
this.#apply_capacity();
|
|
788
|
+
|
|
720
789
|
registry.flush();
|
|
721
790
|
|
|
722
791
|
// All three tables stage into the one database: the emitter rows the author changed, the
|
|
@@ -850,7 +919,7 @@ export class GPUParticleSystem {
|
|
|
850
919
|
device.queue.writeBuffer(this.buffers.warmup_queue, 0, words.buffer, words.byteOffset, bytes);
|
|
851
920
|
|
|
852
921
|
frame.warmup_count = count;
|
|
853
|
-
frame.warmup_capacity = Math.max(1, Math.min(this
|
|
922
|
+
frame.warmup_capacity = Math.max(1, Math.min(this.#pool_capacity, spawned));
|
|
854
923
|
frame.warmup_schedule = warmup_schedule(max_prewarm);
|
|
855
924
|
}
|
|
856
925
|
|
|
@@ -974,6 +1043,18 @@ export class GPUParticleSystem {
|
|
|
974
1043
|
return;
|
|
975
1044
|
}
|
|
976
1045
|
|
|
1046
|
+
this.#replace_sim_list(buckets, true);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* A coherence list for `buckets` programs over the pool as it now stands, in place of the one
|
|
1051
|
+
* there is.
|
|
1052
|
+
*
|
|
1053
|
+
* @param {number} buckets
|
|
1054
|
+
* @param {boolean} carry whether the entries are copied forward — they must be whenever the
|
|
1055
|
+
* next simulate reads the list, and need not be when that frame rebuilds it first
|
|
1056
|
+
*/
|
|
1057
|
+
#replace_sim_list(buckets, carry) {
|
|
977
1058
|
const device = this.graphics.device;
|
|
978
1059
|
const buffers = this.buffers;
|
|
979
1060
|
|
|
@@ -982,20 +1063,216 @@ export class GPUParticleSystem {
|
|
|
982
1063
|
const previous = buffers.sim_list;
|
|
983
1064
|
buffers.sim_list = device.createBuffer({
|
|
984
1065
|
label: "particles/sim_list",
|
|
985
|
-
size: (this
|
|
1066
|
+
size: (this.#pool_capacity + buckets * PARTICLE_SIMULATE_WORKGROUP_SIZE) * WORD,
|
|
986
1067
|
usage: STORAGE_GROWABLE,
|
|
987
1068
|
});
|
|
988
1069
|
|
|
989
1070
|
if (previous !== undefined) {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1071
|
+
if (carry) {
|
|
1072
|
+
const cmd = ShadeGPUCommandContext.create(this.graphics, "particles/coherence grow");
|
|
1073
|
+
cmd.gpu_encoder.copyBufferToBuffer(previous, 0, buffers.sim_list, 0, previous.size);
|
|
1074
|
+
cmd.finish();
|
|
1075
|
+
}
|
|
1076
|
+
|
|
993
1077
|
previous.destroy();
|
|
994
1078
|
}
|
|
995
1079
|
|
|
996
1080
|
this.#sim_bucket_capacity = buckets;
|
|
997
1081
|
}
|
|
998
1082
|
|
|
1083
|
+
/**
|
|
1084
|
+
* Rebuild the pool at {@link #capacity} if it was set to something else since the last frame.
|
|
1085
|
+
* Here, at the top of {@link execute}, because the last frame's graph has been submitted and
|
|
1086
|
+
* nothing recorded reads the buffers being replaced.
|
|
1087
|
+
*/
|
|
1088
|
+
#apply_capacity() {
|
|
1089
|
+
const capacity = this.#capacity;
|
|
1090
|
+
const previous_capacity = this.#pool_capacity;
|
|
1091
|
+
|
|
1092
|
+
if (capacity === previous_capacity) {
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
this.#pool_capacity = capacity;
|
|
1097
|
+
this.#alive_descriptor = BufferResourceDescriptor.from(capacity * WORD, STORAGE);
|
|
1098
|
+
|
|
1099
|
+
if (capacity > previous_capacity) {
|
|
1100
|
+
this.#grow_pool(previous_capacity, capacity);
|
|
1101
|
+
} else {
|
|
1102
|
+
this.#shrink_pool(previous_capacity, capacity);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Every live particle keeps its slot, and the free list keeps its order: both are copied into
|
|
1108
|
+
* buffers of the larger size, and the slots the pool gains are pushed on top of the free list.
|
|
1109
|
+
* The coherence list is carried forward too, so the next simulate reads the list and the count
|
|
1110
|
+
* the last frame left, exactly as it would have.
|
|
1111
|
+
*
|
|
1112
|
+
* The alive list needs nothing: the last frame's is read, if at all, only up to its count, and
|
|
1113
|
+
* the next one is asked for at the new size.
|
|
1114
|
+
*
|
|
1115
|
+
* @param {number} previous_capacity
|
|
1116
|
+
* @param {number} capacity
|
|
1117
|
+
*/
|
|
1118
|
+
#grow_pool(previous_capacity, capacity) {
|
|
1119
|
+
const device = this.graphics.device;
|
|
1120
|
+
const buffers = this.buffers;
|
|
1121
|
+
const pool = buffers.pool;
|
|
1122
|
+
const dead = buffers.dead;
|
|
1123
|
+
|
|
1124
|
+
const grown_pool = device.createBuffer({ label: "particles/pool", size: capacity * PARTICLE_RECORD_WORD_COUNT * WORD, usage: STORAGE });
|
|
1125
|
+
const grown_dead = device.createBuffer({ label: "particles/dead", size: capacity * WORD, usage: STORAGE });
|
|
1126
|
+
|
|
1127
|
+
const cmd = ShadeGPUCommandContext.create(this.graphics, "particles/pool grow");
|
|
1128
|
+
|
|
1129
|
+
this.#copy_words(cmd, pool, grown_pool, previous_capacity * PARTICLE_RECORD_WORD_COUNT);
|
|
1130
|
+
this.#copy_words(cmd, dead, grown_dead, previous_capacity);
|
|
1131
|
+
|
|
1132
|
+
const settings = this.#push_settings;
|
|
1133
|
+
settings.first = previous_capacity;
|
|
1134
|
+
settings.count = capacity - previous_capacity;
|
|
1135
|
+
|
|
1136
|
+
shader_slot_stack_push_range.dispatch({
|
|
1137
|
+
encoder: cmd,
|
|
1138
|
+
group_count_x: this.#launch(settings.count, SLOT_STACK_PUSH_RANGE_WORKGROUP_SIZE),
|
|
1139
|
+
bindings: { settings, counts: buffers.counters, slots: grown_dead },
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
cmd.finish();
|
|
1143
|
+
|
|
1144
|
+
pool.destroy();
|
|
1145
|
+
dead.destroy();
|
|
1146
|
+
|
|
1147
|
+
buffers.pool = grown_pool;
|
|
1148
|
+
buffers.dead = grown_dead;
|
|
1149
|
+
|
|
1150
|
+
if (buffers.sim_list !== undefined) {
|
|
1151
|
+
this.#replace_sim_list(this.#sim_bucket_capacity, true);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* The smaller pool is built beside the larger one and every list is rebuilt into it, on the GPU,
|
|
1157
|
+
* since only the GPU knows which slots are live: the records below the new capacity are copied
|
|
1158
|
+
* across, the free slots below it make the new free list, and the particles past it move into
|
|
1159
|
+
* free slots below it ({@link shader_particle_shrink_alive_list}). The counters move to a buffer
|
|
1160
|
+
* of their own for the same reason — the passes count the new lists while reading the old
|
|
1161
|
+
* lengths — carrying everything else in them across.
|
|
1162
|
+
*
|
|
1163
|
+
* The coherence list names slots that are gone, so it is replaced empty and the next frame
|
|
1164
|
+
* rebuilds it from the new alive list before it simulates.
|
|
1165
|
+
*
|
|
1166
|
+
* @param {number} previous_capacity
|
|
1167
|
+
* @param {number} capacity
|
|
1168
|
+
*/
|
|
1169
|
+
#shrink_pool(previous_capacity, capacity) {
|
|
1170
|
+
const graphics = this.graphics;
|
|
1171
|
+
const device = graphics.device;
|
|
1172
|
+
const allocator = graphics.buffer_allocator_main;
|
|
1173
|
+
const buffers = this.buffers;
|
|
1174
|
+
|
|
1175
|
+
const pool = buffers.pool;
|
|
1176
|
+
const dead = buffers.dead;
|
|
1177
|
+
const counters = buffers.counters;
|
|
1178
|
+
const alive = this.#alive;
|
|
1179
|
+
|
|
1180
|
+
const shrunk_pool = device.createBuffer({ label: "particles/pool", size: capacity * PARTICLE_RECORD_WORD_COUNT * WORD, usage: STORAGE });
|
|
1181
|
+
const shrunk_dead = device.createBuffer({ label: "particles/dead", size: capacity * WORD, usage: STORAGE });
|
|
1182
|
+
const shrunk_counters = device.createBuffer({ label: "particles/counters", size: counters.size, usage: STORAGE_GROWABLE });
|
|
1183
|
+
// Taken before the old list goes back, so the pool cannot answer with the list being read.
|
|
1184
|
+
const shrunk_alive = allocator.get(this.#alive_descriptor);
|
|
1185
|
+
|
|
1186
|
+
const cmd = ShadeGPUCommandContext.create(graphics, "particles/pool shrink");
|
|
1187
|
+
const encoder = cmd.gpu_encoder;
|
|
1188
|
+
|
|
1189
|
+
// The two lists' lengths start at zero and the passes count them up; every other word — the
|
|
1190
|
+
// counts the next frame resets or rebuilds, and the bounds accumulators — comes across.
|
|
1191
|
+
encoder.copyBufferToBuffer(counters, 0, shrunk_counters, 0, counters.size);
|
|
1192
|
+
encoder.clearBuffer(shrunk_counters, PARTICLE_COUNTER.ALIVE * WORD, WORD);
|
|
1193
|
+
encoder.clearBuffer(shrunk_counters, PARTICLE_COUNTER.DEAD * WORD, WORD);
|
|
1194
|
+
|
|
1195
|
+
this.#copy_words(cmd, pool, shrunk_pool, capacity * PARTICLE_RECORD_WORD_COUNT);
|
|
1196
|
+
|
|
1197
|
+
// The lists are as long as the old pool at most; the lanes that find nothing past the GPU's
|
|
1198
|
+
// count return at once.
|
|
1199
|
+
const group_count_x = this.#launch(previous_capacity, PARTICLE_SHRINK_WORKGROUP_SIZE);
|
|
1200
|
+
|
|
1201
|
+
shader_particle_shrink_free_list.dispatch({
|
|
1202
|
+
encoder: cmd,
|
|
1203
|
+
group_count_x,
|
|
1204
|
+
bindings: {
|
|
1205
|
+
capacity,
|
|
1206
|
+
previous_counters: counters, previous_dead: dead,
|
|
1207
|
+
counters: shrunk_counters, dead: shrunk_dead,
|
|
1208
|
+
},
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
shader_particle_shrink_alive_list.dispatch({
|
|
1212
|
+
encoder: cmd,
|
|
1213
|
+
group_count_x,
|
|
1214
|
+
bindings: {
|
|
1215
|
+
capacity,
|
|
1216
|
+
previous_counters: counters, previous_alive: alive, previous_pool: pool,
|
|
1217
|
+
counters: shrunk_counters, dead: shrunk_dead, pool: shrunk_pool, alive: shrunk_alive,
|
|
1218
|
+
},
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
cmd.finish();
|
|
1222
|
+
|
|
1223
|
+
pool.destroy();
|
|
1224
|
+
dead.destroy();
|
|
1225
|
+
counters.destroy();
|
|
1226
|
+
allocator.release(alive);
|
|
1227
|
+
|
|
1228
|
+
buffers.pool = shrunk_pool;
|
|
1229
|
+
buffers.dead = shrunk_dead;
|
|
1230
|
+
buffers.counters = shrunk_counters;
|
|
1231
|
+
|
|
1232
|
+
this.#alive = shrunk_alive;
|
|
1233
|
+
buffers.alive = shrunk_alive;
|
|
1234
|
+
buffers.alive_write = shrunk_alive;
|
|
1235
|
+
|
|
1236
|
+
if (buffers.sim_list !== undefined) {
|
|
1237
|
+
this.#replace_sim_list(this.#sim_bucket_capacity, false);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
this.#rebucket_pending = true;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Copy the first `words` words of `source` into `destination`. On the GPU, because the pool and
|
|
1245
|
+
* the free list are written by shaders alone and carry no copy usage — see the usage masks at
|
|
1246
|
+
* the top of this file.
|
|
1247
|
+
*
|
|
1248
|
+
* @param {ShadeGPUCommandContext} cmd
|
|
1249
|
+
* @param {GPUBuffer} source
|
|
1250
|
+
* @param {GPUBuffer} destination
|
|
1251
|
+
* @param {number} words
|
|
1252
|
+
*/
|
|
1253
|
+
#copy_words(cmd, source, destination, words) {
|
|
1254
|
+
shader_buffer_copy_storage_to_storage.dispatch({
|
|
1255
|
+
encoder: cmd,
|
|
1256
|
+
group_count_x: this.#launch(words, COMPUTE_SHADER_DEFAULT_GROUP_SIZE),
|
|
1257
|
+
bindings: { source, destination, size: words },
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Workgroups for `threads` lanes of a grid-striding pass, clamped to what a dispatch dimension
|
|
1263
|
+
* holds: past that the lanes stride through the rest.
|
|
1264
|
+
*
|
|
1265
|
+
* @param {number} threads
|
|
1266
|
+
* @param {number} workgroup_size
|
|
1267
|
+
* @returns {number}
|
|
1268
|
+
*/
|
|
1269
|
+
#launch(threads, workgroup_size) {
|
|
1270
|
+
return Math.min(
|
|
1271
|
+
Math.ceil(threads / workgroup_size),
|
|
1272
|
+
this.graphics.device.limits.maxComputeWorkgroupsPerDimension,
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
999
1276
|
/**
|
|
1000
1277
|
* Give this frame its alive list, and hand back the ones nothing reads any more.
|
|
1001
1278
|
*
|
|
@@ -1081,17 +1358,14 @@ export class GPUParticleSystem {
|
|
|
1081
1358
|
* the caller's to choose and a dispatch past `maxComputeWorkgroupsPerDimension` is refused.
|
|
1082
1359
|
*/
|
|
1083
1360
|
#init_free_list() {
|
|
1084
|
-
const
|
|
1361
|
+
const capacity = this.#pool_capacity;
|
|
1085
1362
|
|
|
1086
1363
|
const cmd = ShadeGPUCommandContext.create(this.graphics, "particles/free list init");
|
|
1087
1364
|
|
|
1088
1365
|
shader_slot_stack_fill.dispatch({
|
|
1089
1366
|
encoder: cmd,
|
|
1090
|
-
group_count_x:
|
|
1091
|
-
|
|
1092
|
-
device.limits.maxComputeWorkgroupsPerDimension,
|
|
1093
|
-
),
|
|
1094
|
-
bindings: { slots: this.buffers.dead, capacity: this.capacity },
|
|
1367
|
+
group_count_x: this.#launch(capacity, SLOT_STACK_FILL_WORKGROUP_SIZE),
|
|
1368
|
+
bindings: { slots: this.buffers.dead, capacity },
|
|
1095
1369
|
});
|
|
1096
1370
|
|
|
1097
1371
|
cmd.finish();
|
|
@@ -1102,7 +1376,7 @@ export class GPUParticleSystem {
|
|
|
1102
1376
|
*/
|
|
1103
1377
|
#create_buffers() {
|
|
1104
1378
|
const device = this.graphics.device;
|
|
1105
|
-
const capacity = this
|
|
1379
|
+
const capacity = this.#pool_capacity;
|
|
1106
1380
|
const buffers = this.buffers;
|
|
1107
1381
|
|
|
1108
1382
|
buffers.pool = device.createBuffer({ label: "particles/pool", size: capacity * PARTICLE_RECORD_WORD_COUNT * WORD, usage: STORAGE });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The second half of shrinking the particle pool to `capacity` slots: the alive list of the smaller
|
|
3
|
+
* pool, out of the alive list of the larger one. A particle below `capacity` keeps its slot; one at
|
|
4
|
+
* or past it moves into a slot popped off the smaller pool's free list
|
|
5
|
+
* ({@link shader_particle_shrink_free_list}), its record copied across from `previous_pool`. A
|
|
6
|
+
* particle that finds the free list empty is dropped — the smaller pool cannot hold it.
|
|
7
|
+
*
|
|
8
|
+
* `pool` must already hold the larger pool's records below `capacity`: the ones that stay are not
|
|
9
|
+
* copied here. `counters` is the smaller pool's, its `ALIVE` count zero going in; the larger pool's
|
|
10
|
+
* alive count is read out of `previous_counters`.
|
|
11
|
+
*
|
|
12
|
+
* Every live slot below `capacity` is on neither free list, so a pop never hands out a slot a staying
|
|
13
|
+
* particle holds, and the two lists the pass leaves partition `[0, capacity)` again.
|
|
14
|
+
*
|
|
15
|
+
* The launch is the caller's to clamp; every lane strides through the list by the whole grid.
|
|
16
|
+
*
|
|
17
|
+
* @type {ComputeShader}
|
|
18
|
+
*/
|
|
19
|
+
export const shader_particle_shrink_alive_list: ComputeShader;
|
|
20
|
+
import { ComputeShader } from "../../shader/ComputeShader.js";
|
|
21
|
+
//# sourceMappingURL=shader_particle_shrink_alive_list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shader_particle_shrink_alive_list.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/resize/shader_particle_shrink_alive_list.js"],"names":[],"mappings":"AAwBA;;;;;;;;;;;;;;;;;GAiBG;AACH,gDAFU,aAAa,CA0CpB;8BAhF2B,+BAA+B"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { atomic_u32, u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
|
|
2
|
+
import { WebGPUArray } from "../../shader/type/WebGPUArray.js";
|
|
3
|
+
import { ComputeShader } from "../../shader/ComputeShader.js";
|
|
4
|
+
import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
|
|
5
|
+
import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
|
|
6
|
+
import { chunk_slot_stack } from "../../gpu_primitive/slot_stack/chunk_slot_stack.js";
|
|
7
|
+
import { PARTICLE_COUNTER } from "../data/PARTICLE_COUNTERS.js";
|
|
8
|
+
import { PARTICLE_RECORD_WORD_COUNT } from "../ParticleConstants.js";
|
|
9
|
+
import { PARTICLE_SHRINK_WORKGROUP_SIZE } from "./shader_particle_shrink_free_list.js";
|
|
10
|
+
|
|
11
|
+
const resources = new ShaderResourceSetDescriptor();
|
|
12
|
+
|
|
13
|
+
// per-dispatch, four bytes, read-only: immediate data rather than a uniform buffer holding one u32
|
|
14
|
+
resources.setImmediate("capacity", u32);
|
|
15
|
+
|
|
16
|
+
resources.createGroup()
|
|
17
|
+
.addStorageBuffer("previous_counters", WebGPUArray.u32, false)
|
|
18
|
+
.addStorageBuffer("previous_alive", WebGPUArray.u32, false)
|
|
19
|
+
.addStorageBuffer("previous_pool", WebGPUArray.u32, false)
|
|
20
|
+
.addStorageBuffer("counters", WebGPUArray.from(atomic_u32), true)
|
|
21
|
+
.addStorageBuffer("dead", WebGPUArray.u32, true)
|
|
22
|
+
.addStorageBuffer("pool", WebGPUArray.u32, true)
|
|
23
|
+
.addStorageBuffer("alive", WebGPUArray.u32, true);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The second half of shrinking the particle pool to `capacity` slots: the alive list of the smaller
|
|
27
|
+
* pool, out of the alive list of the larger one. A particle below `capacity` keeps its slot; one at
|
|
28
|
+
* or past it moves into a slot popped off the smaller pool's free list
|
|
29
|
+
* ({@link shader_particle_shrink_free_list}), its record copied across from `previous_pool`. A
|
|
30
|
+
* particle that finds the free list empty is dropped — the smaller pool cannot hold it.
|
|
31
|
+
*
|
|
32
|
+
* `pool` must already hold the larger pool's records below `capacity`: the ones that stay are not
|
|
33
|
+
* copied here. `counters` is the smaller pool's, its `ALIVE` count zero going in; the larger pool's
|
|
34
|
+
* alive count is read out of `previous_counters`.
|
|
35
|
+
*
|
|
36
|
+
* Every live slot below `capacity` is on neither free list, so a pop never hands out a slot a staying
|
|
37
|
+
* particle holds, and the two lists the pass leaves partition `[0, capacity)` again.
|
|
38
|
+
*
|
|
39
|
+
* The launch is the caller's to clamp; every lane strides through the list by the whole grid.
|
|
40
|
+
*
|
|
41
|
+
* @type {ComputeShader}
|
|
42
|
+
*/
|
|
43
|
+
export const shader_particle_shrink_alive_list = ComputeShader.from({
|
|
44
|
+
label: "Particle Shrink Alive List",
|
|
45
|
+
resources,
|
|
46
|
+
body: CodeChunk.from(
|
|
47
|
+
//language=WGSL
|
|
48
|
+
`
|
|
49
|
+
const COUNTER_ALIVE : u32 = ${PARTICLE_COUNTER.ALIVE}u;
|
|
50
|
+
const COUNTER_DEAD : u32 = ${PARTICLE_COUNTER.DEAD}u;
|
|
51
|
+
const RECORD_WORDS : u32 = ${PARTICLE_RECORD_WORD_COUNT}u;
|
|
52
|
+
|
|
53
|
+
@compute @workgroup_size(${PARTICLE_SHRINK_WORKGROUP_SIZE}, 1, 1)
|
|
54
|
+
fn main(
|
|
55
|
+
@builtin(global_invocation_id) global_id: vec3<u32>,
|
|
56
|
+
@builtin(num_workgroups) group_count: vec3<u32>,
|
|
57
|
+
) {
|
|
58
|
+
let stride = group_count.x * ${PARTICLE_SHRINK_WORKGROUP_SIZE}u;
|
|
59
|
+
let count = previous_counters[COUNTER_ALIVE];
|
|
60
|
+
|
|
61
|
+
for (var i = global_id.x; i < count; i += stride) {
|
|
62
|
+
var slot = previous_alive[i];
|
|
63
|
+
|
|
64
|
+
if (slot >= capacity) {
|
|
65
|
+
let moved = slot_stack_pop(&counters[COUNTER_DEAD], &dead, capacity);
|
|
66
|
+
|
|
67
|
+
if (moved == SLOT_STACK_NONE) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (var word = 0u; word < RECORD_WORDS; word++) {
|
|
72
|
+
pool[moved * RECORD_WORDS + word] = previous_pool[slot * RECORD_WORDS + word];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
slot = moved;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let a = atomicAdd(&counters[COUNTER_ALIVE], 1u);
|
|
79
|
+
alive[a] = slot;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
`, [chunk_slot_stack]),
|
|
83
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Lanes per workgroup. One free slot each, so this is sized for occupancy and nothing else. */
|
|
2
|
+
export const PARTICLE_SHRINK_WORKGROUP_SIZE: 256;
|
|
3
|
+
/**
|
|
4
|
+
* The first half of shrinking the particle pool to `capacity` slots: the free list of the smaller
|
|
5
|
+
* pool, out of the free list of the larger one. Every free slot below `capacity` is pushed onto
|
|
6
|
+
* `dead`; the slots at or past it go with the old pool.
|
|
7
|
+
*
|
|
8
|
+
* `counters` is the smaller pool's counters buffer, its `DEAD` count zero going in. The larger
|
|
9
|
+
* pool's free-list length is read out of `previous_counters` — a plain view, since nothing writes
|
|
10
|
+
* that buffer here. The order the slots land in is arbitrary, as it is for every push.
|
|
11
|
+
*
|
|
12
|
+
* The launch is the caller's to clamp; every lane strides through the list by the whole grid.
|
|
13
|
+
*
|
|
14
|
+
* {@link shader_particle_shrink_alive_list} pops from the list this builds, so it is a separate
|
|
15
|
+
* dispatch, after this one: a pop can read a slot a concurrent push has counted but not yet written.
|
|
16
|
+
*
|
|
17
|
+
* @type {ComputeShader}
|
|
18
|
+
*/
|
|
19
|
+
export const shader_particle_shrink_free_list: ComputeShader;
|
|
20
|
+
import { ComputeShader } from "../../shader/ComputeShader.js";
|
|
21
|
+
//# sourceMappingURL=shader_particle_shrink_free_list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shader_particle_shrink_free_list.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/resize/shader_particle_shrink_free_list.js"],"names":[],"mappings":"AAQA,gGAAgG;AAChG,iDAAkD;AAalD;;;;;;;;;;;;;;;GAeG;AACH,+CAFU,aAAa,CA2BpB;8BA7D2B,+BAA+B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { atomic_u32, u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
|
|
2
|
+
import { WebGPUArray } from "../../shader/type/WebGPUArray.js";
|
|
3
|
+
import { ComputeShader } from "../../shader/ComputeShader.js";
|
|
4
|
+
import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
|
|
5
|
+
import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
|
|
6
|
+
import { chunk_slot_stack } from "../../gpu_primitive/slot_stack/chunk_slot_stack.js";
|
|
7
|
+
import { PARTICLE_COUNTER } from "../data/PARTICLE_COUNTERS.js";
|
|
8
|
+
|
|
9
|
+
/** Lanes per workgroup. One free slot each, so this is sized for occupancy and nothing else. */
|
|
10
|
+
export const PARTICLE_SHRINK_WORKGROUP_SIZE = 256;
|
|
11
|
+
|
|
12
|
+
const resources = new ShaderResourceSetDescriptor();
|
|
13
|
+
|
|
14
|
+
// per-dispatch, four bytes, read-only: immediate data rather than a uniform buffer holding one u32
|
|
15
|
+
resources.setImmediate("capacity", u32);
|
|
16
|
+
|
|
17
|
+
resources.createGroup()
|
|
18
|
+
.addStorageBuffer("previous_counters", WebGPUArray.u32, false)
|
|
19
|
+
.addStorageBuffer("previous_dead", WebGPUArray.u32, false)
|
|
20
|
+
.addStorageBuffer("counters", WebGPUArray.from(atomic_u32), true)
|
|
21
|
+
.addStorageBuffer("dead", WebGPUArray.u32, true);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The first half of shrinking the particle pool to `capacity` slots: the free list of the smaller
|
|
25
|
+
* pool, out of the free list of the larger one. Every free slot below `capacity` is pushed onto
|
|
26
|
+
* `dead`; the slots at or past it go with the old pool.
|
|
27
|
+
*
|
|
28
|
+
* `counters` is the smaller pool's counters buffer, its `DEAD` count zero going in. The larger
|
|
29
|
+
* pool's free-list length is read out of `previous_counters` — a plain view, since nothing writes
|
|
30
|
+
* that buffer here. The order the slots land in is arbitrary, as it is for every push.
|
|
31
|
+
*
|
|
32
|
+
* The launch is the caller's to clamp; every lane strides through the list by the whole grid.
|
|
33
|
+
*
|
|
34
|
+
* {@link shader_particle_shrink_alive_list} pops from the list this builds, so it is a separate
|
|
35
|
+
* dispatch, after this one: a pop can read a slot a concurrent push has counted but not yet written.
|
|
36
|
+
*
|
|
37
|
+
* @type {ComputeShader}
|
|
38
|
+
*/
|
|
39
|
+
export const shader_particle_shrink_free_list = ComputeShader.from({
|
|
40
|
+
label: "Particle Shrink Free List",
|
|
41
|
+
resources,
|
|
42
|
+
body: CodeChunk.from(
|
|
43
|
+
//language=WGSL
|
|
44
|
+
`
|
|
45
|
+
const COUNTER_DEAD : u32 = ${PARTICLE_COUNTER.DEAD}u;
|
|
46
|
+
|
|
47
|
+
@compute @workgroup_size(${PARTICLE_SHRINK_WORKGROUP_SIZE}, 1, 1)
|
|
48
|
+
fn main(
|
|
49
|
+
@builtin(global_invocation_id) global_id: vec3<u32>,
|
|
50
|
+
@builtin(num_workgroups) group_count: vec3<u32>,
|
|
51
|
+
) {
|
|
52
|
+
let stride = group_count.x * ${PARTICLE_SHRINK_WORKGROUP_SIZE}u;
|
|
53
|
+
let count = previous_counters[COUNTER_DEAD];
|
|
54
|
+
|
|
55
|
+
for (var i = global_id.x; i < count; i += stride) {
|
|
56
|
+
let slot = previous_dead[i];
|
|
57
|
+
|
|
58
|
+
if (slot < capacity) {
|
|
59
|
+
slot_stack_push(&counters[COUNTER_DEAD], &dead, slot);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
`, [chunk_slot_stack]),
|
|
64
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk_restir_resolve.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/restir/di/chunk_restir_resolve.js"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,mCAFU,SAAS,
|
|
1
|
+
{"version":3,"file":"chunk_restir_resolve.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/restir/di/chunk_restir_resolve.js"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,mCAFU,SAAS,CA4DjB;0BApFwB,oCAAoC"}
|