@woosh/meep-engine 3.10.0 → 3.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +168 -152
  2. package/package.json +1 -1
  3. package/src/REVIEW_2026_08_06.md +610 -610
  4. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  5. package/src/core/geom/3d/shape/ConvexHullShape3D.js +28 -4
  6. package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts +26 -0
  7. package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts.map +1 -1
  8. package/src/engine/graphics/render/frame_graph/FrameGraph.js +47 -0
  9. package/src/engine/physics/ecs/PhysicsSystem.d.ts +0 -3
  10. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  11. package/src/engine/physics/ecs/PhysicsSystem.js +0 -3
  12. package/src/shade/device/ShadeGPUCommandContext.d.ts +21 -0
  13. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  14. package/src/shade/device/ShadeGPUCommandContext.js +940 -749
  15. package/src/shade/device/timing/GPUTimerArray.d.ts +22 -4
  16. package/src/shade/device/timing/GPUTimerArray.d.ts.map +1 -1
  17. package/src/shade/device/timing/GPUTimerArray.js +88 -14
  18. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +349 -98
  19. package/src/shade/device/timing/profile/GPUFrameRecorder.d.ts +93 -0
  20. package/src/shade/device/timing/profile/GPUFrameRecorder.d.ts.map +1 -0
  21. package/src/shade/device/timing/profile/GPUFrameRecorder.js +297 -0
  22. package/src/shade/device/timing/profile/GPUProfileCapture.d.ts +62 -0
  23. package/src/shade/device/timing/profile/GPUProfileCapture.d.ts.map +1 -0
  24. package/src/shade/device/timing/profile/GPUProfileCapture.js +77 -0
  25. package/src/shade/device/timing/profile/GPUProfileFrame.d.ts +73 -0
  26. package/src/shade/device/timing/profile/GPUProfileFrame.d.ts.map +1 -0
  27. package/src/shade/device/timing/profile/GPUProfileFrame.js +88 -0
  28. package/src/shade/device/timing/profile/GPUProfileLevel.d.ts +20 -0
  29. package/src/shade/device/timing/profile/GPUProfileLevel.d.ts.map +1 -0
  30. package/src/shade/device/timing/profile/GPUProfileLevel.js +50 -0
  31. package/src/shade/device/timing/profile/GPUProfileMeta.d.ts +63 -0
  32. package/src/shade/device/timing/profile/GPUProfileMeta.d.ts.map +1 -0
  33. package/src/shade/device/timing/profile/GPUProfileMeta.js +73 -0
  34. package/src/shade/device/timing/profile/GPUProfileSession.d.ts +173 -0
  35. package/src/shade/device/timing/profile/GPUProfileSession.d.ts.map +1 -0
  36. package/src/shade/device/timing/profile/GPUProfileSession.js +470 -0
  37. package/src/shade/device/timing/profile/GPUProfileSpan.d.ts +60 -0
  38. package/src/shade/device/timing/profile/GPUProfileSpan.d.ts.map +1 -0
  39. package/src/shade/device/timing/profile/GPUProfileSpan.js +65 -0
  40. package/src/shade/device/timing/profile/GPUProfileSpanKind.d.ts +19 -0
  41. package/src/shade/device/timing/profile/GPUProfileSpanKind.d.ts.map +1 -0
  42. package/src/shade/device/timing/profile/GPUProfileSpanKind.js +25 -0
  43. package/src/shade/device/timing/profile/GPUProfileTopology.d.ts +159 -0
  44. package/src/shade/device/timing/profile/GPUProfileTopology.d.ts.map +1 -0
  45. package/src/shade/device/timing/profile/GPUProfileTopology.js +202 -0
  46. package/src/shade/device/timing/profile/GPUProfileWork.d.ts +90 -0
  47. package/src/shade/device/timing/profile/GPUProfileWork.d.ts.map +1 -0
  48. package/src/shade/device/timing/profile/GPUProfileWork.js +115 -0
  49. package/src/shade/device/timing/profile/SGPTDefect.d.ts +43 -0
  50. package/src/shade/device/timing/profile/SGPTDefect.d.ts.map +1 -0
  51. package/src/shade/device/timing/profile/SGPTDefect.js +65 -0
  52. package/src/shade/device/timing/profile/SGPTHeader.d.ts +38 -0
  53. package/src/shade/device/timing/profile/SGPTHeader.d.ts.map +1 -0
  54. package/src/shade/device/timing/profile/SGPTHeader.js +46 -0
  55. package/src/shade/device/timing/profile/SGPTRecordRef.d.ts +37 -0
  56. package/src/shade/device/timing/profile/SGPTRecordRef.d.ts.map +1 -0
  57. package/src/shade/device/timing/profile/SGPTRecordRef.js +45 -0
  58. package/src/shade/device/timing/profile/SGPTWriteStream.d.ts +103 -0
  59. package/src/shade/device/timing/profile/SGPTWriteStream.d.ts.map +1 -0
  60. package/src/shade/device/timing/profile/SGPTWriteStream.js +287 -0
  61. package/src/shade/device/timing/profile/SGPT_DEFECT.d.ts +19 -0
  62. package/src/shade/device/timing/profile/SGPT_DEFECT.d.ts.map +1 -0
  63. package/src/shade/device/timing/profile/SGPT_DEFECT.js +26 -0
  64. package/src/shade/device/timing/profile/SGPT_FLAG.d.ts +8 -0
  65. package/src/shade/device/timing/profile/SGPT_FLAG.d.ts.map +1 -0
  66. package/src/shade/device/timing/profile/SGPT_FLAG.js +15 -0
  67. package/src/shade/device/timing/profile/SGPT_FORMAT.md +345 -0
  68. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.d.ts +10 -0
  69. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.d.ts.map +1 -0
  70. package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.js +9 -0
  71. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.d.ts +17 -0
  72. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.d.ts.map +1 -0
  73. package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.js +16 -0
  74. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.d.ts +16 -0
  75. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.d.ts.map +1 -0
  76. package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.js +15 -0
  77. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.d.ts +15 -0
  78. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.d.ts.map +1 -0
  79. package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.js +29 -0
  80. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.d.ts +7 -0
  81. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.d.ts.map +1 -0
  82. package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.js +6 -0
  83. package/src/shade/device/timing/profile/SGPT_MAGIC.d.ts +7 -0
  84. package/src/shade/device/timing/profile/SGPT_MAGIC.d.ts.map +1 -0
  85. package/src/shade/device/timing/profile/SGPT_MAGIC.js +6 -0
  86. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.d.ts +16 -0
  87. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.d.ts.map +1 -0
  88. package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.js +15 -0
  89. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.d.ts +7 -0
  90. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.d.ts.map +1 -0
  91. package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.js +6 -0
  92. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.d.ts +13 -0
  93. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.d.ts.map +1 -0
  94. package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.js +23 -0
  95. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.d.ts +12 -0
  96. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.d.ts.map +1 -0
  97. package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.js +11 -0
  98. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.d.ts +18 -0
  99. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.d.ts.map +1 -0
  100. package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.js +46 -0
  101. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts +20 -0
  102. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts.map +1 -0
  103. package/src/shade/device/timing/profile/frame_graph_extract_topology.js +174 -0
  104. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts +21 -0
  105. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts.map +1 -0
  106. package/src/shade/device/timing/profile/make_profiling_pass_encoder.js +114 -0
  107. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts +20 -0
  108. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts.map +1 -0
  109. package/src/shade/device/timing/profile/parse_workgroup_size.js +43 -0
  110. package/src/shade/device/timing/profile/sgpt_fourcc.d.ts +18 -0
  111. package/src/shade/device/timing/profile/sgpt_fourcc.d.ts.map +1 -0
  112. package/src/shade/device/timing/profile/sgpt_fourcc.js +34 -0
  113. package/src/shade/device/timing/profile/sgpt_frame_codec.d.ts +42 -0
  114. package/src/shade/device/timing/profile/sgpt_frame_codec.d.ts.map +1 -0
  115. package/src/shade/device/timing/profile/sgpt_frame_codec.js +172 -0
  116. package/src/shade/device/timing/profile/sgpt_meta_codec.d.ts +20 -0
  117. package/src/shade/device/timing/profile/sgpt_meta_codec.d.ts.map +1 -0
  118. package/src/shade/device/timing/profile/sgpt_meta_codec.js +65 -0
  119. package/src/shade/device/timing/profile/sgpt_read_capture.d.ts +19 -0
  120. package/src/shade/device/timing/profile/sgpt_read_capture.d.ts.map +1 -0
  121. package/src/shade/device/timing/profile/sgpt_read_capture.js +134 -0
  122. package/src/shade/device/timing/profile/sgpt_read_header.d.ts +22 -0
  123. package/src/shade/device/timing/profile/sgpt_read_header.d.ts.map +1 -0
  124. package/src/shade/device/timing/profile/sgpt_read_header.js +94 -0
  125. package/src/shade/device/timing/profile/sgpt_read_records.d.ts +27 -0
  126. package/src/shade/device/timing/profile/sgpt_read_records.d.ts.map +1 -0
  127. package/src/shade/device/timing/profile/sgpt_read_records.js +153 -0
  128. package/src/shade/device/timing/profile/sgpt_topology_codec.d.ts +54 -0
  129. package/src/shade/device/timing/profile/sgpt_topology_codec.d.ts.map +1 -0
  130. package/src/shade/device/timing/profile/sgpt_topology_codec.js +329 -0
  131. package/src/shade/device/timing/profile/sgpt_write_header.d.ts +22 -0
  132. package/src/shade/device/timing/profile/sgpt_write_header.d.ts.map +1 -0
  133. package/src/shade/device/timing/profile/sgpt_write_header.js +47 -0
  134. package/src/shade/device/timing/profile/sgpt_write_record.d.ts +23 -0
  135. package/src/shade/device/timing/profile/sgpt_write_record.d.ts.map +1 -0
  136. package/src/shade/device/timing/profile/sgpt_write_record.js +62 -0
  137. package/src/shade/playground/volumetrics_froxel/README.md +88 -0
  138. package/src/shade/playground/volumetrics_froxel/index.html +251 -0
  139. package/src/shade/playground/volumetrics_froxel/main.d.ts +2 -0
  140. package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -0
  141. package/src/shade/playground/volumetrics_froxel/main.js +619 -0
  142. package/src/shade/renderer/Renderer.d.ts +34 -0
  143. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  144. package/src/shade/renderer/Renderer.js +2536 -2455
  145. package/src/shade/renderer/volumetrics/NOTES.md +42 -1
  146. package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.d.ts +21 -0
  147. package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.d.ts.map +1 -0
  148. package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.js +47 -0
  149. package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.d.ts.map +1 -1
  150. package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.js +19 -8
  151. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.d.ts +37 -0
  152. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.d.ts.map +1 -0
  153. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.js +56 -0
  154. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  155. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +6 -0
  156. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  157. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +367 -324
  158. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.d.ts.map +1 -1
  159. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +14 -3
  160. package/src/shade/renderer/volumetrics/volumetrics_position_world_to_froxel_uvw.d.ts.map +1 -1
  161. package/src/shade/renderer/volumetrics/volumetrics_position_world_to_froxel_uvw.js +48 -36
  162. package/src/engine/save/GameStateLoader.d.ts +0 -49
  163. package/src/engine/save/GameStateLoader.d.ts.map +0 -1
  164. package/src/engine/save/GameStateLoader.js +0 -168
@@ -0,0 +1,88 @@
1
+ /**
2
+ * One recorded frame.
3
+ *
4
+ * @author Alex Goldring
5
+ * @copyright Company Named Limited (c) 2026
6
+ */
7
+ export class GPUProfileFrame {
8
+ /**
9
+ * The renderer's own frame counter, not this frame's position in the capture. A capture started
10
+ * mid-session does not begin at zero, and a frame that dropped its records leaves a gap — both
11
+ * of which are worth seeing.
12
+ * @type {number}
13
+ */
14
+ frame_index = 0;
15
+
16
+ /**
17
+ * `performance.now()` when the frame began encoding.
18
+ * @type {number}
19
+ */
20
+ cpu_begin_ms = 0;
21
+
22
+ /**
23
+ * `performance.now()` when the command buffer was submitted.
24
+ * @type {number}
25
+ */
26
+ cpu_submit_ms = 0;
27
+
28
+ /**
29
+ * The GPU timestamp all of this frame's spans are relative to — the earliest of them.
30
+ *
31
+ * There is no calibration between this clock and `performance.now()`; WebGPU exposes none. The
32
+ * CPU and GPU tracks are anchored per frame at submission and that anchoring is nominal, which
33
+ * is a thing to say out loud rather than paper over.
34
+ *
35
+ * @type {bigint}
36
+ */
37
+ gpu_epoch_ns = 0n;
38
+
39
+ /**
40
+ * Index into the capture's topology list, or -1 when the capture was recorded below
41
+ * {@link GPUProfileLevel.STRUCTURE} and has no structure to point at.
42
+ *
43
+ * Many frames share one: the graph is nearly identical frame to frame, so the structure is
44
+ * stored once and referenced.
45
+ *
46
+ * @type {number}
47
+ */
48
+ topology_id = -1;
49
+
50
+ /**
51
+ * The structure this frame ran, before the session has deduplicated it.
52
+ *
53
+ * Transient: the session hashes it, writes it if the shape is new, sets {@link topology_id},
54
+ * and never looks at this again. A frame decoded from a capture has `null` here and the id
55
+ * instead — the structure lives once, in the capture, rather than once per frame.
56
+ *
57
+ * @type {GPUProfileTopology|null}
58
+ */
59
+ topology = null;
60
+
61
+ /**
62
+ * @type {GPUProfileSpan[]}
63
+ */
64
+ spans = [];
65
+
66
+ /**
67
+ * Passes that went untimed because the frame had more of them than the timer array had slots.
68
+ * @see GPUTimerArray#dropped_count
69
+ * @type {number}
70
+ */
71
+ dropped_pass_count = 0;
72
+
73
+ /**
74
+ * Total GPU time across this frame's spans. Spans can overlap on some hardware, so this is a
75
+ * sum of durations rather than a wall-clock span, and the two are not the same number.
76
+ *
77
+ * @returns {number} nanoseconds
78
+ */
79
+ get gpu_duration_ns() {
80
+ let total = 0;
81
+
82
+ for (const span of this.spans) {
83
+ total += span.duration_ns;
84
+ }
85
+
86
+ return total;
87
+ }
88
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @param {number} level one of {@link GPUProfileLevel}
3
+ * @returns {string}
4
+ */
5
+ export function gpu_profile_level_name(level: number): string;
6
+ /**
7
+ * How much a capture records, and therefore what it costs per frame.
8
+ *
9
+ * The levels are cumulative: each includes everything below it. A capture is uncapped by default,
10
+ * so a per-frame figure is also a per-second one — see the rate table in the profiler proposal
11
+ * before reaching for {@link VERBOSE }.
12
+ */
13
+ export type GPUProfileLevel = number;
14
+ export namespace GPUProfileLevel {
15
+ let TIMING: number;
16
+ let STRUCTURE: number;
17
+ let WORKLOAD: number;
18
+ let VERBOSE: number;
19
+ }
20
+ //# sourceMappingURL=GPUProfileLevel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GPUProfileLevel.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/GPUProfileLevel.js"],"names":[],"mappings":"AAqCA;;;GAGG;AACH,8CAHW,MAAM,GACJ,MAAM,CAUlB;;;;;;;;8BA1CS,MAAM"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * How much a capture records, and therefore what it costs per frame.
3
+ *
4
+ * The levels are cumulative: each includes everything below it. A capture is uncapped by default,
5
+ * so a per-frame figure is also a per-second one — see the rate table in the profiler proposal
6
+ * before reaching for {@link VERBOSE}.
7
+ *
8
+ * @enum {number}
9
+ */
10
+ export const GPUProfileLevel = {
11
+ /**
12
+ * GPU pass spans and frame boundaries. A few kilobytes a frame; runs for many minutes without
13
+ * anyone noticing.
14
+ */
15
+ TIMING: 0,
16
+
17
+ /**
18
+ * Adds the frame graph: passes, resource nodes, edges, scopes, cull decisions, and the declared
19
+ * size of every resource. Nearly free on top of {@link TIMING}, because the topology repeats
20
+ * frame to frame and is stored once.
21
+ */
22
+ STRUCTURE: 1,
23
+
24
+ /**
25
+ * Adds workload: dispatch and draw counts, pipeline identities, workgroup sizes, attachment
26
+ * state. This is the level at which "is this dispatch the right size" becomes answerable.
27
+ */
28
+ WORKLOAD: 2,
29
+
30
+ /**
31
+ * Adds bind group contents, resolved indirect counts, and per-pass CPU timing. Roughly an order
32
+ * of magnitude more data than {@link WORKLOAD}. Meant for a specific repro with a frame limit
33
+ * set, not for leaving running.
34
+ */
35
+ VERBOSE: 3
36
+ };
37
+
38
+ /**
39
+ * @param {number} level one of {@link GPUProfileLevel}
40
+ * @returns {string}
41
+ */
42
+ export function gpu_profile_level_name(level) {
43
+ for (const name in GPUProfileLevel) {
44
+ if (GPUProfileLevel[name] === level) {
45
+ return name;
46
+ }
47
+ }
48
+
49
+ return `UNKNOWN(${level})`;
50
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Everything a reader needs in order for the numbers to mean anything.
3
+ *
4
+ * Without this a shared capture is unreadable: a pass taking 0.4 ms says nothing until you know
5
+ * which GPU, at what resolution, and with which features on.
6
+ *
7
+ * **On timestamp quantization.** Browsers quantize WebGPU timestamps as a timing-attack
8
+ * mitigation — Chrome to 100 µs unless `chrome://flags/#enable-webgpu-developer-features` is set —
9
+ * and the median Shade pass runs well under that. Nothing here measures or compensates for it, by
10
+ * deliberate choice: recovering the quantum is recovering exactly what the browser removed on
11
+ * purpose, and a correction that is slightly wrong skews every number it touches. The capture
12
+ * records what the device reported and says nothing more. Reading a coarse capture is a matter of
13
+ * aggregating across many frames rather than trusting any single one, and that is the reader's
14
+ * judgement to make, not something to bake into the data.
15
+ *
16
+ * @author Alex Goldring
17
+ * @copyright Company Named Limited (c) 2026
18
+ */
19
+ export class GPUProfileMeta {
20
+ /**
21
+ * One of {@link GPUProfileLevel}.
22
+ * @type {number}
23
+ */
24
+ level: number;
25
+ /**
26
+ * `Date.now()` when the session started. Wall clock, for ordering captures against each other
27
+ * and against a bug report — not for timing anything.
28
+ * @type {number}
29
+ */
30
+ wall_clock_start_ms: number;
31
+ /**
32
+ * Whatever the person capturing wanted to say about it.
33
+ * @type {string}
34
+ */
35
+ note: string;
36
+ /**
37
+ * @type {string}
38
+ */
39
+ engine_version: string;
40
+ /**
41
+ * From `GPUAdapterInfo`. Empty where the browser withheld it, which several do.
42
+ * @type {string}
43
+ */
44
+ adapter_vendor: string;
45
+ /**
46
+ * @type {string}
47
+ */
48
+ adapter_architecture: string;
49
+ /**
50
+ * @type {string}
51
+ */
52
+ adapter_device: string;
53
+ /**
54
+ * @type {string}
55
+ */
56
+ adapter_description: string;
57
+ /**
58
+ * `GPUFeatureName`s the device was created with.
59
+ * @type {string[]}
60
+ */
61
+ features: string[];
62
+ }
63
+ //# sourceMappingURL=GPUProfileMeta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GPUProfileMeta.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/GPUProfileMeta.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH;IACI;;;OAGG;IACH,OAFU,MAAM,CAEe;IAE/B;;;;OAIG;IACH,qBAFU,MAAM,CAEQ;IAExB;;;OAGG;IACH,MAFU,MAAM,CAEN;IAEV;;OAEG;IACH,gBAFU,MAAM,CAEI;IAEpB;;;OAGG;IACH,gBAFU,MAAM,CAEI;IAEpB;;OAEG;IACH,sBAFU,MAAM,CAEU;IAE1B;;OAEG;IACH,gBAFU,MAAM,CAEI;IAEpB;;OAEG;IACH,qBAFU,MAAM,CAES;IAEzB;;;OAGG;IACH,UAFU,MAAM,EAAE,CAEJ;CAEjB"}
@@ -0,0 +1,73 @@
1
+ import { GPUProfileLevel } from "./GPUProfileLevel.js";
2
+
3
+ /**
4
+ * Everything a reader needs in order for the numbers to mean anything.
5
+ *
6
+ * Without this a shared capture is unreadable: a pass taking 0.4 ms says nothing until you know
7
+ * which GPU, at what resolution, and with which features on.
8
+ *
9
+ * **On timestamp quantization.** Browsers quantize WebGPU timestamps as a timing-attack
10
+ * mitigation — Chrome to 100 µs unless `chrome://flags/#enable-webgpu-developer-features` is set —
11
+ * and the median Shade pass runs well under that. Nothing here measures or compensates for it, by
12
+ * deliberate choice: recovering the quantum is recovering exactly what the browser removed on
13
+ * purpose, and a correction that is slightly wrong skews every number it touches. The capture
14
+ * records what the device reported and says nothing more. Reading a coarse capture is a matter of
15
+ * aggregating across many frames rather than trusting any single one, and that is the reader's
16
+ * judgement to make, not something to bake into the data.
17
+ *
18
+ * @author Alex Goldring
19
+ * @copyright Company Named Limited (c) 2026
20
+ */
21
+ export class GPUProfileMeta {
22
+ /**
23
+ * One of {@link GPUProfileLevel}.
24
+ * @type {number}
25
+ */
26
+ level = GPUProfileLevel.TIMING;
27
+
28
+ /**
29
+ * `Date.now()` when the session started. Wall clock, for ordering captures against each other
30
+ * and against a bug report — not for timing anything.
31
+ * @type {number}
32
+ */
33
+ wall_clock_start_ms = 0;
34
+
35
+ /**
36
+ * Whatever the person capturing wanted to say about it.
37
+ * @type {string}
38
+ */
39
+ note = "";
40
+
41
+ /**
42
+ * @type {string}
43
+ */
44
+ engine_version = "";
45
+
46
+ /**
47
+ * From `GPUAdapterInfo`. Empty where the browser withheld it, which several do.
48
+ * @type {string}
49
+ */
50
+ adapter_vendor = "";
51
+
52
+ /**
53
+ * @type {string}
54
+ */
55
+ adapter_architecture = "";
56
+
57
+ /**
58
+ * @type {string}
59
+ */
60
+ adapter_device = "";
61
+
62
+ /**
63
+ * @type {string}
64
+ */
65
+ adapter_description = "";
66
+
67
+ /**
68
+ * `GPUFeatureName`s the device was created with.
69
+ * @type {string[]}
70
+ */
71
+ features = [];
72
+
73
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * A GPU profile recording, from `start` to `stop`.
3
+ *
4
+ * **The engine never constructs one of these.** The caller builds it and hands it over:
5
+ *
6
+ * ```js
7
+ * const session = new GPUProfileSession({ level: GPUProfileLevel.TIMING });
8
+ *
9
+ * renderer.profile_session = session;
10
+ *
11
+ * await session.start(graphics);
12
+ * // ... frames run, for as long as you like ...
13
+ * const bytes = await session.stop();
14
+ *
15
+ * renderer.profile_session = null;
16
+ * ```
17
+ *
18
+ * That ownership is the point rather than a style choice. **Nothing in the engine imports this file
19
+ * or anything it reaches** — the write stream, the format codecs, the topology extractor — so an
20
+ * application that never mentions the profiler does not carry any of it. The feature ships without
21
+ * a build flag, a strip-plugin interaction, or a dead branch anyone has to keep honest.
22
+ *
23
+ * Two leaf helpers under this directory *are* linked unconditionally, because
24
+ * {@link ShadeGPUCommandContext} needs them while encoding: `make_profiling_pass_encoder` and
25
+ * `parse_workgroup_size`. They import `assert` and nothing else, and neither knows what a capture
26
+ * is. What remains in the engine beyond those is a nullable field and the null checks around it.
27
+ *
28
+ * Recording is an **open-ended forward stream**: `frame_limit` defaults to `Infinity` and
29
+ * everything captured is kept. There is no ring buffer. Uncapped means a per-frame cost is also a
30
+ * per-second one, so the session reports what it is accumulating rather than letting an
31
+ * application find out when the tab dies.
32
+ *
33
+ * @author Alex Goldring
34
+ * @copyright Company Named Limited (c) 2026
35
+ */
36
+ export class GPUProfileSession {
37
+ /**
38
+ * @param {object} [options]
39
+ * @param {number} [options.level] one of {@link GPUProfileLevel}
40
+ * @param {number} [options.frame_limit] frames to record before stopping on its own. `Infinity`
41
+ * by default — a session runs until `stop()`.
42
+ * @param {string} [options.note] whatever you want the capture to say about itself
43
+ */
44
+ constructor({ level, frame_limit, note }?: {
45
+ level?: number;
46
+ frame_limit?: number;
47
+ note?: string;
48
+ });
49
+ /**
50
+ * How many distinct frame graph structures this capture has needed.
51
+ *
52
+ * Worth watching: if this climbs with the frame count, the graph is changing shape every frame
53
+ * and the size estimates for a long capture do not hold.
54
+ *
55
+ * @returns {number}
56
+ */
57
+ get topology_count(): number;
58
+ /**
59
+ * Bytes at which the session warns — once — that it is getting large. It does **not** stop:
60
+ * full uncapped history is the behaviour that was asked for, and silently truncating a capture
61
+ * is worse than a large one.
62
+ *
63
+ * @type {number}
64
+ */
65
+ byte_budget: number;
66
+ /**
67
+ * Fires per frame with the running byte total.
68
+ *
69
+ * @readonly
70
+ * @type {Signal<number>}
71
+ */
72
+ readonly onBytesWritten: any;
73
+ /**
74
+ * Fires once, with the finished capture, however the session ended — `stop()` or the frame
75
+ * limit running out. Both paths land here so a caller can await one thing.
76
+ *
77
+ * @readonly
78
+ * @type {Signal<ArrayBuffer>}
79
+ */
80
+ readonly onComplete: any;
81
+ /**
82
+ * @returns {number} one of {@link GPUProfileLevel}
83
+ */
84
+ get level(): number;
85
+ /**
86
+ * @returns {boolean}
87
+ */
88
+ get is_running(): boolean;
89
+ /**
90
+ * @returns {number}
91
+ */
92
+ get frames_recorded(): number;
93
+ /**
94
+ * @returns {number}
95
+ */
96
+ get bytes_written(): number;
97
+ /**
98
+ * The metadata this capture will carry. Populate before {@link start}; it is written into the
99
+ * stream at that point and not read again.
100
+ *
101
+ * @returns {GPUProfileMeta}
102
+ */
103
+ get meta(): GPUProfileMeta;
104
+ /**
105
+ * Begin recording.
106
+ *
107
+ * {@link meta} is written into the stream here and not read again, so populate it first.
108
+ *
109
+ * @returns {void}
110
+ */
111
+ start(): void;
112
+ /**
113
+ * Open a recorder for a frame about to be encoded, or null when nothing should be recorded.
114
+ *
115
+ * **This is why the renderer imports nothing from the profiler.** It holds a nullable session
116
+ * and calls methods on it; the recorder type never appears in its module graph, so the whole
117
+ * profiler stays unreachable from any engine entry point and drops out of a bundle that does
118
+ * not ask for it. A `new GPUFrameRecorder(...)` in the renderer would have quietly undone that.
119
+ *
120
+ * @param {number} frame_index the renderer's own frame counter
121
+ * @param {number} cpu_begin_ms
122
+ * @returns {GPUFrameRecorder|null}
123
+ */
124
+ begin_frame(frame_index: number, cpu_begin_ms: number): GPUFrameRecorder | null;
125
+ /**
126
+ * Close a recorder opened by {@link begin_frame} and commit what it collected.
127
+ *
128
+ * @param {GPUFrameRecorder} recorder
129
+ * @param {number} cpu_submit_ms
130
+ * @returns {void}
131
+ */
132
+ end_frame(recorder: GPUFrameRecorder, cpu_submit_ms: number): void;
133
+ /**
134
+ * Commit one frame.
135
+ *
136
+ * Ignored when the session is not running, so a frame that lands after `stop()` — the readback
137
+ * for a frame already submitted, which is the ordinary case — is dropped rather than throwing
138
+ * inside somebody's render loop.
139
+ *
140
+ * @param {GPUProfileFrame} frame
141
+ * @returns {void}
142
+ */
143
+ record_frame(frame: GPUProfileFrame): void;
144
+ /**
145
+ * The capture as it stands, without ending the session.
146
+ *
147
+ * Reads back as a capture that was cut short — no directory, not marked closed — because that
148
+ * is precisely what it is. Recording continues afterwards.
149
+ *
150
+ * @returns {ArrayBuffer}
151
+ */
152
+ snapshot(): ArrayBuffer;
153
+ /**
154
+ * End the recording and hand back the capture.
155
+ *
156
+ * Calling it twice returns the same bytes rather than complaining; a session that hit its
157
+ * frame limit has already stopped itself, and the caller awaiting `stop()` should still get
158
+ * the capture.
159
+ *
160
+ * @returns {ArrayBuffer}
161
+ */
162
+ stop(): ArrayBuffer;
163
+ /**
164
+ * Resolves with the capture when the session ends, whichever way it ends.
165
+ *
166
+ * @returns {Promise<ArrayBuffer>}
167
+ */
168
+ get done(): Promise<ArrayBuffer>;
169
+ #private;
170
+ }
171
+ import { GPUProfileMeta } from "./GPUProfileMeta.js";
172
+ import { GPUFrameRecorder } from "./GPUFrameRecorder.js";
173
+ //# sourceMappingURL=GPUProfileSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GPUProfileSession.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/GPUProfileSession.js"],"names":[],"mappings":"AAuBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH;IAqGI;;;;;;OAMG;IACH;QAL4B,KAAK,GAAtB,MAAM;QACW,WAAW,GAA5B,MAAM;QAEW,IAAI,GAArB,MAAM;OAkBhB;IA7DD;;;;;;;OAOG;IACH,6BAEC;IAED;;;;;;OAMG;IACH,aAFU,MAAM,CAEkB;IAElC;;;;;OAKG;IACH,6BAA8B;IAE9B;;;;;;OAMG;IACH,yBAA0B;IA2B1B;;OAEG;IACH,oBAEC;IAED;;OAEG;IACH,0BAEC;IAED;;OAEG;IACH,8BAEC;IAED;;OAEG;IACH,4BAEC;IAED;;;;;OAKG;IACH,2BAEC;IAED;;;;;;OAMG;IACH,SAFa,IAAI,CAqBhB;IAED;;;;;;;;;;;OAWG;IACH,yBAJW,MAAM,gBACN,MAAM,GACJ,gBAAgB,GAAC,IAAI,CAkBjC;IAED;;;;;;OAMG;IACH,oBAJW,gBAAgB,iBAChB,MAAM,GACJ,IAAI,CAMhB;IAsCD;;;;;;;;;OASG;IACH,sCAFa,IAAI,CAkEhB;IAED;;;;;;;OAOG;IACH,YAFa,WAAW,CAMvB;IAED;;;;;;;;OAQG;IACH,QAFa,WAAW,CAiBvB;IAOD;;;;OAIG;IACH,iCAaC;;CACJ;+BAjd8B,qBAAqB;iCAFnB,uBAAuB"}