@woosh/meep-engine 3.10.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/renderer/Renderer.d.ts +21 -0
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +2513 -2455
- 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,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `'SREC'`, the little-endian u32 that opens every record in the stream.
|
|
3
|
+
*
|
|
4
|
+
* A reader that has lost its place — corruption, a torn write, a capture that ended when the tab
|
|
5
|
+
* did — scans forward for this and resumes. It is not a guarantee on its own: four bytes of payload
|
|
6
|
+
* can spell `SREC` by accident. It is the cheap filter, and the record's length and checksum are
|
|
7
|
+
* what actually confirm the find. See {@link sgpt_read_records}.
|
|
8
|
+
*
|
|
9
|
+
* @type {number}
|
|
10
|
+
*/
|
|
11
|
+
export const SGPT_RECORD_SYNC = 0x43455253;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a record in an `.sgpt` stream carries. Written as a little-endian u32 tag, so a hex dump
|
|
3
|
+
* reads as the four letters.
|
|
4
|
+
*
|
|
5
|
+
* A reader **must** skip a type it does not know, using the record's declared length. That is what
|
|
6
|
+
* lets a newer recorder add record types without a format version bump, and what lets an older
|
|
7
|
+
* inspector open the result and be honest about which parts it cannot show.
|
|
8
|
+
*/
|
|
9
|
+
export type SGPT_RECORD_TYPE = number;
|
|
10
|
+
export namespace SGPT_RECORD_TYPE {
|
|
11
|
+
let META: number;
|
|
12
|
+
let SYMS: number;
|
|
13
|
+
let TOPO: number;
|
|
14
|
+
let FRAM: number;
|
|
15
|
+
let CNTR: number;
|
|
16
|
+
let DIRE: number;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=SGPT_RECORD_TYPE.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SGPT_RECORD_TYPE.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/SGPT_RECORD_TYPE.js"],"names":[],"mappings":";;;;;;;;+BAQU,MAAM"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a record in an `.sgpt` stream carries. Written as a little-endian u32 tag, so a hex dump
|
|
3
|
+
* reads as the four letters.
|
|
4
|
+
*
|
|
5
|
+
* A reader **must** skip a type it does not know, using the record's declared length. That is what
|
|
6
|
+
* lets a newer recorder add record types without a format version bump, and what lets an older
|
|
7
|
+
* inspector open the result and be honest about which parts it cannot show.
|
|
8
|
+
*
|
|
9
|
+
* @enum {number}
|
|
10
|
+
*/
|
|
11
|
+
export const SGPT_RECORD_TYPE = {
|
|
12
|
+
/**
|
|
13
|
+
* Session context: adapter, engine version, renderer settings, verbosity level, the calibrated
|
|
14
|
+
* timestamp period. Written once, immediately after the header.
|
|
15
|
+
*/
|
|
16
|
+
META: 0x4154454D,
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A block of strings, introduced by the record that first needs them and referenced by index
|
|
20
|
+
* afterwards. Emitted inline rather than gathered into one table at the tail, so a truncated
|
|
21
|
+
* capture still resolves every name it references.
|
|
22
|
+
*/
|
|
23
|
+
SYMS: 0x534D5953,
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* One frame graph topology: passes, resource nodes, edges, scopes, descriptors. Content-hashed,
|
|
27
|
+
* so the steady state emits this once and every subsequent frame refers to it by id.
|
|
28
|
+
*/
|
|
29
|
+
TOPO: 0x4F504F54,
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* One frame: which topology, the CPU clocks around it, and the GPU spans that came back.
|
|
33
|
+
*/
|
|
34
|
+
FRAM: 0x4D415246,
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Counter samples over the session — GPU memory in use, scene scale, resolution.
|
|
38
|
+
*/
|
|
39
|
+
CNTR: 0x52544E43,
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The directory, written at {@link GPUProfileSession.stop}. An index, never a dependency: every
|
|
43
|
+
* record above is readable by scanning, and a capture that never got to stop is still a capture.
|
|
44
|
+
*/
|
|
45
|
+
DIRE: 0x45524944
|
|
46
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record the shape of a compiled frame graph.
|
|
3
|
+
*
|
|
4
|
+
* **Call after `compile`, and ideally after `execute`.** Reference counts and cull decisions are
|
|
5
|
+
* what `compile` derives; before it every pass reads as culled, which would be recorded as a frame
|
|
6
|
+
* in which nothing ran.
|
|
7
|
+
*
|
|
8
|
+
* Resources are keyed by **node**, one per version, matching `FrameGraph.exportToJson`. Passes
|
|
9
|
+
* record the node they touched rather than the underlying resource, so a registry-keyed model would
|
|
10
|
+
* leave every reference past version 0 pointing at nothing.
|
|
11
|
+
*
|
|
12
|
+
* @param {FrameGraph} graph
|
|
13
|
+
* @returns {GPUProfileTopology}
|
|
14
|
+
*
|
|
15
|
+
* @author Alex Goldring
|
|
16
|
+
* @copyright Company Named Limited (c) 2026
|
|
17
|
+
*/
|
|
18
|
+
export function frame_graph_extract_topology(graph: FrameGraph): GPUProfileTopology;
|
|
19
|
+
import { GPUProfileTopology } from "./GPUProfileTopology.js";
|
|
20
|
+
//# sourceMappingURL=frame_graph_extract_topology.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame_graph_extract_topology.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/frame_graph_extract_topology.js"],"names":[],"mappings":"AA+CA;;;;;;;;;;;;;;;;GAgBG;AACH,iEALa,kBAAkB,CAkH9B;mCArKM,yBAAyB"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { gpu_texture_format_info } from "../../../descriptor/texture/format/gpu_texture_format_info.js";
|
|
3
|
+
import {
|
|
4
|
+
GPUProfilePass,
|
|
5
|
+
GPUProfileResource,
|
|
6
|
+
GPUProfileResourceKind,
|
|
7
|
+
GPUProfileScope,
|
|
8
|
+
GPUProfileTopology
|
|
9
|
+
} from "./GPUProfileTopology.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Declared bytes for a texture descriptor: the whole mip chain, at the given sample count.
|
|
13
|
+
*
|
|
14
|
+
* A format the engine's table does not know reports 0 rather than throwing. A capture is a
|
|
15
|
+
* diagnostic; refusing to record one because a size could not be worked out would be the wrong
|
|
16
|
+
* trade every time.
|
|
17
|
+
*
|
|
18
|
+
* @param {TextureResourceDescriptor} descriptor
|
|
19
|
+
* @returns {number}
|
|
20
|
+
*/
|
|
21
|
+
function texture_byte_size(descriptor) {
|
|
22
|
+
let info;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
info = gpu_texture_format_info(descriptor.format);
|
|
26
|
+
} catch {
|
|
27
|
+
// `gpu_texture_format_info` throws on a name it does not know. Correct for the renderer,
|
|
28
|
+
// wrong here: a capture is a diagnostic, and losing the whole recording because one
|
|
29
|
+
// resource used a format the table has not caught up with is the wrong trade every time.
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { block_width, block_height, bytes_per_block } = info;
|
|
34
|
+
|
|
35
|
+
let total = 0;
|
|
36
|
+
|
|
37
|
+
for (let mip = 0; mip < descriptor.mipLevelCount; mip++) {
|
|
38
|
+
const w = Math.max(1, Math.floor(descriptor.resolution[0]) >> mip);
|
|
39
|
+
const h = Math.max(1, Math.floor(descriptor.resolution[1]) >> mip);
|
|
40
|
+
const d = Math.max(1, Math.floor(descriptor.resolution[2]) >> mip);
|
|
41
|
+
|
|
42
|
+
total += Math.ceil(w / block_width) * Math.ceil(h / block_height) * d * bytes_per_block;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return total * descriptor.sampleCount;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Record the shape of a compiled frame graph.
|
|
50
|
+
*
|
|
51
|
+
* **Call after `compile`, and ideally after `execute`.** Reference counts and cull decisions are
|
|
52
|
+
* what `compile` derives; before it every pass reads as culled, which would be recorded as a frame
|
|
53
|
+
* in which nothing ran.
|
|
54
|
+
*
|
|
55
|
+
* Resources are keyed by **node**, one per version, matching `FrameGraph.exportToJson`. Passes
|
|
56
|
+
* record the node they touched rather than the underlying resource, so a registry-keyed model would
|
|
57
|
+
* leave every reference past version 0 pointing at nothing.
|
|
58
|
+
*
|
|
59
|
+
* @param {FrameGraph} graph
|
|
60
|
+
* @returns {GPUProfileTopology}
|
|
61
|
+
*
|
|
62
|
+
* @author Alex Goldring
|
|
63
|
+
* @copyright Company Named Limited (c) 2026
|
|
64
|
+
*/
|
|
65
|
+
export function frame_graph_extract_topology(graph) {
|
|
66
|
+
assert.defined(graph, 'graph');
|
|
67
|
+
|
|
68
|
+
const topology = new GPUProfileTopology();
|
|
69
|
+
|
|
70
|
+
topology.name = graph.name;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Scopes are reached through the nodes that were recorded inside them, and flattened to an
|
|
74
|
+
* array here. `FrameGraph` keeps them as a parent chain rather than a list, so this walk is
|
|
75
|
+
* what turns the chain into something a reader can index.
|
|
76
|
+
*
|
|
77
|
+
* @type {Map<FrameGraphScope, number>}
|
|
78
|
+
*/
|
|
79
|
+
const scope_ids = new Map();
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {FrameGraphScope|null} scope
|
|
83
|
+
* @returns {number} index, or -1
|
|
84
|
+
*/
|
|
85
|
+
function intern_scope(scope) {
|
|
86
|
+
if (scope === null || scope === undefined) {
|
|
87
|
+
return -1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const existing = scope_ids.get(scope);
|
|
91
|
+
|
|
92
|
+
if (existing !== undefined) {
|
|
93
|
+
return existing;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// parent first, so a parent always has a lower index than its children and a reader can
|
|
97
|
+
// resolve the chain in one forward pass
|
|
98
|
+
const parent = intern_scope(scope.parent);
|
|
99
|
+
|
|
100
|
+
const record = new GPUProfileScope();
|
|
101
|
+
|
|
102
|
+
record.name = scope.name;
|
|
103
|
+
record.parent = parent;
|
|
104
|
+
|
|
105
|
+
const id = topology.scopes.length;
|
|
106
|
+
|
|
107
|
+
topology.scopes.push(record);
|
|
108
|
+
scope_ids.set(scope, id);
|
|
109
|
+
|
|
110
|
+
return id;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const node of graph.__pass_nodes) {
|
|
114
|
+
const pass = new GPUProfilePass();
|
|
115
|
+
|
|
116
|
+
pass.id = node.id;
|
|
117
|
+
pass.name = node.name;
|
|
118
|
+
pass.scope = intern_scope(node.scope);
|
|
119
|
+
pass.culled = !node.can_execute();
|
|
120
|
+
pass.has_side_effects = node.has_side_effects;
|
|
121
|
+
pass.ref_count = node.ref_count;
|
|
122
|
+
pass.reads = node.resource_reads.slice();
|
|
123
|
+
pass.writes = node.resource_writes.slice();
|
|
124
|
+
pass.creates = node.resource_creates.slice();
|
|
125
|
+
|
|
126
|
+
topology.passes.push(pass);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const node of graph.__resource_nodes) {
|
|
130
|
+
const entry = graph.__resource_registry[node.resource_id];
|
|
131
|
+
|
|
132
|
+
const resource = new GPUProfileResource();
|
|
133
|
+
|
|
134
|
+
resource.id = node.id;
|
|
135
|
+
resource.name = node.name;
|
|
136
|
+
resource.version = node.version;
|
|
137
|
+
resource.producer = node.producer !== null ? node.producer.id : -1;
|
|
138
|
+
|
|
139
|
+
if (entry !== undefined) {
|
|
140
|
+
resource.transient = entry.isTransient();
|
|
141
|
+
resource.imported = entry.isImported();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const descriptor = entry?.resource_descriptor;
|
|
145
|
+
|
|
146
|
+
if (descriptor !== undefined && descriptor !== null) {
|
|
147
|
+
const type = descriptor.type;
|
|
148
|
+
|
|
149
|
+
if (type === 'buffer') {
|
|
150
|
+
resource.kind = GPUProfileResourceKind.BUFFER;
|
|
151
|
+
resource.size = descriptor.size;
|
|
152
|
+
resource.usage = descriptor.usage;
|
|
153
|
+
resource.byte_size = descriptor.size;
|
|
154
|
+
} else if (type === 'texture') {
|
|
155
|
+
resource.kind = GPUProfileResourceKind.TEXTURE;
|
|
156
|
+
resource.resolution = [
|
|
157
|
+
Math.floor(descriptor.resolution[0]),
|
|
158
|
+
Math.floor(descriptor.resolution[1]),
|
|
159
|
+
Math.floor(descriptor.resolution[2])
|
|
160
|
+
];
|
|
161
|
+
resource.mip_level_count = descriptor.mipLevelCount;
|
|
162
|
+
resource.sample_count = descriptor.sampleCount;
|
|
163
|
+
resource.format = descriptor.format;
|
|
164
|
+
resource.dimension = descriptor.dimension;
|
|
165
|
+
resource.usage = descriptor.usage;
|
|
166
|
+
resource.byte_size = texture_byte_size(descriptor);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
topology.resources.push(resource);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return topology;
|
|
174
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a pass encoder so that the work encoded into it is recorded.
|
|
3
|
+
*
|
|
4
|
+
* **A `Proxy` rather than a hand-written forwarder.** A pass encoder has around twenty methods and
|
|
5
|
+
* WebGPU keeps adding them; a forwarding class that misses one does not fail loudly, it silently
|
|
6
|
+
* drops a call that the renderer needed, and only while profiling — the worst possible shape for a
|
|
7
|
+
* bug. Everything not named below is forwarded untouched, including anything added later.
|
|
8
|
+
*
|
|
9
|
+
* The cost is a `Proxy` on the hot encoding path, which is why this exists only while a capture is
|
|
10
|
+
* running at {@link GPUProfileLevel.WORKLOAD} or above. Bound methods are cached per encoder so the
|
|
11
|
+
* `get` trap does not allocate a new closure on every `setBindGroup`.
|
|
12
|
+
*
|
|
13
|
+
* @param {GPUComputePassEncoder|GPURenderPassEncoder} encoder
|
|
14
|
+
* @param {GPUProfileWork} work accumulated into
|
|
15
|
+
* @returns {GPUComputePassEncoder|GPURenderPassEncoder}
|
|
16
|
+
*
|
|
17
|
+
* @author Alex Goldring
|
|
18
|
+
* @copyright Company Named Limited (c) 2026
|
|
19
|
+
*/
|
|
20
|
+
export function make_profiling_pass_encoder(encoder: GPUComputePassEncoder | GPURenderPassEncoder, work: GPUProfileWork): GPUComputePassEncoder | GPURenderPassEncoder;
|
|
21
|
+
//# sourceMappingURL=make_profiling_pass_encoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"make_profiling_pass_encoder.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/make_profiling_pass_encoder.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,qDAPW,4CAA0C,yBAExC,4CAA0C,CAiGtD"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrap a pass encoder so that the work encoded into it is recorded.
|
|
5
|
+
*
|
|
6
|
+
* **A `Proxy` rather than a hand-written forwarder.** A pass encoder has around twenty methods and
|
|
7
|
+
* WebGPU keeps adding them; a forwarding class that misses one does not fail loudly, it silently
|
|
8
|
+
* drops a call that the renderer needed, and only while profiling — the worst possible shape for a
|
|
9
|
+
* bug. Everything not named below is forwarded untouched, including anything added later.
|
|
10
|
+
*
|
|
11
|
+
* The cost is a `Proxy` on the hot encoding path, which is why this exists only while a capture is
|
|
12
|
+
* running at {@link GPUProfileLevel.WORKLOAD} or above. Bound methods are cached per encoder so the
|
|
13
|
+
* `get` trap does not allocate a new closure on every `setBindGroup`.
|
|
14
|
+
*
|
|
15
|
+
* @param {GPUComputePassEncoder|GPURenderPassEncoder} encoder
|
|
16
|
+
* @param {GPUProfileWork} work accumulated into
|
|
17
|
+
* @returns {GPUComputePassEncoder|GPURenderPassEncoder}
|
|
18
|
+
*
|
|
19
|
+
* @author Alex Goldring
|
|
20
|
+
* @copyright Company Named Limited (c) 2026
|
|
21
|
+
*/
|
|
22
|
+
export function make_profiling_pass_encoder(encoder, work) {
|
|
23
|
+
assert.defined(encoder, 'encoder');
|
|
24
|
+
assert.defined(work, 'work');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @type {Map<string|symbol, function>}
|
|
28
|
+
*/
|
|
29
|
+
const bound = new Map();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} name
|
|
33
|
+
* @param {function} original
|
|
34
|
+
* @returns {function}
|
|
35
|
+
*/
|
|
36
|
+
function wrap(name, original) {
|
|
37
|
+
switch (name) {
|
|
38
|
+
|
|
39
|
+
case 'dispatchWorkgroups':
|
|
40
|
+
return function (x, y = 1, z = 1) {
|
|
41
|
+
work.dispatch_count++;
|
|
42
|
+
work.workgroups[0] += x;
|
|
43
|
+
work.workgroups[1] += y;
|
|
44
|
+
work.workgroups[2] += z;
|
|
45
|
+
|
|
46
|
+
return original.call(encoder, x, y, z);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
case 'dispatchWorkgroupsIndirect':
|
|
50
|
+
return function (...args) {
|
|
51
|
+
// Size lives in a GPU buffer, so there is nothing to add to `workgroups`.
|
|
52
|
+
// Counted apart so the totals are not read as covering this.
|
|
53
|
+
work.indirect_dispatch_count++;
|
|
54
|
+
|
|
55
|
+
return original.apply(encoder, args);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
case 'draw':
|
|
59
|
+
return function (vertex_count, instance_count = 1, ...rest) {
|
|
60
|
+
work.draw_count++;
|
|
61
|
+
work.vertex_count += vertex_count;
|
|
62
|
+
work.instance_count += instance_count;
|
|
63
|
+
|
|
64
|
+
return original.call(encoder, vertex_count, instance_count, ...rest);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
case 'drawIndexed':
|
|
68
|
+
return function (index_count, instance_count = 1, ...rest) {
|
|
69
|
+
work.draw_count++;
|
|
70
|
+
work.index_count += index_count;
|
|
71
|
+
work.instance_count += instance_count;
|
|
72
|
+
|
|
73
|
+
return original.call(encoder, index_count, instance_count, ...rest);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
case 'drawIndirect':
|
|
77
|
+
case 'drawIndexedIndirect':
|
|
78
|
+
return function (...args) {
|
|
79
|
+
work.indirect_draw_count++;
|
|
80
|
+
|
|
81
|
+
return original.apply(encoder, args);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
return function (...args) {
|
|
86
|
+
return original.apply(encoder, args);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return new Proxy(encoder, {
|
|
92
|
+
get(target, property) {
|
|
93
|
+
const value = Reflect.get(target, property, target);
|
|
94
|
+
|
|
95
|
+
if (typeof value !== 'function') {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let fn = bound.get(property);
|
|
100
|
+
|
|
101
|
+
if (fn === undefined) {
|
|
102
|
+
fn = wrap(property, value);
|
|
103
|
+
|
|
104
|
+
bound.set(property, fn);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return fn;
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
set(target, property, value) {
|
|
111
|
+
return Reflect.set(target, property, value, target);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a compute entry point's workgroup size out of its WGSL.
|
|
3
|
+
*
|
|
4
|
+
* **Only literal sizes.** WGSL allows `@workgroup_size(WG_X)` against a pipeline-overridable
|
|
5
|
+
* constant, and resolving that would mean tracking override values through pipeline creation for a
|
|
6
|
+
* number that is nice to have. Such a shader reports `[0, 0, 0]`, which the model treats as
|
|
7
|
+
* *unknown* rather than as zero — an invocation count of 0 alongside a real duration would be a
|
|
8
|
+
* lie, and this is the difference between the two.
|
|
9
|
+
*
|
|
10
|
+
* A module with several entry points reports the first size found. Shade compiles one entry point
|
|
11
|
+
* per compute pipeline, so in practice there is only ever one.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} code WGSL source
|
|
14
|
+
* @returns {number[]} `[x, y, z]`, or `[0, 0, 0]` when it could not be read
|
|
15
|
+
*
|
|
16
|
+
* @author Alex Goldring
|
|
17
|
+
* @copyright Company Named Limited (c) 2026
|
|
18
|
+
*/
|
|
19
|
+
export function parse_workgroup_size(code: string): number[];
|
|
20
|
+
//# sourceMappingURL=parse_workgroup_size.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse_workgroup_size.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/parse_workgroup_size.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;GAiBG;AACH,2CANW,MAAM,GACJ,MAAM,EAAE,CAqBpB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@workgroup_size(...)` with literal integer arguments. Whitespace is permitted anywhere WGSL
|
|
3
|
+
* permits it; anything that is not a plain decimal literal deliberately fails to match.
|
|
4
|
+
*
|
|
5
|
+
* @type {RegExp}
|
|
6
|
+
*/
|
|
7
|
+
const WORKGROUP_SIZE = /@workgroup_size\s*\(\s*(\d+)\s*(?:,\s*(\d+)\s*)?(?:,\s*(\d+)\s*)?\)/;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Read a compute entry point's workgroup size out of its WGSL.
|
|
11
|
+
*
|
|
12
|
+
* **Only literal sizes.** WGSL allows `@workgroup_size(WG_X)` against a pipeline-overridable
|
|
13
|
+
* constant, and resolving that would mean tracking override values through pipeline creation for a
|
|
14
|
+
* number that is nice to have. Such a shader reports `[0, 0, 0]`, which the model treats as
|
|
15
|
+
* *unknown* rather than as zero — an invocation count of 0 alongside a real duration would be a
|
|
16
|
+
* lie, and this is the difference between the two.
|
|
17
|
+
*
|
|
18
|
+
* A module with several entry points reports the first size found. Shade compiles one entry point
|
|
19
|
+
* per compute pipeline, so in practice there is only ever one.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} code WGSL source
|
|
22
|
+
* @returns {number[]} `[x, y, z]`, or `[0, 0, 0]` when it could not be read
|
|
23
|
+
*
|
|
24
|
+
* @author Alex Goldring
|
|
25
|
+
* @copyright Company Named Limited (c) 2026
|
|
26
|
+
*/
|
|
27
|
+
export function parse_workgroup_size(code) {
|
|
28
|
+
if (typeof code !== 'string') {
|
|
29
|
+
return [0, 0, 0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const match = WORKGROUP_SIZE.exec(code);
|
|
33
|
+
|
|
34
|
+
if (match === null) {
|
|
35
|
+
return [0, 0, 0];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return [
|
|
39
|
+
Number(match[1]),
|
|
40
|
+
match[2] !== undefined ? Number(match[2]) : 1,
|
|
41
|
+
match[3] !== undefined ? Number(match[3]) : 1
|
|
42
|
+
];
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A four-character tag as the little-endian u32 it occupies on the wire.
|
|
3
|
+
*
|
|
4
|
+
* The container writes every tag — the file magic, the record sync marker, each record type — as a
|
|
5
|
+
* u32, and reads them back the same way, so `'SGPT'` on disk is the bytes `S G P T` in that order.
|
|
6
|
+
* This is the same convention {@link VGEO_MAGIC} follows.
|
|
7
|
+
*
|
|
8
|
+
* Constants are declared as literals rather than calls to this, so nothing is computed at import
|
|
9
|
+
* time; this exists so `sgpt_fourcc.spec.js` can prove each literal is the tag it claims to be.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} tag exactly four characters, each in the ASCII range
|
|
12
|
+
* @returns {number} unsigned 32-bit
|
|
13
|
+
*
|
|
14
|
+
* @author Alex Goldring
|
|
15
|
+
* @copyright Company Named Limited (c) 2026
|
|
16
|
+
*/
|
|
17
|
+
export function sgpt_fourcc(tag: string): number;
|
|
18
|
+
//# sourceMappingURL=sgpt_fourcc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sgpt_fourcc.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/sgpt_fourcc.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,iCANW,MAAM,GACJ,MAAM,CAoBlB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A four-character tag as the little-endian u32 it occupies on the wire.
|
|
5
|
+
*
|
|
6
|
+
* The container writes every tag — the file magic, the record sync marker, each record type — as a
|
|
7
|
+
* u32, and reads them back the same way, so `'SGPT'` on disk is the bytes `S G P T` in that order.
|
|
8
|
+
* This is the same convention {@link VGEO_MAGIC} follows.
|
|
9
|
+
*
|
|
10
|
+
* Constants are declared as literals rather than calls to this, so nothing is computed at import
|
|
11
|
+
* time; this exists so `sgpt_fourcc.spec.js` can prove each literal is the tag it claims to be.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} tag exactly four characters, each in the ASCII range
|
|
14
|
+
* @returns {number} unsigned 32-bit
|
|
15
|
+
*
|
|
16
|
+
* @author Alex Goldring
|
|
17
|
+
* @copyright Company Named Limited (c) 2026
|
|
18
|
+
*/
|
|
19
|
+
export function sgpt_fourcc(tag) {
|
|
20
|
+
assert.isString(tag, 'tag');
|
|
21
|
+
assert.equal(tag.length, 4, 'tag must be exactly 4 characters');
|
|
22
|
+
|
|
23
|
+
let r = 0;
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < 4; i++) {
|
|
26
|
+
const code = tag.charCodeAt(i);
|
|
27
|
+
|
|
28
|
+
assert.ok(code <= 0x7f, `tag[${i}] must be ASCII, was code ${code}`);
|
|
29
|
+
|
|
30
|
+
r |= code << (i * 8);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return r >>> 0;
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode one frame's `FRAM` payload.
|
|
3
|
+
*
|
|
4
|
+
* Timestamps are stored relative to the frame's own epoch rather than absolutely, which is what
|
|
5
|
+
* turns two 8-byte values per span into a u32 and a varint. A u32 of nanoseconds holds 4.29
|
|
6
|
+
* seconds, three orders of magnitude past any frame, and it survives a stall rather than wrapping.
|
|
7
|
+
*
|
|
8
|
+
* Two things a device can report that the format will not carry, both clamped and both counted
|
|
9
|
+
* rather than thrown on — a capture that refuses to record because one span came back strange is
|
|
10
|
+
* worth less than one that records it and says so:
|
|
11
|
+
*
|
|
12
|
+
* - **An end before its begin.** Stored as a zero duration.
|
|
13
|
+
* Work — dispatch and draw counts — rides behind a one-byte presence flag per span rather than a
|
|
14
|
+
* per-frame one, because a frame can mix instrumented and uninstrumented passes: a pass opened
|
|
15
|
+
* before the recorder was attached has none, and a flag per frame would have to lie about one of
|
|
16
|
+
* them.
|
|
17
|
+
*
|
|
18
|
+
* - **A begin more than 4.29 s after the frame epoch.** Only reachable if spans from different
|
|
19
|
+
* query sets turn out not to share a clock, which is unspecified in WebGPU and true in practice.
|
|
20
|
+
* Clamped to the maximum, which will look obviously wrong in the inspector rather than subtly so.
|
|
21
|
+
*
|
|
22
|
+
* @param {BinaryBuffer} buffer
|
|
23
|
+
* @param {GPUProfileFrame} frame
|
|
24
|
+
* @param {function(string): number} intern
|
|
25
|
+
* @returns {void}
|
|
26
|
+
*
|
|
27
|
+
* @author Alex Goldring
|
|
28
|
+
* @copyright Company Named Limited (c) 2026
|
|
29
|
+
*/
|
|
30
|
+
export function sgpt_write_frame(buffer: BinaryBuffer, frame: GPUProfileFrame, intern: (arg0: string) => number): void;
|
|
31
|
+
/**
|
|
32
|
+
* Decode one frame's `FRAM` payload.
|
|
33
|
+
*
|
|
34
|
+
* @param {BinaryBuffer} buffer positioned at the payload
|
|
35
|
+
* @param {string[]} symbols resolved so far
|
|
36
|
+
* @param {number} end offset one past this payload, so an older capture without the trailing
|
|
37
|
+
* topology reference is read as having none rather than running into the next record
|
|
38
|
+
* @returns {GPUProfileFrame}
|
|
39
|
+
*/
|
|
40
|
+
export function sgpt_read_frame(buffer: BinaryBuffer, symbols: string[], end: number): GPUProfileFrame;
|
|
41
|
+
import { GPUProfileFrame } from "./GPUProfileFrame.js";
|
|
42
|
+
//# sourceMappingURL=sgpt_frame_codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sgpt_frame_codec.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/sgpt_frame_codec.js"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,8DAPW,eAAe,iBACN,MAAM,KAAG,MAAM,GACtB,IAAI,CA+DhB;AAED;;;;;;;;GAQG;AACH,+DALW,MAAM,EAAE,OACR,MAAM,GAEJ,eAAe,CA+D3B;gCA1K+B,sBAAsB"}
|