@plasius/gpu-renderer 0.2.41 → 0.2.43

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.41",
3
+ "version": "0.2.43",
4
4
  "description": "Framework-agnostic WebGPU renderer runtime for Plasius projects.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
package/src/index.d.ts CHANGED
@@ -960,12 +960,20 @@ export interface WavefrontPathTracingMemoryEstimate {
960
960
  readonly triangleBytes: number;
961
961
  readonly materialTableBytes: number;
962
962
  readonly bvhNodeBytes: number;
963
+ readonly bvhCombinedBytes: number;
963
964
  readonly bvhLeafReferenceBytes: number;
964
965
  readonly emissiveTriangleMetadataBytes: number;
966
+ readonly meshVertexBytes: number;
967
+ readonly meshIndexBytes: number;
968
+ readonly meshRangeBytes: number;
965
969
  readonly environmentPortalBytes: number;
966
970
  readonly configBytes: number;
971
+ readonly configBufferStride: number;
972
+ readonly frameConfigBytes: number;
973
+ readonly bvhBuildConfigBytes: number;
967
974
  readonly counterBytes: number;
968
975
  readonly indirectDispatchBytes: number;
976
+ readonly sceneStorageBufferBindingBytes: number;
969
977
  readonly totalHotBufferBytes: number;
970
978
  }
971
979
 
@@ -987,6 +995,7 @@ export interface WavefrontGpuParallelismDiagnostics {
987
995
  readonly maxComputeWorkgroupsPerDimension: number | null;
988
996
  readonly maxStorageBuffersPerShaderStage: number | null;
989
997
  readonly maxStorageBufferBindingSize: number | null;
998
+ readonly maxBufferSize: number | null;
990
999
  }>;
991
1000
  readonly configuredWorkgroupSize: number;
992
1001
  readonly directDispatches: number;
@@ -1279,12 +1288,27 @@ export function createWavefrontGpuMaterialSource(
1279
1288
  ): WavefrontGpuMaterialSource;
1280
1289
  export function createDefaultWavefrontSceneObjects(): readonly WavefrontSceneObject[];
1281
1290
  export function estimateWavefrontPathTracingMemory(options?: {
1291
+ width?: number;
1292
+ height?: number;
1293
+ tileSize?: number;
1294
+ samplesPerPixel?: number;
1282
1295
  tilePixelCapacity?: number;
1296
+ maxDepth?: number;
1283
1297
  sceneObjectCapacity?: number;
1284
1298
  triangleCapacity?: number;
1299
+ materialCapacity?: number;
1285
1300
  bvhNodeCapacity?: number;
1286
1301
  bvhLeafSortCapacity?: number;
1287
1302
  emissiveTriangleCapacity?: number;
1303
+ environmentPortalCapacity?: number;
1304
+ meshVertexCount?: number;
1305
+ meshIndexCount?: number;
1306
+ meshRangeCount?: number;
1307
+ bvhSortStageCount?: number;
1308
+ bvhBuildLevelCount?: number;
1309
+ denoise?: boolean;
1310
+ deferredPathResolve?: boolean;
1311
+ minUniformBufferOffsetAlignment?: number;
1288
1312
  }): WavefrontPathTracingMemoryEstimate;
1289
1313
  export function normalizeWavefrontMesh(
1290
1314
  input: WavefrontMeshInput,
@@ -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") {
@@ -224,6 +213,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
224
213
  tileSize: safeTileSize,
225
214
  });
226
215
  }
216
+ config = Object.freeze({
217
+ ...config,
218
+ memory: estimateWavefrontPathTracingMemoryForConfig(config, device.limits),
219
+ });
227
220
  canvas.width = config.width;
228
221
  canvas.height = config.height;
229
222
  context.configure({
@@ -233,82 +226,72 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
233
226
  });
234
227
 
235
228
  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");
229
+ const memory = config.memory;
230
+ const activeQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.activeQueue");
231
+ const nextQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.nextQueue");
232
+ const hitBuffer = createBuffer(device, bufferUsage, memory.hitBytes, "plasius.wavefront.hitBuffer");
243
233
  const accumulationBuffer = createBuffer(
244
234
  device,
245
235
  bufferUsage,
246
- accumulationBytes,
236
+ memory.accumulationBytes,
247
237
  "plasius.wavefront.accumulation"
248
238
  );
249
239
  const pathVertexBuffer = createBuffer(
250
240
  device,
251
241
  bufferUsage,
252
- pathVertexBytes,
242
+ memory.pathVertexBytes,
253
243
  "plasius.wavefront.pathVertices"
254
244
  );
255
245
  const sceneObjectBuffer = createBuffer(
256
246
  device,
257
247
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
258
- config.sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES,
248
+ memory.sceneObjectBytes,
259
249
  "plasius.wavefront.sceneObjects"
260
250
  );
261
251
  const triangleBuffer = createBuffer(
262
252
  device,
263
253
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
264
- Math.max(1, config.triangleCapacity) * TRIANGLE_RECORD_BYTES,
254
+ memory.triangleBytes,
265
255
  "plasius.wavefront.triangles"
266
256
  );
267
257
  const bvhNodeBuffer = createBuffer(
268
258
  device,
269
259
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
270
- Math.max(1, config.bvhNodeCapacity + config.emissiveTriangleCapacity) *
271
- BVH_NODE_RECORD_BYTES,
260
+ memory.bvhCombinedBytes,
272
261
  "plasius.wavefront.bvhNodes"
273
262
  );
274
263
  const meshVertexBuffer = createBuffer(
275
264
  device,
276
265
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
277
- Math.max(1, config.gpuMeshSource.vertices.count) * MESH_VERTEX_RECORD_BYTES,
266
+ memory.meshVertexBytes,
278
267
  "plasius.wavefront.meshVertices"
279
268
  );
280
269
  const meshIndexBuffer = createBuffer(
281
270
  device,
282
271
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
283
- Math.max(1, config.gpuMeshSource.indices.count) * 4,
272
+ memory.meshIndexBytes,
284
273
  "plasius.wavefront.meshIndices"
285
274
  );
286
275
  const meshRangeBuffer = createBuffer(
287
276
  device,
288
277
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
289
- Math.max(1, config.gpuMeshSource.meshes.count) * MESH_RANGE_RECORD_BYTES,
278
+ memory.meshRangeBytes,
290
279
  "plasius.wavefront.meshRanges"
291
280
  );
292
281
  const environmentPortalBuffer = createBuffer(
293
282
  device,
294
283
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
295
- Math.max(1, config.environmentPortalCapacity) * ENVIRONMENT_PORTAL_RECORD_BYTES,
284
+ memory.environmentPortalBytes,
296
285
  "plasius.wavefront.environmentPortals"
297
286
  );
298
287
  const bvhLeafRefBuffer = createBuffer(
299
288
  device,
300
289
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
301
- Math.max(1, config.bvhLeafSortCapacity) * BVH_LEAF_REF_RECORD_BYTES,
290
+ memory.bvhLeafReferenceBytes,
302
291
  "plasius.wavefront.bvhLeafRefs"
303
292
  );
304
293
  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
- );
294
+ const configBufferStride = memory.configBufferStride;
312
295
  const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
313
296
  const frameConfigSlotCount = Math.max(
314
297
  1,
@@ -317,27 +300,25 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
317
300
  const configBuffer = createBuffer(
318
301
  device,
319
302
  constants.buffer.UNIFORM | constants.buffer.COPY_DST,
320
- frameConfigSlotCount * configBufferStride,
303
+ memory.frameConfigBytes,
321
304
  "plasius.wavefront.frameConfig"
322
305
  );
323
- const bvhBuildConfigSlots =
324
- 1 + config.bvhSortStages.length + config.bvhBuildLevels.length;
325
306
  const bvhBuildConfigBuffer = createBuffer(
326
307
  device,
327
308
  constants.buffer.UNIFORM | constants.buffer.COPY_DST,
328
- Math.max(1, bvhBuildConfigSlots) * configBufferStride,
309
+ memory.bvhBuildConfigBytes,
329
310
  "plasius.wavefront.bvhBuildConfig"
330
311
  );
331
312
  const counterBuffer = createBuffer(
332
313
  device,
333
314
  constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
334
- COUNTER_BUFFER_BYTES,
315
+ memory.counterBytes,
335
316
  "plasius.wavefront.counters"
336
317
  );
337
318
  const activeDispatchBuffer = createBuffer(
338
319
  device,
339
320
  constants.buffer.INDIRECT | constants.buffer.COPY_DST,
340
- INDIRECT_DISPATCH_ARGS_BYTES,
321
+ memory.indirectDispatchBytes,
341
322
  "plasius.wavefront.activeDispatchArgs"
342
323
  );
343
324
 
@@ -19,7 +19,6 @@ import {
19
19
  EMISSIVE_TRIANGLE_INDEX_BYTES,
20
20
  EMPTY_TERMINATION_METRICS,
21
21
  ENVIRONMENT_PORTAL_RECORD_BYTES,
22
- GPU_MATERIAL_RECORD_BYTES,
23
22
  HIT_RECORD_BYTES,
24
23
  INDIRECT_DISPATCH_ARGS_BYTES,
25
24
  MAX_PATH_TRACING_DEPTH,
@@ -69,6 +68,16 @@ import {
69
68
  resolveAccelerationBuildMode,
70
69
  } from "./wavefront-scene-data.js";
71
70
 
71
+ const WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 256;
72
+
73
+ function allocatedRecordBytes(count, recordBytes) {
74
+ return Math.max(1, count) * recordBytes;
75
+ }
76
+
77
+ function alignByteSize(value, alignment) {
78
+ return Math.ceil(value / alignment) * alignment;
79
+ }
80
+
72
81
  function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
73
82
  const source = input ?? {};
74
83
  return Object.freeze({
@@ -257,6 +266,18 @@ function resolveCamera(input, width, height) {
257
266
  }
258
267
 
259
268
  export function estimateWavefrontPathTracingMemory(options = {}) {
269
+ const width = readPositiveInteger("width", options.width, DEFAULT_WIDTH);
270
+ const height = readPositiveInteger("height", options.height, DEFAULT_HEIGHT);
271
+ const tileSize = readPositiveInteger("tileSize", options.tileSize, DEFAULT_TILE_SIZE);
272
+ const samplesPerPixel = clamp(
273
+ readPositiveInteger(
274
+ "samplesPerPixel",
275
+ options.samplesPerPixel,
276
+ DEFAULT_SAMPLES_PER_PIXEL
277
+ ),
278
+ 1,
279
+ MAX_SAMPLES_PER_PIXEL
280
+ );
260
281
  const tilePixelCapacity = readPositiveInteger(
261
282
  "tilePixelCapacity",
262
283
  options.tilePixelCapacity,
@@ -289,20 +310,106 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
289
310
  options.environmentPortalCapacity,
290
311
  0
291
312
  );
292
- const materialCapacity = readNonNegativeInteger("materialCapacity", options.materialCapacity, 0);
313
+ readNonNegativeInteger("materialCapacity", options.materialCapacity, 0);
314
+ const meshVertexCount = readNonNegativeInteger(
315
+ "meshVertexCount",
316
+ options.meshVertexCount,
317
+ 0
318
+ );
319
+ const meshIndexCount = readNonNegativeInteger(
320
+ "meshIndexCount",
321
+ options.meshIndexCount,
322
+ 0
323
+ );
324
+ const meshRangeCount = readNonNegativeInteger(
325
+ "meshRangeCount",
326
+ options.meshRangeCount,
327
+ 0
328
+ );
329
+ const bvhSortStageCount = readNonNegativeInteger(
330
+ "bvhSortStageCount",
331
+ options.bvhSortStageCount,
332
+ 0
333
+ );
334
+ const bvhBuildLevelCount = readNonNegativeInteger(
335
+ "bvhBuildLevelCount",
336
+ options.bvhBuildLevelCount,
337
+ 0
338
+ );
339
+ const minUniformBufferOffsetAlignment = readPositiveInteger(
340
+ "minUniformBufferOffsetAlignment",
341
+ options.minUniformBufferOffsetAlignment,
342
+ WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT
343
+ );
344
+ const denoise = options.denoise !== false;
345
+ const deferredPathResolve = options.deferredPathResolve !== false;
293
346
  const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
294
347
  const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
295
348
  const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
296
349
  const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
297
350
  const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
298
- const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
299
- const materialTableBytes = materialCapacity * GPU_MATERIAL_RECORD_BYTES;
351
+ const triangleBytes = allocatedRecordBytes(triangleCapacity, TRIANGLE_RECORD_BYTES);
352
+ const materialTableBytes = 0;
300
353
  const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
301
- const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
354
+ const bvhLeafReferenceBytes = allocatedRecordBytes(
355
+ bvhLeafSortCapacity,
356
+ BVH_LEAF_REF_RECORD_BYTES
357
+ );
302
358
  const emissiveTriangleMetadataBytes =
303
359
  emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
304
- const environmentPortalBytes =
305
- environmentPortalCapacity * ENVIRONMENT_PORTAL_RECORD_BYTES;
360
+ const bvhCombinedBytes = allocatedRecordBytes(
361
+ bvhNodeCapacity + emissiveTriangleCapacity,
362
+ BVH_NODE_RECORD_BYTES
363
+ );
364
+ const meshVertexBytes = allocatedRecordBytes(meshVertexCount, MESH_VERTEX_RECORD_BYTES);
365
+ const meshIndexBytes = allocatedRecordBytes(meshIndexCount, 4);
366
+ const meshRangeBytes = allocatedRecordBytes(meshRangeCount, MESH_RANGE_RECORD_BYTES);
367
+ const environmentPortalBytes = allocatedRecordBytes(
368
+ environmentPortalCapacity,
369
+ ENVIRONMENT_PORTAL_RECORD_BYTES
370
+ );
371
+ const tileCount = Math.ceil(width / tileSize) * Math.ceil(height / tileSize);
372
+ const outputConfigSlotCount = deferredPathResolve ? 0 : tileCount;
373
+ const frameConfigSlotCount = Math.max(
374
+ 1,
375
+ tileCount * samplesPerPixel + outputConfigSlotCount + (denoise ? 1 : 0)
376
+ );
377
+ const configBufferStride = alignByteSize(
378
+ CONFIG_BUFFER_BYTES,
379
+ minUniformBufferOffsetAlignment
380
+ );
381
+ const frameConfigBytes = frameConfigSlotCount * configBufferStride;
382
+ const bvhBuildConfigBytes = Math.max(
383
+ 1,
384
+ 1 + bvhSortStageCount + bvhBuildLevelCount
385
+ ) * configBufferStride;
386
+ const sceneStorageBufferBindingBytes = Math.max(
387
+ sceneObjectBytes,
388
+ triangleBytes,
389
+ bvhCombinedBytes,
390
+ meshVertexBytes,
391
+ meshIndexBytes,
392
+ meshRangeBytes,
393
+ environmentPortalBytes,
394
+ bvhLeafReferenceBytes
395
+ );
396
+ const totalHotBufferBytes =
397
+ queueBytes * 2 +
398
+ hitBytes +
399
+ accumulationBytes +
400
+ pathVertexBytes +
401
+ sceneObjectBytes +
402
+ triangleBytes +
403
+ bvhCombinedBytes +
404
+ meshVertexBytes +
405
+ meshIndexBytes +
406
+ meshRangeBytes +
407
+ bvhLeafReferenceBytes +
408
+ environmentPortalBytes +
409
+ frameConfigBytes +
410
+ bvhBuildConfigBytes +
411
+ COUNTER_BUFFER_BYTES +
412
+ INDIRECT_DISPATCH_ARGS_BYTES;
306
413
 
307
414
  return Object.freeze({
308
415
  queueBytes,
@@ -314,27 +421,48 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
314
421
  triangleBytes,
315
422
  materialTableBytes,
316
423
  bvhNodeBytes,
424
+ bvhCombinedBytes,
317
425
  bvhLeafReferenceBytes,
318
426
  emissiveTriangleMetadataBytes,
427
+ meshVertexBytes,
428
+ meshIndexBytes,
429
+ meshRangeBytes,
319
430
  environmentPortalBytes,
320
431
  configBytes: CONFIG_BUFFER_BYTES,
432
+ configBufferStride,
433
+ frameConfigBytes,
434
+ bvhBuildConfigBytes,
321
435
  counterBytes: COUNTER_BUFFER_BYTES,
322
436
  indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
323
- totalHotBufferBytes:
324
- queueBytes * 2 +
325
- hitBytes +
326
- accumulationBytes +
327
- pathVertexBytes +
328
- sceneObjectBytes +
329
- triangleBytes +
330
- materialTableBytes +
331
- bvhNodeBytes +
332
- bvhLeafReferenceBytes +
333
- emissiveTriangleMetadataBytes +
334
- environmentPortalBytes +
335
- CONFIG_BUFFER_BYTES +
336
- COUNTER_BUFFER_BYTES +
337
- INDIRECT_DISPATCH_ARGS_BYTES,
437
+ sceneStorageBufferBindingBytes,
438
+ totalHotBufferBytes,
439
+ });
440
+ }
441
+
442
+ export function estimateWavefrontPathTracingMemoryForConfig(config, limits = {}) {
443
+ return estimateWavefrontPathTracingMemory({
444
+ width: config.width,
445
+ height: config.height,
446
+ tileSize: config.tileSize,
447
+ samplesPerPixel: config.samplesPerPixel,
448
+ tilePixelCapacity: config.tilePixelCapacity,
449
+ maxDepth: config.maxDepth,
450
+ sceneObjectCapacity: config.sceneObjectCapacity,
451
+ triangleCapacity: config.triangleCapacity,
452
+ bvhNodeCapacity: config.bvhNodeCapacity,
453
+ bvhLeafSortCapacity: config.bvhLeafSortCapacity,
454
+ emissiveTriangleCapacity: config.emissiveTriangleCapacity,
455
+ environmentPortalCapacity: config.environmentPortalCapacity,
456
+ meshVertexCount: config.gpuMeshSource.vertices.count,
457
+ meshIndexCount: config.gpuMeshSource.indices.count,
458
+ meshRangeCount: config.gpuMeshSource.meshes.count,
459
+ bvhSortStageCount: config.bvhSortStages.length,
460
+ bvhBuildLevelCount: config.bvhBuildLevels.length,
461
+ denoise: config.denoise,
462
+ deferredPathResolve: config.deferredPathResolve,
463
+ minUniformBufferOffsetAlignment:
464
+ limits.minUniformBufferOffsetAlignment ??
465
+ WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT,
338
466
  });
339
467
  }
340
468
 
@@ -509,15 +637,25 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
509
637
  denoise: options.denoise !== false,
510
638
  frameIndex: readNonNegativeInteger("frameIndex", options.frameIndex, 0),
511
639
  memory: estimateWavefrontPathTracingMemory({
640
+ width,
641
+ height,
642
+ tileSize,
643
+ samplesPerPixel,
512
644
  tilePixelCapacity,
513
645
  maxDepth,
514
646
  sceneObjectCapacity,
515
647
  triangleCapacity,
516
- materialCapacity: gpuMaterialSource.count,
517
648
  bvhNodeCapacity,
518
649
  bvhLeafSortCapacity,
519
650
  emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
520
651
  environmentPortalCapacity,
652
+ meshVertexCount: gpuMeshSource.vertices.count,
653
+ meshIndexCount: gpuMeshSource.indices.count,
654
+ meshRangeCount: gpuMeshSource.meshes.count,
655
+ bvhSortStageCount: bvhSortStages.length,
656
+ bvhBuildLevelCount: bvhBuildLevels.length,
657
+ denoise: options.denoise !== false,
658
+ deferredPathResolve,
521
659
  }),
522
660
  });
523
661
  }
@@ -297,7 +297,11 @@ export function createWavefrontFrameTelemetryResources({
297
297
  rayCountReadbackBuffer.unmap();
298
298
  rayReadbackMapped = false;
299
299
 
300
- const bounceHistogram = Array.from({ length: Math.max(0, ...bounceIndices) + 1 }, () => 0);
300
+ let maximumBounceIndex = 0;
301
+ for (const bounceIndex of bounceIndices) {
302
+ maximumBounceIndex = Math.max(maximumBounceIndex, bounceIndex);
303
+ }
304
+ const bounceHistogram = Array.from({ length: maximumBounceIndex + 1 }, () => 0);
301
305
  let observedPrimaryRays = 0;
302
306
  let secondaryRays = 0;
303
307
  let totalPathSegments = 0;
@@ -10,6 +10,37 @@ import {
10
10
  readPositiveInteger,
11
11
  } from "./wavefront-core.js";
12
12
 
13
+ const WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE = 128 * 1024 * 1024;
14
+ const WEBGPU_DEFAULT_MAX_BUFFER_SIZE = 256 * 1024 * 1024;
15
+
16
+ function readRequestedDeviceLimit(source, name) {
17
+ const rawValue = source?.[name];
18
+ if (rawValue === undefined || rawValue === null) {
19
+ return 0;
20
+ }
21
+ const value = Number(rawValue);
22
+ if (!Number.isSafeInteger(value) || value < 0) {
23
+ throw new Error(`${name} must be a non-negative safe integer.`);
24
+ }
25
+ return value;
26
+ }
27
+
28
+ function assertAdapterSupportsRequiredLimit(adapter, name, requiredValue) {
29
+ const availableValue = Number(adapter?.limits?.[name]);
30
+ if (!Number.isFinite(availableValue)) {
31
+ throw new Error(
32
+ `Wavefront scene buffers require ${name}>=${requiredValue} bytes, ` +
33
+ "but this adapter does not expose that limit."
34
+ );
35
+ }
36
+ if (availableValue < requiredValue) {
37
+ throw new Error(
38
+ `Wavefront scene buffers require ${name}>=${requiredValue} bytes, ` +
39
+ `but this adapter exposes ${availableValue} bytes.`
40
+ );
41
+ }
42
+ }
43
+
13
44
  export function getGpuUsageConstants() {
14
45
  if (
15
46
  typeof GPUBufferUsage === "undefined" ||
@@ -123,8 +154,13 @@ export async function createRenderPipeline(device, descriptor) {
123
154
  return device.createRenderPipeline(descriptor);
124
155
  }
125
156
 
126
- export function createWavefrontDeviceDescriptor(adapter, options = {}) {
127
- const requiredLimits = { ...(options.requiredLimits ?? {}) };
157
+ export function createWavefrontDeviceDescriptor(adapter, options = {}, memory = null) {
158
+ const descriptor = { ...(options.deviceDescriptor ?? {}) };
159
+ const descriptorRequiredLimits = { ...(descriptor.requiredLimits ?? {}) };
160
+ const requiredLimits = {
161
+ ...descriptorRequiredLimits,
162
+ ...(options.requiredLimits ?? {}),
163
+ };
128
164
  const exposedStorageBufferLimit = Number(adapter?.limits?.maxStorageBuffersPerShaderStage);
129
165
  if (Number.isFinite(exposedStorageBufferLimit)) {
130
166
  if (exposedStorageBufferLimit < TRACE_STORAGE_BUFFER_BINDINGS) {
@@ -153,7 +189,40 @@ export function createWavefrontDeviceDescriptor(adapter, options = {}) {
153
189
  );
154
190
  }
155
191
 
156
- const descriptor = { ...(options.deviceDescriptor ?? {}) };
192
+ const sceneStorageBufferBindingBytes = Number(
193
+ memory?.sceneStorageBufferBindingBytes ?? 0
194
+ );
195
+ const requestedStorageBufferBindingSize = Math.max(
196
+ readRequestedDeviceLimit(descriptorRequiredLimits, "maxStorageBufferBindingSize"),
197
+ readRequestedDeviceLimit(options.requiredLimits, "maxStorageBufferBindingSize"),
198
+ sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE
199
+ ? sceneStorageBufferBindingBytes
200
+ : 0
201
+ );
202
+ if (requestedStorageBufferBindingSize > 0) {
203
+ assertAdapterSupportsRequiredLimit(
204
+ adapter,
205
+ "maxStorageBufferBindingSize",
206
+ requestedStorageBufferBindingSize
207
+ );
208
+ requiredLimits.maxStorageBufferBindingSize = requestedStorageBufferBindingSize;
209
+ }
210
+
211
+ const requestedMaxBufferSize = Math.max(
212
+ readRequestedDeviceLimit(descriptorRequiredLimits, "maxBufferSize"),
213
+ readRequestedDeviceLimit(options.requiredLimits, "maxBufferSize"),
214
+ sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_BUFFER_SIZE
215
+ ? sceneStorageBufferBindingBytes
216
+ : 0,
217
+ requestedStorageBufferBindingSize > WEBGPU_DEFAULT_MAX_BUFFER_SIZE
218
+ ? requestedStorageBufferBindingSize
219
+ : 0
220
+ );
221
+ if (requestedMaxBufferSize > 0) {
222
+ assertAdapterSupportsRequiredLimit(adapter, "maxBufferSize", requestedMaxBufferSize);
223
+ requiredLimits.maxBufferSize = requestedMaxBufferSize;
224
+ }
225
+
157
226
  const requiredFeatures = Array.from(descriptor.requiredFeatures ?? []);
158
227
  const adapterFeatures = adapter?.features;
159
228
  const timestampQuerySupported =
@@ -217,6 +286,7 @@ export function createGpuAdapterParallelismDiagnostics(adapter, device) {
217
286
  maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
218
287
  maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
219
288
  maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize"),
289
+ maxBufferSize: readGpuLimit(adapter, device, "maxBufferSize"),
220
290
  }),
221
291
  configuredWorkgroupSize: WORKGROUP_SIZE,
222
292
  });