@plasius/gpu-renderer 0.2.42 → 0.2.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/gpu-renderer",
3
- "version": "0.2.42",
3
+ "version": "0.2.44",
4
4
  "description": "Framework-agnostic WebGPU renderer runtime for Plasius projects.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -27,7 +27,7 @@
27
27
  "scripts": {
28
28
  "build": "tsup",
29
29
  "demo": "python3 -m http.server --directory ..",
30
- "typecheck": "node scripts/check-source-syntax.cjs",
30
+ "typecheck": "node scripts/check-source-syntax.cjs && tsc -p tsconfig.types.json && node scripts/verify-clean-type-consumer.cjs",
31
31
  "audit:eslint": "eslint . --max-warnings=0",
32
32
  "audit:deps": "npm ls --all --omit=optional --omit=peer > /dev/null 2>&1 || true",
33
33
  "audit:npm": "npm audit --audit-level=high --omit=dev",
@@ -53,14 +53,15 @@
53
53
  "license": "Apache-2.0",
54
54
  "dependencies": {
55
55
  "@plasius/gpu-camera": "^0.1.18",
56
- "@plasius/gpu-shared": "^1.0.14",
57
- "@plasius/gpu-xr": "^0.1.17"
56
+ "@plasius/gpu-shared": "^1.1.1",
57
+ "@plasius/gpu-xr": "^0.1.17",
58
+ "@types/webxr": "^0.5.24"
58
59
  },
59
60
  "devDependencies": {
60
61
  "@eslint/js": "^10.0.1",
61
62
  "c8": "^11.0.0",
62
- "eslint": "^10.6.0",
63
- "globals": "^17.7.0",
63
+ "eslint": "^10.9.1",
64
+ "globals": "^17.11.0",
64
65
  "tsup": "^8.5.1",
65
66
  "typescript": "^7.0.2"
66
67
  },
@@ -0,0 +1,190 @@
1
+ import {
2
+ FEEDBACK_GAME_DIAGNOSTICS_CONTRACT_VERSION,
3
+ feedbackGameDiagnosticSurfaceRegistrations,
4
+ parseFeedbackGameDiagnostics,
5
+ } from "@plasius/gpu-shared/feedback-diagnostics";
6
+
7
+ const inputKeys = Object.freeze([
8
+ "featureEnabled",
9
+ "capabilityGranted",
10
+ "consentConfirmed",
11
+ "surfaceId",
12
+ "renderer",
13
+ "backend",
14
+ "viewportWidth",
15
+ "viewportHeight",
16
+ "frameRate",
17
+ "frameTimeMs",
18
+ "featureIds",
19
+ "counters",
20
+ "errorCodes",
21
+ ]);
22
+
23
+ export function bucketFeedbackGameViewport(width, height) {
24
+ if (
25
+ typeof width !== "number" ||
26
+ typeof height !== "number" ||
27
+ !Number.isFinite(width) ||
28
+ !Number.isFinite(height) ||
29
+ width <= 0 ||
30
+ height <= 0
31
+ ) {
32
+ return "unknown";
33
+ }
34
+
35
+ const shorterEdge = Math.min(width, height);
36
+ const longerEdge = Math.max(width, height);
37
+ const size =
38
+ shorterEdge < 600
39
+ ? "small"
40
+ : shorterEdge < 900 || longerEdge < 1_440
41
+ ? "medium"
42
+ : "large";
43
+ const orientation = width > height ? "landscape" : "portrait";
44
+ return `${size}-${orientation}`;
45
+ }
46
+
47
+ export function bucketFeedbackGameFrameRate(frameRate) {
48
+ if (
49
+ typeof frameRate !== "number" ||
50
+ !Number.isFinite(frameRate) ||
51
+ frameRate < 0
52
+ ) {
53
+ return "unknown";
54
+ }
55
+ if (frameRate < 15) {
56
+ return "under-15";
57
+ }
58
+ if (frameRate < 30) {
59
+ return "15-29";
60
+ }
61
+ if (frameRate < 60) {
62
+ return "30-59";
63
+ }
64
+ return "60-plus";
65
+ }
66
+
67
+ export function bucketFeedbackGameFrameTime(frameTimeMs) {
68
+ if (
69
+ typeof frameTimeMs !== "number" ||
70
+ !Number.isFinite(frameTimeMs) ||
71
+ frameTimeMs < 0
72
+ ) {
73
+ return "unknown";
74
+ }
75
+ if (frameTimeMs < 17) {
76
+ return "under-17ms";
77
+ }
78
+ if (frameTimeMs < 34) {
79
+ return "17-33ms";
80
+ }
81
+ if (frameTimeMs < 67) {
82
+ return "34-66ms";
83
+ }
84
+ return "over-66ms";
85
+ }
86
+
87
+ export function createFeedbackGameDiagnosticSnapshot(input) {
88
+ try {
89
+ return createFeedbackGameDiagnosticSnapshotInternal(input);
90
+ } catch {
91
+ throw new TypeError("Invalid privacy-safe renderer diagnostics input.");
92
+ }
93
+ }
94
+
95
+ function createFeedbackGameDiagnosticSnapshotInternal(input) {
96
+ const snapshot = snapshotInputIfEnabled(input);
97
+ if (snapshot === null) {
98
+ return null;
99
+ }
100
+
101
+ const registration =
102
+ typeof snapshot.surfaceId === "string" &&
103
+ Object.hasOwn(
104
+ feedbackGameDiagnosticSurfaceRegistrations,
105
+ snapshot.surfaceId
106
+ )
107
+ ? feedbackGameDiagnosticSurfaceRegistrations[snapshot.surfaceId]
108
+ : undefined;
109
+
110
+ return parseFeedbackGameDiagnostics({
111
+ type: "feedback-game-diagnostics",
112
+ version: FEEDBACK_GAME_DIAGNOSTICS_CONTRACT_VERSION,
113
+ surfaceId: snapshot.surfaceId,
114
+ consentConfirmed: true,
115
+ provenanceContractId: registration?.provenanceContractId,
116
+ renderer: snapshot.renderer,
117
+ backend: snapshot.backend,
118
+ viewportBucket: bucketFeedbackGameViewport(
119
+ snapshot.viewportWidth,
120
+ snapshot.viewportHeight
121
+ ),
122
+ frameRateBucket: bucketFeedbackGameFrameRate(snapshot.frameRate),
123
+ frameTimeBucket: bucketFeedbackGameFrameTime(snapshot.frameTimeMs),
124
+ featureIds: snapshot.featureIds,
125
+ counters: snapshot.counters,
126
+ errorCodes: snapshot.errorCodes,
127
+ });
128
+ }
129
+
130
+ function snapshotInputIfEnabled(input) {
131
+ assertValid(
132
+ input !== null &&
133
+ typeof input === "object" &&
134
+ !Array.isArray(input) &&
135
+ (Object.getPrototypeOf(input) === Object.prototype ||
136
+ Object.getPrototypeOf(input) === null)
137
+ );
138
+
139
+ const gateKeys = [
140
+ "featureEnabled",
141
+ "capabilityGranted",
142
+ "consentConfirmed",
143
+ ];
144
+ const snapshot = Object.create(null);
145
+ for (const key of gateKeys) {
146
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
147
+ assertValid(
148
+ descriptor !== undefined &&
149
+ descriptor.enumerable === true &&
150
+ Object.hasOwn(descriptor, "value") &&
151
+ typeof descriptor.value === "boolean"
152
+ );
153
+ snapshot[key] = descriptor.value;
154
+ }
155
+
156
+ const { featureEnabled, capabilityGranted, consentConfirmed } = snapshot;
157
+ if (
158
+ !featureEnabled ||
159
+ !capabilityGranted ||
160
+ !consentConfirmed
161
+ ) {
162
+ return null;
163
+ }
164
+
165
+ const actualKeys = Reflect.ownKeys(input);
166
+ assertValid(
167
+ actualKeys.length === inputKeys.length &&
168
+ inputKeys.every((key) => actualKeys.includes(key)) &&
169
+ actualKeys.every(
170
+ (key) => typeof key === "string" && inputKeys.includes(key)
171
+ )
172
+ );
173
+
174
+ for (const key of inputKeys.filter((key) => !gateKeys.includes(key))) {
175
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
176
+ assertValid(
177
+ descriptor !== undefined &&
178
+ descriptor.enumerable === true &&
179
+ Object.hasOwn(descriptor, "value")
180
+ );
181
+ snapshot[key] = descriptor.value;
182
+ }
183
+ return Object.freeze(snapshot);
184
+ }
185
+
186
+ function assertValid(condition) {
187
+ if (!condition) {
188
+ throw new TypeError("Invalid privacy-safe renderer diagnostics input.");
189
+ }
190
+ }
package/src/index.d.ts CHANGED
@@ -1,3 +1,60 @@
1
+ /// <reference types="webxr" />
2
+
3
+ import type {
4
+ FeedbackGameDiagnosticBackend,
5
+ FeedbackGameDiagnosticCounter,
6
+ FeedbackGameDiagnosticErrorCode,
7
+ FeedbackGameDiagnosticFeatureId,
8
+ FeedbackGameDiagnosticFrameRateBucket,
9
+ FeedbackGameDiagnosticFrameTimeBucket,
10
+ FeedbackGameDiagnostics,
11
+ FeedbackGameDiagnosticRenderer,
12
+ FeedbackGameDiagnosticSurfaceId,
13
+ FeedbackGameDiagnosticViewportBucket,
14
+ } from "@plasius/gpu-shared/feedback-diagnostics";
15
+ import type { XrStoreState } from "@plasius/gpu-xr";
16
+
17
+ export interface CreateFeedbackGameDiagnosticSnapshotInput {
18
+ readonly featureEnabled: boolean;
19
+ readonly capabilityGranted: boolean;
20
+ readonly consentConfirmed: boolean;
21
+ readonly surfaceId: FeedbackGameDiagnosticSurfaceId;
22
+ readonly renderer: FeedbackGameDiagnosticRenderer;
23
+ readonly backend: FeedbackGameDiagnosticBackend;
24
+ /** CSS-pixel viewport width; it is bucketed synchronously and never returned. */
25
+ readonly viewportWidth: number | null;
26
+ /** CSS-pixel viewport height; it is bucketed synchronously and never returned. */
27
+ readonly viewportHeight: number | null;
28
+ /** Recent coarse FPS observation; it is bucketed synchronously and never returned. */
29
+ readonly frameRate: number | null;
30
+ /** Recent coarse frame-time observation; it is bucketed synchronously and never returned. */
31
+ readonly frameTimeMs: number | null;
32
+ readonly featureIds: readonly FeedbackGameDiagnosticFeatureId[];
33
+ readonly counters: readonly FeedbackGameDiagnosticCounter[];
34
+ readonly errorCodes: readonly FeedbackGameDiagnosticErrorCode[];
35
+ }
36
+
37
+ export function bucketFeedbackGameViewport(
38
+ width: unknown,
39
+ height: unknown,
40
+ ): FeedbackGameDiagnosticViewportBucket;
41
+
42
+ export function bucketFeedbackGameFrameRate(
43
+ frameRate: unknown,
44
+ ): FeedbackGameDiagnosticFrameRateBucket;
45
+
46
+ export function bucketFeedbackGameFrameTime(
47
+ frameTimeMs: unknown,
48
+ ): FeedbackGameDiagnosticFrameTimeBucket;
49
+
50
+ export function createFeedbackGameDiagnosticSnapshot<
51
+ SurfaceId extends FeedbackGameDiagnosticSurfaceId,
52
+ >(
53
+ input: CreateFeedbackGameDiagnosticSnapshotInput & {
54
+ readonly surfaceId: SurfaceId;
55
+ },
56
+ ): Extract<FeedbackGameDiagnostics, { readonly surfaceId: SurfaceId }> | null;
57
+
1
58
  export type RendererColor = string | [number, number, number, number?];
2
59
  export type AnimatedSceneVector3 = readonly [number, number, number] | readonly number[];
3
60
  export type AnimatedScenePropKind =
@@ -509,7 +566,6 @@ export interface WavefrontMeshInput {
509
566
  readonly occlusionTexture?: WavefrontTextureSampleInput | null;
510
567
  readonly emissiveTexture?: WavefrontTextureSampleInput | null;
511
568
  readonly medium?: WavefrontMediumInput | null;
512
- readonly extensions?: Record<string, Record<string, unknown>>;
513
569
  }
514
570
 
515
571
  export interface WavefrontTriangleRecord {
@@ -960,12 +1016,20 @@ export interface WavefrontPathTracingMemoryEstimate {
960
1016
  readonly triangleBytes: number;
961
1017
  readonly materialTableBytes: number;
962
1018
  readonly bvhNodeBytes: number;
1019
+ readonly bvhCombinedBytes: number;
963
1020
  readonly bvhLeafReferenceBytes: number;
964
1021
  readonly emissiveTriangleMetadataBytes: number;
1022
+ readonly meshVertexBytes: number;
1023
+ readonly meshIndexBytes: number;
1024
+ readonly meshRangeBytes: number;
965
1025
  readonly environmentPortalBytes: number;
966
1026
  readonly configBytes: number;
1027
+ readonly configBufferStride: number;
1028
+ readonly frameConfigBytes: number;
1029
+ readonly bvhBuildConfigBytes: number;
967
1030
  readonly counterBytes: number;
968
1031
  readonly indirectDispatchBytes: number;
1032
+ readonly sceneStorageBufferBindingBytes: number;
969
1033
  readonly totalHotBufferBytes: number;
970
1034
  }
971
1035
 
@@ -987,6 +1051,7 @@ export interface WavefrontGpuParallelismDiagnostics {
987
1051
  readonly maxComputeWorkgroupsPerDimension: number | null;
988
1052
  readonly maxStorageBuffersPerShaderStage: number | null;
989
1053
  readonly maxStorageBufferBindingSize: number | null;
1054
+ readonly maxBufferSize: number | null;
990
1055
  }>;
991
1056
  readonly configuredWorkgroupSize: number;
992
1057
  readonly directDispatches: number;
@@ -1279,12 +1344,27 @@ export function createWavefrontGpuMaterialSource(
1279
1344
  ): WavefrontGpuMaterialSource;
1280
1345
  export function createDefaultWavefrontSceneObjects(): readonly WavefrontSceneObject[];
1281
1346
  export function estimateWavefrontPathTracingMemory(options?: {
1347
+ width?: number;
1348
+ height?: number;
1349
+ tileSize?: number;
1350
+ samplesPerPixel?: number;
1282
1351
  tilePixelCapacity?: number;
1352
+ maxDepth?: number;
1283
1353
  sceneObjectCapacity?: number;
1284
1354
  triangleCapacity?: number;
1355
+ materialCapacity?: number;
1285
1356
  bvhNodeCapacity?: number;
1286
1357
  bvhLeafSortCapacity?: number;
1287
1358
  emissiveTriangleCapacity?: number;
1359
+ environmentPortalCapacity?: number;
1360
+ meshVertexCount?: number;
1361
+ meshIndexCount?: number;
1362
+ meshRangeCount?: number;
1363
+ bvhSortStageCount?: number;
1364
+ bvhBuildLevelCount?: number;
1365
+ denoise?: boolean;
1366
+ deferredPathResolve?: boolean;
1367
+ minUniformBufferOffsetAlignment?: number;
1288
1368
  }): WavefrontPathTracingMemoryEstimate;
1289
1369
  export function normalizeWavefrontMesh(
1290
1370
  input: WavefrontMeshInput,
@@ -1570,13 +1650,12 @@ export interface GpuRenderer {
1570
1650
  setXrActive(active: boolean): void;
1571
1651
  getSnapshot(): RendererSnapshot;
1572
1652
  bindXrManager(
1573
- xrManager: {
1574
- subscribe: (listener: (state: { activeSession: XRSession | null }) => void) => () => void;
1575
- getState?: () => { activeSession: XRSession | null };
1576
- store?: { getSnapshot: () => { activeSession: XRSession | null } };
1577
- },
1653
+ xrManager: RendererXrManager,
1578
1654
  bindOptions?: {
1579
- onSessionStart?: (session: XRSession, renderer: GpuRenderer) => void;
1655
+ onSessionStart?: (
1656
+ session: NonNullable<XrStoreState["activeSession"]>,
1657
+ renderer: GpuRenderer
1658
+ ) => void;
1580
1659
  onSessionEnd?: (renderer: GpuRenderer) => void;
1581
1660
  }
1582
1661
  ): () => void;
@@ -1612,15 +1691,22 @@ export function createRendererDebugHooks(
1612
1691
  options: RendererDebugHooksOptions
1613
1692
  ): Pick<RendererHooks, "onFrameStart" | "onFrameComplete">;
1614
1693
 
1694
+ export type RendererXrState = Pick<XrStoreState, "activeSession">;
1695
+
1696
+ export interface RendererXrManager {
1697
+ subscribe(listener: (state: RendererXrState) => void): () => void;
1698
+ getState?(): RendererXrState;
1699
+ store?: { getSnapshot(): RendererXrState };
1700
+ }
1701
+
1615
1702
  export function bindRendererToXrManager(
1616
1703
  renderer: Pick<GpuRenderer, "setXrActive">,
1617
- xrManager: {
1618
- subscribe: (listener: (state: { activeSession: XRSession | null }) => void) => () => void;
1619
- getState?: () => { activeSession: XRSession | null };
1620
- store?: { getSnapshot: () => { activeSession: XRSession | null } };
1621
- },
1704
+ xrManager: RendererXrManager,
1622
1705
  options?: {
1623
- onSessionStart?: (session: XRSession, renderer: Pick<GpuRenderer, "setXrActive">) => void;
1706
+ onSessionStart?: (
1707
+ session: NonNullable<XrStoreState["activeSession"]>,
1708
+ renderer: Pick<GpuRenderer, "setXrActive">
1709
+ ) => void;
1624
1710
  onSessionEnd?: (renderer: Pick<GpuRenderer, "setXrActive">) => void;
1625
1711
  }
1626
1712
  ): () => void;
package/src/index.js CHANGED
@@ -4,6 +4,12 @@ export {
4
4
  export {
5
5
  createProfessionalAnimatedSceneRenderer,
6
6
  } from "./professional-animated-scene-renderer.js";
7
+ export {
8
+ bucketFeedbackGameFrameRate,
9
+ bucketFeedbackGameFrameTime,
10
+ bucketFeedbackGameViewport,
11
+ createFeedbackGameDiagnosticSnapshot,
12
+ } from "./feedback-diagnostics.js";
7
13
  export {
8
14
  createDefaultWavefrontSceneObjects,
9
15
  createWavefrontBvhBuildLevels,
@@ -15,12 +15,12 @@ import {
15
15
  createWavefrontMeshAcceleration,
16
16
  } from "./wavefront-scene-data.js";
17
17
  import {
18
+ estimateWavefrontPathTracingMemoryForConfig,
18
19
  createWavefrontPathTracingComputeConfig,
19
20
  packEnvironmentPortals,
20
21
  supportsWavefrontPathTracingCompute,
21
22
  } from "./wavefront-config.js";
22
23
  import {
23
- alignTo,
24
24
  clampTileSizeForDevice,
25
25
  createAtlasTextureResource,
26
26
  createBrdfLutResource,
@@ -76,11 +76,7 @@ import {
76
76
  } from "./wavefront-runtime-support.js";
77
77
 
78
78
  import {
79
- ACCUMULATION_RECORD_BYTES,
80
- BVH_LEAF_REF_RECORD_BYTES,
81
79
  BVH_NODE_RECORD_BYTES,
82
- CONFIG_BUFFER_BYTES,
83
- COUNTER_BUFFER_BYTES,
84
80
  DEFAULT_AMBIENT_COLOR,
85
81
  DEFAULT_BRDF_LUT_SAMPLE_COUNT,
86
82
  DEFAULT_BRDF_LUT_SIZE,
@@ -98,14 +94,11 @@ import {
98
94
  DEFAULT_WIDTH,
99
95
  EMISSIVE_TRIANGLE_INDEX_BYTES,
100
96
  EMPTY_TERMINATION_METRICS,
101
- ENVIRONMENT_PORTAL_RECORD_BYTES,
102
97
  GPU_MATERIAL_RECORD_BYTES,
103
98
  GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
104
99
  GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
105
- HIT_RECORD_BYTES,
106
100
  HIT_TYPE_EMISSIVE,
107
101
  HIT_TYPE_SURFACE,
108
- INDIRECT_DISPATCH_ARGS_BYTES,
109
102
  MATERIAL_DIELECTRIC,
110
103
  MATERIAL_DIFFUSE,
111
104
  MATERIAL_EMISSIVE,
@@ -114,19 +107,13 @@ import {
114
107
  MAX_PATH_TRACING_DEPTH,
115
108
  MAX_SAMPLES_PER_PIXEL,
116
109
  MEDIUM_TABLE_ROWS,
117
- MESH_RANGE_RECORD_BYTES,
118
- MESH_VERTEX_RECORD_BYTES,
119
110
  OBJECT_KIND_BOX,
120
111
  OBJECT_KIND_SPHERE,
121
- PATH_VERTEX_RECORD_BYTES,
122
- RAY_RECORD_BYTES,
123
- SCENE_OBJECT_RECORD_BYTES,
124
112
  TERMINAL_SOURCE_KIND_AMBIENT_MAX_DEPTH,
125
113
  TERMINAL_SOURCE_KIND_AMBIENT_QUEUE_OVERFLOW,
126
114
  TERMINAL_SOURCE_KIND_EMISSIVE,
127
115
  TERMINAL_SOURCE_KIND_ENVIRONMENT,
128
116
  TRACE_STORAGE_BUFFER_BINDINGS,
129
- TRIANGLE_RECORD_BYTES,
130
117
  add,
131
118
  asColor,
132
119
  asUnitVec3,
@@ -201,7 +188,9 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
201
188
  throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
202
189
  }
203
190
 
204
- const device = await adapter.requestDevice(createWavefrontDeviceDescriptor(adapter, options));
191
+ const device = await adapter.requestDevice(
192
+ createWavefrontDeviceDescriptor(adapter, options, initialConfig.memory)
193
+ );
205
194
  const gpuAdapterParallelism = createGpuAdapterParallelismDiagnostics(adapter, device);
206
195
  const context = canvas.getContext("webgpu");
207
196
  if (!context || typeof context.configure !== "function") {
@@ -222,8 +211,16 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
222
211
  width: config.width,
223
212
  height: config.height,
224
213
  tileSize: safeTileSize,
214
+ tilePixelCapacity: Math.min(
215
+ config.tilePixelCapacity,
216
+ safeTileSize * safeTileSize
217
+ ),
225
218
  });
226
219
  }
220
+ config = Object.freeze({
221
+ ...config,
222
+ memory: estimateWavefrontPathTracingMemoryForConfig(config, device.limits),
223
+ });
227
224
  canvas.width = config.width;
228
225
  canvas.height = config.height;
229
226
  context.configure({
@@ -233,82 +230,72 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
233
230
  });
234
231
 
235
232
  const bufferUsage = constants.buffer.STORAGE | constants.buffer.COPY_DST;
236
- const rayQueueBytes = config.tilePixelCapacity * RAY_RECORD_BYTES;
237
- const hitBytes = config.tilePixelCapacity * HIT_RECORD_BYTES;
238
- const accumulationBytes = config.tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
239
- const pathVertexBytes = config.tilePixelCapacity * (config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
240
- const activeQueue = createBuffer(device, bufferUsage, rayQueueBytes, "plasius.wavefront.activeQueue");
241
- const nextQueue = createBuffer(device, bufferUsage, rayQueueBytes, "plasius.wavefront.nextQueue");
242
- const hitBuffer = createBuffer(device, bufferUsage, hitBytes, "plasius.wavefront.hitBuffer");
233
+ const memory = config.memory;
234
+ const activeQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.activeQueue");
235
+ const nextQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.nextQueue");
236
+ const hitBuffer = createBuffer(device, bufferUsage, memory.hitBytes, "plasius.wavefront.hitBuffer");
243
237
  const accumulationBuffer = createBuffer(
244
238
  device,
245
239
  bufferUsage,
246
- accumulationBytes,
240
+ memory.accumulationBytes,
247
241
  "plasius.wavefront.accumulation"
248
242
  );
249
243
  const pathVertexBuffer = createBuffer(
250
244
  device,
251
245
  bufferUsage,
252
- pathVertexBytes,
246
+ memory.pathVertexBytes,
253
247
  "plasius.wavefront.pathVertices"
254
248
  );
255
249
  const sceneObjectBuffer = createBuffer(
256
250
  device,
257
251
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
258
- config.sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES,
252
+ memory.sceneObjectBytes,
259
253
  "plasius.wavefront.sceneObjects"
260
254
  );
261
255
  const triangleBuffer = createBuffer(
262
256
  device,
263
257
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
264
- Math.max(1, config.triangleCapacity) * TRIANGLE_RECORD_BYTES,
258
+ memory.triangleBytes,
265
259
  "plasius.wavefront.triangles"
266
260
  );
267
261
  const bvhNodeBuffer = createBuffer(
268
262
  device,
269
263
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
270
- Math.max(1, config.bvhNodeCapacity + config.emissiveTriangleCapacity) *
271
- BVH_NODE_RECORD_BYTES,
264
+ memory.bvhCombinedBytes,
272
265
  "plasius.wavefront.bvhNodes"
273
266
  );
274
267
  const meshVertexBuffer = createBuffer(
275
268
  device,
276
269
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
277
- Math.max(1, config.gpuMeshSource.vertices.count) * MESH_VERTEX_RECORD_BYTES,
270
+ memory.meshVertexBytes,
278
271
  "plasius.wavefront.meshVertices"
279
272
  );
280
273
  const meshIndexBuffer = createBuffer(
281
274
  device,
282
275
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
283
- Math.max(1, config.gpuMeshSource.indices.count) * 4,
276
+ memory.meshIndexBytes,
284
277
  "plasius.wavefront.meshIndices"
285
278
  );
286
279
  const meshRangeBuffer = createBuffer(
287
280
  device,
288
281
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
289
- Math.max(1, config.gpuMeshSource.meshes.count) * MESH_RANGE_RECORD_BYTES,
282
+ memory.meshRangeBytes,
290
283
  "plasius.wavefront.meshRanges"
291
284
  );
292
285
  const environmentPortalBuffer = createBuffer(
293
286
  device,
294
287
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
295
- Math.max(1, config.environmentPortalCapacity) * ENVIRONMENT_PORTAL_RECORD_BYTES,
288
+ memory.environmentPortalBytes,
296
289
  "plasius.wavefront.environmentPortals"
297
290
  );
298
291
  const bvhLeafRefBuffer = createBuffer(
299
292
  device,
300
293
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
301
- Math.max(1, config.bvhLeafSortCapacity) * BVH_LEAF_REF_RECORD_BYTES,
294
+ memory.bvhLeafReferenceBytes,
302
295
  "plasius.wavefront.bvhLeafRefs"
303
296
  );
304
297
  const tiles = createTiles(config.width, config.height, config.tileSize);
305
- const uniformOffsetAlignment = Number(device?.limits?.minUniformBufferOffsetAlignment);
306
- const configBufferStride = alignTo(
307
- CONFIG_BUFFER_BYTES,
308
- Number.isFinite(uniformOffsetAlignment) && uniformOffsetAlignment > 0
309
- ? uniformOffsetAlignment
310
- : CONFIG_BUFFER_BYTES
311
- );
298
+ const configBufferStride = memory.configBufferStride;
312
299
  const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
313
300
  const frameConfigSlotCount = Math.max(
314
301
  1,
@@ -317,27 +304,25 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
317
304
  const configBuffer = createBuffer(
318
305
  device,
319
306
  constants.buffer.UNIFORM | constants.buffer.COPY_DST,
320
- frameConfigSlotCount * configBufferStride,
307
+ memory.frameConfigBytes,
321
308
  "plasius.wavefront.frameConfig"
322
309
  );
323
- const bvhBuildConfigSlots =
324
- 1 + config.bvhSortStages.length + config.bvhBuildLevels.length;
325
310
  const bvhBuildConfigBuffer = createBuffer(
326
311
  device,
327
312
  constants.buffer.UNIFORM | constants.buffer.COPY_DST,
328
- Math.max(1, bvhBuildConfigSlots) * configBufferStride,
313
+ memory.bvhBuildConfigBytes,
329
314
  "plasius.wavefront.bvhBuildConfig"
330
315
  );
331
316
  const counterBuffer = createBuffer(
332
317
  device,
333
318
  constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
334
- COUNTER_BUFFER_BYTES,
319
+ memory.counterBytes,
335
320
  "plasius.wavefront.counters"
336
321
  );
337
322
  const activeDispatchBuffer = createBuffer(
338
323
  device,
339
324
  constants.buffer.INDIRECT | constants.buffer.COPY_DST,
340
- INDIRECT_DISPATCH_ARGS_BYTES,
325
+ memory.indirectDispatchBytes,
341
326
  "plasius.wavefront.activeDispatchArgs"
342
327
  );
343
328