@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
@@ -85,6 +85,7 @@ import { View } from "./view/View.js";
85
85
  import { ViewManager } from "./view/ViewManager.js";
86
86
 
87
87
  /**
88
+ * @typedef {import("../device/timing/profile/GPUFrameRecorder.js").GPUFrameRecorder} GPUFrameRecorder
88
89
  * @typedef {import("../device/timing/profile/GPUProfileSession.js").GPUProfileSession} GPUProfileSession
89
90
  * @typedef {import("./camera/PerspectiveCamera.js").PerspectiveCamera} PerspectiveCamera
90
91
  * @typedef {import("./extension/RenderExtension.js").RenderExtension} RenderExtension
@@ -472,6 +473,17 @@ export class Renderer {
472
473
  */
473
474
  onDeviceFailure = new Signal();
474
475
 
476
+ /**
477
+ * Sent at the end of every frame {@link render} or {@link render_to_target} completes, with the
478
+ * number of frames completed so far. A frame that throws is not one of them: whatever it had
479
+ * recorded may still have been submitted, but it sends nothing and is not counted.
480
+ *
481
+ * **Submitted, not finished.** The frame's commands have been handed to the queue and the GPU
482
+ * may not have started them; a listener that needs the GPU to be done with the frame waits on
483
+ * {@link GPUQueue#onSubmittedWorkDone} instead.
484
+ *
485
+ * @type {Signal<number>}
486
+ */
475
487
  onFrameFinished = new Signal();
476
488
 
477
489
  /**
@@ -1898,7 +1910,9 @@ export class Renderer {
1898
1910
  * simulation, the animation tick and exposure adaptation all step by it. Zero by default, which
1899
1911
  * draws the current state and moves nothing; a fixed fraction of a second here instead would run
1900
1912
  * the simulation at that rate no matter what the caller's clock was doing.
1901
- * @returns {boolean} whether frame was rendered, will return false if previous frame hasn't finished yet
1913
+ * @returns {boolean} whether a frame was encoded and submitted. False only when there is nothing
1914
+ * to draw with — the device was lost, or the renderer destroyed. Frames the GPU has not finished
1915
+ * yet neither hold this one back nor make it return false.
1902
1916
  */
1903
1917
  render(
1904
1918
  camera,
@@ -1937,13 +1951,100 @@ export class Renderer {
1937
1951
  * @param {Scene} scene
1938
1952
  * @param {number} time_delta_seconds
1939
1953
  * @param {GPUTextureContext} target must match {@link output_resolution}
1940
- * @returns {boolean} whether frame was rendered, will return false if previous frame hasn't finished yet
1954
+ * @returns {boolean} whether a frame was encoded and submitted. False only when there is nothing
1955
+ * to draw with — the device was lost, or the renderer destroyed. Frames the GPU has not finished
1956
+ * yet neither hold this one back nor make it return false.
1941
1957
  */
1942
1958
  render_to_target(
1943
1959
  camera,
1944
1960
  scene,
1945
1961
  time_delta_seconds,
1946
1962
  target
1963
+ ) {
1964
+ try {
1965
+ return this.#draw_to_target(camera, scene, time_delta_seconds, target);
1966
+ } catch (error) {
1967
+ this.#finish_stranded_frame();
1968
+
1969
+ throw error;
1970
+ }
1971
+ }
1972
+
1973
+ /**
1974
+ * The frame's command context from the moment it is created until the frame's teardown takes
1975
+ * it, and null otherwise — so a frame that throws before its teardown leaves it here for
1976
+ * {@link render_to_target} to finish.
1977
+ *
1978
+ * @type {ShadeGPUCommandContext|null}
1979
+ */
1980
+ #frame_command_ctx = null;
1981
+
1982
+ /**
1983
+ * The profiler's recorder for the frame {@link #frame_command_ctx} belongs to, and the session
1984
+ * that handed it out — null when that frame is not profiled — so a frame that throws before its
1985
+ * teardown still closes its profile frame. Otherwise a capture leaves out exactly the frames
1986
+ * that failed.
1987
+ *
1988
+ * @type {GPUFrameRecorder|null}
1989
+ */
1990
+ #frame_profile_recorder = null;
1991
+
1992
+ /**
1993
+ * @type {GPUProfileSession|null}
1994
+ */
1995
+ #frame_profile_session = null;
1996
+
1997
+ /**
1998
+ * Finish the context a frame left behind when it threw before its teardown: the scene tick, the
1999
+ * shadow draw and every extension phase run between the context's creation and the teardown,
2000
+ * and a throw from any of them used to leave it open for good, holding its upload block, its
2001
+ * transient buffers and its profiler's buffers — every frame it happened.
2002
+ *
2003
+ * What comes back is what it holds. What it recorded is submitted if the encoder can still be
2004
+ * finished: a throw once the `Shade` debug group is open leaves that group unclosed, the
2005
+ * command buffer is invalid, and the submission is refused with nothing of the frame run — as it
2006
+ * is for a pass that throws, whose teardown shares the same unclosed group.
2007
+ */
2008
+ #finish_stranded_frame() {
2009
+ const stranded = this.#frame_command_ctx;
2010
+ const recorder = this.#frame_profile_recorder;
2011
+ const session = this.#frame_profile_session;
2012
+
2013
+ this.#frame_command_ctx = null;
2014
+ this.#frame_profile_recorder = null;
2015
+ this.#frame_profile_session = null;
2016
+
2017
+ if (stranded === null) {
2018
+ return;
2019
+ }
2020
+
2021
+ try {
2022
+ stranded.finish();
2023
+ } catch (teardown_error) {
2024
+ // already unwinding: the frame's own error is the one the caller needs to see
2025
+ }
2026
+
2027
+ if (recorder !== null) {
2028
+ stranded.profiling_absorbed.then(() => {
2029
+ session.end_frame(recorder, performance.now());
2030
+ });
2031
+ }
2032
+ }
2033
+
2034
+ /**
2035
+ * The whole of {@link render_to_target}, which only adds the finishing of a frame that throws.
2036
+ *
2037
+ * @param {PerspectiveCamera} camera
2038
+ * @param {Scene} scene
2039
+ * @param {number} time_delta_seconds
2040
+ * @param {GPUTextureContext} target
2041
+ * @returns {boolean}
2042
+ */
2043
+ #draw_to_target(
2044
+ camera,
2045
+ scene,
2046
+ time_delta_seconds,
2047
+ target
1947
2048
  ) {
1948
2049
  assert.defined(target, 'target');
1949
2050
  assert.equal(target.isGPUTextureContext, true, 'target.isGPUTextureContext !== true');
@@ -2020,6 +2121,8 @@ export class Renderer {
2020
2121
  "Renderer/main-0"
2021
2122
  );
2022
2123
 
2124
+ this.#frame_command_ctx = command_ctx;
2125
+
2023
2126
  /**
2024
2127
  * The frame recorder for this frame, or null when nothing is profiling.
2025
2128
  * @type {GPUFrameRecorder|null}
@@ -2033,6 +2136,9 @@ export class Renderer {
2033
2136
 
2034
2137
  if (profile_recorder !== null) {
2035
2138
  command_ctx.enable_profiling(profile_recorder, 0);
2139
+
2140
+ this.#frame_profile_recorder = profile_recorder;
2141
+ this.#frame_profile_session = this.profile_session;
2036
2142
  }
2037
2143
  }
2038
2144
 
@@ -2822,6 +2928,11 @@ export class Renderer {
2822
2928
  destroyed, and the session's frame accounting silently short a frame. A render loop that
2823
2929
  catches and carries on repeats that every failing frame until the device complains.
2824
2930
  */
2931
+ // the teardown has it from here, whatever the finish does
2932
+ this.#frame_command_ctx = null;
2933
+ this.#frame_profile_recorder = null;
2934
+ this.#frame_profile_session = null;
2935
+
2825
2936
  try {
2826
2937
  command_ctx.finish();
2827
2938
  } catch (teardown_error) {
@@ -418,8 +418,10 @@ export class GPUAnimationManager {
418
418
  * correct ping-pong behaviour across frames without leaking the
419
419
  * buffer-role bookkeeping into every caller.
420
420
  *
421
- * No-op when the joints table has no rows to walk
422
- * (`group_count_x === 0`).
421
+ * No-op when the joints table has no page for it to walk
422
+ * (`group_count_x === 0`), which is never while a skin is
423
+ * registered: a page emptied of joints goes at the next database
424
+ * update.
423
425
  *
424
426
  * Per-frame ordering:
425
427
  * 1. Flip {@link #skin_matrices_current_index}: last frame's
@@ -427,11 +429,12 @@ export class GPUAnimationManager {
427
429
  * ({@link prev_skin_matrices_buffer}), and the buffer that
428
430
  * held the frame before it becomes the write target.
429
431
  * 2. Dispatch the prep shader into the new "current".
430
- * 3. If {@link #first_frame_skin_matrices} is set — a run handed
431
- * out, a pending skin's joints named, or the buffers grown
432
- * since the last dispatch — copy all of "current" over
433
- * "previous", so the first velocity read over those rows sees
434
- * matching matrices and produces zero velocity.
432
+ * 3. Copy "current" over "previous" on the rows that have no last
433
+ * frame — every run handed out and every pending skin's run
434
+ * named since the last dispatch, or all of it after a grow —
435
+ * so the first velocity read over those rows sees matching
436
+ * matrices and produces zero velocity. Every other row of
437
+ * "previous" keeps last frame's matrices.
435
438
  *
436
439
  * @param {ShadeGPUCommandContext} cmd_ctx
437
440
  */
@@ -1 +1 @@
1
- {"version":3,"file":"GPUAnimationManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/animation/GPUAnimationManager.js"],"names":[],"mappings":"AA+IA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IAsOI;;;;OAIG;IACH,sCAHW,MAAM,iBACN,eAAe,EAezB;IAED;;OAEG;IACH,4BAEC;IAED;;;;;;;;;;;;OAYG;IACH,gCAEC;IAED;;;;;;;;;;OAUG;IACH,qCAEC;IAED;;;;;;;;OAQG;IACH,gCAEC;IAED;;;;;;;;;;OAUG;IACH,mCAEC;IAED;;;;;;;;;;;;OAYG;IACH,4CAFW,MAAM,QA6BhB;IAED;;;;;OAKG;IACH,kBAHW,MAAM,GACJ;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAKvD;IAED;;;;;;;;;;;;;OAaG;IACH,gCAHW,MAAM,GACJ,MAAM,CAUlB;IAED;;;;;;;;;OASG;IACH,iBAHW,cAAc,GACZ,MAAM,CAmBlB;IAED;;;;;;;;;;;;;;OAcG;IACH,oBAJW;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAEhD,MAAM,CA4BlB;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,eARW;QACV,IAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAc,EAAE,MAAM;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC1E,GACS,MAAM,CA0ClB;IA4BD;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAHW,MAAM,QACN,MAAM,QAWhB;IAED;;;;;;;;OAQG;IACH,2BAHW,MAAM,QACN,MAAM,QAKhB;IAED;;;;;;;;;;;;;;OAcG;IACH,6BAHW,MAAM,UACN,MAAM,QAWhB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,mBAHW,MAAM,SACN,MAAM,QAMhB;IAED;;;;;;;OAOG;IACH,qBAHW,MAAM,SACN,MAAM,QAMhB;IAED;;;;;;;;;;;OAWG;IACH,eAFW,MAAM,QAMhB;IAED;;;;;;;;;OASG;IACH,cAFW,MAAM,QAMhB;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,eALW;QACV,MAAY,EAAE,MAAM;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,YAAY,GAAG,MAAM,EAAE,CAAA;SAAE,CAAC,CAAC;KAC1E,GACS,MAAM,CAuBlB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAHW,IAAI,GACF,MAAM,CA4BlB;IAwGD;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,oBAHW,kBAAkB,GAChB,MAAM,CA2DlB;IAoND;;;;;;;;;;;;;;OAcG;IACH,yBAFW,MAAM,QA4BhB;IAED;;;;;;;;;;;;;;;OAeG;IACH,yBAFW,MAAM,QA2BhB;IA4JD;;;;;;;;;;;;;;;;;OAiBG;IACH,gBAFW,sBAAsB,QAMhC;IAoBD;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,mCAFW,sBAAsB,QA6ChC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,cALW,sBAAsB,cACtB,MAAM,GACJ,OAAO,CA0CnB;IA8FD,gBAoBC;;CACJ;6BAtzDY,OAAO,mDAAmD,EAAE,cAAc;8BAC1E,OAAO,iDAAiD,EAAE,eAAe;qCACzE,OAAO,wCAAwC,EAAE,sBAAsB;8BACvE,OAAO,6BAA6B,EAAE,eAAe;iCACrD,OAAO,yBAAyB,EAAE,kBAAkB;mBACpD,OAAO,WAAW,EAAE,IAAI;4BA/CT,gCAAgC"}
1
+ {"version":3,"file":"GPUAnimationManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/animation/GPUAnimationManager.js"],"names":[],"mappings":"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"}
@@ -200,8 +200,8 @@ export class GPUAnimationManager {
200
200
  *
201
201
  * Lazily allocated on first {@link add_skin}; both grow together
202
202
  * by doubling. After a grow both buffers are zero-initialised and
203
- * {@link #first_frame_skin_matrices} is set so the next prep
204
- * dispatch seeds the "previous" buffer with a copy of the freshly
203
+ * {@link #skin_matrix_seed_all} is set so the next prep dispatch
204
+ * seeds all of the "previous" buffer with a copy of the freshly
205
205
  * written "current" — without this, the first velocity readback
206
206
  * after a grow would interpolate against zero matrices.
207
207
  *
@@ -217,15 +217,31 @@ export class GPUAnimationManager {
217
217
  #skin_matrices_current_index = 0;
218
218
 
219
219
  /**
220
- * When true, the next prep dispatch will copy the freshly-written
221
- * "current" buffer into the "previous" buffer at the end of the
222
- * pass — set when a run of joints is handed out, when a pending
223
- * skin's joints are named, and when the buffers grow, so the first
224
- * velocity computation over those rows has matching prev/current
225
- * matrices (zero velocity).
220
+ * The runs of matrix rows the next prep dispatch seeds "previous" from "current" over, each
221
+ * `[first_row, count]`: a run handed out, and a pending skin's run whose joints were just
222
+ * named. The frame before either is not a pose — "previous" over those rows holds zeros, or
223
+ * the last pose of a skin that had them — so the first velocity read over them has to see
224
+ * matching matrices, which is zero velocity.
225
+ *
226
+ * Only those rows: a settled skin's rows of "previous" hold its pose last frame, which is what
227
+ * its motion is measured from.
228
+ *
229
+ * A skin given back takes its queued runs with it ({@link unregister_skin}), so every run here
230
+ * belongs to a skin still registered and not yet seeded — however long no prep runs, as while
231
+ * a loading screen draws nothing and the game goes on spawning.
232
+ *
233
+ * @type {Array<[number, number]>}
234
+ */
235
+ #skin_matrix_seed_runs = [];
236
+
237
+ /**
238
+ * When true, the next prep dispatch seeds all of "previous" from "current", whatever
239
+ * {@link #skin_matrix_seed_runs} holds. Set when the buffers grow: both are made again,
240
+ * zeroed, so no row has a last frame left to keep.
241
+ *
226
242
  * @type {boolean}
227
243
  */
228
- #first_frame_skin_matrices = true;
244
+ #skin_matrix_seed_all = false;
229
245
 
230
246
  /**
231
247
  * Capacity of each of {@link #skin_matrices_buffers} in matrices
@@ -1056,7 +1072,7 @@ export class GPUAnimationManager {
1056
1072
  // The joints' matrices are written from the next prep on, and "previous" over their rows
1057
1073
  // holds whatever was there before — zeros, or the last pose of a skin that had them — so
1058
1074
  // it is seeded, as it is for a run just handed out.
1059
- this.#first_frame_skin_matrices = true;
1075
+ this.#skin_matrix_seed_runs.push([pending.joints_first, pending.joint_count]);
1060
1076
 
1061
1077
  return true;
1062
1078
  }
@@ -1436,10 +1452,34 @@ export class GPUAnimationManager {
1436
1452
  return;
1437
1453
  }
1438
1454
 
1455
+ // Its rows go back owed no seed. Left queued, a run outlives its skin until the next prep,
1456
+ // and while nothing draws that is every spawn since drawing stopped.
1457
+ this.#drop_seed_runs(skins_table.get_word(skin_id, SKIN_JOINTS_FIRST_WORD));
1458
+
1439
1459
  // The joints go with the skin — a pending skin's too, nodes or not.
1440
1460
  skins_table.remove(skin_id);
1441
1461
  }
1442
1462
 
1463
+ /**
1464
+ * Drop every queued seed run starting at `first_row`: the run of a skin being given back,
1465
+ * queued when it was handed out, and again if its joints have been named since.
1466
+ *
1467
+ * No other skin's run can start there, since a skin's rows are its own until it is given back.
1468
+ *
1469
+ * @param {number} first_row
1470
+ */
1471
+ #drop_seed_runs(first_row) {
1472
+ const runs = this.#skin_matrix_seed_runs;
1473
+
1474
+ // the prep sorts what it finds, so a dropped run's place goes to the last one
1475
+ for (let i = runs.length - 1; i >= 0; i--) {
1476
+ if (runs[i][0] === first_row) {
1477
+ runs[i] = runs[runs.length - 1];
1478
+ runs.pop();
1479
+ }
1480
+ }
1481
+ }
1482
+
1443
1483
  /**
1444
1484
  * Cached wrapper around {@link add_curve}: one upload per distinct curve, where distinct means
1445
1485
  * distinct *content*.
@@ -1541,25 +1581,31 @@ export class GPUAnimationManager {
1541
1581
  // its rows are fresh or some despawned skin's, so the next prep dispatch has to seed
1542
1582
  // "previous" from "current" for it.
1543
1583
  //
1544
- // This flag used to be set only inside #ensure_skin_matrices_capacity, i.e. only when the
1545
- // buffer actually grew. Rows given back by another skin never grow the buffer, and fresh
1546
- // rows below the current capacity hit that helper's early-out - so in both cases the
1547
- // `previous` buffer over the run still held either all-zero matrices or the last pose of
1548
- // the skin that had those rows before. The skinning pass reads
1549
- // `prev_skin_matrices[skin_base + j]` unconditionally, so the new character's first frame
1550
- // produced motion vectors from someone else's pose - a screen-crossing smear through TAA
1551
- // and motion blur - on exactly the spawn/despawn loop that reuses rows.
1552
- this.#first_frame_skin_matrices = true;
1584
+ // Whether or not the buffers grew: rows given back by another skin never grow them, and
1585
+ // neither do fresh rows below the capacity, so "previous" over the run holds all-zero
1586
+ // matrices or the last pose of the skin that had those rows before. The skinning pass
1587
+ // reads `prev_skin_matrices[skin_base + j]` unconditionally, so unseeded, the new
1588
+ // character's first frame would take its motion vectors from someone else's pose — a
1589
+ // screen-crossing smear through TAA and motion blur — on exactly the spawn/despawn loop
1590
+ // that reuses rows.
1591
+ this.#skin_matrix_seed_runs.push([joints_first, joints.length]);
1553
1592
 
1554
1593
  return joints_first;
1555
1594
  }
1556
1595
 
1557
1596
  /**
1558
- * Grow the skinning-matrix scratch buffer if `required_count`
1559
- * exceeds current capacity. Doubles on grow until the requirement
1560
- * fits. The buffer's contents are scratch (rewritten every frame by
1561
- * the prep pass), so growth doesn't need to copy old data — but it
1562
- * does invalidate the previous {@link #skin_matrices_buffer}
1597
+ * Grow the skinning-matrix buffers if `required_count` exceeds
1598
+ * current capacity. Doubles on grow until the requirement fits.
1599
+ *
1600
+ * Nothing is copied across. "Current" would be rewritten by the
1601
+ * next prep anyway, but "previous" held every skin's pose last
1602
+ * frame, which its motion is measured from: the grow loses that,
1603
+ * and the next prep seeds all of "previous" from "current" instead
1604
+ * ({@link #skin_matrix_seed_all}), so every skinned character has
1605
+ * zero motion vectors for that one frame. Capacity doubles, so
1606
+ * that is a handful of frames over a session.
1607
+ *
1608
+ * The grow also invalidates the {@link #skin_matrices_buffer}
1563
1609
  * reference, which is why callers re-fetch through the getter.
1564
1610
  *
1565
1611
  * @param {number} required_count number of matrices that must fit
@@ -1578,8 +1624,9 @@ export class GPUAnimationManager {
1578
1624
  // capacity. After grow both are zero-initialised, so the next
1579
1625
  // prep dispatch needs to seed "previous" from "current"
1580
1626
  // (otherwise velocity would interpolate against zero
1581
- // matrices). The flag below tells dispatch_skin_matrix_prep
1582
- // to issue that copy.
1627
+ // matrices), and all of it: no row has a last frame left to
1628
+ // keep. The flag below tells dispatch_skin_matrix_prep to
1629
+ // issue that copy.
1583
1630
  for (let i = 0; i < 2; i++) {
1584
1631
  if (this.#skin_matrices_buffers[i] !== null) {
1585
1632
  this.#skin_matrices_buffers[i].destroy();
@@ -1591,7 +1638,7 @@ export class GPUAnimationManager {
1591
1638
  });
1592
1639
  }
1593
1640
  this.#skin_matrices_capacity = new_capacity;
1594
- this.#first_frame_skin_matrices = true;
1641
+ this.#skin_matrix_seed_all = true;
1595
1642
  }
1596
1643
 
1597
1644
  /**
@@ -1643,8 +1690,10 @@ export class GPUAnimationManager {
1643
1690
  * correct ping-pong behaviour across frames without leaking the
1644
1691
  * buffer-role bookkeeping into every caller.
1645
1692
  *
1646
- * No-op when the joints table has no rows to walk
1647
- * (`group_count_x === 0`).
1693
+ * No-op when the joints table has no page for it to walk
1694
+ * (`group_count_x === 0`), which is never while a skin is
1695
+ * registered: a page emptied of joints goes at the next database
1696
+ * update.
1648
1697
  *
1649
1698
  * Per-frame ordering:
1650
1699
  * 1. Flip {@link #skin_matrices_current_index}: last frame's
@@ -1652,11 +1701,12 @@ export class GPUAnimationManager {
1652
1701
  * ({@link prev_skin_matrices_buffer}), and the buffer that
1653
1702
  * held the frame before it becomes the write target.
1654
1703
  * 2. Dispatch the prep shader into the new "current".
1655
- * 3. If {@link #first_frame_skin_matrices} is set — a run handed
1656
- * out, a pending skin's joints named, or the buffers grown
1657
- * since the last dispatch — copy all of "current" over
1658
- * "previous", so the first velocity read over those rows sees
1659
- * matching matrices and produces zero velocity.
1704
+ * 3. Copy "current" over "previous" on the rows that have no last
1705
+ * frame — every run handed out and every pending skin's run
1706
+ * named since the last dispatch, or all of it after a grow —
1707
+ * so the first velocity read over those rows sees matching
1708
+ * matrices and produces zero velocity. Every other row of
1709
+ * "previous" keeps last frame's matrices.
1660
1710
  *
1661
1711
  * @param {ShadeGPUCommandContext} cmd_ctx
1662
1712
  */
@@ -1665,6 +1715,9 @@ export class GPUAnimationManager {
1665
1715
  .get('animation_skin_joints')
1666
1716
  .dispatch_group_count(SKIN_MATRIX_PREP_WORKGROUP_SIZE);
1667
1717
  if (groups === 0) {
1718
+ // no page to walk means no skin registered, and a skin given back takes its runs
1719
+ assert.equal(this.#skin_matrix_seed_runs.length, 0, 'seed runs queued for skins no longer registered');
1720
+
1668
1721
  return;
1669
1722
  }
1670
1723
 
@@ -1689,20 +1742,69 @@ export class GPUAnimationManager {
1689
1742
  },
1690
1743
  });
1691
1744
 
1692
- // First-frame init (or post-grow): "previous" buffer is
1693
- // zero-initialised, which would have the velocity pass
1694
- // interpolate against the origin and produce huge nonsense
1695
- // on the first frame the skin appears. Seed prev from
1696
- // current — first velocity reads then yield zero, which is
1697
- // the right answer (no motion has happened yet).
1698
- if (this.#first_frame_skin_matrices) {
1745
+ this.#seed_previous_skin_matrices(cmd_ctx, current, previous);
1746
+ }
1747
+
1748
+ /**
1749
+ * Copy "current" over "previous" on the rows where "previous" holds no pose of the frame
1750
+ * before: all of it after a grow, otherwise the runs queued since the last prep. Zeros there,
1751
+ * or the last pose of a skin that had the rows, would have the velocity pass produce huge
1752
+ * nonsense on the first frame the skin appears; seeded, the first velocity read yields zero,
1753
+ * which is the right answer — no motion has happened yet.
1754
+ *
1755
+ * Every other row is left alone: a settled skin's rows hold its pose last frame, which its
1756
+ * motion is measured from, and the rest are read by no one. Seeded too, every skinned
1757
+ * character on screen would lose its motion vectors for the frame whenever one spawned.
1758
+ *
1759
+ * Runs are coalesced by sorting — a frame's worth of spawns is a handful of runs — and adjacent
1760
+ * or overlapping ones go as one copy.
1761
+ *
1762
+ * @param {ShadeGPUCommandContext} cmd_ctx
1763
+ * @param {GPUBuffer} current
1764
+ * @param {GPUBuffer} previous
1765
+ */
1766
+ #seed_previous_skin_matrices(cmd_ctx, current, previous) {
1767
+ const runs = this.#skin_matrix_seed_runs;
1768
+
1769
+ if (this.#skin_matrix_seed_all) {
1770
+ cmd_ctx.copyBufferToBuffer(current, 0, previous, 0, current.size);
1771
+
1772
+ this.#skin_matrix_seed_all = false;
1773
+ runs.length = 0;
1774
+
1775
+ return;
1776
+ }
1777
+
1778
+ if (runs.length === 0) {
1779
+ // a settled frame: every row of "previous" is last frame's
1780
+ return;
1781
+ }
1782
+
1783
+ runs.sort((a, b) => a[0] - b[0]);
1784
+
1785
+ let i = 0;
1786
+
1787
+ while (i < runs.length) {
1788
+ const first = runs[i][0];
1789
+ let end = first + runs[i][1];
1790
+
1791
+ i++;
1792
+
1793
+ // a run starting at or before this copy's end is part of it
1794
+ while (i < runs.length && runs[i][0] <= end) {
1795
+ end = Math.max(end, runs[i][0] + runs[i][1]);
1796
+
1797
+ i++;
1798
+ }
1799
+
1699
1800
  cmd_ctx.copyBufferToBuffer(
1700
- current, 0,
1701
- previous, 0,
1702
- current.size,
1801
+ current, first * SKIN_MATRIX_BYTES,
1802
+ previous, first * SKIN_MATRIX_BYTES,
1803
+ (end - first) * SKIN_MATRIX_BYTES,
1703
1804
  );
1704
- this.#first_frame_skin_matrices = false;
1705
1805
  }
1806
+
1807
+ runs.length = 0;
1706
1808
  }
1707
1809
 
1708
1810
  /**
@@ -1,6 +1,11 @@
1
1
  /**
2
- * Provides staging buffers for pushing data from CPU to GPU.
2
+ * Mapped `MAP_WRITE | COPY_SRC` buffers for pushing data from the CPU to the GPU, one per upload.
3
3
  *
4
+ * A buffer comes out mapped; its holder fills it, unmaps it, copies out of it and gives it back, and
5
+ * the pool maps it again — a round trip through the GPU — before it can come out again. For bytes
6
+ * that is the cheaper road; for thousands of small writes a frame the round trips are the cost, which
7
+ * is why a {@link ShadeGPUCommandContext} brings only its large writes here (see
8
+ * `MAPPED_UPLOAD_THRESHOLD`).
4
9
  */
5
10
  export class GPUBufferStagingAllocator {
6
11
  /**
@@ -9,6 +14,8 @@ export class GPUBufferStagingAllocator {
9
14
  */
10
15
  constructor(device: GPUDevice);
11
16
  /**
17
+ * Bytes in every buffer this pool has made and not destroyed: free, handed out, or on the way
18
+ * back.
12
19
  *
13
20
  * @returns {number}
14
21
  */
@@ -22,8 +29,10 @@ export class GPUBufferStagingAllocator {
22
29
  /**
23
30
  *
24
31
  * @param {GPUBuffer} buffer
32
+ * @returns {boolean} whether it was taken back: false for a buffer given back already, or one
33
+ * this pool did not hand out
25
34
  */
26
- release(buffer: GPUBuffer): void;
35
+ release(buffer: GPUBuffer): boolean;
27
36
  destroy(): void;
28
37
  #private;
29
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GPUBufferStagingAllocator.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/buffer/GPUBufferStagingAllocator.js"],"names":[],"mappings":"AASA;;;GAGG;AACH;IAaI;;;OAGG;IACH,+BAIC;IAED;;;OAGG;IACH,+BAQC;IAGD;;;;OAIG;IACH,oBAHW,MAAM,aAiChB;IAgCD;;;OAGG;IACH,iCAiBC;IAED,gBAOC;;CACJ"}
1
+ {"version":3,"file":"GPUBufferStagingAllocator.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/buffer/GPUBufferStagingAllocator.js"],"names":[],"mappings":"AASA;;;;;;;;GAQG;AACH;IA8CI;;;OAGG;IACH,+BAIC;IAED;;;;;OAKG;IACH,+BAEC;IAGD;;;;OAIG;IACH,oBAHW,MAAM,aAsChB;IAkCD;;;;;OAKG;IACH,4BAHa,OAAO,CA0BnB;IAgCD,gBAUC;;CACJ"}