@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,172 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { GPUProfileFrame } from "./GPUProfileFrame.js";
|
|
3
|
+
import { GPUProfileSpan } from "./GPUProfileSpan.js";
|
|
4
|
+
import { GPUProfileWork } from "./GPUProfileWork.js";
|
|
5
|
+
import { gpu_profile_span_kind_from_name, gpu_profile_span_kind_name } from "./GPUProfileSpanKind.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Largest span offset representable in the u32 the payload uses.
|
|
9
|
+
* @type {number}
|
|
10
|
+
*/
|
|
11
|
+
const MAX_BEGIN_OFFSET = 0xffffffff;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Encode one frame's `FRAM` payload.
|
|
15
|
+
*
|
|
16
|
+
* Timestamps are stored relative to the frame's own epoch rather than absolutely, which is what
|
|
17
|
+
* turns two 8-byte values per span into a u32 and a varint. A u32 of nanoseconds holds 4.29
|
|
18
|
+
* seconds, three orders of magnitude past any frame, and it survives a stall rather than wrapping.
|
|
19
|
+
*
|
|
20
|
+
* Two things a device can report that the format will not carry, both clamped and both counted
|
|
21
|
+
* rather than thrown on — a capture that refuses to record because one span came back strange is
|
|
22
|
+
* worth less than one that records it and says so:
|
|
23
|
+
*
|
|
24
|
+
* - **An end before its begin.** Stored as a zero duration.
|
|
25
|
+
* Work — dispatch and draw counts — rides behind a one-byte presence flag per span rather than a
|
|
26
|
+
* per-frame one, because a frame can mix instrumented and uninstrumented passes: a pass opened
|
|
27
|
+
* before the recorder was attached has none, and a flag per frame would have to lie about one of
|
|
28
|
+
* them.
|
|
29
|
+
*
|
|
30
|
+
* - **A begin more than 4.29 s after the frame epoch.** Only reachable if spans from different
|
|
31
|
+
* query sets turn out not to share a clock, which is unspecified in WebGPU and true in practice.
|
|
32
|
+
* Clamped to the maximum, which will look obviously wrong in the inspector rather than subtly so.
|
|
33
|
+
*
|
|
34
|
+
* @param {BinaryBuffer} buffer
|
|
35
|
+
* @param {GPUProfileFrame} frame
|
|
36
|
+
* @param {function(string): number} intern
|
|
37
|
+
* @returns {void}
|
|
38
|
+
*
|
|
39
|
+
* @author Alex Goldring
|
|
40
|
+
* @copyright Company Named Limited (c) 2026
|
|
41
|
+
*/
|
|
42
|
+
export function sgpt_write_frame(buffer, frame, intern) {
|
|
43
|
+
assert.defined(buffer, 'buffer');
|
|
44
|
+
assert.defined(frame, 'frame');
|
|
45
|
+
assert.isFunction(intern, 'intern');
|
|
46
|
+
|
|
47
|
+
const spans = frame.spans;
|
|
48
|
+
|
|
49
|
+
buffer.writeUintVar(frame.frame_index);
|
|
50
|
+
buffer.writeFloat64(frame.cpu_begin_ms);
|
|
51
|
+
buffer.writeFloat64(frame.cpu_submit_ms);
|
|
52
|
+
buffer.writeUint64(frame.gpu_epoch_ns);
|
|
53
|
+
buffer.writeUintVar(frame.dropped_pass_count);
|
|
54
|
+
buffer.writeUintVar(spans.length);
|
|
55
|
+
|
|
56
|
+
for (const span of spans) {
|
|
57
|
+
buffer.writeUintVar(intern(span.label));
|
|
58
|
+
buffer.writeUint8(gpu_profile_span_kind_from_name(span.kind));
|
|
59
|
+
buffer.writeUint8(span.query_set_id);
|
|
60
|
+
|
|
61
|
+
const begin = span.t_begin_ns;
|
|
62
|
+
|
|
63
|
+
buffer.writeUint32(begin < 0 ? 0 : (begin > MAX_BEGIN_OFFSET ? MAX_BEGIN_OFFSET : begin));
|
|
64
|
+
|
|
65
|
+
buffer.writeUintVar(span.duration_ns < 0 ? 0 : span.duration_ns);
|
|
66
|
+
|
|
67
|
+
buffer.writeUintVar(span.graph_pass_id + 1); // -1 (none) becomes 0, so it fits a uintVar
|
|
68
|
+
|
|
69
|
+
const work = span.work;
|
|
70
|
+
|
|
71
|
+
if (work === null || work === undefined) {
|
|
72
|
+
buffer.writeUint8(0);
|
|
73
|
+
} else {
|
|
74
|
+
buffer.writeUint8(1);
|
|
75
|
+
|
|
76
|
+
buffer.writeUintVar(work.dispatch_count);
|
|
77
|
+
buffer.writeUintVar(work.indirect_dispatch_count);
|
|
78
|
+
buffer.writeUintVar(work.workgroups[0]);
|
|
79
|
+
buffer.writeUintVar(work.workgroups[1]);
|
|
80
|
+
buffer.writeUintVar(work.workgroups[2]);
|
|
81
|
+
buffer.writeUintVar(work.draw_count);
|
|
82
|
+
buffer.writeUintVar(work.indirect_draw_count);
|
|
83
|
+
buffer.writeUintVar(work.vertex_count);
|
|
84
|
+
buffer.writeUintVar(work.index_count);
|
|
85
|
+
buffer.writeUintVar(work.instance_count);
|
|
86
|
+
buffer.writeUintVar(intern(work.pipeline));
|
|
87
|
+
buffer.writeUintVar(work.workgroup_size[0]);
|
|
88
|
+
buffer.writeUintVar(work.workgroup_size[1]);
|
|
89
|
+
buffer.writeUintVar(work.workgroup_size[2]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
Appended after the span loop rather than beside `frame_index`, so that a reader written
|
|
95
|
+
against format 1 — which stops once it has read `span_count` spans — decodes everything it
|
|
96
|
+
knows about and simply never sees this. That is why `min_reader_version` did not have to move
|
|
97
|
+
when the topology link was added.
|
|
98
|
+
*/
|
|
99
|
+
buffer.writeUintVar(frame.topology_id + 1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Decode one frame's `FRAM` payload.
|
|
104
|
+
*
|
|
105
|
+
* @param {BinaryBuffer} buffer positioned at the payload
|
|
106
|
+
* @param {string[]} symbols resolved so far
|
|
107
|
+
* @param {number} end offset one past this payload, so an older capture without the trailing
|
|
108
|
+
* topology reference is read as having none rather than running into the next record
|
|
109
|
+
* @returns {GPUProfileFrame}
|
|
110
|
+
*/
|
|
111
|
+
export function sgpt_read_frame(buffer, symbols, end) {
|
|
112
|
+
assert.defined(buffer, 'buffer');
|
|
113
|
+
assert.isArray(symbols, 'symbols');
|
|
114
|
+
assert.isNonNegativeInteger(end, 'end');
|
|
115
|
+
|
|
116
|
+
const frame = new GPUProfileFrame();
|
|
117
|
+
|
|
118
|
+
frame.frame_index = buffer.readUintVar();
|
|
119
|
+
frame.cpu_begin_ms = buffer.readFloat64();
|
|
120
|
+
frame.cpu_submit_ms = buffer.readFloat64();
|
|
121
|
+
frame.gpu_epoch_ns = buffer.readUint64();
|
|
122
|
+
frame.dropped_pass_count = buffer.readUintVar();
|
|
123
|
+
|
|
124
|
+
const span_count = buffer.readUintVar();
|
|
125
|
+
|
|
126
|
+
const spans = frame.spans;
|
|
127
|
+
|
|
128
|
+
for (let i = 0; i < span_count; i++) {
|
|
129
|
+
const span = new GPUProfileSpan();
|
|
130
|
+
|
|
131
|
+
const label_ref = buffer.readUintVar();
|
|
132
|
+
|
|
133
|
+
// A symbol block always precedes the record referencing it, so an unresolved index means
|
|
134
|
+
// the capture is damaged rather than merely incomplete. Naming it beats a blank label.
|
|
135
|
+
span.label = symbols[label_ref] !== undefined
|
|
136
|
+
? symbols[label_ref]
|
|
137
|
+
: `<unresolved symbol ${label_ref}>`;
|
|
138
|
+
|
|
139
|
+
span.kind = gpu_profile_span_kind_name(buffer.readUint8());
|
|
140
|
+
span.query_set_id = buffer.readUint8();
|
|
141
|
+
span.t_begin_ns = buffer.readUint32();
|
|
142
|
+
span.duration_ns = buffer.readUintVar();
|
|
143
|
+
span.graph_pass_id = buffer.readUintVar() - 1;
|
|
144
|
+
|
|
145
|
+
if (buffer.readUint8() === 1) {
|
|
146
|
+
const work = new GPUProfileWork();
|
|
147
|
+
|
|
148
|
+
work.dispatch_count = buffer.readUintVar();
|
|
149
|
+
work.indirect_dispatch_count = buffer.readUintVar();
|
|
150
|
+
work.workgroups = [buffer.readUintVar(), buffer.readUintVar(), buffer.readUintVar()];
|
|
151
|
+
work.draw_count = buffer.readUintVar();
|
|
152
|
+
work.indirect_draw_count = buffer.readUintVar();
|
|
153
|
+
work.vertex_count = buffer.readUintVar();
|
|
154
|
+
work.index_count = buffer.readUintVar();
|
|
155
|
+
work.instance_count = buffer.readUintVar();
|
|
156
|
+
|
|
157
|
+
const pipeline_ref = buffer.readUintVar();
|
|
158
|
+
|
|
159
|
+
work.pipeline = symbols[pipeline_ref] ?? "";
|
|
160
|
+
work.workgroup_size = [buffer.readUintVar(), buffer.readUintVar(), buffer.readUintVar()];
|
|
161
|
+
|
|
162
|
+
span.work = work;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
spans.push(span);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Absent in a format-1 capture, which simply has no topology to point at.
|
|
169
|
+
frame.topology_id = buffer.position < end ? buffer.readUintVar() - 1 : -1;
|
|
170
|
+
|
|
171
|
+
return frame;
|
|
172
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode the `META` payload.
|
|
3
|
+
*
|
|
4
|
+
* @param {BinaryBuffer} buffer
|
|
5
|
+
* @param {GPUProfileMeta} meta
|
|
6
|
+
* @returns {void}
|
|
7
|
+
*
|
|
8
|
+
* @author Alex Goldring
|
|
9
|
+
* @copyright Company Named Limited (c) 2026
|
|
10
|
+
*/
|
|
11
|
+
export function sgpt_write_meta(buffer: BinaryBuffer, meta: GPUProfileMeta): void;
|
|
12
|
+
/**
|
|
13
|
+
* Decode the `META` payload.
|
|
14
|
+
*
|
|
15
|
+
* @param {BinaryBuffer} buffer positioned at the payload
|
|
16
|
+
* @returns {GPUProfileMeta}
|
|
17
|
+
*/
|
|
18
|
+
export function sgpt_read_meta(buffer: BinaryBuffer): GPUProfileMeta;
|
|
19
|
+
import { GPUProfileMeta } from "./GPUProfileMeta.js";
|
|
20
|
+
//# sourceMappingURL=sgpt_meta_codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sgpt_meta_codec.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/sgpt_meta_codec.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,4DANW,cAAc,GACZ,IAAI,CAyBhB;AAED;;;;;GAKG;AACH,sDAFa,cAAc,CAyB1B;+BA/D8B,qBAAqB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { GPUProfileMeta } from "./GPUProfileMeta.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Encode the `META` payload.
|
|
6
|
+
*
|
|
7
|
+
* @param {BinaryBuffer} buffer
|
|
8
|
+
* @param {GPUProfileMeta} meta
|
|
9
|
+
* @returns {void}
|
|
10
|
+
*
|
|
11
|
+
* @author Alex Goldring
|
|
12
|
+
* @copyright Company Named Limited (c) 2026
|
|
13
|
+
*/
|
|
14
|
+
export function sgpt_write_meta(buffer, meta) {
|
|
15
|
+
assert.defined(buffer, 'buffer');
|
|
16
|
+
assert.defined(meta, 'meta');
|
|
17
|
+
|
|
18
|
+
buffer.writeUint8(meta.level);
|
|
19
|
+
buffer.writeFloat64(meta.wall_clock_start_ms);
|
|
20
|
+
|
|
21
|
+
buffer.writeUTF8String(meta.note);
|
|
22
|
+
buffer.writeUTF8String(meta.engine_version);
|
|
23
|
+
|
|
24
|
+
buffer.writeUTF8String(meta.adapter_vendor);
|
|
25
|
+
buffer.writeUTF8String(meta.adapter_architecture);
|
|
26
|
+
buffer.writeUTF8String(meta.adapter_device);
|
|
27
|
+
buffer.writeUTF8String(meta.adapter_description);
|
|
28
|
+
|
|
29
|
+
buffer.writeUintVar(meta.features.length);
|
|
30
|
+
|
|
31
|
+
for (const feature of meta.features) {
|
|
32
|
+
buffer.writeUTF8String(feature);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Decode the `META` payload.
|
|
38
|
+
*
|
|
39
|
+
* @param {BinaryBuffer} buffer positioned at the payload
|
|
40
|
+
* @returns {GPUProfileMeta}
|
|
41
|
+
*/
|
|
42
|
+
export function sgpt_read_meta(buffer) {
|
|
43
|
+
assert.defined(buffer, 'buffer');
|
|
44
|
+
|
|
45
|
+
const meta = new GPUProfileMeta();
|
|
46
|
+
|
|
47
|
+
meta.level = buffer.readUint8();
|
|
48
|
+
meta.wall_clock_start_ms = buffer.readFloat64();
|
|
49
|
+
|
|
50
|
+
meta.note = buffer.readUTF8String();
|
|
51
|
+
meta.engine_version = buffer.readUTF8String();
|
|
52
|
+
|
|
53
|
+
meta.adapter_vendor = buffer.readUTF8String();
|
|
54
|
+
meta.adapter_architecture = buffer.readUTF8String();
|
|
55
|
+
meta.adapter_device = buffer.readUTF8String();
|
|
56
|
+
meta.adapter_description = buffer.readUTF8String();
|
|
57
|
+
|
|
58
|
+
const feature_count = buffer.readUintVar();
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < feature_count; i++) {
|
|
61
|
+
meta.features.push(buffer.readUTF8String());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return meta;
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode a whole `.sgpt` capture.
|
|
3
|
+
*
|
|
4
|
+
* Reads by **scanning records**, not by consulting the directory. The directory is an index over
|
|
5
|
+
* the stream rather than a prerequisite for reading it, and a capture that ended when the tab did
|
|
6
|
+
* has none at all — which is exactly the capture somebody most wants to open.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here throws on damage. A capture comes back whole, with whatever could be decoded and a
|
|
9
|
+
* list of what could not.
|
|
10
|
+
*
|
|
11
|
+
* @param {ArrayBuffer} bytes
|
|
12
|
+
* @returns {GPUProfileCapture}
|
|
13
|
+
*
|
|
14
|
+
* @author Alex Goldring
|
|
15
|
+
* @copyright Company Named Limited (c) 2026
|
|
16
|
+
*/
|
|
17
|
+
export function sgpt_read_capture(bytes: ArrayBuffer): GPUProfileCapture;
|
|
18
|
+
import { GPUProfileCapture } from "./GPUProfileCapture.js";
|
|
19
|
+
//# sourceMappingURL=sgpt_read_capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sgpt_read_capture.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/sgpt_read_capture.js"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;GAeG;AACH,yCANW,WAAW,GACT,iBAAiB,CA8G7B;kCAnIiC,wBAAwB"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { BinaryBuffer } from "../../../../core/binary/BinaryBuffer.js";
|
|
3
|
+
import { GPUProfileCapture } from "./GPUProfileCapture.js";
|
|
4
|
+
import { SGPT_DEFECT } from "./SGPT_DEFECT.js";
|
|
5
|
+
import { SGPTDefect } from "./SGPTDefect.js";
|
|
6
|
+
import { sgpt_read_frame } from "./sgpt_frame_codec.js";
|
|
7
|
+
import { sgpt_read_header } from "./sgpt_read_header.js";
|
|
8
|
+
import { sgpt_read_meta } from "./sgpt_meta_codec.js";
|
|
9
|
+
import { sgpt_read_records } from "./sgpt_read_records.js";
|
|
10
|
+
import { sgpt_read_topology } from "./sgpt_topology_codec.js";
|
|
11
|
+
import { SGPT_RECORD_TYPE } from "./SGPT_RECORD_TYPE.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Decode a whole `.sgpt` capture.
|
|
15
|
+
*
|
|
16
|
+
* Reads by **scanning records**, not by consulting the directory. The directory is an index over
|
|
17
|
+
* the stream rather than a prerequisite for reading it, and a capture that ended when the tab did
|
|
18
|
+
* has none at all — which is exactly the capture somebody most wants to open.
|
|
19
|
+
*
|
|
20
|
+
* Nothing here throws on damage. A capture comes back whole, with whatever could be decoded and a
|
|
21
|
+
* list of what could not.
|
|
22
|
+
*
|
|
23
|
+
* @param {ArrayBuffer} bytes
|
|
24
|
+
* @returns {GPUProfileCapture}
|
|
25
|
+
*
|
|
26
|
+
* @author Alex Goldring
|
|
27
|
+
* @copyright Company Named Limited (c) 2026
|
|
28
|
+
*/
|
|
29
|
+
export function sgpt_read_capture(bytes) {
|
|
30
|
+
assert.isInstanceOf(bytes, ArrayBuffer, 'bytes');
|
|
31
|
+
|
|
32
|
+
const capture = new GPUProfileCapture();
|
|
33
|
+
|
|
34
|
+
const buffer = BinaryBuffer.fromArrayBuffer(bytes);
|
|
35
|
+
|
|
36
|
+
const end = bytes.byteLength;
|
|
37
|
+
|
|
38
|
+
const header = sgpt_read_header(buffer, capture.defects);
|
|
39
|
+
|
|
40
|
+
capture.header = header;
|
|
41
|
+
|
|
42
|
+
if (header === null) {
|
|
43
|
+
return capture;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const records = sgpt_read_records(buffer, end, capture.defects);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Symbol table, accumulated as `SYMS` blocks arrive. Blocks always precede the records that
|
|
50
|
+
* reference them, so this is complete by the time each reference is resolved.
|
|
51
|
+
*
|
|
52
|
+
* @type {string[]}
|
|
53
|
+
*/
|
|
54
|
+
const symbols = [];
|
|
55
|
+
|
|
56
|
+
let seen_meta = false;
|
|
57
|
+
|
|
58
|
+
for (const record of records) {
|
|
59
|
+
|
|
60
|
+
buffer.position = record.payload_offset;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
switch (record.type) {
|
|
64
|
+
|
|
65
|
+
case SGPT_RECORD_TYPE.META:
|
|
66
|
+
capture.meta = sgpt_read_meta(buffer);
|
|
67
|
+
seen_meta = true;
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
case SGPT_RECORD_TYPE.SYMS: {
|
|
71
|
+
const first_index = buffer.readUintVar();
|
|
72
|
+
const count = buffer.readUintVar();
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < count; i++) {
|
|
75
|
+
symbols[first_index + i] = buffer.readUTF8String();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case SGPT_RECORD_TYPE.FRAM:
|
|
82
|
+
capture.frames.push(sgpt_read_frame(
|
|
83
|
+
buffer,
|
|
84
|
+
symbols,
|
|
85
|
+
record.payload_offset + record.payload_byte_length
|
|
86
|
+
));
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case SGPT_RECORD_TYPE.TOPO: {
|
|
90
|
+
const topology = sgpt_read_topology(buffer, symbols);
|
|
91
|
+
|
|
92
|
+
// Indexed by the id the writer assigned, not by arrival order — a resynchronised
|
|
93
|
+
// reader may have skipped one, and shifting the rest up would mis-attribute
|
|
94
|
+
// every frame after the damage.
|
|
95
|
+
capture.topologies[topology.id] = topology;
|
|
96
|
+
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
case SGPT_RECORD_TYPE.DIRE:
|
|
101
|
+
// Deliberately not read. Everything it indexes has already been walked, and
|
|
102
|
+
// trusting it over the records would make a capture without one a lesser
|
|
103
|
+
// capture. It exists so a future reader can seek instead of scan.
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
default: {
|
|
107
|
+
const seen = capture.unknown_record_counts.get(record.type);
|
|
108
|
+
|
|
109
|
+
capture.unknown_record_counts.set(record.type, seen === undefined ? 1 : seen + 1);
|
|
110
|
+
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// One record failing to decode costs that record. The framing already told us where
|
|
116
|
+
// the next one starts, so there is no reason for it to cost anything else.
|
|
117
|
+
capture.defects.push(new SGPTDefect(
|
|
118
|
+
SGPT_DEFECT.PAYLOAD,
|
|
119
|
+
`record type 0x${record.type.toString(16)} failed to decode: ${e.message}`,
|
|
120
|
+
record.offset
|
|
121
|
+
));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!seen_meta) {
|
|
126
|
+
capture.defects.push(new SGPTDefect(
|
|
127
|
+
SGPT_DEFECT.PAYLOAD,
|
|
128
|
+
'no META record; adapter, settings and timestamp resolution are unknown for this capture',
|
|
129
|
+
-1
|
|
130
|
+
));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return capture;
|
|
134
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode the head of an `.sgpt` stream.
|
|
3
|
+
*
|
|
4
|
+
* Three conditions are fatal and return `null`, because nothing downstream can mean anything
|
|
5
|
+
* without them: too few bytes to hold a header, the wrong magic, and a `min_reader_version` above
|
|
6
|
+
* what this build understands. Everything else is reported through `defects` and the header is
|
|
7
|
+
* still returned — a bad header checksum, in particular, does not stop the records from being
|
|
8
|
+
* readable by scanning, and refusing on it would throw away recoverable captures.
|
|
9
|
+
*
|
|
10
|
+
* Leaves `position` at {@link SGPT_HEADER_SIZE} on success.
|
|
11
|
+
*
|
|
12
|
+
* @param {BinaryBuffer} buffer positioned at 0
|
|
13
|
+
* @param {SGPTDefect[]} defects appended to
|
|
14
|
+
* @returns {SGPTHeader|null} null when the stream is not readable as `.sgpt` at all
|
|
15
|
+
*
|
|
16
|
+
* @author Alex Goldring
|
|
17
|
+
* @copyright Company Named Limited (c) 2026
|
|
18
|
+
*/
|
|
19
|
+
export function sgpt_read_header(buffer: BinaryBuffer, defects: SGPTDefect[]): SGPTHeader | null;
|
|
20
|
+
import { SGPTDefect } from "./SGPTDefect.js";
|
|
21
|
+
import { SGPTHeader } from "./SGPTHeader.js";
|
|
22
|
+
//# sourceMappingURL=sgpt_read_header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sgpt_read_header.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/sgpt_read_header.js"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;;GAiBG;AACH,gEANW,UAAU,EAAE,GACV,UAAU,GAAC,IAAI,CAqE3B;2BA1F0B,iBAAiB;2BAMjB,iBAAiB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { crc32 } from "../../../../core/binary/hash/crc32.js";
|
|
3
|
+
import { SGPT_DEFECT } from "./SGPT_DEFECT.js";
|
|
4
|
+
import { SGPTDefect } from "./SGPTDefect.js";
|
|
5
|
+
import { SGPT_FORMAT_VERSION } from "./SGPT_FORMAT_VERSION.js";
|
|
6
|
+
import { SGPT_HEADER_CHECKSUM_COVERAGE } from "./SGPT_HEADER_CHECKSUM_COVERAGE.js";
|
|
7
|
+
import { SGPT_HEADER_OFFSET } from "./SGPT_HEADER_OFFSET.js";
|
|
8
|
+
import { SGPT_HEADER_SIZE } from "./SGPT_HEADER_SIZE.js";
|
|
9
|
+
import { SGPT_MAGIC } from "./SGPT_MAGIC.js";
|
|
10
|
+
import { SGPTHeader } from "./SGPTHeader.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Decode the head of an `.sgpt` stream.
|
|
14
|
+
*
|
|
15
|
+
* Three conditions are fatal and return `null`, because nothing downstream can mean anything
|
|
16
|
+
* without them: too few bytes to hold a header, the wrong magic, and a `min_reader_version` above
|
|
17
|
+
* what this build understands. Everything else is reported through `defects` and the header is
|
|
18
|
+
* still returned — a bad header checksum, in particular, does not stop the records from being
|
|
19
|
+
* readable by scanning, and refusing on it would throw away recoverable captures.
|
|
20
|
+
*
|
|
21
|
+
* Leaves `position` at {@link SGPT_HEADER_SIZE} on success.
|
|
22
|
+
*
|
|
23
|
+
* @param {BinaryBuffer} buffer positioned at 0
|
|
24
|
+
* @param {SGPTDefect[]} defects appended to
|
|
25
|
+
* @returns {SGPTHeader|null} null when the stream is not readable as `.sgpt` at all
|
|
26
|
+
*
|
|
27
|
+
* @author Alex Goldring
|
|
28
|
+
* @copyright Company Named Limited (c) 2026
|
|
29
|
+
*/
|
|
30
|
+
export function sgpt_read_header(buffer, defects) {
|
|
31
|
+
assert.defined(buffer, 'buffer');
|
|
32
|
+
assert.isArray(defects, 'defects');
|
|
33
|
+
|
|
34
|
+
const available = buffer.capacity;
|
|
35
|
+
|
|
36
|
+
if (available < SGPT_HEADER_SIZE) {
|
|
37
|
+
defects.push(new SGPTDefect(
|
|
38
|
+
SGPT_DEFECT.HEADER,
|
|
39
|
+
`stream is ${available} bytes, a header needs ${SGPT_HEADER_SIZE}`,
|
|
40
|
+
0
|
|
41
|
+
));
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
buffer.position = 0;
|
|
47
|
+
|
|
48
|
+
const magic = buffer.readUint32();
|
|
49
|
+
|
|
50
|
+
if (magic !== SGPT_MAGIC) {
|
|
51
|
+
defects.push(new SGPTDefect(
|
|
52
|
+
SGPT_DEFECT.HEADER,
|
|
53
|
+
`not an .sgpt stream: magic 0x${(magic >>> 0).toString(16)}, expected 0x${SGPT_MAGIC.toString(16)}`,
|
|
54
|
+
0
|
|
55
|
+
));
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const header = new SGPTHeader();
|
|
61
|
+
|
|
62
|
+
header.format_version = buffer.readUint16();
|
|
63
|
+
header.min_reader_version = buffer.readUint16();
|
|
64
|
+
header.flags = buffer.readUint32();
|
|
65
|
+
|
|
66
|
+
const stored_checksum = buffer.readUint32();
|
|
67
|
+
const actual_checksum = crc32(buffer.raw_bytes, 0, SGPT_HEADER_CHECKSUM_COVERAGE);
|
|
68
|
+
|
|
69
|
+
if (stored_checksum !== actual_checksum) {
|
|
70
|
+
// Not fatal on purpose. The checksum covers the magic and versions only, and nothing the
|
|
71
|
+
// records need in order to be scannable, so a mismatch means "this file has been damaged
|
|
72
|
+
// somewhere" rather than "there is nothing here to read".
|
|
73
|
+
defects.push(new SGPTDefect(
|
|
74
|
+
SGPT_DEFECT.CHECKSUM,
|
|
75
|
+
`header checksum 0x${stored_checksum.toString(16)} does not match computed 0x${actual_checksum.toString(16)}`,
|
|
76
|
+
SGPT_HEADER_OFFSET.HEADER_CHECKSUM
|
|
77
|
+
));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
header.directory_offset = Number(buffer.readUint64());
|
|
81
|
+
header.directory_byte_length = Number(buffer.readUint64());
|
|
82
|
+
|
|
83
|
+
if (header.min_reader_version > SGPT_FORMAT_VERSION) {
|
|
84
|
+
defects.push(new SGPTDefect(
|
|
85
|
+
SGPT_DEFECT.HEADER,
|
|
86
|
+
`capture requires reader version ${header.min_reader_version}, this build is ${SGPT_FORMAT_VERSION}`,
|
|
87
|
+
SGPT_HEADER_OFFSET.MIN_READER_VERSION
|
|
88
|
+
));
|
|
89
|
+
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return header;
|
|
94
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk the record stream, resynchronising past anything that is not a record.
|
|
3
|
+
*
|
|
4
|
+
* The stream is designed to be read this way rather than through the directory, and the directory
|
|
5
|
+
* is an index over the result rather than a prerequisite for it. A capture that ended when the tab
|
|
6
|
+
* did has no directory at all, and it is exactly the capture worth reading.
|
|
7
|
+
*
|
|
8
|
+
* Two recoverable outcomes are reported and kept going:
|
|
9
|
+
*
|
|
10
|
+
* - **Resync.** Bytes that are not a record are skipped and a {@link SGPT_DEFECT.RESYNC} names how
|
|
11
|
+
* many. Everything after the damage is still decoded.
|
|
12
|
+
* - **Truncation.** The stream ends inside a record. A {@link SGPT_DEFECT.TRUNCATED} says so, and
|
|
13
|
+
* the records before it stand. This is the ordinary end state of an interrupted session, not a
|
|
14
|
+
* sign that anything went wrong with the writing.
|
|
15
|
+
*
|
|
16
|
+
* @param {BinaryBuffer} buffer
|
|
17
|
+
* @param {number} end offset one past the last readable byte
|
|
18
|
+
* @param {SGPTDefect[]} defects appended to
|
|
19
|
+
* @returns {SGPTRecordRef[]} in stream order
|
|
20
|
+
*
|
|
21
|
+
* @author Alex Goldring
|
|
22
|
+
* @copyright Company Named Limited (c) 2026
|
|
23
|
+
*/
|
|
24
|
+
export function sgpt_read_records(buffer: BinaryBuffer, end: number, defects: SGPTDefect[]): SGPTRecordRef[];
|
|
25
|
+
import { SGPTDefect } from "./SGPTDefect.js";
|
|
26
|
+
import { SGPTRecordRef } from "./SGPTRecordRef.js";
|
|
27
|
+
//# sourceMappingURL=sgpt_read_records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sgpt_read_records.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/device/timing/profile/sgpt_read_records.js"],"names":[],"mappings":"AAiEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,6DAPW,MAAM,WACN,UAAU,EAAE,GACV,aAAa,EAAE,CAqE3B;2BArJ0B,iBAAiB;8BAId,oBAAoB"}
|