@woosh/meep-engine 3.31.0 → 3.32.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 (72) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -1
  4. package/src/engine/graphics3/DebugDrawSystem.js +6 -2
  5. package/src/engine/graphics3/PathDisplaySystem.d.ts.map +1 -1
  6. package/src/engine/graphics3/PathDisplaySystem.js +311 -307
  7. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  8. package/src/shade/RENDERER_CONTRACT.md +15 -2
  9. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +5 -0
  10. package/src/shade/device/ShadeGPUCommandContext.d.ts +15 -2
  11. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  12. package/src/shade/device/ShadeGPUCommandContext.js +154 -25
  13. package/src/shade/renderer/GraphicsContext.d.ts +9 -1
  14. package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
  15. package/src/shade/renderer/GraphicsContext.js +54 -15
  16. package/src/shade/renderer/Renderer.d.ts +19 -4
  17. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  18. package/src/shade/renderer/Renderer.js +113 -2
  19. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +10 -7
  20. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  21. package/src/shade/renderer/animation/GPUAnimationManager.js +147 -45
  22. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts +11 -2
  23. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.d.ts.map +1 -1
  24. package/src/shade/renderer/buffer/GPUBufferStagingAllocator.js +95 -13
  25. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts +60 -0
  26. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.d.ts.map +1 -0
  27. package/src/shade/renderer/buffer/GPUBufferUploadAllocator.js +202 -0
  28. package/src/shade/renderer/buffer/GPUUploadBlock.d.ts +68 -0
  29. package/src/shade/renderer/buffer/GPUUploadBlock.d.ts.map +1 -0
  30. package/src/shade/renderer/buffer/GPUUploadBlock.js +97 -0
  31. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts +20 -7
  32. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.d.ts.map +1 -1
  33. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +20 -7
  34. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts +5 -0
  35. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts.map +1 -1
  36. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.js +19 -0
  37. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  38. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  39. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +3 -1
  40. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -1
  41. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +3 -1
  42. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.d.ts.map +1 -1
  43. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.js +134 -88
  44. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
  45. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +57 -26
  46. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  47. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +28 -20
  48. package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts +38 -0
  49. package/src/shade/renderer/rasterize/graph_clear_raster_targets.d.ts.map +1 -0
  50. package/src/shade/renderer/rasterize/graph_clear_raster_targets.js +105 -0
  51. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts +1 -1
  52. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  53. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +21 -1
  54. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  55. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +12 -20
  56. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts +2 -1
  57. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.d.ts.map +1 -1
  58. package/src/shade/renderer/shader/graph/graph_import_typed_buffer.js +15 -28
  59. package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts +0 -73
  60. package/src/core/geom/3d/atlas/atlas_bench_lib.d.ts.map +0 -1
  61. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts +0 -59
  62. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.d.ts.map +0 -1
  63. package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts +0 -22
  64. package/src/engine/graphics3/terrain/pack_terrain_row_table.d.ts.map +0 -1
  65. package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts +0 -26
  66. package/src/engine/graphics3/terrain/shader_terrain_splat.d.ts.map +0 -1
  67. package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts +0 -25
  68. package/src/shade/renderer/animation/ANIMATION_BOUND_TRACK_GROUP_STRUCT.d.ts.map +0 -1
  69. package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts +0 -26
  70. package/src/shade/renderer/animation/ANIMATION_KEYFRAME_BLOCK_STRUCT.d.ts.map +0 -1
  71. package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts +0 -29
  72. package/src/shade/renderer/animation/ANIMATION_SKIN_JOINT_BLOCK_STRUCT.d.ts.map +0 -1
@@ -64,11 +64,11 @@ export class FluidObstacleSystem extends System<any> {
64
64
  /**
65
65
  * @param {FluidComponent} fluid
66
66
  */
67
- static "__#415@#refresh_masks"(fluid: FluidComponent): void;
67
+ static "__#416@#refresh_masks"(fluid: FluidComponent): void;
68
68
  /**
69
69
  * @param {FluidComponent} fluid
70
70
  */
71
- static "__#415@#clear_field"(fluid: FluidComponent): void;
71
+ static "__#416@#clear_field"(fluid: FluidComponent): void;
72
72
  /**
73
73
  * Mark every cell of `fluid` whose centre lies within `inflation` of the
74
74
  * posed shape as solid. Iteration is clipped to the shape's world AABB
@@ -80,7 +80,7 @@ export class FluidObstacleSystem extends System<any> {
80
80
  * @param {number} inflation world-units SDF threshold
81
81
  * @param {Float64Array} point length-3 scratch
82
82
  */
83
- static "__#415@#voxelize"(fluid: FluidComponent, posed: PosedShape3D, aabb: Float64Array, inflation: number, point: Float64Array): void;
83
+ static "__#416@#voxelize"(fluid: FluidComponent, posed: PosedShape3D, aabb: Float64Array, inflation: number, point: Float64Array): void;
84
84
  /**
85
85
  * Write the obstacle's translation velocity onto every face of every cell
86
86
  * it voxelized — the moving-wall boundary condition. Runs AFTER the mask
@@ -100,7 +100,7 @@ export class FluidObstacleSystem extends System<any> {
100
100
  * @param {number} wvy
101
101
  * @param {number} wvz
102
102
  */
103
- static "__#415@#stamp_wall_velocity"(fluid: FluidComponent, posed: PosedShape3D, aabb: Float64Array, inflation: number, point: Float64Array, wvx: number, wvy: number, wvz: number): void;
103
+ static "__#416@#stamp_wall_velocity"(fluid: FluidComponent, posed: PosedShape3D, aabb: Float64Array, inflation: number, point: Float64Array, wvx: number, wvy: number, wvz: number): void;
104
104
  constructor();
105
105
  dependencies: (typeof FluidObstacle)[];
106
106
  components_used: (ResourceAccessSpecification<typeof Transform64> | ResourceAccessSpecification<typeof RigidBody> | ResourceAccessSpecification<typeof Collider> | ResourceAccessSpecification<typeof FluidComponent> | ResourceAccessSpecification<typeof FluidObstacle>)[];
@@ -398,14 +398,18 @@ class PathExtension extends RenderExtension {
398
398
  }
399
399
 
400
400
  const scene = frame.get(SceneColor);
401
+ const view = frame.get(ViewTextures);
401
402
 
402
- scene.color = this.#renderer.graph_draw({
403
+ const drawn = this.#renderer.graph_draw({
403
404
  graph: frame.graph,
404
405
  batch: this.#batch,
405
406
  color: scene.color,
406
- depth: frame.get(ViewTextures).depth,
407
+ depth: view.depth,
407
408
  camera: frame.view.camera.buffer
408
409
  });
410
+
411
+ scene.color = drawn.color;
412
+ view.depth = drawn.depth;
409
413
  }
410
414
  }
411
415
 
@@ -590,6 +594,15 @@ which is what effects want. Set it and the pass writes depth instead: a solid su
590
594
  build behind it has no depth ordering otherwise, and no CPU sort can give it one, because a painter's
591
595
  order over triangles is wrong wherever two of them interpenetrate.
592
596
 
597
+ `graph_draw` hands back `{ color, depth }`, and an extension publishes both, as in §6. The depth is a
598
+ new handle when the pass wrote it. Anything reading the scene depth after you — depth of field,
599
+ motion blur, the NSS upscaler, the next extension — has to be given that handle; a development build
600
+ refuses the frame when it is not. Left `false`, the depth comes back as it went in.
601
+
602
+ That is also what decides the phase for solid content. At `AfterLighting` transparency has not run
603
+ yet, and it tests against the depth you published, so glass in front of the batch covers it. At
604
+ `AfterTransparency` the glass is already resolved into the colour, and the batch is drawn over it.
605
+
593
606
  Residency is keyed on **the geometry**, not on batch membership, so a mesh that blinks off for a
594
607
  frame or is removed and re-added keeps its buffers.
595
608
 
@@ -562,3 +562,8 @@ harness. Keeping the door open costs nothing; walking through it preemptively co
562
562
  call sites are the strongest case for immediates, but the standing TODO there is a separate bug —
563
563
  the 2025-09-13 note says the obvious fix produced garbage and was never chased down. Immediates
564
564
  route around it for the ≤64-byte cases without explaining it.
565
+ Answered 2026-09-26: fixed independently. The garbage was frame-graph aliasing — a `queue.writeBuffer`
566
+ into a pooled buffer lands ahead of the whole submission, and a pass earlier in it had written that
567
+ buffer for another resource. The import now writes with `writeValueBuffer`, in order, and creates no
568
+ buffer per frame; `graph_import_typed_buffer.spec.js` pins both. Immediates would still drop the
569
+ buffer altogether for the ≤64-byte cases.
@@ -268,6 +268,8 @@ export class ShadeGPUCommandContext {
268
268
  */
269
269
  allocateTransientValueBuffer<T>(type: WebGPUType, value: T, usage?: number | GPUBufferUsage): GPUBuffer;
270
270
  /**
271
+ * Like {@link writeBuffer}, with the bytes serialised from `value` as `type` lays them out.
272
+ *
271
273
  * @template T
272
274
  * @param {GPUBuffer} buffer
273
275
  * @param {number} buffer_offset
@@ -276,12 +278,22 @@ export class ShadeGPUCommandContext {
276
278
  */
277
279
  writeValueBuffer<T_1>(buffer: GPUBuffer, buffer_offset: number, type: WebGPUType, value: T_1): void;
278
280
  /**
279
- * Very similar to {@link GPUQueue.writeBuffer}, but instead of doing the operation direction on the queue - uses a staging buffer under the hood and performs the actual write operation as part of the commmand buffer
281
+ * Write bytes into `buffer` at this point in the command stream: after everything recorded
282
+ * before this call, and before everything recorded after it.
283
+ *
284
+ * That is the difference from {@link GPUQueue#writeBuffer}, which lands ahead of the whole
285
+ * submission — ahead of passes recorded earlier in it that may still need the old contents. Here
286
+ * the bytes are staged, and a copy out of the staging is recorded in their place. Small writes
287
+ * are staged in this context's upload blocks, which reach the GPU with one queue write each when
288
+ * the context finishes, so a frame of thousands of them costs no mapping at all; writes of
289
+ * {@link MAPPED_UPLOAD_THRESHOLD} bytes or more go through a mapped staging buffer each, which is
290
+ * the faster road for bytes.
291
+ *
280
292
  * @param {GPUBuffer} buffer
281
293
  * @param {number} buffer_offset
282
294
  * @param {ArrayBuffer} data
283
295
  * @param {number} data_offset
284
- * @param {number} size
296
+ * @param {number} size a multiple of 4, as for any buffer copy
285
297
  */
286
298
  writeBuffer(buffer: GPUBuffer, buffer_offset: number, data: ArrayBuffer, data_offset: number, size: number): void;
287
299
  /**
@@ -297,6 +309,7 @@ export type GraphicsContext = import("../renderer/GraphicsContext.js").GraphicsC
297
309
  export type ShaderResourceSetDescriptor = import("../renderer/shader/resource/ShaderResourceSetDescriptor.js").ShaderResourceSetDescriptor;
298
310
  export type WebGPUType = import("../renderer/shader/type/WebGPUType.js").WebGPUType;
299
311
  export type TextureManager = import("../renderer/texture/TextureManager.js").TextureManager;
312
+ export type GPUUploadBlock = import("../renderer/buffer/GPUUploadBlock.js").GPUUploadBlock;
300
313
  export type GPUFrameRecorder = import("./timing/profile/GPUFrameRecorder.js").GPUFrameRecorder;
301
314
  import { GraphicsExecutionContext } from "./graph/GraphicsExecutionContext.js";
302
315
  import { ShadeGPUComputePassEncoder } from "./pass/ShadeGPUComputePassEncoder.js";
@@ -1 +1 @@
1
- {"version":3,"file":"ShadeGPUCommandContext.d.ts","sourceRoot":"","sources":["../../../../src/shade/device/ShadeGPUCommandContext.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ShadeGPUCommandContext.d.ts","sourceRoot":"","sources":["../../../../src/shade/device/ShadeGPUCommandContext.js"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ import { ShadeGPURenderPassEncoder } from "./pass/ShadeGPURenderPassEncoder.js";
10
10
  import { ShadeGPUContextProfiler } from "./timing/ShadeGPUContextProfiler.js";
11
11
  import { warn_limited } from "../util/warn_limited.js";
12
12
  import { write_gpu_typed_buffer } from "../renderer/shader/type/serde/write_gpu_typed_buffer.js";
13
+ import { MAPPED_UPLOAD_THRESHOLD } from "../renderer/buffer/GPUBufferUploadAllocator.js";
13
14
  import { BufferResourceDescriptor } from "./graph/BufferResourceDescriptor.js";
14
15
  import { GraphicsExecutionContext } from "./graph/GraphicsExecutionContext.js";
15
16
 
@@ -24,6 +25,7 @@ import { GraphicsExecutionContext } from "./graph/GraphicsExecutionContext.js";
24
25
  * @typedef {import("../renderer/shader/resource/ShaderResourceSetDescriptor.js").ShaderResourceSetDescriptor} ShaderResourceSetDescriptor
25
26
  * @typedef {import("../renderer/shader/type/WebGPUType.js").WebGPUType} WebGPUType
26
27
  * @typedef {import("../renderer/texture/TextureManager.js").TextureManager} TextureManager
28
+ * @typedef {import("../renderer/buffer/GPUUploadBlock.js").GPUUploadBlock} GPUUploadBlock
27
29
  * @typedef {import("./timing/profile/GPUFrameRecorder.js").GPUFrameRecorder} GPUFrameRecorder
28
30
  */
29
31
 
@@ -180,6 +182,17 @@ export class ShadeGPUCommandContext {
180
182
  #transient_buffers = [];
181
183
 
182
184
  /**
185
+ * Upload blocks this context has written its small uploads into, the one being filled last. Sent
186
+ * to the GPU just before the submit, one queue write per block, and given back when the context
187
+ * finishes.
188
+ *
189
+ * @type {GPUUploadBlock[]}
190
+ */
191
+ #upload_blocks = [];
192
+
193
+ /**
194
+ * Mapped staging buffers this context's large uploads went through, one each. Given back when the
195
+ * context finishes, and mapped again by their pool before it hands them out anew.
183
196
  *
184
197
  * @type {GPUBuffer[]}
185
198
  */
@@ -727,6 +740,8 @@ export class ShadeGPUCommandContext {
727
740
  }
728
741
 
729
742
  /**
743
+ * Like {@link writeBuffer}, with the bytes serialised from `value` as `type` lays them out.
744
+ *
730
745
  * @template T
731
746
  * @param {GPUBuffer} buffer
732
747
  * @param {number} buffer_offset
@@ -737,30 +752,48 @@ export class ShadeGPUCommandContext {
737
752
 
738
753
  assert.notOk(this.#finished, 'already finished');
739
754
 
740
- const gfx = this.#graphics;
741
-
742
755
  const size = type.aligned_size;
743
756
 
744
- const stage = gfx.buffer_allocator_staging.get(size);
757
+ if (size >= MAPPED_UPLOAD_THRESHOLD) {
758
+ const stage = this.#graphics.buffer_allocator_staging.get(size);
759
+
760
+ // recorded before it is filled, so a fill that throws still gives it back
761
+ this.#staging_buffers.push(stage);
745
762
 
746
- const mapped = stage.getMappedRange(0, size);
763
+ write_gpu_typed_buffer(value, type, stage.getMappedRange(0, size), 0);
747
764
 
748
- write_gpu_typed_buffer(value, type, mapped, 0);
765
+ stage.unmap();
749
766
 
750
- stage.unmap();
767
+ this.copyBufferToBuffer(stage, 0, buffer, buffer_offset, size);
751
768
 
752
- this.#staging_buffers.push(stage);
769
+ return;
770
+ }
771
+
772
+ const block = this.#upload_block_with_room(size);
773
+ const offset = block.allocate(size);
774
+
775
+ write_gpu_typed_buffer(value, type, block.data, offset);
753
776
 
754
- this.copyBufferToBuffer(stage, 0, buffer, buffer_offset, size);
777
+ this.copyBufferToBuffer(block.buffer, offset, buffer, buffer_offset, size);
755
778
  }
756
779
 
757
780
  /**
758
- * Very similar to {@link GPUQueue.writeBuffer}, but instead of doing the operation direction on the queue - uses a staging buffer under the hood and performs the actual write operation as part of the commmand buffer
781
+ * Write bytes into `buffer` at this point in the command stream: after everything recorded
782
+ * before this call, and before everything recorded after it.
783
+ *
784
+ * That is the difference from {@link GPUQueue#writeBuffer}, which lands ahead of the whole
785
+ * submission — ahead of passes recorded earlier in it that may still need the old contents. Here
786
+ * the bytes are staged, and a copy out of the staging is recorded in their place. Small writes
787
+ * are staged in this context's upload blocks, which reach the GPU with one queue write each when
788
+ * the context finishes, so a frame of thousands of them costs no mapping at all; writes of
789
+ * {@link MAPPED_UPLOAD_THRESHOLD} bytes or more go through a mapped staging buffer each, which is
790
+ * the faster road for bytes.
791
+ *
759
792
  * @param {GPUBuffer} buffer
760
793
  * @param {number} buffer_offset
761
794
  * @param {ArrayBuffer} data
762
795
  * @param {number} data_offset
763
- * @param {number} size
796
+ * @param {number} size a multiple of 4, as for any buffer copy
764
797
  */
765
798
  writeBuffer(buffer, buffer_offset, data, data_offset, size) {
766
799
  assert.defined(buffer, 'buffer');
@@ -769,20 +802,95 @@ export class ShadeGPUCommandContext {
769
802
  assert.isNonNegativeInteger(data_offset, 'data_offset');
770
803
  assert.isNonNegativeInteger(size, 'size');
771
804
 
772
- const gfx = this.#graphics;
805
+ assert.notOk(this.#finished, 'already finished');
806
+
807
+ if (size % 4 !== 0) {
808
+ // Refused here rather than left to the copy, as a mapped staging buffer of this size
809
+ // always was: a copy moves whole 4-byte words, and one that cannot takes the context's
810
+ // whole submission down with it, later, without naming the write that did it.
811
+ throw new RangeError(`writeBuffer: size ${size} is not a multiple of 4`);
812
+ }
813
+
814
+ if (size === 0) {
815
+ // a copy of nothing changes nothing
816
+ return;
817
+ }
818
+
819
+ if (size >= MAPPED_UPLOAD_THRESHOLD) {
820
+ const stage = this.#graphics.buffer_allocator_staging.get(size);
773
821
 
774
- const stage = gfx.buffer_allocator_staging.get(size);
822
+ // recorded before it is filled, so a fill that throws still gives it back
823
+ this.#staging_buffers.push(stage);
775
824
 
776
- const mapped = stage.getMappedRange(0, size);
825
+ array_buffer_copy(data, data_offset, stage.getMappedRange(0, size), 0, size);
777
826
 
778
- array_buffer_copy(data, data_offset, mapped, 0, size);
827
+ stage.unmap();
779
828
 
780
- stage.unmap();
829
+ this.copyBufferToBuffer(stage, 0, buffer, buffer_offset, size);
781
830
 
782
- this.#staging_buffers.push(stage);
831
+ return;
832
+ }
783
833
 
784
- this.copyBufferToBuffer(stage, 0, buffer, buffer_offset, size);
834
+ const block = this.#upload_block_with_room(size);
835
+ const offset = block.allocate(size);
785
836
 
837
+ array_buffer_copy(data, data_offset, block.data, offset, size);
838
+
839
+ this.copyBufferToBuffer(block.buffer, offset, buffer, buffer_offset, size);
840
+ }
841
+
842
+ /**
843
+ * The block to take the next `size` bytes of upload from: the one being filled, or a fresh one
844
+ * when that is full. Every write that comes here is smaller than
845
+ * {@link MAPPED_UPLOAD_THRESHOLD}, so any empty block holds it.
846
+ *
847
+ * A block that cannot take the next write is not revisited: what is left in it is wasted until
848
+ * the context finishes, which bounds the waste at one block's tail per block and keeps every
849
+ * write a single contiguous copy.
850
+ *
851
+ * @param {number} size
852
+ * @returns {GPUUploadBlock}
853
+ */
854
+ #upload_block_with_room(size) {
855
+ const blocks = this.#upload_blocks;
856
+ const last = blocks.length - 1;
857
+
858
+ if (last >= 0 && blocks[last].fits(size)) {
859
+ return blocks[last];
860
+ }
861
+
862
+ const block = this.#graphics.buffer_allocator_upload.get();
863
+
864
+ blocks.push(block);
865
+
866
+ return block;
867
+ }
868
+
869
+ /**
870
+ * Put this context's upload blocks on the GPU: one queue write per block, of the part written.
871
+ *
872
+ * Must come immediately before the submit, and this is the whole of why a block can go back to
873
+ * its pool the moment its context finishes rather than when the GPU is done with it. The queue
874
+ * runs its operations in order, so a write issued here lands after everything submitted before
875
+ * this context — including the copies of any earlier context that held the same block — and
876
+ * before this context's own copies read it. Nothing is mapped, so nothing is waited for.
877
+ *
878
+ * A queue write lands ahead of its whole submission, which is why it goes only into a block and
879
+ * never into a destination: blocks come from a pool of their own that nothing aliases, while
880
+ * the frame graph's buffers are aliased within a submission, and a queue write into one of those
881
+ * would overwrite what an earlier pass in it still needs.
882
+ *
883
+ * @param {GPUQueue} queue
884
+ */
885
+ #send_uploads(queue) {
886
+ const blocks = this.#upload_blocks;
887
+ const count = blocks.length;
888
+
889
+ for (let i = 0; i < count; i++) {
890
+ const block = blocks[i];
891
+
892
+ queue.writeBuffer(block.buffer, 0, block.data, 0, block.used);
893
+ }
786
894
  }
787
895
 
788
896
  #cleanup() {
@@ -802,11 +910,29 @@ export class ShadeGPUCommandContext {
802
910
 
803
911
  }
804
912
 
805
- // staging buffers
913
+ // Blocks go back now rather than when the GPU is done with them — see #send_uploads.
914
+ const buffer_allocator_upload = this.#graphics.buffer_allocator_upload;
915
+
916
+ for (const block of this.#upload_blocks) {
917
+ buffer_allocator_upload.release(block);
918
+ }
919
+
920
+ this.#upload_blocks.length = 0;
921
+
922
+ // staging buffers are mapped again by their pool, which is when they are free again
806
923
  const buffer_allocator_staging = this.#graphics.buffer_allocator_staging;
924
+
807
925
  for (const staging of this.#staging_buffers) {
926
+ if (staging.mapState === "mapped") {
927
+ // a write that threw while filling it, so no copy out of it was recorded; unmapped
928
+ // here so its pool maps it afresh rather than handing out a range already given out
929
+ staging.unmap();
930
+ }
931
+
808
932
  buffer_allocator_staging.release(staging);
809
933
  }
934
+
935
+ this.#staging_buffers.length = 0;
810
936
  }
811
937
 
812
938
  /**
@@ -844,19 +970,22 @@ export class ShadeGPUCommandContext {
844
970
 
845
971
  GPUCommandEncoder.finish() throws when the encoder is locked — any pass left un-end()ed
846
972
  anywhere in the frame graph — and queue.submit throws on a malformed argument. This used
847
- to be a bare sequence, so either throw skipped #cleanup() and every buffer in
848
- #transient_buffers and #staging_buffers stayed checked out of its allocator for good: one
849
- staging buffer per writeBuffer call, which is most of the uniform traffic, every frame,
850
- until the process ran out of GPU memory. onFinished never fired either, so a promise
851
- already awaiting done never woke and profiling_absorbed hung with it — a recoverable
852
- encoding bug turned into an unbounded leak plus a hung frame, with the original error
853
- hidden behind the hang.
973
+ to be a bare sequence, so either throw skipped #cleanup() and every buffer the context held
974
+ stayed checked out of its allocator for good — then, a staging buffer per writeBuffer
975
+ call, which is most of the uniform traffic, every frame, until the process ran out of GPU
976
+ memory. onFinished never fired either, so a promise already awaiting done never woke and
977
+ profiling_absorbed hung with it — a recoverable encoding bug turned into an unbounded leak
978
+ plus a hung frame, with the original error hidden behind the hang.
854
979
 
855
980
  The error still propagates: it is the thing that names the misplaced end().
856
981
  */
857
982
  try {
858
983
  const command_buffer = encoder.finish();
859
984
 
985
+ // after finish(), so a command buffer that could not be made sends nothing, and
986
+ // immediately before the submit, which is what makes the blocks safe to hand back
987
+ this.#send_uploads(queue);
988
+
860
989
  queue.submit([command_buffer]);
861
990
  } finally {
862
991
  // cleanup allocated memory
@@ -64,10 +64,17 @@ export class GraphicsContext {
64
64
  */
65
65
  buffer_allocator_native: GPUBufferAllocatorNative;
66
66
  /**
67
- * Used to upload data to the GPU
67
+ * Mapped `MAP_WRITE` buffers, one per upload: where a {@link ShadeGPUCommandContext} sends its
68
+ * large writes, for which a mapping is the faster road (see `MAPPED_UPLOAD_THRESHOLD`).
68
69
  * @type {GPUBufferStagingAllocator}
69
70
  */
70
71
  buffer_allocator_staging: GPUBufferStagingAllocator;
72
+ /**
73
+ * Upload blocks for {@link ShadeGPUCommandContext}'s small writes — every uniform a pass is
74
+ * handed by value among them. Sent with one queue write per block, not mapped.
75
+ * @type {GPUBufferUploadAllocator}
76
+ */
77
+ buffer_allocator_upload: GPUBufferUploadAllocator;
71
78
  /**
72
79
  * Used to read data back from the GPU: `MAP_READ` buffers, held by the CPU across frames, which
73
80
  * is why they cannot come from {@link buffer_allocator_main}.
@@ -142,6 +149,7 @@ import { BindGroupManager } from "./binding/BindGroupManager.js";
142
149
  import { GPUBufferAllocator } from "./buffer/GPUBufferAllocator.js";
143
150
  import { GPUBufferAllocatorNative } from "./buffer/GPUBufferAllocatorNative.js";
144
151
  import { GPUBufferStagingAllocator } from "./buffer/GPUBufferStagingAllocator.js";
152
+ import { GPUBufferUploadAllocator } from "./buffer/GPUBufferUploadAllocator.js";
145
153
  import { GPUBufferReadbackAllocator } from "./buffer/GPUBufferReadbackAllocator.js";
146
154
  import { GPUTextureAllocator } from "./texture/GPUTextureAllocator.js";
147
155
  import { GPUBufferContext } from "./buffer/GPUBufferContext.js";
@@ -1 +1 @@
1
- {"version":3,"file":"GraphicsContext.d.ts","sourceRoot":"","sources":["../../../../src/shade/renderer/GraphicsContext.js"],"names":[],"mappings":"AAkCA;IA2JI,yBAgCC;IA1LD;;OAEG;IACH,YAFU,kBAAkB,CAEjB;IAEX;;OAEG;IACH,WAFU,kBAAkB,CAElB;IAOV;;OAEG;IACH,UAFU,iBAAiB,CAElB;IAET;;;OAGG;IACH,qDAOC;IAOD;;;OAGG;IACH,6CAEC;IAED;;OAEG;IACH,kBAFU,qBAAqB,CAEd;IAEjB;;OAEG;IACH,kBAFU,qBAAqB,CAEd;IAEjB;;OAEG;IACH,mBAFU,sBAAsB,CAEd;IAGlB;;OAEG;IACH,gBAFU,oBAAoB,CAEf;IAEf;;OAEG;IACH,UAFU,cAAc,CAEf;IAET;;;;OAIG;IACH,kBAFU,qBAAqB,CAEd;IAEjB;;OAEG;IACH,aAFU,gBAAgB,CAEd;IAQZ;;;OAGG;IACH,uBAFU,kBAAkB,CAEN;IAEtB;;;;OAIG;IACH,yBAFU,wBAAwB,CAEV;IAExB;;;OAGG;IACH,0BAFU,yBAAyB,CAEV;IAEzB;;;;OAIG;IACH,2BAFU,0BAA0B,CAEV;IAE1B;;;OAGG;IACH,oBAFU,mBAAmB,CAEV;IAUnB;;;OAGG;IACH,8BAEC;IAED,uBAMC;IAED;;;OAGG;IACH,wBAEC;IAoCD;;;;;;;;OAQG;IACH,6DANW,sBAAsB,aACtB,oBAAoB,EAAE,cACtB;QAAC,MAAM,EAAE,YAAY,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,QAmE9D;IAED;;;;OAIG;IACH,gCAHW,gBAAgB,GACd,gBAAgB,CAI5B;IAED;;OAEG;IACH,+BAeC;IAED,4BAEC;IAED,eAyBC;IAED,gBAmBC;IAGL;;;OAGG;IACH,4BAFU,OAAO,CAE0B;;CAN1C;+BAjWY,OAAO,0CAA0C,EAAE,gBAAgB;qCACnE,OAAO,kDAAkD,EAAE,sBAAsB;2BAKjF,OAAO,mCAAmC,EAAE,YAAY;mCAjBlC,kCAAkC;mCAGlC,kCAAkC;kCADnC,wBAAwB;2CAEf,mDAAmD;oCAH1D,2CAA2C;sCAZzC,6CAA6C;sCAC7C,6CAA6C;uCAF5C,8CAA8C;qCAIhD,0CAA0C;+BAehD,6BAA6B;sCACtB,4CAA4C;iCAdjD,+BAA+B;mCAC7B,gCAAgC;yCAC1B,sCAAsC;0CAGrC,uCAAuC;2CADtC,wCAAwC;oCAQ/C,kCAAkC;iCATtB,8BAA8B"}
1
+ {"version":3,"file":"GraphicsContext.d.ts","sourceRoot":"","sources":["../../../../src/shade/renderer/GraphicsContext.js"],"names":[],"mappings":"AAmCA;IAoKI,yBAiCC;IApMD;;OAEG;IACH,YAFU,kBAAkB,CAEjB;IAEX;;OAEG;IACH,WAFU,kBAAkB,CAElB;IAOV;;OAEG;IACH,UAFU,iBAAiB,CAElB;IAET;;;OAGG;IACH,qDAOC;IAOD;;;OAGG;IACH,6CAEC;IAED;;OAEG;IACH,kBAFU,qBAAqB,CAEd;IAEjB;;OAEG;IACH,kBAFU,qBAAqB,CAEd;IAEjB;;OAEG;IACH,mBAFU,sBAAsB,CAEd;IAGlB;;OAEG;IACH,gBAFU,oBAAoB,CAEf;IAEf;;OAEG;IACH,UAFU,cAAc,CAEf;IAET;;;;OAIG;IACH,kBAFU,qBAAqB,CAEd;IAEjB;;OAEG;IACH,aAFU,gBAAgB,CAEd;IAQZ;;;OAGG;IACH,uBAFU,kBAAkB,CAEN;IAEtB;;;;OAIG;IACH,yBAFU,wBAAwB,CAEV;IAExB;;;;OAIG;IACH,0BAFU,yBAAyB,CAEV;IAEzB;;;;OAIG;IACH,yBAFU,wBAAwB,CAEV;IAExB;;;;OAIG;IACH,2BAFU,0BAA0B,CAEV;IAE1B;;;OAGG;IACH,oBAFU,mBAAmB,CAEV;IAUnB;;;OAGG;IACH,8BAEC;IAED,uBAOC;IAED;;;OAGG;IACH,wBAEC;IAqCD;;;;;;;;OAQG;IACH,6DANW,sBAAsB,aACtB,oBAAoB,EAAE,cACtB;QAAC,MAAM,EAAE,YAAY,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,QAmE9D;IAED;;;;OAIG;IACH,gCAHW,gBAAgB,GACd,gBAAgB,CAI5B;IAED;;OAEG;IACH,+BAgBC;IAED,4BAEC;IAED,eAmDC;IAED,gBAoBC;IAGL;;;OAGG;IACH,4BAFU,OAAO,CAE0B;;CAN1C;+BAvYY,OAAO,0CAA0C,EAAE,gBAAgB;qCACnE,OAAO,kDAAkD,EAAE,sBAAsB;2BAKjF,OAAO,mCAAmC,EAAE,YAAY;mCAjBlC,kCAAkC;mCAGlC,kCAAkC;kCADnC,wBAAwB;2CAEf,mDAAmD;oCAH1D,2CAA2C;sCAbzC,6CAA6C;sCAC7C,6CAA6C;uCAF5C,8CAA8C;qCAIhD,0CAA0C;+BAgBhD,6BAA6B;sCACtB,4CAA4C;iCAfjD,+BAA+B;mCAC7B,gCAAgC;yCAC1B,sCAAsC;0CAGrC,uCAAuC;yCACxC,sCAAsC;2CAFpC,wCAAwC;oCAS/C,kCAAkC;iCAVtB,8BAA8B"}
@@ -11,6 +11,7 @@ import { GPUBufferAllocatorNative } from "./buffer/GPUBufferAllocatorNative.js";
11
11
  import { create_buffer, GPUBufferContext } from "./buffer/GPUBufferContext.js";
12
12
  import { GPUBufferReadbackAllocator } from "./buffer/GPUBufferReadbackAllocator.js";
13
13
  import { GPUBufferStagingAllocator } from "./buffer/GPUBufferStagingAllocator.js";
14
+ import { GPUBufferUploadAllocator } from "./buffer/GPUBufferUploadAllocator.js";
14
15
  import { GPUGeometryManager } from "./geometry/GPUGeometryManager.js";
15
16
  import { GPUCollectionLimits } from "./rasterize/expand/GPUCollectionLimits.js";
16
17
  import { GPUSamplerManager } from "./GPUSamplerManager.js";
@@ -137,11 +138,19 @@ export class GraphicsContext {
137
138
  buffer_allocator_native;
138
139
 
139
140
  /**
140
- * Used to upload data to the GPU
141
+ * Mapped `MAP_WRITE` buffers, one per upload: where a {@link ShadeGPUCommandContext} sends its
142
+ * large writes, for which a mapping is the faster road (see `MAPPED_UPLOAD_THRESHOLD`).
141
143
  * @type {GPUBufferStagingAllocator}
142
144
  */
143
145
  buffer_allocator_staging;
144
146
 
147
+ /**
148
+ * Upload blocks for {@link ShadeGPUCommandContext}'s small writes — every uniform a pass is
149
+ * handed by value among them. Sent with one queue write per block, not mapped.
150
+ * @type {GPUBufferUploadAllocator}
151
+ */
152
+ buffer_allocator_upload;
153
+
145
154
  /**
146
155
  * Used to read data back from the GPU: `MAP_READ` buffers, held by the CPU across frames, which
147
156
  * is why they cannot come from {@link buffer_allocator_main}.
@@ -174,8 +183,9 @@ export class GraphicsContext {
174
183
  increment_time() {
175
184
  this.#timer_increment++;
176
185
 
177
- // advance the single frame clock for both substitutable pools in lockstep
186
+ // advance the single frame clock for the substitutable pools in lockstep
178
187
  this.buffer_allocator_main.increment_time();
188
+ this.buffer_allocator_upload.increment_time();
179
189
  this.allocator_textures.increment_time();
180
190
  }
181
191
 
@@ -197,6 +207,7 @@ export class GraphicsContext {
197
207
  this.buffer_allocator_main = new GPUBufferAllocator(device);
198
208
  this.buffer_allocator_native = new GPUBufferAllocatorNative(device);
199
209
  this.buffer_allocator_staging = new GPUBufferStagingAllocator(device);
210
+ this.buffer_allocator_upload = new GPUBufferUploadAllocator(device);
200
211
  this.buffer_allocator_readback = new GPUBufferReadbackAllocator(device);
201
212
 
202
213
  this.allocator_textures = new GPUTextureAllocator(device);
@@ -316,6 +327,7 @@ export class GraphicsContext {
316
327
 
317
328
  r += this.buffer_allocator_main.gpu_memory_usage;
318
329
  r += this.buffer_allocator_staging.gpu_memory_usage;
330
+ r += this.buffer_allocator_upload.gpu_memory_usage;
319
331
  r += this.buffer_allocator_readback.gpu_memory_usage;
320
332
 
321
333
  r += this.allocator_textures.gpu_memory_usage;
@@ -331,28 +343,54 @@ export class GraphicsContext {
331
343
  update() {
332
344
  const command_ctx = ShadeGPUCommandContext.create(this, 'GraphicsContext.update');
333
345
 
334
- // The one place per frame the meshlet pool compacts, at the head of the frame: before the
335
- // clone flush in geometries.update, before the VG runtime's flush, before any pass. Its
336
- // per-frame bound is a bound because this is the only call (COMPACTION_PLAN_2026_09_16
337
- // §4.5).
338
- this.geometries.meshlets.sweep(command_ctx);
346
+ /**
347
+ * What the update raised, if anything. Held so the context is finished before it is
348
+ * re-raised: a context never finished keeps everything it took for good — an upload block,
349
+ * transient buffers — and a manager that throws every frame would leak that much every
350
+ * frame.
351
+ * @type {*}
352
+ */
353
+ let update_error = null;
354
+
355
+ try {
356
+ // The one place per frame the meshlet pool compacts, at the head of the frame: before the
357
+ // clone flush in geometries.update, before the VG runtime's flush, before any pass. Its
358
+ // per-frame bound is a bound because this is the only call (COMPACTION_PLAN_2026_09_16
359
+ // §4.5).
360
+ this.geometries.meshlets.sweep(command_ctx);
361
+
362
+ this.geometries.update(command_ctx);
339
363
 
340
- this.geometries.update(command_ctx);
364
+ this.textures.update();
365
+ this.materials.update(command_ctx);
341
366
 
342
- this.textures.update();
343
- this.materials.update(command_ctx);
367
+ this.bind_groups.update();
344
368
 
345
- this.bind_groups.update();
369
+ this.increment_time();
346
370
 
347
- this.increment_time();
371
+ this.allocator_textures.update();
348
372
 
349
- this.allocator_textures.update();
373
+ this.#collection_limits.update(command_ctx, this.buffer_allocator_main);
374
+ } catch (e) {
375
+ update_error = e;
376
+ }
350
377
 
351
- this.#collection_limits.update(command_ctx, this.buffer_allocator_main);
378
+ try {
379
+ command_ctx.finish();
380
+ } catch (teardown_error) {
381
+ if (update_error === null) {
382
+ throw teardown_error;
383
+ }
352
384
 
353
- command_ctx.finish();
385
+ // already unwinding: the update's own error is the one the caller needs to see
386
+ }
387
+
388
+ if (update_error !== null) {
389
+ throw update_error;
390
+ }
354
391
 
355
392
  this.buffer_allocator_main.update();
393
+ this.buffer_allocator_upload.update();
356
394
  }
357
395
 
358
396
  destroy() {
@@ -371,6 +409,7 @@ export class GraphicsContext {
371
409
  this.buffer_allocator_main.destroy();
372
410
  this.buffer_allocator_native.destroy();
373
411
  this.buffer_allocator_staging.destroy();
412
+ this.buffer_allocator_upload.destroy();
374
413
  this.buffer_allocator_readback.destroy();
375
414
 
376
415
  this.#collection_limits.destroy();
@@ -210,7 +210,18 @@ export class Renderer {
210
210
  * @type {Signal<ShadeDeviceFailure>}
211
211
  */
212
212
  onDeviceFailure: any;
213
- onFrameFinished: Signal;
213
+ /**
214
+ * Sent at the end of every frame {@link render} or {@link render_to_target} completes, with the
215
+ * number of frames completed so far. A frame that throws is not one of them: whatever it had
216
+ * recorded may still have been submitted, but it sends nothing and is not counted.
217
+ *
218
+ * **Submitted, not finished.** The frame's commands have been handed to the queue and the GPU
219
+ * may not have started them; a listener that needs the GPU to be done with the frame waits on
220
+ * {@link GPUQueue#onSubmittedWorkDone} instead.
221
+ *
222
+ * @type {Signal<number>}
223
+ */
224
+ onFrameFinished: any;
214
225
  /**
215
226
  *
216
227
  * @param {number} v
@@ -440,7 +451,9 @@ export class Renderer {
440
451
  * simulation, the animation tick and exposure adaptation all step by it. Zero by default, which
441
452
  * draws the current state and moves nothing; a fixed fraction of a second here instead would run
442
453
  * the simulation at that rate no matter what the caller's clock was doing.
443
- * @returns {boolean} whether frame was rendered, will return false if previous frame hasn't finished yet
454
+ * @returns {boolean} whether a frame was encoded and submitted. False only when there is nothing
455
+ * to draw with — the device was lost, or the renderer destroyed. Frames the GPU has not finished
456
+ * yet neither hold this one back nor make it return false.
444
457
  */
445
458
  render(camera: PerspectiveCamera, scene: Scene, time_delta_seconds?: number): boolean;
446
459
  /**
@@ -457,11 +470,14 @@ export class Renderer {
457
470
  * @param {Scene} scene
458
471
  * @param {number} time_delta_seconds
459
472
  * @param {GPUTextureContext} target must match {@link output_resolution}
460
- * @returns {boolean} whether frame was rendered, will return false if previous frame hasn't finished yet
473
+ * @returns {boolean} whether a frame was encoded and submitted. False only when there is nothing
474
+ * to draw with — the device was lost, or the renderer destroyed. Frames the GPU has not finished
475
+ * yet neither hold this one back nor make it return false.
461
476
  */
462
477
  render_to_target(camera: PerspectiveCamera, scene: Scene, time_delta_seconds: number, target: GPUTextureContext): boolean;
463
478
  #private;
464
479
  }
480
+ export type GPUFrameRecorder = import("../device/timing/profile/GPUFrameRecorder.js").GPUFrameRecorder;
465
481
  export type GPUProfileSession = import("../device/timing/profile/GPUProfileSession.js").GPUProfileSession;
466
482
  export type PerspectiveCamera = import("./camera/PerspectiveCamera.js").PerspectiveCamera;
467
483
  export type RenderExtension = import("./extension/RenderExtension.js").RenderExtension;
@@ -477,7 +493,6 @@ import { DepthOfField } from "./postprocess/dof/raymarch/DepthOfField.js";
477
493
  import { DepthOfFieldU } from "./postprocess/dof/gather/DepthOfFieldU.js";
478
494
  import { ViewManager } from "./view/ViewManager.js";
479
495
  import { GraphicsContext } from "./GraphicsContext.js";
480
- import Signal from "../../core/events/signal/Signal.js";
481
496
  import Vector2 from "../../core/geom/Vector2.js";
482
497
  import { GPUTextureContext } from "./texture/GPUTextureContext.js";
483
498
  import { FramePhase } from "./extension/FramePhase.js";
@@ -1 +1 @@
1
- {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../../../src/shade/renderer/Renderer.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../../../src/shade/renderer/Renderer.js"],"names":[],"mappings":"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"}