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