@plasius/gpu-renderer 0.1.6 → 0.1.7
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/CHANGELOG.md +31 -0
- package/README.md +55 -0
- package/dist/index.cjs +621 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +612 -6
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/index.d.ts +143 -1
- package/src/index.js +644 -3
package/src/index.js
CHANGED
|
@@ -1,5 +1,468 @@
|
|
|
1
1
|
const DEFAULT_CLEAR_COLOR = Object.freeze([0.07, 0.11, 0.18, 1.0]);
|
|
2
2
|
const DEFAULT_CANVAS_SELECTOR = "canvas[data-plasius-gpu-renderer]";
|
|
3
|
+
export const rendererDebugOwner = "renderer";
|
|
4
|
+
export const rendererWorkerQueueClass = "render";
|
|
5
|
+
export const defaultRendererWorkerProfile = "realtime";
|
|
6
|
+
|
|
7
|
+
function buildRendererWorkerBudgetLevels(jobType, queueClass, levels) {
|
|
8
|
+
return Object.freeze(
|
|
9
|
+
levels.map((level) =>
|
|
10
|
+
Object.freeze({
|
|
11
|
+
id: level.id,
|
|
12
|
+
estimatedCostMs: level.estimatedCostMs,
|
|
13
|
+
config: Object.freeze({
|
|
14
|
+
maxDispatchesPerFrame: level.config.maxDispatchesPerFrame,
|
|
15
|
+
maxJobsPerDispatch: level.config.maxJobsPerDispatch,
|
|
16
|
+
cadenceDivisor: level.config.cadenceDivisor,
|
|
17
|
+
workgroupScale: level.config.workgroupScale,
|
|
18
|
+
maxQueueDepth: level.config.maxQueueDepth,
|
|
19
|
+
metadata: Object.freeze({
|
|
20
|
+
owner: rendererDebugOwner,
|
|
21
|
+
queueClass,
|
|
22
|
+
jobType,
|
|
23
|
+
quality: level.id,
|
|
24
|
+
}),
|
|
25
|
+
}),
|
|
26
|
+
})
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const rendererWorkerProfileSpecs = {
|
|
32
|
+
realtime: {
|
|
33
|
+
description:
|
|
34
|
+
"Frame-stage DAG for flat rendering with visibility, main encode, post-processing, and submit.",
|
|
35
|
+
suggestedAllocationIds: [
|
|
36
|
+
"renderer.surface.current",
|
|
37
|
+
"renderer.visibility.worklist",
|
|
38
|
+
"renderer.post-process.history",
|
|
39
|
+
],
|
|
40
|
+
jobs: {
|
|
41
|
+
acquire: {
|
|
42
|
+
priority: 5,
|
|
43
|
+
dependencies: [],
|
|
44
|
+
domain: "resolution",
|
|
45
|
+
importance: "critical",
|
|
46
|
+
levels: [
|
|
47
|
+
{
|
|
48
|
+
id: "fixed",
|
|
49
|
+
estimatedCostMs: 0.2,
|
|
50
|
+
config: {
|
|
51
|
+
maxDispatchesPerFrame: 1,
|
|
52
|
+
maxJobsPerDispatch: 1,
|
|
53
|
+
cadenceDivisor: 1,
|
|
54
|
+
workgroupScale: 1,
|
|
55
|
+
maxQueueDepth: 1,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
suggestedAllocationIds: ["renderer.surface.current"],
|
|
60
|
+
},
|
|
61
|
+
visibility: {
|
|
62
|
+
priority: 4,
|
|
63
|
+
dependencies: [],
|
|
64
|
+
domain: "geometry",
|
|
65
|
+
importance: "high",
|
|
66
|
+
levels: [
|
|
67
|
+
{
|
|
68
|
+
id: "low",
|
|
69
|
+
estimatedCostMs: 0.4,
|
|
70
|
+
config: {
|
|
71
|
+
maxDispatchesPerFrame: 1,
|
|
72
|
+
maxJobsPerDispatch: 128,
|
|
73
|
+
cadenceDivisor: 2,
|
|
74
|
+
workgroupScale: 0.5,
|
|
75
|
+
maxQueueDepth: 256,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "medium",
|
|
80
|
+
estimatedCostMs: 0.8,
|
|
81
|
+
config: {
|
|
82
|
+
maxDispatchesPerFrame: 1,
|
|
83
|
+
maxJobsPerDispatch: 256,
|
|
84
|
+
cadenceDivisor: 1,
|
|
85
|
+
workgroupScale: 0.75,
|
|
86
|
+
maxQueueDepth: 384,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "high",
|
|
91
|
+
estimatedCostMs: 1.2,
|
|
92
|
+
config: {
|
|
93
|
+
maxDispatchesPerFrame: 2,
|
|
94
|
+
maxJobsPerDispatch: 512,
|
|
95
|
+
cadenceDivisor: 1,
|
|
96
|
+
workgroupScale: 1,
|
|
97
|
+
maxQueueDepth: 512,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
suggestedAllocationIds: ["renderer.visibility.worklist"],
|
|
102
|
+
},
|
|
103
|
+
mainEncode: {
|
|
104
|
+
priority: 4,
|
|
105
|
+
dependencies: ["acquire", "visibility"],
|
|
106
|
+
domain: "geometry",
|
|
107
|
+
importance: "critical",
|
|
108
|
+
levels: [
|
|
109
|
+
{
|
|
110
|
+
id: "low",
|
|
111
|
+
estimatedCostMs: 1.2,
|
|
112
|
+
config: {
|
|
113
|
+
maxDispatchesPerFrame: 1,
|
|
114
|
+
maxJobsPerDispatch: 128,
|
|
115
|
+
cadenceDivisor: 1,
|
|
116
|
+
workgroupScale: 0.6,
|
|
117
|
+
maxQueueDepth: 192,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "medium",
|
|
122
|
+
estimatedCostMs: 2.1,
|
|
123
|
+
config: {
|
|
124
|
+
maxDispatchesPerFrame: 1,
|
|
125
|
+
maxJobsPerDispatch: 256,
|
|
126
|
+
cadenceDivisor: 1,
|
|
127
|
+
workgroupScale: 0.8,
|
|
128
|
+
maxQueueDepth: 256,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "high",
|
|
133
|
+
estimatedCostMs: 3,
|
|
134
|
+
config: {
|
|
135
|
+
maxDispatchesPerFrame: 1,
|
|
136
|
+
maxJobsPerDispatch: 384,
|
|
137
|
+
cadenceDivisor: 1,
|
|
138
|
+
workgroupScale: 1,
|
|
139
|
+
maxQueueDepth: 384,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
suggestedAllocationIds: ["renderer.surface.current"],
|
|
144
|
+
},
|
|
145
|
+
postProcess: {
|
|
146
|
+
priority: 3,
|
|
147
|
+
dependencies: ["mainEncode"],
|
|
148
|
+
domain: "post-processing",
|
|
149
|
+
importance: "high",
|
|
150
|
+
levels: [
|
|
151
|
+
{
|
|
152
|
+
id: "low",
|
|
153
|
+
estimatedCostMs: 0.5,
|
|
154
|
+
config: {
|
|
155
|
+
maxDispatchesPerFrame: 1,
|
|
156
|
+
maxJobsPerDispatch: 64,
|
|
157
|
+
cadenceDivisor: 2,
|
|
158
|
+
workgroupScale: 0.5,
|
|
159
|
+
maxQueueDepth: 96,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "medium",
|
|
164
|
+
estimatedCostMs: 0.9,
|
|
165
|
+
config: {
|
|
166
|
+
maxDispatchesPerFrame: 1,
|
|
167
|
+
maxJobsPerDispatch: 128,
|
|
168
|
+
cadenceDivisor: 1,
|
|
169
|
+
workgroupScale: 0.75,
|
|
170
|
+
maxQueueDepth: 128,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "high",
|
|
175
|
+
estimatedCostMs: 1.4,
|
|
176
|
+
config: {
|
|
177
|
+
maxDispatchesPerFrame: 2,
|
|
178
|
+
maxJobsPerDispatch: 192,
|
|
179
|
+
cadenceDivisor: 1,
|
|
180
|
+
workgroupScale: 1,
|
|
181
|
+
maxQueueDepth: 192,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
suggestedAllocationIds: ["renderer.post-process.history"],
|
|
186
|
+
},
|
|
187
|
+
submit: {
|
|
188
|
+
priority: 2,
|
|
189
|
+
dependencies: ["postProcess"],
|
|
190
|
+
domain: "resolution",
|
|
191
|
+
importance: "critical",
|
|
192
|
+
levels: [
|
|
193
|
+
{
|
|
194
|
+
id: "fixed",
|
|
195
|
+
estimatedCostMs: 0.2,
|
|
196
|
+
config: {
|
|
197
|
+
maxDispatchesPerFrame: 1,
|
|
198
|
+
maxJobsPerDispatch: 1,
|
|
199
|
+
cadenceDivisor: 1,
|
|
200
|
+
workgroupScale: 1,
|
|
201
|
+
maxQueueDepth: 1,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
suggestedAllocationIds: ["renderer.surface.current"],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
xr: {
|
|
210
|
+
description:
|
|
211
|
+
"Frame-stage DAG for XR rendering with late-latch coordination before main encode and submit.",
|
|
212
|
+
suggestedAllocationIds: [
|
|
213
|
+
"renderer.xr.surface.current",
|
|
214
|
+
"renderer.xr.visibility.worklist",
|
|
215
|
+
],
|
|
216
|
+
jobs: {
|
|
217
|
+
acquire: {
|
|
218
|
+
priority: 5,
|
|
219
|
+
dependencies: [],
|
|
220
|
+
domain: "xr",
|
|
221
|
+
importance: "critical",
|
|
222
|
+
levels: [
|
|
223
|
+
{
|
|
224
|
+
id: "fixed",
|
|
225
|
+
estimatedCostMs: 0.2,
|
|
226
|
+
config: {
|
|
227
|
+
maxDispatchesPerFrame: 1,
|
|
228
|
+
maxJobsPerDispatch: 1,
|
|
229
|
+
cadenceDivisor: 1,
|
|
230
|
+
workgroupScale: 1,
|
|
231
|
+
maxQueueDepth: 1,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
suggestedAllocationIds: ["renderer.xr.surface.current"],
|
|
236
|
+
},
|
|
237
|
+
visibility: {
|
|
238
|
+
priority: 4,
|
|
239
|
+
dependencies: [],
|
|
240
|
+
domain: "geometry",
|
|
241
|
+
importance: "high",
|
|
242
|
+
levels: [
|
|
243
|
+
{
|
|
244
|
+
id: "low",
|
|
245
|
+
estimatedCostMs: 0.5,
|
|
246
|
+
config: {
|
|
247
|
+
maxDispatchesPerFrame: 1,
|
|
248
|
+
maxJobsPerDispatch: 96,
|
|
249
|
+
cadenceDivisor: 2,
|
|
250
|
+
workgroupScale: 0.5,
|
|
251
|
+
maxQueueDepth: 192,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: "medium",
|
|
256
|
+
estimatedCostMs: 0.9,
|
|
257
|
+
config: {
|
|
258
|
+
maxDispatchesPerFrame: 1,
|
|
259
|
+
maxJobsPerDispatch: 192,
|
|
260
|
+
cadenceDivisor: 1,
|
|
261
|
+
workgroupScale: 0.75,
|
|
262
|
+
maxQueueDepth: 256,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "high",
|
|
267
|
+
estimatedCostMs: 1.3,
|
|
268
|
+
config: {
|
|
269
|
+
maxDispatchesPerFrame: 2,
|
|
270
|
+
maxJobsPerDispatch: 320,
|
|
271
|
+
cadenceDivisor: 1,
|
|
272
|
+
workgroupScale: 1,
|
|
273
|
+
maxQueueDepth: 320,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
suggestedAllocationIds: ["renderer.xr.visibility.worklist"],
|
|
278
|
+
},
|
|
279
|
+
lateLatch: {
|
|
280
|
+
priority: 5,
|
|
281
|
+
dependencies: ["acquire"],
|
|
282
|
+
domain: "xr",
|
|
283
|
+
importance: "critical",
|
|
284
|
+
levels: [
|
|
285
|
+
{
|
|
286
|
+
id: "fixed",
|
|
287
|
+
estimatedCostMs: 0.15,
|
|
288
|
+
config: {
|
|
289
|
+
maxDispatchesPerFrame: 1,
|
|
290
|
+
maxJobsPerDispatch: 1,
|
|
291
|
+
cadenceDivisor: 1,
|
|
292
|
+
workgroupScale: 1,
|
|
293
|
+
maxQueueDepth: 1,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
suggestedAllocationIds: ["renderer.xr.surface.current"],
|
|
298
|
+
},
|
|
299
|
+
mainEncode: {
|
|
300
|
+
priority: 4,
|
|
301
|
+
dependencies: ["visibility", "lateLatch"],
|
|
302
|
+
domain: "xr",
|
|
303
|
+
importance: "critical",
|
|
304
|
+
levels: [
|
|
305
|
+
{
|
|
306
|
+
id: "low",
|
|
307
|
+
estimatedCostMs: 1.1,
|
|
308
|
+
config: {
|
|
309
|
+
maxDispatchesPerFrame: 1,
|
|
310
|
+
maxJobsPerDispatch: 96,
|
|
311
|
+
cadenceDivisor: 1,
|
|
312
|
+
workgroupScale: 0.6,
|
|
313
|
+
maxQueueDepth: 128,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
id: "medium",
|
|
318
|
+
estimatedCostMs: 1.8,
|
|
319
|
+
config: {
|
|
320
|
+
maxDispatchesPerFrame: 1,
|
|
321
|
+
maxJobsPerDispatch: 192,
|
|
322
|
+
cadenceDivisor: 1,
|
|
323
|
+
workgroupScale: 0.8,
|
|
324
|
+
maxQueueDepth: 192,
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "high",
|
|
329
|
+
estimatedCostMs: 2.6,
|
|
330
|
+
config: {
|
|
331
|
+
maxDispatchesPerFrame: 1,
|
|
332
|
+
maxJobsPerDispatch: 256,
|
|
333
|
+
cadenceDivisor: 1,
|
|
334
|
+
workgroupScale: 1,
|
|
335
|
+
maxQueueDepth: 256,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
suggestedAllocationIds: ["renderer.xr.surface.current"],
|
|
340
|
+
},
|
|
341
|
+
submit: {
|
|
342
|
+
priority: 2,
|
|
343
|
+
dependencies: ["mainEncode"],
|
|
344
|
+
domain: "xr",
|
|
345
|
+
importance: "critical",
|
|
346
|
+
levels: [
|
|
347
|
+
{
|
|
348
|
+
id: "fixed",
|
|
349
|
+
estimatedCostMs: 0.2,
|
|
350
|
+
config: {
|
|
351
|
+
maxDispatchesPerFrame: 1,
|
|
352
|
+
maxJobsPerDispatch: 1,
|
|
353
|
+
cadenceDivisor: 1,
|
|
354
|
+
workgroupScale: 1,
|
|
355
|
+
maxQueueDepth: 1,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
suggestedAllocationIds: ["renderer.xr.surface.current"],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
function buildRendererWorkerProfile(name, spec) {
|
|
366
|
+
return Object.freeze({
|
|
367
|
+
name,
|
|
368
|
+
description: spec.description,
|
|
369
|
+
jobs: Object.freeze(Object.keys(spec.jobs)),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function buildRendererWorkerManifestJob(profileName, jobName, spec) {
|
|
374
|
+
const label = `renderer.${profileName}.${jobName}`;
|
|
375
|
+
return Object.freeze({
|
|
376
|
+
key: jobName,
|
|
377
|
+
label,
|
|
378
|
+
worker: Object.freeze({
|
|
379
|
+
jobType: label,
|
|
380
|
+
queueClass: rendererWorkerQueueClass,
|
|
381
|
+
priority: spec.priority,
|
|
382
|
+
dependencies: Object.freeze(
|
|
383
|
+
spec.dependencies.map((dependency) => `renderer.${profileName}.${dependency}`)
|
|
384
|
+
),
|
|
385
|
+
schedulerMode: "dag",
|
|
386
|
+
}),
|
|
387
|
+
performance: Object.freeze({
|
|
388
|
+
id: label,
|
|
389
|
+
jobType: label,
|
|
390
|
+
queueClass: rendererWorkerQueueClass,
|
|
391
|
+
domain: spec.domain,
|
|
392
|
+
authority: "visual",
|
|
393
|
+
importance: spec.importance,
|
|
394
|
+
levels: buildRendererWorkerBudgetLevels(
|
|
395
|
+
label,
|
|
396
|
+
rendererWorkerQueueClass,
|
|
397
|
+
spec.levels
|
|
398
|
+
),
|
|
399
|
+
}),
|
|
400
|
+
debug: Object.freeze({
|
|
401
|
+
owner: rendererDebugOwner,
|
|
402
|
+
queueClass: rendererWorkerQueueClass,
|
|
403
|
+
jobType: label,
|
|
404
|
+
tags: Object.freeze(["renderer", profileName, jobName, spec.domain]),
|
|
405
|
+
suggestedAllocationIds: Object.freeze([...spec.suggestedAllocationIds]),
|
|
406
|
+
}),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function buildRendererWorkerManifest(name, spec) {
|
|
411
|
+
return Object.freeze({
|
|
412
|
+
schemaVersion: 1,
|
|
413
|
+
owner: rendererDebugOwner,
|
|
414
|
+
profile: name,
|
|
415
|
+
description: spec.description,
|
|
416
|
+
queueClass: rendererWorkerQueueClass,
|
|
417
|
+
schedulerMode: "dag",
|
|
418
|
+
suggestedAllocationIds: Object.freeze([...spec.suggestedAllocationIds]),
|
|
419
|
+
jobs: Object.freeze(
|
|
420
|
+
Object.entries(spec.jobs).map(([jobName, jobSpec]) =>
|
|
421
|
+
buildRendererWorkerManifestJob(name, jobName, jobSpec)
|
|
422
|
+
)
|
|
423
|
+
),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export const rendererWorkerProfiles = Object.freeze(
|
|
428
|
+
Object.fromEntries(
|
|
429
|
+
Object.entries(rendererWorkerProfileSpecs).map(([name, spec]) => [
|
|
430
|
+
name,
|
|
431
|
+
buildRendererWorkerProfile(name, spec),
|
|
432
|
+
])
|
|
433
|
+
)
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
export const rendererWorkerProfileNames = Object.freeze(
|
|
437
|
+
Object.keys(rendererWorkerProfiles)
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
export const rendererWorkerManifests = Object.freeze(
|
|
441
|
+
Object.fromEntries(
|
|
442
|
+
Object.entries(rendererWorkerProfileSpecs).map(([name, spec]) => [
|
|
443
|
+
name,
|
|
444
|
+
buildRendererWorkerManifest(name, spec),
|
|
445
|
+
])
|
|
446
|
+
)
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
export function getRendererWorkerProfile(name = defaultRendererWorkerProfile) {
|
|
450
|
+
const profile = rendererWorkerProfiles[name];
|
|
451
|
+
if (!profile) {
|
|
452
|
+
const available = rendererWorkerProfileNames.join(", ");
|
|
453
|
+
throw new Error(`Unknown renderer worker profile "${name}". Available: ${available}.`);
|
|
454
|
+
}
|
|
455
|
+
return profile;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function getRendererWorkerManifest(name = defaultRendererWorkerProfile) {
|
|
459
|
+
const manifest = rendererWorkerManifests[name];
|
|
460
|
+
if (!manifest) {
|
|
461
|
+
const available = rendererWorkerProfileNames.join(", ");
|
|
462
|
+
throw new Error(`Unknown renderer worker profile "${name}". Available: ${available}.`);
|
|
463
|
+
}
|
|
464
|
+
return manifest;
|
|
465
|
+
}
|
|
3
466
|
|
|
4
467
|
function clamp01(value) {
|
|
5
468
|
return Math.min(1, Math.max(0, value));
|
|
@@ -41,6 +504,16 @@ function normalizeColor(value) {
|
|
|
41
504
|
return [...DEFAULT_CLEAR_COLOR];
|
|
42
505
|
}
|
|
43
506
|
|
|
507
|
+
function readPositiveNumber(name, value) {
|
|
508
|
+
if (value === undefined) {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
512
|
+
throw new Error(`${name} must be a finite number greater than zero.`);
|
|
513
|
+
}
|
|
514
|
+
return value;
|
|
515
|
+
}
|
|
516
|
+
|
|
44
517
|
function now() {
|
|
45
518
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
46
519
|
return performance.now();
|
|
@@ -48,6 +521,121 @@ function now() {
|
|
|
48
521
|
return Date.now();
|
|
49
522
|
}
|
|
50
523
|
|
|
524
|
+
function normalizeFrameId(value) {
|
|
525
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
526
|
+
throw new Error("frameIdFactory must return a non-empty string.");
|
|
527
|
+
}
|
|
528
|
+
return value.trim();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function resolveTargetFrameTimeMs(options, event) {
|
|
532
|
+
const {
|
|
533
|
+
targetFrameTimeMs: fixedTargetFrameTimeMs,
|
|
534
|
+
targetFrameRate,
|
|
535
|
+
getTargetFrameTimeMs,
|
|
536
|
+
} = options;
|
|
537
|
+
|
|
538
|
+
if (typeof getTargetFrameTimeMs === "function") {
|
|
539
|
+
const resolved = getTargetFrameTimeMs(event);
|
|
540
|
+
return readPositiveNumber("getTargetFrameTimeMs()", resolved);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (fixedTargetFrameTimeMs !== undefined) {
|
|
544
|
+
return fixedTargetFrameTimeMs;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (targetFrameRate !== undefined) {
|
|
548
|
+
return 1000 / targetFrameRate;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return undefined;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export function createRendererDebugHooks(options = {}) {
|
|
555
|
+
const {
|
|
556
|
+
debugSession,
|
|
557
|
+
targetFrameTimeMs,
|
|
558
|
+
targetFrameRate,
|
|
559
|
+
getTargetFrameTimeMs,
|
|
560
|
+
onFrameStart,
|
|
561
|
+
onFrameComplete,
|
|
562
|
+
} = options;
|
|
563
|
+
|
|
564
|
+
if (!debugSession || typeof debugSession.recordFrame !== "function") {
|
|
565
|
+
throw new Error(
|
|
566
|
+
"debugSession must expose recordFrame(sample). Use @plasius/gpu-debug createGpuDebugSession()."
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const fixedTargetFrameTimeMs = readPositiveNumber(
|
|
571
|
+
"targetFrameTimeMs",
|
|
572
|
+
targetFrameTimeMs
|
|
573
|
+
);
|
|
574
|
+
const fixedTargetFrameRate = readPositiveNumber(
|
|
575
|
+
"targetFrameRate",
|
|
576
|
+
targetFrameRate
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
if (
|
|
580
|
+
fixedTargetFrameTimeMs !== undefined &&
|
|
581
|
+
fixedTargetFrameRate !== undefined
|
|
582
|
+
) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
"Provide either targetFrameTimeMs or targetFrameRate, not both."
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (
|
|
589
|
+
getTargetFrameTimeMs !== undefined &&
|
|
590
|
+
typeof getTargetFrameTimeMs !== "function"
|
|
591
|
+
) {
|
|
592
|
+
throw new Error("getTargetFrameTimeMs must be a function when provided.");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const resolvedOptions = {
|
|
596
|
+
targetFrameTimeMs: fixedTargetFrameTimeMs,
|
|
597
|
+
targetFrameRate: fixedTargetFrameRate,
|
|
598
|
+
getTargetFrameTimeMs,
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
onFrameStart(event) {
|
|
603
|
+
if (typeof onFrameStart === "function") {
|
|
604
|
+
onFrameStart({
|
|
605
|
+
...event,
|
|
606
|
+
owner: rendererDebugOwner,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
onFrameComplete(event) {
|
|
611
|
+
const resolvedTargetFrameTimeMs = resolveTargetFrameTimeMs(
|
|
612
|
+
resolvedOptions,
|
|
613
|
+
event
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
if (
|
|
617
|
+
typeof event.frameTimeMs === "number" &&
|
|
618
|
+
Number.isFinite(event.frameTimeMs) &&
|
|
619
|
+
event.frameTimeMs > 0
|
|
620
|
+
) {
|
|
621
|
+
debugSession.recordFrame({
|
|
622
|
+
frameId: event.frameId,
|
|
623
|
+
frameTimeMs: event.frameTimeMs,
|
|
624
|
+
targetFrameTimeMs: resolvedTargetFrameTimeMs,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (typeof onFrameComplete === "function") {
|
|
629
|
+
onFrameComplete({
|
|
630
|
+
...event,
|
|
631
|
+
owner: rendererDebugOwner,
|
|
632
|
+
targetFrameTimeMs: resolvedTargetFrameTimeMs,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
51
639
|
function readNavigator(navigatorOverride) {
|
|
52
640
|
const currentNavigator = navigatorOverride ?? globalThis.navigator;
|
|
53
641
|
if (!currentNavigator || typeof currentNavigator !== "object") {
|
|
@@ -139,8 +727,11 @@ export async function createGpuRenderer(options = {}) {
|
|
|
139
727
|
clearColor = DEFAULT_CLEAR_COLOR,
|
|
140
728
|
requestAnimationFrame = globalThis.requestAnimationFrame?.bind(globalThis),
|
|
141
729
|
cancelAnimationFrame = globalThis.cancelAnimationFrame?.bind(globalThis),
|
|
730
|
+
frameIdFactory,
|
|
731
|
+
onFrameStart,
|
|
142
732
|
onBeforeEncode,
|
|
143
733
|
onAfterSubmit,
|
|
734
|
+
onFrameComplete,
|
|
144
735
|
} = options;
|
|
145
736
|
|
|
146
737
|
const gpu = readGpu(navigatorOverride);
|
|
@@ -178,6 +769,33 @@ export async function createGpuRenderer(options = {}) {
|
|
|
178
769
|
throw new Error("Renderer was destroyed.");
|
|
179
770
|
}
|
|
180
771
|
|
|
772
|
+
const frameNumber = frame + 1;
|
|
773
|
+
const frameId = normalizeFrameId(
|
|
774
|
+
typeof frameIdFactory === "function"
|
|
775
|
+
? frameIdFactory({
|
|
776
|
+
frame: frameNumber,
|
|
777
|
+
timestamp,
|
|
778
|
+
canvas: targetCanvas,
|
|
779
|
+
xrActive,
|
|
780
|
+
})
|
|
781
|
+
: `renderer.frame.${frameNumber}`
|
|
782
|
+
);
|
|
783
|
+
const frameTimeMs =
|
|
784
|
+
lastTimestamp > 0 ? Math.max(0, timestamp - lastTimestamp) : undefined;
|
|
785
|
+
|
|
786
|
+
if (typeof onFrameStart === "function") {
|
|
787
|
+
onFrameStart({
|
|
788
|
+
frame: frameNumber,
|
|
789
|
+
frameId,
|
|
790
|
+
frameTimeMs,
|
|
791
|
+
timestamp,
|
|
792
|
+
device,
|
|
793
|
+
context,
|
|
794
|
+
canvas: targetCanvas,
|
|
795
|
+
xrActive,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
181
799
|
const texture = context.getCurrentTexture?.();
|
|
182
800
|
if (!texture || typeof texture.createView !== "function") {
|
|
183
801
|
throw new Error("WebGPU context returned an invalid current texture.");
|
|
@@ -193,12 +811,16 @@ export async function createGpuRenderer(options = {}) {
|
|
|
193
811
|
if (typeof onBeforeEncode === "function") {
|
|
194
812
|
onBeforeEncode({
|
|
195
813
|
frame,
|
|
814
|
+
frameNumber,
|
|
815
|
+
frameId,
|
|
816
|
+
frameTimeMs,
|
|
196
817
|
timestamp,
|
|
197
818
|
device,
|
|
198
819
|
context,
|
|
199
820
|
encoder,
|
|
200
821
|
pass,
|
|
201
822
|
canvas: targetCanvas,
|
|
823
|
+
xrActive,
|
|
202
824
|
});
|
|
203
825
|
}
|
|
204
826
|
|
|
@@ -209,21 +831,40 @@ export async function createGpuRenderer(options = {}) {
|
|
|
209
831
|
const commandBuffer = encoder.finish();
|
|
210
832
|
device.queue.submit([commandBuffer]);
|
|
211
833
|
|
|
212
|
-
frame
|
|
834
|
+
frame = frameNumber;
|
|
213
835
|
lastTimestamp = timestamp;
|
|
214
836
|
|
|
215
837
|
if (typeof onAfterSubmit === "function") {
|
|
216
838
|
onAfterSubmit({
|
|
217
|
-
frame,
|
|
839
|
+
frame: frameNumber,
|
|
840
|
+
frameNumber,
|
|
841
|
+
frameId,
|
|
842
|
+
frameTimeMs,
|
|
843
|
+
timestamp,
|
|
844
|
+
device,
|
|
845
|
+
context,
|
|
846
|
+
canvas: targetCanvas,
|
|
847
|
+
xrActive,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (typeof onFrameComplete === "function") {
|
|
852
|
+
onFrameComplete({
|
|
853
|
+
frame: frameNumber,
|
|
854
|
+
frameId,
|
|
855
|
+
frameTimeMs,
|
|
218
856
|
timestamp,
|
|
219
857
|
device,
|
|
220
858
|
context,
|
|
221
859
|
canvas: targetCanvas,
|
|
860
|
+
xrActive,
|
|
222
861
|
});
|
|
223
862
|
}
|
|
224
863
|
|
|
225
864
|
return {
|
|
226
|
-
frame,
|
|
865
|
+
frame: frameNumber,
|
|
866
|
+
frameId,
|
|
867
|
+
frameTimeMs,
|
|
227
868
|
timestamp,
|
|
228
869
|
};
|
|
229
870
|
};
|