@woosh/meep-engine 3.31.0 → 3.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/core/collection/table/RowFirstTable.d.ts +8 -0
  4. package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
  5. package/src/core/collection/table/RowFirstTable.js +40 -11
  6. package/src/engine/graphics/ecs/sprite/Sprite.d.ts +3 -0
  7. package/src/engine/graphics/ecs/sprite/Sprite.d.ts.map +1 -1
  8. package/src/engine/graphics/ecs/sprite/Sprite.js +3 -0
  9. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts +3 -0
  10. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  11. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +3 -0
  12. package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -1
  13. package/src/engine/graphics3/DebugDrawSystem.js +6 -2
  14. package/src/engine/graphics3/PathDisplaySystem.d.ts.map +1 -1
  15. package/src/engine/graphics3/PathDisplaySystem.js +311 -307
  16. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  17. package/src/shade/RENDERER_CONTRACT.md +15 -2
  18. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +5 -0
  19. package/src/shade/device/ShadeGPUCommandContext.d.ts +15 -2
  20. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  21. package/src/shade/device/ShadeGPUCommandContext.js +154 -25
  22. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts +20 -3
  23. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts.map +1 -1
  24. package/src/shade/device/mock/SoftwareGPURenderPassEncoder.js +21 -3
  25. package/src/shade/renderer/GraphicsContext.d.ts +20 -1
  26. package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
  27. package/src/shade/renderer/GraphicsContext.js +105 -52
  28. package/src/shade/renderer/Renderer.d.ts +19 -4
  29. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  30. package/src/shade/renderer/Renderer.js +113 -2
  31. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +10 -7
  32. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  33. package/src/shade/renderer/animation/GPUAnimationManager.js +147 -45
  34. package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.d.ts +6 -5
  35. package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.d.ts.map +1 -1
  36. package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.js +241 -240
  37. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts +62 -2
  38. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts.map +1 -1
  39. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.js +306 -38
  40. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts +60 -0
  41. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts.map +1 -0
  42. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.js +202 -0
  43. package/src/shade/renderer/buffer/GPUUploadBlock.d.ts +68 -0
  44. package/src/shade/renderer/buffer/GPUUploadBlock.d.ts.map +1 -0
  45. package/src/shade/renderer/buffer/GPUUploadBlock.js +97 -0
  46. package/src/shade/renderer/dynamic/DynamicMeshAVBOITChannel.d.ts +2 -1
  47. package/src/shade/renderer/dynamic/DynamicMeshAVBOITChannel.d.ts.map +1 -1
  48. package/src/shade/renderer/dynamic/DynamicMeshAVBOITChannel.js +19 -17
  49. package/src/shade/renderer/dynamic/GPUDynamicMeshDrawList.d.ts +70 -0
  50. package/src/shade/renderer/dynamic/GPUDynamicMeshDrawList.d.ts.map +1 -0
  51. package/src/shade/renderer/dynamic/GPUDynamicMeshDrawList.js +257 -0
  52. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts +20 -7
  53. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts.map +1 -1
  54. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +39 -41
  55. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.d.ts +6 -20
  56. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.d.ts.map +1 -1
  57. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +42 -36
  58. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.d.ts +2 -1
  59. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.d.ts.map +1 -1
  60. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.js +4 -3
  61. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_vertex.d.ts +4 -3
  62. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_vertex.d.ts.map +1 -1
  63. package/src/shade/renderer/dynamic/chunk_dynamic_mesh_vertex.js +7 -4
  64. package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.d.ts +14 -15
  65. package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.d.ts.map +1 -1
  66. package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.js +49 -83
  67. package/src/shade/renderer/dynamic/shader_dynamic_mesh.d.ts +19 -3
  68. package/src/shade/renderer/dynamic/shader_dynamic_mesh.d.ts.map +1 -1
  69. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +26 -6
  70. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.d.ts +2 -1
  71. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.d.ts.map +1 -1
  72. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.js +12 -6
  73. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.d.ts +3 -2
  74. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.d.ts.map +1 -1
  75. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.js +21 -13
  76. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts +5 -0
  77. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts.map +1 -1
  78. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.js +19 -0
  79. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  80. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  81. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +3 -1
  82. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -1
  83. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +3 -1
  84. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.d.ts.map +1 -1
  85. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.js +134 -88
  86. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
  87. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +57 -26
  88. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  89. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +28 -20
  90. package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts +38 -0
  91. package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts.map +1 -0
  92. package/src/shade/renderer/rasterize/graph_clear_raster_targets.js +105 -0
  93. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +20 -0
  94. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts +1 -1
  95. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  96. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +21 -1
  97. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  98. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +12 -20
  99. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts +2 -1
  100. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts.map +1 -1
  101. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.js +15 -28
  102. package/src/shade/renderer/shader/type/serde/write_gpu_typed_buffer.d.ts.map +1 -1
  103. package/src/shade/renderer/shader/type/serde/write_gpu_typed_buffer.js +8 -1
  104. package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts +0 -73
  105. package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts.map +0 -1
  106. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts +0 -59
  107. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts.map +0 -1
  108. package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts +0 -22
  109. package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts.map +0 -1
  110. package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts +0 -26
  111. package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts.map +0 -1
  112. package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts +0 -25
  113. package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts.map +0 -1
  114. package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts +0 -26
  115. package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts.map +0 -1
  116. package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts +0 -29
  117. package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts.map +0 -1
@@ -0,0 +1,68 @@
1
+ /**
2
+ * One piece of upload memory: a GPU buffer, and the bytes it is to be given, held on the CPU until
3
+ * they are sent.
4
+ *
5
+ * A {@link ShadeGPUCommandContext} fills regions of {@link data} as it records, and copies each
6
+ * region to its destination with a `copyBufferToBuffer` out of {@link buffer} recorded where the
7
+ * write was made; the part in use reaches {@link buffer} in one queue write just before the context
8
+ * submits. Why the block can go back to its pool as soon as the context finishes is set out once,
9
+ * at `ShadeGPUCommandContext#send_uploads`.
10
+ *
11
+ * Both halves live as long as the block does — the CPU half is not given up once sent — so a block
12
+ * costs its size twice for as long as its pool keeps it.
13
+ */
14
+ export class GPUUploadBlock {
15
+ /**
16
+ * @param {GPUBuffer} buffer
17
+ * @param {ArrayBuffer} data
18
+ */
19
+ constructor(buffer: GPUBuffer, data: ArrayBuffer);
20
+ /**
21
+ * `COPY_SRC | COPY_DST`, never mapped.
22
+ *
23
+ * @type {GPUBuffer}
24
+ */
25
+ buffer: GPUBuffer;
26
+ /**
27
+ * What {@link buffer} is to hold, the same size as it. Past {@link used} it holds whatever the
28
+ * last use left.
29
+ *
30
+ * @type {ArrayBuffer}
31
+ */
32
+ data: ArrayBuffer;
33
+ /**
34
+ * Bytes handed out, from the start. A multiple of 4, so every region starts on one: WebGPU's
35
+ * buffer copies work in whole 4-byte words, on both offsets and the size.
36
+ *
37
+ * @type {number}
38
+ */
39
+ used: number;
40
+ /**
41
+ * Whether the device refused to back {@link buffer} — out of memory, in practice.
42
+ *
43
+ * WebGPU says so through its error scopes, after the fact, and hands back a buffer object either
44
+ * way. By the time this is set the context that took the block has recorded copies out of an
45
+ * invalid buffer and its submission is lost with them; what this prevents is the same loss on
46
+ * every frame after, which is what a pool that kept handing the block out would cause.
47
+ *
48
+ * @type {boolean}
49
+ */
50
+ refused: boolean;
51
+ /**
52
+ * @returns {number} bytes
53
+ */
54
+ get size(): number;
55
+ /**
56
+ * @param {number} size bytes
57
+ * @returns {boolean} whether a region of `size` bytes is still free
58
+ */
59
+ fits(size: number): boolean;
60
+ /**
61
+ * Take the next `size` bytes.
62
+ *
63
+ * @param {number} size bytes; the region taken is rounded up to a whole number of 4-byte words
64
+ * @returns {number} byte offset of the region in {@link data} and {@link buffer}
65
+ */
66
+ allocate(size: number): number;
67
+ }
68
+ //# sourceMappingURL=GPUUploadBlock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GPUUploadBlock.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/buffer/GPUUploadBlock.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH;IAoCI;;;OAGG;IACH,qCAFW,WAAW,EASrB;IA9CD;;;;OAIG;IACH,kBAAO;IAEP;;;;;OAKG;IACH,MAFU,WAAW,CAEhB;IAEL;;;;;OAKG;IACH,MAFU,MAAM,CAEP;IAET;;;;;;;;;OASG;IACH,SAFU,OAAO,CAED;IAehB;;OAEG;IACH,mBAEC;IAED;;;OAGG;IACH,WAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;OAKG;IACH,eAHW,MAAM,GACJ,MAAM,CAWlB;CACJ"}
@@ -0,0 +1,97 @@
1
+ import { assert } from "../../../core/assert.js";
2
+ import { align_4 } from "../../../core/binary/align_4.js";
3
+
4
+ /**
5
+ * One piece of upload memory: a GPU buffer, and the bytes it is to be given, held on the CPU until
6
+ * they are sent.
7
+ *
8
+ * A {@link ShadeGPUCommandContext} fills regions of {@link data} as it records, and copies each
9
+ * region to its destination with a `copyBufferToBuffer` out of {@link buffer} recorded where the
10
+ * write was made; the part in use reaches {@link buffer} in one queue write just before the context
11
+ * submits. Why the block can go back to its pool as soon as the context finishes is set out once,
12
+ * at `ShadeGPUCommandContext#send_uploads`.
13
+ *
14
+ * Both halves live as long as the block does — the CPU half is not given up once sent — so a block
15
+ * costs its size twice for as long as its pool keeps it.
16
+ */
17
+ export class GPUUploadBlock {
18
+ /**
19
+ * `COPY_SRC | COPY_DST`, never mapped.
20
+ *
21
+ * @type {GPUBuffer}
22
+ */
23
+ buffer;
24
+
25
+ /**
26
+ * What {@link buffer} is to hold, the same size as it. Past {@link used} it holds whatever the
27
+ * last use left.
28
+ *
29
+ * @type {ArrayBuffer}
30
+ */
31
+ data;
32
+
33
+ /**
34
+ * Bytes handed out, from the start. A multiple of 4, so every region starts on one: WebGPU's
35
+ * buffer copies work in whole 4-byte words, on both offsets and the size.
36
+ *
37
+ * @type {number}
38
+ */
39
+ used = 0;
40
+
41
+ /**
42
+ * Whether the device refused to back {@link buffer} — out of memory, in practice.
43
+ *
44
+ * WebGPU says so through its error scopes, after the fact, and hands back a buffer object either
45
+ * way. By the time this is set the context that took the block has recorded copies out of an
46
+ * invalid buffer and its submission is lost with them; what this prevents is the same loss on
47
+ * every frame after, which is what a pool that kept handing the block out would cause.
48
+ *
49
+ * @type {boolean}
50
+ */
51
+ refused = false;
52
+
53
+ /**
54
+ * @param {GPUBuffer} buffer
55
+ * @param {ArrayBuffer} data
56
+ */
57
+ constructor(buffer, data) {
58
+ assert.defined(buffer, 'buffer');
59
+ assert.defined(data, 'data');
60
+ assert.equal(data.byteLength, buffer.size, 'data.byteLength !== buffer.size');
61
+
62
+ this.buffer = buffer;
63
+ this.data = data;
64
+ }
65
+
66
+ /**
67
+ * @returns {number} bytes
68
+ */
69
+ get size() {
70
+ return this.data.byteLength;
71
+ }
72
+
73
+ /**
74
+ * @param {number} size bytes
75
+ * @returns {boolean} whether a region of `size` bytes is still free
76
+ */
77
+ fits(size) {
78
+ return this.used + size <= this.data.byteLength;
79
+ }
80
+
81
+ /**
82
+ * Take the next `size` bytes.
83
+ *
84
+ * @param {number} size bytes; the region taken is rounded up to a whole number of 4-byte words
85
+ * @returns {number} byte offset of the region in {@link data} and {@link buffer}
86
+ */
87
+ allocate(size) {
88
+ assert.isNonNegativeInteger(size, 'size');
89
+ assert.ok(this.fits(size), 'does not fit');
90
+
91
+ const offset = this.used;
92
+
93
+ this.used = offset + align_4(size);
94
+
95
+ return offset;
96
+ }
97
+ }
@@ -7,7 +7,8 @@
7
7
  *
8
8
  * - **occupancy** — one world box per mesh, over the triangles that can still put a fragment on
9
9
  * screen (`dynamic_mesh_live_bounds`), marked on the GPU through the particles' own chunks. The
10
- * upload of the batch's vertices happens here too, being the first of the three at execution time.
10
+ * upload of the batch's vertices and of its draws' constants happens here too, being the first of
11
+ * the three at execution time; the splat and the draw read the one upload.
11
12
  * - **splat** — the meshes at the volume's resolution, coverage as the vertex and mesh alpha give it.
12
13
  * - **draw** — the meshes at internal resolution into the accumulators, straight alpha premultiplied,
13
14
  * fogged, weighted by the transmittance in front, routed by `avboit_premultiplied_contribution`.
@@ -1 +1 @@
1
- {"version":3,"file":"DynamicMeshAVBOITChannel.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/dynamic/DynamicMeshAVBOITChannel.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH;IA0BI;;OAEG;IACH,qCAKC;IAED;;;;;;;OAOG;IACH,mBAFa,OAAO,CAcnB;IAED;;;;;;;;;;OAUG;IACH;QANwB,KAAK,EAAlB,MAAM;QACO,MAAM,EAAnB,MAAM;QACkB,kBAAkB,EAA1C,UAAU,MAAM,CAAC;QACJ,SAAS,EAAtB,MAAM;QACJ,MAAM,CAsBlB;IAED;;;;;;;;;;OAUG;IACH;QAPwB,KAAK,EAAlB,MAAM;QACO,MAAM,EAAnB,MAAM;QACO,IAAI,EAAjB,MAAM;QACS,gBAAgB,EAA/B,MAAM,EAAE;QACoD,MAAM,EAAlE;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAC;QACnD;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAkBjE;IAED;;;;;;;;;;;;;;OAcG;IACH;QAXwB,KAAK,EAAlB,MAAM;QACmD,QAAQ,EAAjE,OAAO,2BAA2B,EAAE,cAAc;QACrC,MAAM,EAAnB,MAAM;QACO,aAAa,EAA1B,MAAM;QACO,IAAI,EAAjB,MAAM;QACO,QAAQ,EAArB,MAAM;QACO,eAAe,EAA5B,MAAM;QACO,oBAAoB,EAAjC,MAAM;QACoE,YAAY,EAAtF;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAC;QACjE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAmC/E;IAED;;;;;;OAMG;IACH,6BAFa,OAAO,CAInB;IAED;;;OAGG;IACH,gBAIC;;CAoDJ"}
1
+ {"version":3,"file":"DynamicMeshAVBOITChannel.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/dynamic/DynamicMeshAVBOITChannel.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH;IA2BI;;OAEG;IACH,qCAKC;IAED;;;;;;;OAOG;IACH,mBAFa,OAAO,CAcnB;IAED;;;;;;;;;;OAUG;IACH;QANwB,KAAK,EAAlB,MAAM;QACO,MAAM,EAAnB,MAAM;QACkB,kBAAkB,EAA1C,UAAU,MAAM,CAAC;QACJ,SAAS,EAAtB,MAAM;QACJ,MAAM,CAuBlB;IAED;;;;;;;;;;OAUG;IACH;QAPwB,KAAK,EAAlB,MAAM;QACO,MAAM,EAAnB,MAAM;QACO,IAAI,EAAjB,MAAM;QACS,gBAAgB,EAA/B,MAAM,EAAE;QACoD,MAAM,EAAlE;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAC;QACnD;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAiBjE;IAED;;;;;;;;;;;;;;OAcG;IACH;QAXwB,KAAK,EAAlB,MAAM;QACmD,QAAQ,EAAjE,OAAO,2BAA2B,EAAE,cAAc;QACrC,MAAM,EAAnB,MAAM;QACO,aAAa,EAA1B,MAAM;QACO,IAAI,EAAjB,MAAM;QACO,QAAQ,EAArB,MAAM;QACO,eAAe,EAA5B,MAAM;QACO,oBAAoB,EAAjC,MAAM;QACoE,YAAY,EAAtF;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAC;QACjE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAkC/E;IAED;;;;;;OAMG;IACH,6BAFa,OAAO,CAInB;IAED;;;OAGG;IACH,gBAIC;;CAoDJ"}
@@ -1,6 +1,7 @@
1
1
  import { assert } from "../../../core/assert.js";
2
2
  import { aabb3_matrix4_project } from "../../../core/geom/3d/aabb/aabb3_matrix4_project.js";
3
3
  import { dynamic_mesh_live_bounds } from "./dynamic_mesh_live_bounds.js";
4
+ import { GPUDynamicMeshDrawList } from "./GPUDynamicMeshDrawList.js";
4
5
  import {
5
6
  graph_dynamic_mesh_avboit_draw,
6
7
  graph_dynamic_mesh_avboit_occupancy,
@@ -25,7 +26,8 @@ const scratch_world = new Float64Array(6);
25
26
  *
26
27
  * - **occupancy** — one world box per mesh, over the triangles that can still put a fragment on
27
28
  * screen (`dynamic_mesh_live_bounds`), marked on the GPU through the particles' own chunks. The
28
- * upload of the batch's vertices happens here too, being the first of the three at execution time.
29
+ * upload of the batch's vertices and of its draws' constants happens here too, being the first of
30
+ * the three at execution time; the splat and the draw read the one upload.
29
31
  * - **splat** — the meshes at the volume's resolution, coverage as the vertex and mesh alpha give it.
30
32
  * - **draw** — the meshes at internal resolution into the accumulators, straight alpha premultiplied,
31
33
  * fogged, weighted by the transmittance in front, routed by `avboit_premultiplied_contribution`.
@@ -55,16 +57,17 @@ export class DynamicMeshAVBOITChannel {
55
57
 
56
58
  /**
57
59
  * What {@link avboit_occupancy} decided for the graph it was asked in — the graph, the live
58
- * meshes, and a world box for each ({@link DYNAMIC_MESH_AVBOIT_BOX_FLOATS} floats apiece) — for
59
- * the splat and the draw of the same graph to take.
60
+ * meshes, a world box for each ({@link DYNAMIC_MESH_AVBOIT_BOX_FLOATS} floats apiece), and the
61
+ * list their draws are written into when the occupancy executes — for the splat and the draw of
62
+ * the same graph to take.
60
63
  *
61
64
  * **Dropped as soon as the draw is recorded.** The passes carry their own references to the list
62
65
  * and the boxes into execution, so the channel keeps nothing of a frame past its recording: a
63
66
  * channel that stops being asked — its last mesh faded, its renderer replaced — does not hold a
64
- * whole frame graph, and every pass closure in it, reachable. And since each frame's list and
67
+ * whole frame graph, and every pass closure in it, reachable. And since each frame's lists and
65
68
  * boxes are its own, two graphs recorded before either executes cannot see each other's.
66
69
  *
67
- * @type {{graph: object, live: DynamicMesh[], boxes: Float32Array}|null}
70
+ * @type {{graph: object, live: DynamicMesh[], boxes: Float32Array, draws: GPUDynamicMeshDrawList}|null}
68
71
  */
69
72
  #frame = null;
70
73
 
@@ -130,6 +133,7 @@ export class DynamicMeshAVBOITChannel {
130
133
  residency: this.#residency,
131
134
  meshes: frame.live,
132
135
  boxes: frame.boxes,
136
+ draws: frame.draws,
133
137
  });
134
138
  }
135
139
 
@@ -145,9 +149,9 @@ export class DynamicMeshAVBOITChannel {
145
149
  * @returns {{extinction: number, overflow: number, dummy: number}} the volume after the write
146
150
  */
147
151
  avboit_splat({ graph, camera, warp, voxel_resolution, volume }) {
148
- const meshes = this.#frame_for(graph).live;
152
+ const frame = this.#frame_for(graph);
149
153
 
150
- if (meshes.length === 0) {
154
+ if (frame.live.length === 0) {
151
155
  return volume;
152
156
  }
153
157
 
@@ -157,8 +161,7 @@ export class DynamicMeshAVBOITChannel {
157
161
  warp,
158
162
  voxel_resolution,
159
163
  volume,
160
- residency: this.#residency,
161
- meshes,
164
+ draws: frame.draws,
162
165
  });
163
166
  }
164
167
 
@@ -188,12 +191,12 @@ export class DynamicMeshAVBOITChannel {
188
191
  volumetrics_metadata,
189
192
  accumulators
190
193
  }) {
191
- const meshes = this.#frame_for(graph).live;
194
+ const frame = this.#frame_for(graph);
192
195
 
193
196
  // the last of the three: the frame is the passes' from here on, and none of the channel's
194
197
  this.#frame = null;
195
198
 
196
- if (meshes.length === 0) {
199
+ if (frame.live.length === 0) {
197
200
  return accumulators;
198
201
  }
199
202
 
@@ -207,8 +210,7 @@ export class DynamicMeshAVBOITChannel {
207
210
  volumetrics_lut,
208
211
  volumetrics_metadata,
209
212
  accumulators,
210
- residency: this.#residency,
211
- meshes,
213
+ draws: frame.draws,
212
214
  });
213
215
  }
214
216
 
@@ -239,7 +241,7 @@ export class DynamicMeshAVBOITChannel {
239
241
  * would upload nothing and draw some other frame's list, so it is refused.
240
242
  *
241
243
  * @param {object} graph
242
- * @returns {{graph: object, live: DynamicMesh[], boxes: Float32Array}}
244
+ * @returns {{graph: object, live: DynamicMesh[], boxes: Float32Array, draws: GPUDynamicMeshDrawList}}
243
245
  */
244
246
  #frame_for(graph) {
245
247
  const frame = this.#frame;
@@ -251,10 +253,10 @@ export class DynamicMeshAVBOITChannel {
251
253
 
252
254
  /**
253
255
  * Find the batch's live meshes, and a world box for each — into a list and a buffer of this
254
- * frame's own.
256
+ * frame's own, beside the draw list the occupancy will write their draws into.
255
257
  *
256
258
  * @param {object} graph
257
- * @returns {{graph: object, live: DynamicMesh[], boxes: Float32Array}}
259
+ * @returns {{graph: object, live: DynamicMesh[], boxes: Float32Array, draws: GPUDynamicMeshDrawList}}
258
260
  */
259
261
  #gather(graph) {
260
262
  const meshes = this.#batch.meshes;
@@ -281,7 +283,7 @@ export class DynamicMeshAVBOITChannel {
281
283
  live.push(mesh);
282
284
  }
283
285
 
284
- return { graph, live, boxes };
286
+ return { graph, live, boxes, draws: new GPUDynamicMeshDrawList() };
285
287
  }
286
288
  }
287
289
 
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The most draws one chunk of a frame's constants holds — and so the most draws one bind of the
3
+ * constants' group serves.
4
+ *
5
+ * Bounded rather than one table for every mesh the renderer draws: a chunk is one transient buffer
6
+ * and one write of at most 80 KiB of constants, whatever the frame's mesh count, so the write never
7
+ * grows with the scene and a chunk's buffer is the pool's ordinary business.
8
+ *
9
+ * @type {number}
10
+ */
11
+ export const DYNAMIC_MESH_DRAWS_PER_CHUNK: number;
12
+ /**
13
+ * One frame's draws of a set of {@link DynamicMesh}es: which geometry each draws, and its per-draw
14
+ * constants ({@link DYNAMIC_MESH_DRAW_STRUCT}), written in chunks of up to
15
+ * {@link DYNAMIC_MESH_DRAWS_PER_CHUNK}.
16
+ *
17
+ * **The constants are all written anew every frame**, into transient buffers of the frame's own: a
18
+ * dynamic mesh's transform and colour are as free to change as its vertices, so nothing is kept for
19
+ * a mesh from one frame to the next. What the table buys is that the frame pays per *chunk* for the
20
+ * buffer, the copy and the bind group, where it used to pay per mesh for each. Per draw, what is
21
+ * left is the geometry's bind group — the same one every frame, while its buffers keep their size —
22
+ * and the draw, which names its row of the chunk through its `first_instance`.
23
+ *
24
+ * Draws keep the order of the meshes they came from, across chunks: the pass over the scene blends,
25
+ * and composites in that order.
26
+ *
27
+ * {@link write} before a render pass is opened on the encoder — the constants go up through copies,
28
+ * which a command encoder refuses while a pass is open on it — and then {@link draw} into as many
29
+ * passes of the frame as draw these meshes. The AVBOIT splat and draw read one write.
30
+ */
31
+ export class GPUDynamicMeshDrawList {
32
+ /**
33
+ * How many draws the last {@link write} laid out.
34
+ *
35
+ * @returns {number}
36
+ */
37
+ get count(): number;
38
+ /**
39
+ * Lay out a draw for every visible mesh of `meshes` with triangles resident in `residency`, and
40
+ * send their constants — as they are now — in one write per chunk. Replaces whatever the list
41
+ * held.
42
+ *
43
+ * The buffers are the encoder's transients, so the list is good for as long as that encoder is
44
+ * recording and no longer.
45
+ *
46
+ * @param {ShadeGPUCommandContext} encoder
47
+ * @param {GPUDynamicMeshResidencyTable} residency brought up to date for `meshes` this frame
48
+ * @param {DynamicMesh[]} meshes
49
+ */
50
+ write(encoder: ShadeGPUCommandContext, residency: GPUDynamicMeshResidencyTable, meshes: DynamicMesh[]): void;
51
+ /**
52
+ * Let go of the frame: the geometries it drew and the transient buffers it wrote, which the
53
+ * encoder hands back when it finishes. For a list that outlives its frame, once its last pass is
54
+ * recorded.
55
+ */
56
+ clear(): void;
57
+ /**
58
+ * Record every draw into `render_pass`: the constants' group once per chunk, then per draw the
59
+ * geometry's group and the draw at its row. The pass's other groups are the caller's to set.
60
+ *
61
+ * @param {ShadeGPURenderPassEncoder} render_pass running `pipeline`
62
+ * @param {GraphicsContext} graphics
63
+ * @param {RenderPipelineDescriptor} pipeline one of the dynamic-mesh pipelines, whose layout opens
64
+ * with `DYNAMIC_MESH_RESOURCES`' groups
65
+ * @param {GPUBuffer} camera
66
+ */
67
+ draw(render_pass: ShadeGPURenderPassEncoder, graphics: GraphicsContext, pipeline: RenderPipelineDescriptor, camera: GPUBuffer): void;
68
+ #private;
69
+ }
70
+ //# sourceMappingURL=GPUDynamicMeshDrawList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GPUDynamicMeshDrawList.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/dynamic/GPUDynamicMeshDrawList.js"],"names":[],"mappings":"AASA;;;;;;;;;GASG;AACH,2CAFU,MAAM,CAEiC;AAsEjD;;;;;;;;;;;;;;;;;;GAkBG;AACH;IAgBI;;;;OAIG;IACH,oBAEC;IAED;;;;;;;;;;;OAWG;IACH,wFAFW,aAAa,QAoEvB;IAED;;;;OAIG;IACH,cAGC;IAED;;;;;;;;;OASG;IACH,qIAsBC;;CACJ"}
@@ -0,0 +1,257 @@
1
+ import { assert } from "../../../core/assert.js";
2
+ import { ceilPowerOfTwo } from "../../../core/binary/operations/ceilPowerOfTwo.js";
3
+ import { round_up } from "../../../core/math/round_up.js";
4
+ import {
5
+ DYNAMIC_MESH_BIND_GROUP_DRAWS,
6
+ DYNAMIC_MESH_DRAW_STRUCT,
7
+ DYNAMIC_MESH_RESOURCES
8
+ } from "./shader_dynamic_mesh.js";
9
+
10
+ /**
11
+ * The most draws one chunk of a frame's constants holds — and so the most draws one bind of the
12
+ * constants' group serves.
13
+ *
14
+ * Bounded rather than one table for every mesh the renderer draws: a chunk is one transient buffer
15
+ * and one write of at most 80 KiB of constants, whatever the frame's mesh count, so the write never
16
+ * grows with the scene and a chunk's buffer is the pool's ordinary business.
17
+ *
18
+ * @type {number}
19
+ */
20
+ export const DYNAMIC_MESH_DRAWS_PER_CHUNK = 1024;
21
+
22
+ /**
23
+ * The fewest draws a chunk is sized for. See {@link chunk_capacity}.
24
+ *
25
+ * @type {number}
26
+ */
27
+ const DRAWS_PER_CHUNK_MIN = 64;
28
+
29
+ /**
30
+ * Bytes from one draw's constants to the next: the array stride of {@link DYNAMIC_MESH_DRAW_STRUCT}.
31
+ *
32
+ * @type {number}
33
+ */
34
+ const DRAW_STRIDE = round_up(DYNAMIC_MESH_DRAW_STRUCT.size, DYNAMIC_MESH_DRAW_STRUCT.align);
35
+
36
+ const DRAW_STRIDE_FLOATS = DRAW_STRIDE / 4;
37
+ const MODEL_OFFSET_FLOATS = DYNAMIC_MESH_DRAW_STRUCT.get("model").offset / 4;
38
+ const COLOR_OFFSET_FLOATS = DYNAMIC_MESH_DRAW_STRUCT.get("color").offset / 4;
39
+
40
+ /**
41
+ * Where a frame's constants are laid out on the CPU before they are sent. Only ever used for the
42
+ * length of one {@link GPUDynamicMeshDrawList#write} — the write copies them out before it returns —
43
+ * so one serves every list.
44
+ *
45
+ * @type {Float32Array}
46
+ */
47
+ let scratch = new Float32Array(DRAW_STRIDE_FLOATS * DRAWS_PER_CHUNK_MIN);
48
+
49
+ /**
50
+ * The scratch is given back once it holds more than four times what a write needs and more than this
51
+ * many bytes: a burst of meshes should not pin its constants' worth of memory for the rest of the
52
+ * process, and a count that swings within an ordinary range should not reallocate every frame.
53
+ *
54
+ * @type {number}
55
+ */
56
+ const SCRATCH_RETAINED_BYTES = 1024 * 1024;
57
+
58
+ /**
59
+ * How many draws' worth of buffer a chunk of `count` draws takes, and writes: the next power of two,
60
+ * between {@link DRAWS_PER_CHUNK_MIN} and {@link DYNAMIC_MESH_DRAWS_PER_CHUNK}.
61
+ *
62
+ * Sized by class rather than to the count because the count is the part of a frame most certain to
63
+ * change. Buffers of the exact size are a new size whenever one effect appears, and the transient
64
+ * pool hands out a buffer only for a request close enough to its size — so a count climbing by a
65
+ * mesh a frame made a new transient buffer every frame. Five sizes cover every chunk; the
66
+ * price is writing up to twice the bytes of the draws, rows past the last draw holding whatever the
67
+ * scratch held, which no draw reads.
68
+ *
69
+ * @param {number} count draws in the chunk, 1 to {@link DYNAMIC_MESH_DRAWS_PER_CHUNK}
70
+ * @returns {number}
71
+ */
72
+ function chunk_capacity(count) {
73
+ return Math.min(DYNAMIC_MESH_DRAWS_PER_CHUNK, Math.max(DRAWS_PER_CHUNK_MIN, ceilPowerOfTwo(count)));
74
+ }
75
+
76
+ /**
77
+ * Rows of scratch that a write of up to `mesh_count` draws can send, padding included.
78
+ *
79
+ * @param {number} mesh_count
80
+ * @returns {number}
81
+ */
82
+ function scratch_rows(mesh_count) {
83
+ if (mesh_count <= DYNAMIC_MESH_DRAWS_PER_CHUNK) {
84
+ return chunk_capacity(mesh_count);
85
+ }
86
+
87
+ return round_up(mesh_count, DYNAMIC_MESH_DRAWS_PER_CHUNK);
88
+ }
89
+
90
+ /**
91
+ * One frame's draws of a set of {@link DynamicMesh}es: which geometry each draws, and its per-draw
92
+ * constants ({@link DYNAMIC_MESH_DRAW_STRUCT}), written in chunks of up to
93
+ * {@link DYNAMIC_MESH_DRAWS_PER_CHUNK}.
94
+ *
95
+ * **The constants are all written anew every frame**, into transient buffers of the frame's own: a
96
+ * dynamic mesh's transform and colour are as free to change as its vertices, so nothing is kept for
97
+ * a mesh from one frame to the next. What the table buys is that the frame pays per *chunk* for the
98
+ * buffer, the copy and the bind group, where it used to pay per mesh for each. Per draw, what is
99
+ * left is the geometry's bind group — the same one every frame, while its buffers keep their size —
100
+ * and the draw, which names its row of the chunk through its `first_instance`.
101
+ *
102
+ * Draws keep the order of the meshes they came from, across chunks: the pass over the scene blends,
103
+ * and composites in that order.
104
+ *
105
+ * {@link write} before a render pass is opened on the encoder — the constants go up through copies,
106
+ * which a command encoder refuses while a pass is open on it — and then {@link draw} into as many
107
+ * passes of the frame as draw these meshes. The AVBOIT splat and draw read one write.
108
+ */
109
+ export class GPUDynamicMeshDrawList {
110
+ /**
111
+ * What each draw draws, in draw order.
112
+ *
113
+ * @type {GPUDynamicMeshResidency[]}
114
+ */
115
+ #residencies = [];
116
+
117
+ /**
118
+ * One transient storage buffer per chunk, {@link DYNAMIC_MESH_DRAWS_PER_CHUNK} draws apiece and
119
+ * the rest in the last, each sized by {@link chunk_capacity}.
120
+ *
121
+ * @type {GPUBuffer[]}
122
+ */
123
+ #chunks = [];
124
+
125
+ /**
126
+ * How many draws the last {@link write} laid out.
127
+ *
128
+ * @returns {number}
129
+ */
130
+ get count() {
131
+ return this.#residencies.length;
132
+ }
133
+
134
+ /**
135
+ * Lay out a draw for every visible mesh of `meshes` with triangles resident in `residency`, and
136
+ * send their constants — as they are now — in one write per chunk. Replaces whatever the list
137
+ * held.
138
+ *
139
+ * The buffers are the encoder's transients, so the list is good for as long as that encoder is
140
+ * recording and no longer.
141
+ *
142
+ * @param {ShadeGPUCommandContext} encoder
143
+ * @param {GPUDynamicMeshResidencyTable} residency brought up to date for `meshes` this frame
144
+ * @param {DynamicMesh[]} meshes
145
+ */
146
+ write(encoder, residency, meshes) {
147
+ assert.defined(encoder, 'encoder');
148
+ assert.defined(residency, 'residency');
149
+ assert.isArray(meshes, 'meshes');
150
+
151
+ const residencies = this.#residencies;
152
+ const chunks = this.#chunks;
153
+
154
+ residencies.length = 0;
155
+ chunks.length = 0;
156
+
157
+ const mesh_count = meshes.length;
158
+ const needed = scratch_rows(mesh_count) * DRAW_STRIDE_FLOATS;
159
+
160
+ if (scratch.length < needed || (scratch.length > needed * 4 && scratch.byteLength > SCRATCH_RETAINED_BYTES)) {
161
+ scratch = new Float32Array(needed);
162
+ }
163
+
164
+ const constants = scratch;
165
+
166
+ for (let i = 0; i < mesh_count; i++) {
167
+ const mesh = meshes[i];
168
+
169
+ if (!mesh.visible) {
170
+ continue;
171
+ }
172
+
173
+ const resident = residency.get(mesh.geometry);
174
+
175
+ if (resident === undefined || resident.index_count === 0) {
176
+ // never uploaded, or nothing to draw
177
+ continue;
178
+ }
179
+
180
+ const row = residencies.length * DRAW_STRIDE_FLOATS;
181
+
182
+ // the transform is its own matrix from element 0; `matrix` would hand out a fresh view
183
+ const transform = mesh.transform;
184
+ const model = row + MODEL_OFFSET_FLOATS;
185
+
186
+ for (let k = 0; k < 16; k++) {
187
+ constants[model + k] = transform[k];
188
+ }
189
+
190
+ const color = mesh.color;
191
+ const tint = row + COLOR_OFFSET_FLOATS;
192
+
193
+ constants[tint] = color[0];
194
+ constants[tint + 1] = color[1];
195
+ constants[tint + 2] = color[2];
196
+ constants[tint + 3] = color[3];
197
+
198
+ residencies.push(resident);
199
+ }
200
+
201
+ const draw_count = residencies.length;
202
+
203
+ for (let first = 0; first < draw_count; first += DYNAMIC_MESH_DRAWS_PER_CHUNK) {
204
+ const bytes = chunk_capacity(Math.min(DYNAMIC_MESH_DRAWS_PER_CHUNK, draw_count - first)) * DRAW_STRIDE;
205
+
206
+ const chunk = encoder.allocateTransientBuffer(GPUBufferUsage.STORAGE, bytes);
207
+
208
+ encoder.writeBuffer(chunk, 0, constants.buffer, constants.byteOffset + first * DRAW_STRIDE, bytes);
209
+
210
+ chunks.push(chunk);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Let go of the frame: the geometries it drew and the transient buffers it wrote, which the
216
+ * encoder hands back when it finishes. For a list that outlives its frame, once its last pass is
217
+ * recorded.
218
+ */
219
+ clear() {
220
+ this.#residencies.length = 0;
221
+ this.#chunks.length = 0;
222
+ }
223
+
224
+ /**
225
+ * Record every draw into `render_pass`: the constants' group once per chunk, then per draw the
226
+ * geometry's group and the draw at its row. The pass's other groups are the caller's to set.
227
+ *
228
+ * @param {ShadeGPURenderPassEncoder} render_pass running `pipeline`
229
+ * @param {GraphicsContext} graphics
230
+ * @param {RenderPipelineDescriptor} pipeline one of the dynamic-mesh pipelines, whose layout opens
231
+ * with `DYNAMIC_MESH_RESOURCES`' groups
232
+ * @param {GPUBuffer} camera
233
+ */
234
+ draw(render_pass, graphics, pipeline, camera) {
235
+ const residencies = this.#residencies;
236
+ const chunks = this.#chunks;
237
+
238
+ const draw_count = residencies.length;
239
+ const group = DYNAMIC_MESH_RESOURCES.groups[DYNAMIC_MESH_BIND_GROUP_DRAWS];
240
+
241
+ for (let c = 0; c < chunks.length; c++) {
242
+ graphics.setPipelineBindGroup(
243
+ render_pass,
244
+ pipeline,
245
+ DYNAMIC_MESH_BIND_GROUP_DRAWS,
246
+ group.generateBindings({ camera, draws: chunks[c] })
247
+ );
248
+
249
+ const first = c * DYNAMIC_MESH_DRAWS_PER_CHUNK;
250
+ const end = Math.min(first + DYNAMIC_MESH_DRAWS_PER_CHUNK, draw_count);
251
+
252
+ for (let i = first; i < end; i++) {
253
+ residencies[i].draw(render_pass, graphics, pipeline, i - first);
254
+ }
255
+ }
256
+ }
257
+ }