@plasius/gpu-renderer 0.1.6 → 0.1.8
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 +53 -0
- package/README.md +78 -0
- package/dist/index.cjs +813 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +800 -6
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/index.d.ts +239 -1
- package/src/index.js +862 -3
package/src/index.js
CHANGED
|
@@ -1,5 +1,686 @@
|
|
|
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
|
+
export const rendererRepresentationBands = Object.freeze([
|
|
7
|
+
"near",
|
|
8
|
+
"mid",
|
|
9
|
+
"far",
|
|
10
|
+
"horizon",
|
|
11
|
+
]);
|
|
12
|
+
export const rendererAccelerationStructureUpdateClasses = Object.freeze([
|
|
13
|
+
"static",
|
|
14
|
+
"rigid-dynamic",
|
|
15
|
+
"deforming",
|
|
16
|
+
"proxy",
|
|
17
|
+
]);
|
|
18
|
+
export const rendererRayTracingStageOrder = Object.freeze([
|
|
19
|
+
"primaryVisibility",
|
|
20
|
+
"shadowAssist",
|
|
21
|
+
"opaqueFoundation",
|
|
22
|
+
"rtDirectLighting",
|
|
23
|
+
"rtReflections",
|
|
24
|
+
"rtGlobalIllumination",
|
|
25
|
+
"denoiseTemporal",
|
|
26
|
+
"transparents",
|
|
27
|
+
"composition",
|
|
28
|
+
"present",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const rendererRayTracingStageDefinitions = Object.freeze(
|
|
32
|
+
rendererRayTracingStageOrder.map((key, index) =>
|
|
33
|
+
Object.freeze({
|
|
34
|
+
key,
|
|
35
|
+
order: index + 1,
|
|
36
|
+
required: true,
|
|
37
|
+
description:
|
|
38
|
+
{
|
|
39
|
+
primaryVisibility: "Primary visibility and depth preparation.",
|
|
40
|
+
shadowAssist: "Shadow assist passes and regional shadow preparation.",
|
|
41
|
+
opaqueFoundation: "Main opaque foundation for shading and tracing inputs.",
|
|
42
|
+
rtDirectLighting: "Ray-traced direct lighting and premium shadows.",
|
|
43
|
+
rtReflections: "Ray-traced reflections for important surfaces.",
|
|
44
|
+
rtGlobalIllumination: "Selective ray-traced indirect lighting and GI.",
|
|
45
|
+
denoiseTemporal: "Required denoise and temporal accumulation stage.",
|
|
46
|
+
transparents: "Transparents, particles, and volumetrics composition.",
|
|
47
|
+
composition: "Final world composition and color resolve.",
|
|
48
|
+
present: "Presentation to the active surface.",
|
|
49
|
+
}[key],
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const rendererRepresentationBandPolicies = Object.freeze({
|
|
55
|
+
near: Object.freeze({
|
|
56
|
+
band: "near",
|
|
57
|
+
rasterMode: "full-live",
|
|
58
|
+
rtParticipation: "premium",
|
|
59
|
+
shadowSource: "ray-traced-primary",
|
|
60
|
+
temporalReuse: "balanced",
|
|
61
|
+
updateCadenceDivisor: 1,
|
|
62
|
+
}),
|
|
63
|
+
mid: Object.freeze({
|
|
64
|
+
band: "mid",
|
|
65
|
+
rasterMode: "simplified-live",
|
|
66
|
+
rtParticipation: "selective",
|
|
67
|
+
shadowSource: "regional-raster-and-proxy",
|
|
68
|
+
temporalReuse: "aggressive",
|
|
69
|
+
updateCadenceDivisor: 2,
|
|
70
|
+
}),
|
|
71
|
+
far: Object.freeze({
|
|
72
|
+
band: "far",
|
|
73
|
+
rasterMode: "proxy-or-cached",
|
|
74
|
+
rtParticipation: "proxy",
|
|
75
|
+
shadowSource: "merged-proxy-casters",
|
|
76
|
+
temporalReuse: "high",
|
|
77
|
+
updateCadenceDivisor: 8,
|
|
78
|
+
}),
|
|
79
|
+
horizon: Object.freeze({
|
|
80
|
+
band: "horizon",
|
|
81
|
+
rasterMode: "horizon-shell",
|
|
82
|
+
rtParticipation: "disabled",
|
|
83
|
+
shadowSource: "baked-impression",
|
|
84
|
+
temporalReuse: "cached",
|
|
85
|
+
updateCadenceDivisor: 60,
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const rendererAccelerationStructurePolicies = Object.freeze(
|
|
90
|
+
rendererAccelerationStructureUpdateClasses.map((updateClass) =>
|
|
91
|
+
Object.freeze({
|
|
92
|
+
updateClass,
|
|
93
|
+
description:
|
|
94
|
+
{
|
|
95
|
+
static: "Stable static world geometry with infrequent rebuilds.",
|
|
96
|
+
"rigid-dynamic":
|
|
97
|
+
"Rigid transforms that can be refit or relinked without full deformation updates.",
|
|
98
|
+
deforming:
|
|
99
|
+
"Skinned or vertex-deforming content treated as a managed RT cost center.",
|
|
100
|
+
proxy:
|
|
101
|
+
"Low-cost RT proxy or distant representation updates.",
|
|
102
|
+
}[updateClass],
|
|
103
|
+
})
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
function buildRendererWorkerBudgetLevels(jobType, queueClass, levels) {
|
|
108
|
+
return Object.freeze(
|
|
109
|
+
levels.map((level) =>
|
|
110
|
+
Object.freeze({
|
|
111
|
+
id: level.id,
|
|
112
|
+
estimatedCostMs: level.estimatedCostMs,
|
|
113
|
+
config: Object.freeze({
|
|
114
|
+
maxDispatchesPerFrame: level.config.maxDispatchesPerFrame,
|
|
115
|
+
maxJobsPerDispatch: level.config.maxJobsPerDispatch,
|
|
116
|
+
cadenceDivisor: level.config.cadenceDivisor,
|
|
117
|
+
workgroupScale: level.config.workgroupScale,
|
|
118
|
+
maxQueueDepth: level.config.maxQueueDepth,
|
|
119
|
+
metadata: Object.freeze({
|
|
120
|
+
owner: rendererDebugOwner,
|
|
121
|
+
queueClass,
|
|
122
|
+
jobType,
|
|
123
|
+
quality: level.id,
|
|
124
|
+
}),
|
|
125
|
+
}),
|
|
126
|
+
})
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const rendererWorkerProfileSpecs = {
|
|
132
|
+
realtime: {
|
|
133
|
+
description:
|
|
134
|
+
"Frame-stage DAG for flat rendering with visibility, main encode, post-processing, and submit.",
|
|
135
|
+
suggestedAllocationIds: [
|
|
136
|
+
"renderer.surface.current",
|
|
137
|
+
"renderer.visibility.worklist",
|
|
138
|
+
"renderer.post-process.history",
|
|
139
|
+
],
|
|
140
|
+
jobs: {
|
|
141
|
+
acquire: {
|
|
142
|
+
priority: 5,
|
|
143
|
+
dependencies: [],
|
|
144
|
+
domain: "resolution",
|
|
145
|
+
importance: "critical",
|
|
146
|
+
levels: [
|
|
147
|
+
{
|
|
148
|
+
id: "fixed",
|
|
149
|
+
estimatedCostMs: 0.2,
|
|
150
|
+
config: {
|
|
151
|
+
maxDispatchesPerFrame: 1,
|
|
152
|
+
maxJobsPerDispatch: 1,
|
|
153
|
+
cadenceDivisor: 1,
|
|
154
|
+
workgroupScale: 1,
|
|
155
|
+
maxQueueDepth: 1,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
suggestedAllocationIds: ["renderer.surface.current"],
|
|
160
|
+
},
|
|
161
|
+
visibility: {
|
|
162
|
+
priority: 4,
|
|
163
|
+
dependencies: [],
|
|
164
|
+
domain: "geometry",
|
|
165
|
+
importance: "high",
|
|
166
|
+
levels: [
|
|
167
|
+
{
|
|
168
|
+
id: "low",
|
|
169
|
+
estimatedCostMs: 0.4,
|
|
170
|
+
config: {
|
|
171
|
+
maxDispatchesPerFrame: 1,
|
|
172
|
+
maxJobsPerDispatch: 128,
|
|
173
|
+
cadenceDivisor: 2,
|
|
174
|
+
workgroupScale: 0.5,
|
|
175
|
+
maxQueueDepth: 256,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "medium",
|
|
180
|
+
estimatedCostMs: 0.8,
|
|
181
|
+
config: {
|
|
182
|
+
maxDispatchesPerFrame: 1,
|
|
183
|
+
maxJobsPerDispatch: 256,
|
|
184
|
+
cadenceDivisor: 1,
|
|
185
|
+
workgroupScale: 0.75,
|
|
186
|
+
maxQueueDepth: 384,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "high",
|
|
191
|
+
estimatedCostMs: 1.2,
|
|
192
|
+
config: {
|
|
193
|
+
maxDispatchesPerFrame: 2,
|
|
194
|
+
maxJobsPerDispatch: 512,
|
|
195
|
+
cadenceDivisor: 1,
|
|
196
|
+
workgroupScale: 1,
|
|
197
|
+
maxQueueDepth: 512,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
suggestedAllocationIds: ["renderer.visibility.worklist"],
|
|
202
|
+
},
|
|
203
|
+
mainEncode: {
|
|
204
|
+
priority: 4,
|
|
205
|
+
dependencies: ["acquire", "visibility"],
|
|
206
|
+
domain: "geometry",
|
|
207
|
+
importance: "critical",
|
|
208
|
+
levels: [
|
|
209
|
+
{
|
|
210
|
+
id: "low",
|
|
211
|
+
estimatedCostMs: 1.2,
|
|
212
|
+
config: {
|
|
213
|
+
maxDispatchesPerFrame: 1,
|
|
214
|
+
maxJobsPerDispatch: 128,
|
|
215
|
+
cadenceDivisor: 1,
|
|
216
|
+
workgroupScale: 0.6,
|
|
217
|
+
maxQueueDepth: 192,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "medium",
|
|
222
|
+
estimatedCostMs: 2.1,
|
|
223
|
+
config: {
|
|
224
|
+
maxDispatchesPerFrame: 1,
|
|
225
|
+
maxJobsPerDispatch: 256,
|
|
226
|
+
cadenceDivisor: 1,
|
|
227
|
+
workgroupScale: 0.8,
|
|
228
|
+
maxQueueDepth: 256,
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "high",
|
|
233
|
+
estimatedCostMs: 3,
|
|
234
|
+
config: {
|
|
235
|
+
maxDispatchesPerFrame: 1,
|
|
236
|
+
maxJobsPerDispatch: 384,
|
|
237
|
+
cadenceDivisor: 1,
|
|
238
|
+
workgroupScale: 1,
|
|
239
|
+
maxQueueDepth: 384,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
suggestedAllocationIds: ["renderer.surface.current"],
|
|
244
|
+
},
|
|
245
|
+
postProcess: {
|
|
246
|
+
priority: 3,
|
|
247
|
+
dependencies: ["mainEncode"],
|
|
248
|
+
domain: "post-processing",
|
|
249
|
+
importance: "high",
|
|
250
|
+
levels: [
|
|
251
|
+
{
|
|
252
|
+
id: "low",
|
|
253
|
+
estimatedCostMs: 0.5,
|
|
254
|
+
config: {
|
|
255
|
+
maxDispatchesPerFrame: 1,
|
|
256
|
+
maxJobsPerDispatch: 64,
|
|
257
|
+
cadenceDivisor: 2,
|
|
258
|
+
workgroupScale: 0.5,
|
|
259
|
+
maxQueueDepth: 96,
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: "medium",
|
|
264
|
+
estimatedCostMs: 0.9,
|
|
265
|
+
config: {
|
|
266
|
+
maxDispatchesPerFrame: 1,
|
|
267
|
+
maxJobsPerDispatch: 128,
|
|
268
|
+
cadenceDivisor: 1,
|
|
269
|
+
workgroupScale: 0.75,
|
|
270
|
+
maxQueueDepth: 128,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "high",
|
|
275
|
+
estimatedCostMs: 1.4,
|
|
276
|
+
config: {
|
|
277
|
+
maxDispatchesPerFrame: 2,
|
|
278
|
+
maxJobsPerDispatch: 192,
|
|
279
|
+
cadenceDivisor: 1,
|
|
280
|
+
workgroupScale: 1,
|
|
281
|
+
maxQueueDepth: 192,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
suggestedAllocationIds: ["renderer.post-process.history"],
|
|
286
|
+
},
|
|
287
|
+
submit: {
|
|
288
|
+
priority: 2,
|
|
289
|
+
dependencies: ["postProcess"],
|
|
290
|
+
domain: "resolution",
|
|
291
|
+
importance: "critical",
|
|
292
|
+
levels: [
|
|
293
|
+
{
|
|
294
|
+
id: "fixed",
|
|
295
|
+
estimatedCostMs: 0.2,
|
|
296
|
+
config: {
|
|
297
|
+
maxDispatchesPerFrame: 1,
|
|
298
|
+
maxJobsPerDispatch: 1,
|
|
299
|
+
cadenceDivisor: 1,
|
|
300
|
+
workgroupScale: 1,
|
|
301
|
+
maxQueueDepth: 1,
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
suggestedAllocationIds: ["renderer.surface.current"],
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
xr: {
|
|
310
|
+
description:
|
|
311
|
+
"Frame-stage DAG for XR rendering with late-latch coordination before main encode and submit.",
|
|
312
|
+
suggestedAllocationIds: [
|
|
313
|
+
"renderer.xr.surface.current",
|
|
314
|
+
"renderer.xr.visibility.worklist",
|
|
315
|
+
],
|
|
316
|
+
jobs: {
|
|
317
|
+
acquire: {
|
|
318
|
+
priority: 5,
|
|
319
|
+
dependencies: [],
|
|
320
|
+
domain: "xr",
|
|
321
|
+
importance: "critical",
|
|
322
|
+
levels: [
|
|
323
|
+
{
|
|
324
|
+
id: "fixed",
|
|
325
|
+
estimatedCostMs: 0.2,
|
|
326
|
+
config: {
|
|
327
|
+
maxDispatchesPerFrame: 1,
|
|
328
|
+
maxJobsPerDispatch: 1,
|
|
329
|
+
cadenceDivisor: 1,
|
|
330
|
+
workgroupScale: 1,
|
|
331
|
+
maxQueueDepth: 1,
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
suggestedAllocationIds: ["renderer.xr.surface.current"],
|
|
336
|
+
},
|
|
337
|
+
visibility: {
|
|
338
|
+
priority: 4,
|
|
339
|
+
dependencies: [],
|
|
340
|
+
domain: "geometry",
|
|
341
|
+
importance: "high",
|
|
342
|
+
levels: [
|
|
343
|
+
{
|
|
344
|
+
id: "low",
|
|
345
|
+
estimatedCostMs: 0.5,
|
|
346
|
+
config: {
|
|
347
|
+
maxDispatchesPerFrame: 1,
|
|
348
|
+
maxJobsPerDispatch: 96,
|
|
349
|
+
cadenceDivisor: 2,
|
|
350
|
+
workgroupScale: 0.5,
|
|
351
|
+
maxQueueDepth: 192,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: "medium",
|
|
356
|
+
estimatedCostMs: 0.9,
|
|
357
|
+
config: {
|
|
358
|
+
maxDispatchesPerFrame: 1,
|
|
359
|
+
maxJobsPerDispatch: 192,
|
|
360
|
+
cadenceDivisor: 1,
|
|
361
|
+
workgroupScale: 0.75,
|
|
362
|
+
maxQueueDepth: 256,
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "high",
|
|
367
|
+
estimatedCostMs: 1.3,
|
|
368
|
+
config: {
|
|
369
|
+
maxDispatchesPerFrame: 2,
|
|
370
|
+
maxJobsPerDispatch: 320,
|
|
371
|
+
cadenceDivisor: 1,
|
|
372
|
+
workgroupScale: 1,
|
|
373
|
+
maxQueueDepth: 320,
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
suggestedAllocationIds: ["renderer.xr.visibility.worklist"],
|
|
378
|
+
},
|
|
379
|
+
lateLatch: {
|
|
380
|
+
priority: 5,
|
|
381
|
+
dependencies: ["acquire"],
|
|
382
|
+
domain: "xr",
|
|
383
|
+
importance: "critical",
|
|
384
|
+
levels: [
|
|
385
|
+
{
|
|
386
|
+
id: "fixed",
|
|
387
|
+
estimatedCostMs: 0.15,
|
|
388
|
+
config: {
|
|
389
|
+
maxDispatchesPerFrame: 1,
|
|
390
|
+
maxJobsPerDispatch: 1,
|
|
391
|
+
cadenceDivisor: 1,
|
|
392
|
+
workgroupScale: 1,
|
|
393
|
+
maxQueueDepth: 1,
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
suggestedAllocationIds: ["renderer.xr.surface.current"],
|
|
398
|
+
},
|
|
399
|
+
mainEncode: {
|
|
400
|
+
priority: 4,
|
|
401
|
+
dependencies: ["visibility", "lateLatch"],
|
|
402
|
+
domain: "xr",
|
|
403
|
+
importance: "critical",
|
|
404
|
+
levels: [
|
|
405
|
+
{
|
|
406
|
+
id: "low",
|
|
407
|
+
estimatedCostMs: 1.1,
|
|
408
|
+
config: {
|
|
409
|
+
maxDispatchesPerFrame: 1,
|
|
410
|
+
maxJobsPerDispatch: 96,
|
|
411
|
+
cadenceDivisor: 1,
|
|
412
|
+
workgroupScale: 0.6,
|
|
413
|
+
maxQueueDepth: 128,
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
id: "medium",
|
|
418
|
+
estimatedCostMs: 1.8,
|
|
419
|
+
config: {
|
|
420
|
+
maxDispatchesPerFrame: 1,
|
|
421
|
+
maxJobsPerDispatch: 192,
|
|
422
|
+
cadenceDivisor: 1,
|
|
423
|
+
workgroupScale: 0.8,
|
|
424
|
+
maxQueueDepth: 192,
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: "high",
|
|
429
|
+
estimatedCostMs: 2.6,
|
|
430
|
+
config: {
|
|
431
|
+
maxDispatchesPerFrame: 1,
|
|
432
|
+
maxJobsPerDispatch: 256,
|
|
433
|
+
cadenceDivisor: 1,
|
|
434
|
+
workgroupScale: 1,
|
|
435
|
+
maxQueueDepth: 256,
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
suggestedAllocationIds: ["renderer.xr.surface.current"],
|
|
440
|
+
},
|
|
441
|
+
submit: {
|
|
442
|
+
priority: 2,
|
|
443
|
+
dependencies: ["mainEncode"],
|
|
444
|
+
domain: "xr",
|
|
445
|
+
importance: "critical",
|
|
446
|
+
levels: [
|
|
447
|
+
{
|
|
448
|
+
id: "fixed",
|
|
449
|
+
estimatedCostMs: 0.2,
|
|
450
|
+
config: {
|
|
451
|
+
maxDispatchesPerFrame: 1,
|
|
452
|
+
maxJobsPerDispatch: 1,
|
|
453
|
+
cadenceDivisor: 1,
|
|
454
|
+
workgroupScale: 1,
|
|
455
|
+
maxQueueDepth: 1,
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
suggestedAllocationIds: ["renderer.xr.surface.current"],
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
function buildRendererInputBoundary(profile) {
|
|
466
|
+
return Object.freeze({
|
|
467
|
+
type: "stable-visual-snapshot",
|
|
468
|
+
owner: rendererDebugOwner,
|
|
469
|
+
profile,
|
|
470
|
+
authority: "visual",
|
|
471
|
+
source: "scene-preparation",
|
|
472
|
+
stable: true,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function buildRendererRenderStages(profile) {
|
|
477
|
+
return Object.freeze(
|
|
478
|
+
rendererRayTracingStageDefinitions.map((stage) =>
|
|
479
|
+
Object.freeze({
|
|
480
|
+
...stage,
|
|
481
|
+
profile,
|
|
482
|
+
workerJobKeys:
|
|
483
|
+
profile === "xr" && stage.key === "primaryVisibility"
|
|
484
|
+
? Object.freeze(["lateLatch", "visibility"])
|
|
485
|
+
: stage.key === "present"
|
|
486
|
+
? Object.freeze(["submit"])
|
|
487
|
+
: stage.key === "denoiseTemporal" ||
|
|
488
|
+
stage.key === "transparents" ||
|
|
489
|
+
stage.key === "composition"
|
|
490
|
+
? Object.freeze(["postProcess"])
|
|
491
|
+
: stage.key === "primaryVisibility"
|
|
492
|
+
? Object.freeze(["visibility"])
|
|
493
|
+
: stage.key === "shadowAssist" ||
|
|
494
|
+
stage.key === "opaqueFoundation" ||
|
|
495
|
+
stage.key === "rtDirectLighting" ||
|
|
496
|
+
stage.key === "rtReflections" ||
|
|
497
|
+
stage.key === "rtGlobalIllumination"
|
|
498
|
+
? Object.freeze(["mainEncode"])
|
|
499
|
+
: Object.freeze(["mainEncode"]),
|
|
500
|
+
})
|
|
501
|
+
)
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function buildRendererRepresentationBands(profile) {
|
|
506
|
+
return Object.freeze(
|
|
507
|
+
rendererRepresentationBands.map((band) =>
|
|
508
|
+
Object.freeze({
|
|
509
|
+
...rendererRepresentationBandPolicies[band],
|
|
510
|
+
profile,
|
|
511
|
+
})
|
|
512
|
+
)
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function buildRendererAccelerationStructureUpdates(profile) {
|
|
517
|
+
return Object.freeze(
|
|
518
|
+
rendererAccelerationStructurePolicies.map((policy) =>
|
|
519
|
+
Object.freeze({
|
|
520
|
+
...policy,
|
|
521
|
+
profile,
|
|
522
|
+
})
|
|
523
|
+
)
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function assertRendererIdentifier(name, value) {
|
|
528
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
529
|
+
throw new Error(`${name} must be a non-empty string.`);
|
|
530
|
+
}
|
|
531
|
+
return value.trim();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function buildRendererWorkerProfile(name, spec) {
|
|
535
|
+
return Object.freeze({
|
|
536
|
+
name,
|
|
537
|
+
description: spec.description,
|
|
538
|
+
jobs: Object.freeze(Object.keys(spec.jobs)),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function buildRendererWorkerManifestJob(profileName, jobName, spec) {
|
|
543
|
+
const label = `renderer.${profileName}.${jobName}`;
|
|
544
|
+
return Object.freeze({
|
|
545
|
+
key: jobName,
|
|
546
|
+
label,
|
|
547
|
+
worker: Object.freeze({
|
|
548
|
+
jobType: label,
|
|
549
|
+
queueClass: rendererWorkerQueueClass,
|
|
550
|
+
priority: spec.priority,
|
|
551
|
+
dependencies: Object.freeze(
|
|
552
|
+
spec.dependencies.map((dependency) => `renderer.${profileName}.${dependency}`)
|
|
553
|
+
),
|
|
554
|
+
schedulerMode: "dag",
|
|
555
|
+
}),
|
|
556
|
+
performance: Object.freeze({
|
|
557
|
+
id: label,
|
|
558
|
+
jobType: label,
|
|
559
|
+
queueClass: rendererWorkerQueueClass,
|
|
560
|
+
domain: spec.domain,
|
|
561
|
+
authority: "visual",
|
|
562
|
+
importance: spec.importance,
|
|
563
|
+
levels: buildRendererWorkerBudgetLevels(
|
|
564
|
+
label,
|
|
565
|
+
rendererWorkerQueueClass,
|
|
566
|
+
spec.levels
|
|
567
|
+
),
|
|
568
|
+
}),
|
|
569
|
+
debug: Object.freeze({
|
|
570
|
+
owner: rendererDebugOwner,
|
|
571
|
+
queueClass: rendererWorkerQueueClass,
|
|
572
|
+
jobType: label,
|
|
573
|
+
tags: Object.freeze(["renderer", profileName, jobName, spec.domain]),
|
|
574
|
+
suggestedAllocationIds: Object.freeze([...spec.suggestedAllocationIds]),
|
|
575
|
+
}),
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function buildRendererWorkerManifest(name, spec) {
|
|
580
|
+
return Object.freeze({
|
|
581
|
+
schemaVersion: 1,
|
|
582
|
+
owner: rendererDebugOwner,
|
|
583
|
+
profile: name,
|
|
584
|
+
description: spec.description,
|
|
585
|
+
queueClass: rendererWorkerQueueClass,
|
|
586
|
+
schedulerMode: "dag",
|
|
587
|
+
inputBoundary: buildRendererInputBoundary(name),
|
|
588
|
+
renderStages: buildRendererRenderStages(name),
|
|
589
|
+
representationBands: buildRendererRepresentationBands(name),
|
|
590
|
+
accelerationStructureUpdates: buildRendererAccelerationStructureUpdates(name),
|
|
591
|
+
suggestedAllocationIds: Object.freeze([...spec.suggestedAllocationIds]),
|
|
592
|
+
jobs: Object.freeze(
|
|
593
|
+
Object.entries(spec.jobs).map(([jobName, jobSpec]) =>
|
|
594
|
+
buildRendererWorkerManifestJob(name, jobName, jobSpec)
|
|
595
|
+
)
|
|
596
|
+
),
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export const rendererWorkerProfiles = Object.freeze(
|
|
601
|
+
Object.fromEntries(
|
|
602
|
+
Object.entries(rendererWorkerProfileSpecs).map(([name, spec]) => [
|
|
603
|
+
name,
|
|
604
|
+
buildRendererWorkerProfile(name, spec),
|
|
605
|
+
])
|
|
606
|
+
)
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
export const rendererWorkerProfileNames = Object.freeze(
|
|
610
|
+
Object.keys(rendererWorkerProfiles)
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
export const rendererWorkerManifests = Object.freeze(
|
|
614
|
+
Object.fromEntries(
|
|
615
|
+
Object.entries(rendererWorkerProfileSpecs).map(([name, spec]) => [
|
|
616
|
+
name,
|
|
617
|
+
buildRendererWorkerManifest(name, spec),
|
|
618
|
+
])
|
|
619
|
+
)
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
export function getRendererWorkerProfile(name = defaultRendererWorkerProfile) {
|
|
623
|
+
const profile = rendererWorkerProfiles[name];
|
|
624
|
+
if (!profile) {
|
|
625
|
+
const available = rendererWorkerProfileNames.join(", ");
|
|
626
|
+
throw new Error(`Unknown renderer worker profile "${name}". Available: ${available}.`);
|
|
627
|
+
}
|
|
628
|
+
return profile;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export function getRendererWorkerManifest(name = defaultRendererWorkerProfile) {
|
|
632
|
+
const manifest = rendererWorkerManifests[name];
|
|
633
|
+
if (!manifest) {
|
|
634
|
+
const available = rendererWorkerProfileNames.join(", ");
|
|
635
|
+
throw new Error(`Unknown renderer worker profile "${name}". Available: ${available}.`);
|
|
636
|
+
}
|
|
637
|
+
return manifest;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export function createRayTracingRenderPlan(options = {}) {
|
|
641
|
+
const profile = options.profile ?? defaultRendererWorkerProfile;
|
|
642
|
+
const snapshotId = assertRendererIdentifier(
|
|
643
|
+
"snapshotId",
|
|
644
|
+
options.snapshotId
|
|
645
|
+
);
|
|
646
|
+
const workerManifest = getRendererWorkerManifest(profile);
|
|
647
|
+
const representations = Array.isArray(options.representations)
|
|
648
|
+
? Object.freeze(
|
|
649
|
+
options.representations.map((representation, index) => {
|
|
650
|
+
if (!representation || typeof representation !== "object") {
|
|
651
|
+
throw new Error(`representations[${index}] must be an object.`);
|
|
652
|
+
}
|
|
653
|
+
const band = assertRendererIdentifier(
|
|
654
|
+
`representations[${index}].band`,
|
|
655
|
+
representation.band
|
|
656
|
+
);
|
|
657
|
+
if (!rendererRepresentationBands.includes(band)) {
|
|
658
|
+
throw new Error(
|
|
659
|
+
`representations[${index}].band must be one of: ${rendererRepresentationBands.join(", ")}.`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
return Object.freeze({
|
|
663
|
+
...representation,
|
|
664
|
+
band,
|
|
665
|
+
});
|
|
666
|
+
})
|
|
667
|
+
)
|
|
668
|
+
: workerManifest.representationBands;
|
|
669
|
+
|
|
670
|
+
return Object.freeze({
|
|
671
|
+
schemaVersion: 1,
|
|
672
|
+
owner: rendererDebugOwner,
|
|
673
|
+
profile,
|
|
674
|
+
inputBoundary: Object.freeze({
|
|
675
|
+
...workerManifest.inputBoundary,
|
|
676
|
+
snapshotId,
|
|
677
|
+
}),
|
|
678
|
+
renderStages: workerManifest.renderStages,
|
|
679
|
+
representationBands: representations,
|
|
680
|
+
accelerationStructureUpdates: workerManifest.accelerationStructureUpdates,
|
|
681
|
+
workerManifest,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
3
684
|
|
|
4
685
|
function clamp01(value) {
|
|
5
686
|
return Math.min(1, Math.max(0, value));
|
|
@@ -41,6 +722,16 @@ function normalizeColor(value) {
|
|
|
41
722
|
return [...DEFAULT_CLEAR_COLOR];
|
|
42
723
|
}
|
|
43
724
|
|
|
725
|
+
function readPositiveNumber(name, value) {
|
|
726
|
+
if (value === undefined) {
|
|
727
|
+
return undefined;
|
|
728
|
+
}
|
|
729
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
730
|
+
throw new Error(`${name} must be a finite number greater than zero.`);
|
|
731
|
+
}
|
|
732
|
+
return value;
|
|
733
|
+
}
|
|
734
|
+
|
|
44
735
|
function now() {
|
|
45
736
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
46
737
|
return performance.now();
|
|
@@ -48,6 +739,121 @@ function now() {
|
|
|
48
739
|
return Date.now();
|
|
49
740
|
}
|
|
50
741
|
|
|
742
|
+
function normalizeFrameId(value) {
|
|
743
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
744
|
+
throw new Error("frameIdFactory must return a non-empty string.");
|
|
745
|
+
}
|
|
746
|
+
return value.trim();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function resolveTargetFrameTimeMs(options, event) {
|
|
750
|
+
const {
|
|
751
|
+
targetFrameTimeMs: fixedTargetFrameTimeMs,
|
|
752
|
+
targetFrameRate,
|
|
753
|
+
getTargetFrameTimeMs,
|
|
754
|
+
} = options;
|
|
755
|
+
|
|
756
|
+
if (typeof getTargetFrameTimeMs === "function") {
|
|
757
|
+
const resolved = getTargetFrameTimeMs(event);
|
|
758
|
+
return readPositiveNumber("getTargetFrameTimeMs()", resolved);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (fixedTargetFrameTimeMs !== undefined) {
|
|
762
|
+
return fixedTargetFrameTimeMs;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (targetFrameRate !== undefined) {
|
|
766
|
+
return 1000 / targetFrameRate;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return undefined;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
export function createRendererDebugHooks(options = {}) {
|
|
773
|
+
const {
|
|
774
|
+
debugSession,
|
|
775
|
+
targetFrameTimeMs,
|
|
776
|
+
targetFrameRate,
|
|
777
|
+
getTargetFrameTimeMs,
|
|
778
|
+
onFrameStart,
|
|
779
|
+
onFrameComplete,
|
|
780
|
+
} = options;
|
|
781
|
+
|
|
782
|
+
if (!debugSession || typeof debugSession.recordFrame !== "function") {
|
|
783
|
+
throw new Error(
|
|
784
|
+
"debugSession must expose recordFrame(sample). Use @plasius/gpu-debug createGpuDebugSession()."
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const fixedTargetFrameTimeMs = readPositiveNumber(
|
|
789
|
+
"targetFrameTimeMs",
|
|
790
|
+
targetFrameTimeMs
|
|
791
|
+
);
|
|
792
|
+
const fixedTargetFrameRate = readPositiveNumber(
|
|
793
|
+
"targetFrameRate",
|
|
794
|
+
targetFrameRate
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
if (
|
|
798
|
+
fixedTargetFrameTimeMs !== undefined &&
|
|
799
|
+
fixedTargetFrameRate !== undefined
|
|
800
|
+
) {
|
|
801
|
+
throw new Error(
|
|
802
|
+
"Provide either targetFrameTimeMs or targetFrameRate, not both."
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (
|
|
807
|
+
getTargetFrameTimeMs !== undefined &&
|
|
808
|
+
typeof getTargetFrameTimeMs !== "function"
|
|
809
|
+
) {
|
|
810
|
+
throw new Error("getTargetFrameTimeMs must be a function when provided.");
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const resolvedOptions = {
|
|
814
|
+
targetFrameTimeMs: fixedTargetFrameTimeMs,
|
|
815
|
+
targetFrameRate: fixedTargetFrameRate,
|
|
816
|
+
getTargetFrameTimeMs,
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
onFrameStart(event) {
|
|
821
|
+
if (typeof onFrameStart === "function") {
|
|
822
|
+
onFrameStart({
|
|
823
|
+
...event,
|
|
824
|
+
owner: rendererDebugOwner,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
onFrameComplete(event) {
|
|
829
|
+
const resolvedTargetFrameTimeMs = resolveTargetFrameTimeMs(
|
|
830
|
+
resolvedOptions,
|
|
831
|
+
event
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
if (
|
|
835
|
+
typeof event.frameTimeMs === "number" &&
|
|
836
|
+
Number.isFinite(event.frameTimeMs) &&
|
|
837
|
+
event.frameTimeMs > 0
|
|
838
|
+
) {
|
|
839
|
+
debugSession.recordFrame({
|
|
840
|
+
frameId: event.frameId,
|
|
841
|
+
frameTimeMs: event.frameTimeMs,
|
|
842
|
+
targetFrameTimeMs: resolvedTargetFrameTimeMs,
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (typeof onFrameComplete === "function") {
|
|
847
|
+
onFrameComplete({
|
|
848
|
+
...event,
|
|
849
|
+
owner: rendererDebugOwner,
|
|
850
|
+
targetFrameTimeMs: resolvedTargetFrameTimeMs,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
51
857
|
function readNavigator(navigatorOverride) {
|
|
52
858
|
const currentNavigator = navigatorOverride ?? globalThis.navigator;
|
|
53
859
|
if (!currentNavigator || typeof currentNavigator !== "object") {
|
|
@@ -139,8 +945,11 @@ export async function createGpuRenderer(options = {}) {
|
|
|
139
945
|
clearColor = DEFAULT_CLEAR_COLOR,
|
|
140
946
|
requestAnimationFrame = globalThis.requestAnimationFrame?.bind(globalThis),
|
|
141
947
|
cancelAnimationFrame = globalThis.cancelAnimationFrame?.bind(globalThis),
|
|
948
|
+
frameIdFactory,
|
|
949
|
+
onFrameStart,
|
|
142
950
|
onBeforeEncode,
|
|
143
951
|
onAfterSubmit,
|
|
952
|
+
onFrameComplete,
|
|
144
953
|
} = options;
|
|
145
954
|
|
|
146
955
|
const gpu = readGpu(navigatorOverride);
|
|
@@ -178,6 +987,33 @@ export async function createGpuRenderer(options = {}) {
|
|
|
178
987
|
throw new Error("Renderer was destroyed.");
|
|
179
988
|
}
|
|
180
989
|
|
|
990
|
+
const frameNumber = frame + 1;
|
|
991
|
+
const frameId = normalizeFrameId(
|
|
992
|
+
typeof frameIdFactory === "function"
|
|
993
|
+
? frameIdFactory({
|
|
994
|
+
frame: frameNumber,
|
|
995
|
+
timestamp,
|
|
996
|
+
canvas: targetCanvas,
|
|
997
|
+
xrActive,
|
|
998
|
+
})
|
|
999
|
+
: `renderer.frame.${frameNumber}`
|
|
1000
|
+
);
|
|
1001
|
+
const frameTimeMs =
|
|
1002
|
+
lastTimestamp > 0 ? Math.max(0, timestamp - lastTimestamp) : undefined;
|
|
1003
|
+
|
|
1004
|
+
if (typeof onFrameStart === "function") {
|
|
1005
|
+
onFrameStart({
|
|
1006
|
+
frame: frameNumber,
|
|
1007
|
+
frameId,
|
|
1008
|
+
frameTimeMs,
|
|
1009
|
+
timestamp,
|
|
1010
|
+
device,
|
|
1011
|
+
context,
|
|
1012
|
+
canvas: targetCanvas,
|
|
1013
|
+
xrActive,
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
181
1017
|
const texture = context.getCurrentTexture?.();
|
|
182
1018
|
if (!texture || typeof texture.createView !== "function") {
|
|
183
1019
|
throw new Error("WebGPU context returned an invalid current texture.");
|
|
@@ -193,12 +1029,16 @@ export async function createGpuRenderer(options = {}) {
|
|
|
193
1029
|
if (typeof onBeforeEncode === "function") {
|
|
194
1030
|
onBeforeEncode({
|
|
195
1031
|
frame,
|
|
1032
|
+
frameNumber,
|
|
1033
|
+
frameId,
|
|
1034
|
+
frameTimeMs,
|
|
196
1035
|
timestamp,
|
|
197
1036
|
device,
|
|
198
1037
|
context,
|
|
199
1038
|
encoder,
|
|
200
1039
|
pass,
|
|
201
1040
|
canvas: targetCanvas,
|
|
1041
|
+
xrActive,
|
|
202
1042
|
});
|
|
203
1043
|
}
|
|
204
1044
|
|
|
@@ -209,21 +1049,40 @@ export async function createGpuRenderer(options = {}) {
|
|
|
209
1049
|
const commandBuffer = encoder.finish();
|
|
210
1050
|
device.queue.submit([commandBuffer]);
|
|
211
1051
|
|
|
212
|
-
frame
|
|
1052
|
+
frame = frameNumber;
|
|
213
1053
|
lastTimestamp = timestamp;
|
|
214
1054
|
|
|
215
1055
|
if (typeof onAfterSubmit === "function") {
|
|
216
1056
|
onAfterSubmit({
|
|
217
|
-
frame,
|
|
1057
|
+
frame: frameNumber,
|
|
1058
|
+
frameNumber,
|
|
1059
|
+
frameId,
|
|
1060
|
+
frameTimeMs,
|
|
1061
|
+
timestamp,
|
|
1062
|
+
device,
|
|
1063
|
+
context,
|
|
1064
|
+
canvas: targetCanvas,
|
|
1065
|
+
xrActive,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (typeof onFrameComplete === "function") {
|
|
1070
|
+
onFrameComplete({
|
|
1071
|
+
frame: frameNumber,
|
|
1072
|
+
frameId,
|
|
1073
|
+
frameTimeMs,
|
|
218
1074
|
timestamp,
|
|
219
1075
|
device,
|
|
220
1076
|
context,
|
|
221
1077
|
canvas: targetCanvas,
|
|
1078
|
+
xrActive,
|
|
222
1079
|
});
|
|
223
1080
|
}
|
|
224
1081
|
|
|
225
1082
|
return {
|
|
226
|
-
frame,
|
|
1083
|
+
frame: frameNumber,
|
|
1084
|
+
frameId,
|
|
1085
|
+
frameTimeMs,
|
|
227
1086
|
timestamp,
|
|
228
1087
|
};
|
|
229
1088
|
};
|