@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.
- package/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/core/collection/table/RowFirstTable.d.ts +8 -0
- package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
- package/src/core/collection/table/RowFirstTable.js +40 -11
- package/src/engine/graphics/ecs/sprite/Sprite.d.ts +3 -0
- package/src/engine/graphics/ecs/sprite/Sprite.d.ts.map +1 -1
- package/src/engine/graphics/ecs/sprite/Sprite.js +3 -0
- package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts +3 -0
- package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
- package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +3 -0
- package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -1
- package/src/engine/graphics3/DebugDrawSystem.js +6 -2
- package/src/engine/graphics3/PathDisplaySystem.d.ts.map +1 -1
- package/src/engine/graphics3/PathDisplaySystem.js +311 -307
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/RENDERER_CONTRACT.md +15 -2
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +5 -0
- package/src/shade/device/ShadeGPUCommandContext.d.ts +15 -2
- package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
- package/src/shade/device/ShadeGPUCommandContext.js +154 -25
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts +20 -3
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.js +21 -3
- package/src/shade/renderer/GraphicsContext.d.ts +20 -1
- package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
- package/src/shade/renderer/GraphicsContext.js +105 -52
- package/src/shade/renderer/Renderer.d.ts +19 -4
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +113 -2
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts +10 -7
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
- package/src/shade/renderer/animation/GPUAnimationManager.js +147 -45
- package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.d.ts +6 -5
- package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.d.ts.map +1 -1
- package/src/shade/renderer/buffer/GPUBufferReadbackAllocator.js +241 -240
- package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts +62 -2
- package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts.map +1 -1
- package/src/shade/renderer/buffer/GPUBufferStagingAllocator.js +306 -38
- package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts +60 -0
- package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts.map +1 -0
- package/src/shade/renderer/buffer/GPUBufferUploadAllocator.js +202 -0
- package/src/shade/renderer/buffer/GPUUploadBlock.d.ts +68 -0
- package/src/shade/renderer/buffer/GPUUploadBlock.d.ts.map +1 -0
- package/src/shade/renderer/buffer/GPUUploadBlock.js +97 -0
- package/src/shade/renderer/dynamic/DynamicMeshAVBOITChannel.d.ts +2 -1
- package/src/shade/renderer/dynamic/DynamicMeshAVBOITChannel.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/DynamicMeshAVBOITChannel.js +19 -17
- package/src/shade/renderer/dynamic/GPUDynamicMeshDrawList.d.ts +70 -0
- package/src/shade/renderer/dynamic/GPUDynamicMeshDrawList.d.ts.map +1 -0
- package/src/shade/renderer/dynamic/GPUDynamicMeshDrawList.js +257 -0
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts +20 -7
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +39 -41
- package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.d.ts +6 -20
- package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +42 -36
- package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.d.ts +2 -1
- package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/chunk_dynamic_mesh_avboit_vertex.js +4 -3
- package/src/shade/renderer/dynamic/chunk_dynamic_mesh_vertex.d.ts +4 -3
- package/src/shade/renderer/dynamic/chunk_dynamic_mesh_vertex.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/chunk_dynamic_mesh_vertex.js +7 -4
- package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.d.ts +14 -15
- package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/graph_dynamic_mesh_avboit.js +49 -83
- package/src/shade/renderer/dynamic/shader_dynamic_mesh.d.ts +19 -3
- package/src/shade/renderer/dynamic/shader_dynamic_mesh.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +26 -6
- package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.d.ts +2 -1
- package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_draw.js +12 -6
- package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.d.ts +3 -2
- package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.d.ts.map +1 -1
- package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.js +21 -13
- package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts +5 -0
- package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.js +19 -0
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +3 -1
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +3 -1
- package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.js +134 -88
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +57 -26
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +28 -20
- package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts +38 -0
- package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/graph_clear_raster_targets.js +105 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +20 -0
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +21 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +12 -20
- package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts +2 -1
- package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/graph_import_typed_buffer.js +15 -28
- package/src/shade/renderer/shader/type/serde/write_gpu_typed_buffer.d.ts.map +1 -1
- package/src/shade/renderer/shader/type/serde/write_gpu_typed_buffer.js +8 -1
- package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts +0 -73
- package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts.map +0 -1
- package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts +0 -59
- package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts.map +0 -1
- package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts +0 -22
- package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts.map +0 -1
- package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts +0 -26
- package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts.map +0 -1
- package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts +0 -25
- package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts.map +0 -1
- package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts +0 -26
- package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts.map +0 -1
- package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts +0 -29
- 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
|
|
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":"
|
|
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
|
|
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,
|
|
59
|
-
*
|
|
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
|
|
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
|
|
152
|
+
const frame = this.#frame_for(graph);
|
|
149
153
|
|
|
150
|
-
if (
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
+
}
|