@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.
@@ -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
  }
@@ -4,6 +4,7 @@ import {
4
4
  DEFAULT_BRDF_LUT_SIZE,
5
5
  HIT_RECORD_BYTES,
6
6
  MEDIUM_TABLE_ROWS,
7
+ PATH_VERTEX_RECORD_BYTES,
7
8
  RAY_RECORD_BYTES,
8
9
  } from "./wavefront-core.js";
9
10
  import {
@@ -28,7 +29,13 @@ export function clampTileSizeForDevice(config, device) {
28
29
 
29
30
  const maxPixelsByRay = Math.floor(limit / RAY_RECORD_BYTES);
30
31
  const maxPixelsByHit = Math.floor(limit / HIT_RECORD_BYTES);
31
- const maxPixels = Math.max(256, Math.min(maxPixelsByRay, maxPixelsByHit));
32
+ const maxPixelsByPathVertex = Math.floor(
33
+ limit / ((config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES)
34
+ );
35
+ const maxPixels = Math.max(
36
+ 256,
37
+ Math.min(maxPixelsByRay, maxPixelsByHit, maxPixelsByPathVertex)
38
+ );
32
39
  if (config.tilePixelCapacity <= maxPixels) {
33
40
  return config.tileSize;
34
41
  }
@@ -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
  });