@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.
- package/README.md +168 -152
- package/package.json +1 -1
- package/src/REVIEW_2026_08_06.md +610 -610
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +28 -4
- package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts +26 -0
- package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts.map +1 -1
- package/src/engine/graphics/render/frame_graph/FrameGraph.js +47 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +0 -3
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +0 -3
- package/src/shade/device/ShadeGPUCommandContext.d.ts +21 -0
- package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
- package/src/shade/device/ShadeGPUCommandContext.js +940 -749
- package/src/shade/device/timing/GPUTimerArray.d.ts +22 -4
- package/src/shade/device/timing/GPUTimerArray.d.ts.map +1 -1
- package/src/shade/device/timing/GPUTimerArray.js +88 -14
- package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +349 -98
- package/src/shade/device/timing/profile/GPUFrameRecorder.d.ts +93 -0
- package/src/shade/device/timing/profile/GPUFrameRecorder.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUFrameRecorder.js +297 -0
- package/src/shade/device/timing/profile/GPUProfileCapture.d.ts +62 -0
- package/src/shade/device/timing/profile/GPUProfileCapture.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileCapture.js +77 -0
- package/src/shade/device/timing/profile/GPUProfileFrame.d.ts +73 -0
- package/src/shade/device/timing/profile/GPUProfileFrame.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileFrame.js +88 -0
- package/src/shade/device/timing/profile/GPUProfileLevel.d.ts +20 -0
- package/src/shade/device/timing/profile/GPUProfileLevel.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileLevel.js +50 -0
- package/src/shade/device/timing/profile/GPUProfileMeta.d.ts +63 -0
- package/src/shade/device/timing/profile/GPUProfileMeta.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileMeta.js +73 -0
- package/src/shade/device/timing/profile/GPUProfileSession.d.ts +173 -0
- package/src/shade/device/timing/profile/GPUProfileSession.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileSession.js +470 -0
- package/src/shade/device/timing/profile/GPUProfileSpan.d.ts +60 -0
- package/src/shade/device/timing/profile/GPUProfileSpan.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileSpan.js +65 -0
- package/src/shade/device/timing/profile/GPUProfileSpanKind.d.ts +19 -0
- package/src/shade/device/timing/profile/GPUProfileSpanKind.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileSpanKind.js +25 -0
- package/src/shade/device/timing/profile/GPUProfileTopology.d.ts +159 -0
- package/src/shade/device/timing/profile/GPUProfileTopology.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileTopology.js +202 -0
- package/src/shade/device/timing/profile/GPUProfileWork.d.ts +90 -0
- package/src/shade/device/timing/profile/GPUProfileWork.d.ts.map +1 -0
- package/src/shade/device/timing/profile/GPUProfileWork.js +115 -0
- package/src/shade/device/timing/profile/SGPTDefect.d.ts +43 -0
- package/src/shade/device/timing/profile/SGPTDefect.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPTDefect.js +65 -0
- package/src/shade/device/timing/profile/SGPTHeader.d.ts +38 -0
- package/src/shade/device/timing/profile/SGPTHeader.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPTHeader.js +46 -0
- package/src/shade/device/timing/profile/SGPTRecordRef.d.ts +37 -0
- package/src/shade/device/timing/profile/SGPTRecordRef.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPTRecordRef.js +45 -0
- package/src/shade/device/timing/profile/SGPTWriteStream.d.ts +103 -0
- package/src/shade/device/timing/profile/SGPTWriteStream.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPTWriteStream.js +287 -0
- package/src/shade/device/timing/profile/SGPT_DEFECT.d.ts +19 -0
- package/src/shade/device/timing/profile/SGPT_DEFECT.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_DEFECT.js +26 -0
- package/src/shade/device/timing/profile/SGPT_FLAG.d.ts +8 -0
- package/src/shade/device/timing/profile/SGPT_FLAG.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_FLAG.js +15 -0
- package/src/shade/device/timing/profile/SGPT_FORMAT.md +345 -0
- package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.d.ts +10 -0
- package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_FORMAT_VERSION.js +9 -0
- package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.d.ts +17 -0
- package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_FRAME_SLACK.js +16 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.d.ts +16 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_CHECKSUM_COVERAGE.js +15 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.d.ts +15 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_OFFSET.js +29 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.d.ts +7 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_HEADER_SIZE.js +6 -0
- package/src/shade/device/timing/profile/SGPT_MAGIC.d.ts +7 -0
- package/src/shade/device/timing/profile/SGPT_MAGIC.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_MAGIC.js +6 -0
- package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.d.ts +16 -0
- package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_MIN_READER_VERSION.js +15 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.d.ts +7 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_HEADER_SIZE.js +6 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.d.ts +13 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_OFFSET.js +23 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.d.ts +12 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_SYNC.js +11 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.d.ts +18 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.d.ts.map +1 -0
- package/src/shade/device/timing/profile/SGPT_RECORD_TYPE.js +46 -0
- package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts +20 -0
- package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts.map +1 -0
- package/src/shade/device/timing/profile/frame_graph_extract_topology.js +174 -0
- package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts +21 -0
- package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts.map +1 -0
- package/src/shade/device/timing/profile/make_profiling_pass_encoder.js +114 -0
- package/src/shade/device/timing/profile/parse_workgroup_size.d.ts +20 -0
- package/src/shade/device/timing/profile/parse_workgroup_size.d.ts.map +1 -0
- package/src/shade/device/timing/profile/parse_workgroup_size.js +43 -0
- package/src/shade/device/timing/profile/sgpt_fourcc.d.ts +18 -0
- package/src/shade/device/timing/profile/sgpt_fourcc.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_fourcc.js +34 -0
- package/src/shade/device/timing/profile/sgpt_frame_codec.d.ts +42 -0
- package/src/shade/device/timing/profile/sgpt_frame_codec.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_frame_codec.js +172 -0
- package/src/shade/device/timing/profile/sgpt_meta_codec.d.ts +20 -0
- package/src/shade/device/timing/profile/sgpt_meta_codec.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_meta_codec.js +65 -0
- package/src/shade/device/timing/profile/sgpt_read_capture.d.ts +19 -0
- package/src/shade/device/timing/profile/sgpt_read_capture.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_read_capture.js +134 -0
- package/src/shade/device/timing/profile/sgpt_read_header.d.ts +22 -0
- package/src/shade/device/timing/profile/sgpt_read_header.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_read_header.js +94 -0
- package/src/shade/device/timing/profile/sgpt_read_records.d.ts +27 -0
- package/src/shade/device/timing/profile/sgpt_read_records.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_read_records.js +153 -0
- package/src/shade/device/timing/profile/sgpt_topology_codec.d.ts +54 -0
- package/src/shade/device/timing/profile/sgpt_topology_codec.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_topology_codec.js +329 -0
- package/src/shade/device/timing/profile/sgpt_write_header.d.ts +22 -0
- package/src/shade/device/timing/profile/sgpt_write_header.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_write_header.js +47 -0
- package/src/shade/device/timing/profile/sgpt_write_record.d.ts +23 -0
- package/src/shade/device/timing/profile/sgpt_write_record.d.ts.map +1 -0
- package/src/shade/device/timing/profile/sgpt_write_record.js +62 -0
- package/src/shade/playground/volumetrics_froxel/README.md +88 -0
- package/src/shade/playground/volumetrics_froxel/index.html +251 -0
- package/src/shade/playground/volumetrics_froxel/main.d.ts +2 -0
- package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -0
- package/src/shade/playground/volumetrics_froxel/main.js +619 -0
- package/src/shade/renderer/Renderer.d.ts +34 -0
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +2536 -2455
- package/src/shade/renderer/volumetrics/NOTES.md +42 -1
- package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.d.ts +21 -0
- package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_camera_ray_planar_depth_scale.js +47 -0
- package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.js +19 -8
- package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.d.ts +37 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_content_offset.js +56 -0
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +6 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +367 -324
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +14 -3
- package/src/shade/renderer/volumetrics/volumetrics_position_world_to_froxel_uvw.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/volumetrics_position_world_to_froxel_uvw.js +48 -36
- package/src/engine/save/GameStateLoader.d.ts +0 -49
- package/src/engine/save/GameStateLoader.d.ts.map +0 -1
- 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"}
|