@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/CHANGELOG.md CHANGED
@@ -23,6 +23,44 @@ All notable changes to this project will be documented in this file.
23
23
  - **Security**
24
24
  - (placeholder)
25
25
 
26
+ ## [0.2.43] - 2026-08-31
27
+
28
+ - **Added**
29
+ - (placeholder)
30
+
31
+ - **Changed**
32
+ - (placeholder)
33
+
34
+ - **Fixed**
35
+ - Negotiated scene-derived `maxStorageBufferBindingSize` and, when required,
36
+ `maxBufferSize` before WebGPU device creation so large mesh/BVH scenes fail
37
+ early on unsupported adapters and render on adapters that expose sufficient
38
+ non-default limits.
39
+ - Corrected wavefront memory telemetry to match every persistent GPU buffer
40
+ descriptor exactly once, including placeholder records, combined BVH and
41
+ emissive metadata, mesh sources, and aligned frame/build config buffers,
42
+ while removing the nonexistent material-table allocation.
43
+
44
+ - **Security**
45
+ - (placeholder)
46
+
47
+ ## [0.2.42] - 2026-08-31
48
+
49
+ - **Added**
50
+ - (placeholder)
51
+
52
+ - **Changed**
53
+ - (placeholder)
54
+
55
+ - **Fixed**
56
+ - Made fixed-SPP ray-count reduction stack-safe for the supported
57
+ 4K/eight-bounce/128-SPP envelope by replacing unbounded argument spreading
58
+ with a constant-stack linear maximum scan.
59
+ - (placeholder)
60
+
61
+ - **Security**
62
+ - (placeholder)
63
+
26
64
  ## [0.2.41] - 2026-08-30
27
65
 
28
66
  - **Added**
@@ -1029,3 +1067,5 @@ All notable changes to this project will be documented in this file.
1029
1067
  [0.2.39]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.39
1030
1068
  [0.2.40]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.40
1031
1069
  [0.2.41]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.41
1070
+ [0.2.42]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.42
1071
+ [0.2.43]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.43
package/README.md CHANGED
@@ -429,6 +429,17 @@ without rebuilding scene buffers. `renderFrame(...)` also accepts an optional
429
429
  guarantees at least the minimum full-screen pass, and frame stats report both
430
430
  configured `samplesPerPixel` and actual `renderedSamplesPerPixel` so realtime
431
431
  callers can budget motion frames without overstating delivered quality.
432
+ Before requesting a WebGPU device, the renderer derives the largest scene
433
+ storage binding from the normalized scene, mesh, and BVH records. It requests
434
+ the smallest sufficient `maxStorageBufferBindingSize` (and `maxBufferSize` only
435
+ when the WebGPU default is also exceeded), preserves stricter caller limits,
436
+ and rejects unsupported adapters before device creation. Default-sized scenes
437
+ do not elevate either size limit. `config.memory` and snapshot memory telemetry
438
+ describe the actual persistent GPU buffer descriptors: placeholder records,
439
+ combined BVH/emissive storage, mesh source buffers, aligned frame/build uniform
440
+ buffers, counters, and dispatch arguments are each counted exactly once;
441
+ `materialTableBytes` is zero because material data is packed into mesh and
442
+ triangle records rather than a standalone GPU buffer.
432
443
  For consumers that want to hand wavefront SPP adaptation to
433
444
  `@plasius/gpu-performance`, `createWavefrontAdaptiveSamplingLevels(...)` exposes
434
445
  a bounded low-to-high ladder of per-frame `samplesPerPixel`,
@@ -478,6 +489,9 @@ lazily allocated telemetry buffer; it does not add diagnostic shader atomics or
478
489
  change WGSL transport. `rayCounts.status` distinguishes available,
479
490
  unavailable, failed, and not-requested evidence. A mismatch between scheduled
480
491
  and observed primary rays fails the `ray-count-telemetry` transport guardrail.
492
+ Ray-count reduction uses constant call-stack space, including the supported
493
+ 4K/eight-bounce/128-SPP stress envelope where hundreds of thousands of
494
+ tile/sample/depth records collapse into a small per-bounce histogram.
481
495
 
482
496
  When the adapter exposes `timestamp-query`, the device requests it
483
497
  opportunistically and two timestamps measure the GPU span from the first
package/dist/index.cjs CHANGED
@@ -4527,6 +4527,13 @@ function dispatchWavefrontGpuAccelerationBuild({
4527
4527
  }
4528
4528
 
4529
4529
  // src/wavefront-config.js
4530
+ var WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 256;
4531
+ function allocatedRecordBytes(count, recordBytes) {
4532
+ return Math.max(1, count) * recordBytes;
4533
+ }
4534
+ function alignByteSize(value, alignment) {
4535
+ return Math.ceil(value / alignment) * alignment;
4536
+ }
4530
4537
  function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
4531
4538
  const source = input ?? {};
4532
4539
  return Object.freeze({
@@ -4703,6 +4710,18 @@ function resolveCamera(input, width, height) {
4703
4710
  });
4704
4711
  }
4705
4712
  function estimateWavefrontPathTracingMemory(options = {}) {
4713
+ const width = readPositiveInteger2("width", options.width, DEFAULT_WIDTH);
4714
+ const height = readPositiveInteger2("height", options.height, DEFAULT_HEIGHT);
4715
+ const tileSize = readPositiveInteger2("tileSize", options.tileSize, DEFAULT_TILE_SIZE);
4716
+ const samplesPerPixel = clamp2(
4717
+ readPositiveInteger2(
4718
+ "samplesPerPixel",
4719
+ options.samplesPerPixel,
4720
+ DEFAULT_SAMPLES_PER_PIXEL
4721
+ ),
4722
+ 1,
4723
+ MAX_SAMPLES_PER_PIXEL
4724
+ );
4706
4725
  const tilePixelCapacity = readPositiveInteger2(
4707
4726
  "tilePixelCapacity",
4708
4727
  options.tilePixelCapacity,
@@ -4735,18 +4754,89 @@ function estimateWavefrontPathTracingMemory(options = {}) {
4735
4754
  options.environmentPortalCapacity,
4736
4755
  0
4737
4756
  );
4738
- const materialCapacity = readNonNegativeInteger2("materialCapacity", options.materialCapacity, 0);
4757
+ readNonNegativeInteger2("materialCapacity", options.materialCapacity, 0);
4758
+ const meshVertexCount = readNonNegativeInteger2(
4759
+ "meshVertexCount",
4760
+ options.meshVertexCount,
4761
+ 0
4762
+ );
4763
+ const meshIndexCount = readNonNegativeInteger2(
4764
+ "meshIndexCount",
4765
+ options.meshIndexCount,
4766
+ 0
4767
+ );
4768
+ const meshRangeCount = readNonNegativeInteger2(
4769
+ "meshRangeCount",
4770
+ options.meshRangeCount,
4771
+ 0
4772
+ );
4773
+ const bvhSortStageCount = readNonNegativeInteger2(
4774
+ "bvhSortStageCount",
4775
+ options.bvhSortStageCount,
4776
+ 0
4777
+ );
4778
+ const bvhBuildLevelCount = readNonNegativeInteger2(
4779
+ "bvhBuildLevelCount",
4780
+ options.bvhBuildLevelCount,
4781
+ 0
4782
+ );
4783
+ const minUniformBufferOffsetAlignment = readPositiveInteger2(
4784
+ "minUniformBufferOffsetAlignment",
4785
+ options.minUniformBufferOffsetAlignment,
4786
+ WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT
4787
+ );
4788
+ const denoise = options.denoise !== false;
4789
+ const deferredPathResolve = options.deferredPathResolve !== false;
4739
4790
  const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
4740
4791
  const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
4741
4792
  const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
4742
4793
  const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
4743
4794
  const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
4744
- const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
4745
- const materialTableBytes = materialCapacity * GPU_MATERIAL_RECORD_BYTES;
4795
+ const triangleBytes = allocatedRecordBytes(triangleCapacity, TRIANGLE_RECORD_BYTES);
4796
+ const materialTableBytes = 0;
4746
4797
  const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
4747
- const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
4798
+ const bvhLeafReferenceBytes = allocatedRecordBytes(
4799
+ bvhLeafSortCapacity,
4800
+ BVH_LEAF_REF_RECORD_BYTES
4801
+ );
4748
4802
  const emissiveTriangleMetadataBytes = emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
4749
- const environmentPortalBytes = environmentPortalCapacity * ENVIRONMENT_PORTAL_RECORD_BYTES;
4803
+ const bvhCombinedBytes = allocatedRecordBytes(
4804
+ bvhNodeCapacity + emissiveTriangleCapacity,
4805
+ BVH_NODE_RECORD_BYTES
4806
+ );
4807
+ const meshVertexBytes = allocatedRecordBytes(meshVertexCount, MESH_VERTEX_RECORD_BYTES);
4808
+ const meshIndexBytes = allocatedRecordBytes(meshIndexCount, 4);
4809
+ const meshRangeBytes = allocatedRecordBytes(meshRangeCount, MESH_RANGE_RECORD_BYTES);
4810
+ const environmentPortalBytes = allocatedRecordBytes(
4811
+ environmentPortalCapacity,
4812
+ ENVIRONMENT_PORTAL_RECORD_BYTES
4813
+ );
4814
+ const tileCount = Math.ceil(width / tileSize) * Math.ceil(height / tileSize);
4815
+ const outputConfigSlotCount = deferredPathResolve ? 0 : tileCount;
4816
+ const frameConfigSlotCount = Math.max(
4817
+ 1,
4818
+ tileCount * samplesPerPixel + outputConfigSlotCount + (denoise ? 1 : 0)
4819
+ );
4820
+ const configBufferStride = alignByteSize(
4821
+ CONFIG_BUFFER_BYTES,
4822
+ minUniformBufferOffsetAlignment
4823
+ );
4824
+ const frameConfigBytes = frameConfigSlotCount * configBufferStride;
4825
+ const bvhBuildConfigBytes = Math.max(
4826
+ 1,
4827
+ 1 + bvhSortStageCount + bvhBuildLevelCount
4828
+ ) * configBufferStride;
4829
+ const sceneStorageBufferBindingBytes = Math.max(
4830
+ sceneObjectBytes,
4831
+ triangleBytes,
4832
+ bvhCombinedBytes,
4833
+ meshVertexBytes,
4834
+ meshIndexBytes,
4835
+ meshRangeBytes,
4836
+ environmentPortalBytes,
4837
+ bvhLeafReferenceBytes
4838
+ );
4839
+ const totalHotBufferBytes = queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + bvhCombinedBytes + meshVertexBytes + meshIndexBytes + meshRangeBytes + bvhLeafReferenceBytes + environmentPortalBytes + frameConfigBytes + bvhBuildConfigBytes + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES;
4750
4840
  return Object.freeze({
4751
4841
  queueBytes,
4752
4842
  queuePairBytes: queueBytes * 2,
@@ -4757,13 +4847,45 @@ function estimateWavefrontPathTracingMemory(options = {}) {
4757
4847
  triangleBytes,
4758
4848
  materialTableBytes,
4759
4849
  bvhNodeBytes,
4850
+ bvhCombinedBytes,
4760
4851
  bvhLeafReferenceBytes,
4761
4852
  emissiveTriangleMetadataBytes,
4853
+ meshVertexBytes,
4854
+ meshIndexBytes,
4855
+ meshRangeBytes,
4762
4856
  environmentPortalBytes,
4763
4857
  configBytes: CONFIG_BUFFER_BYTES,
4858
+ configBufferStride,
4859
+ frameConfigBytes,
4860
+ bvhBuildConfigBytes,
4764
4861
  counterBytes: COUNTER_BUFFER_BYTES,
4765
4862
  indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
4766
- totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + materialTableBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
4863
+ sceneStorageBufferBindingBytes,
4864
+ totalHotBufferBytes
4865
+ });
4866
+ }
4867
+ function estimateWavefrontPathTracingMemoryForConfig(config, limits = {}) {
4868
+ return estimateWavefrontPathTracingMemory({
4869
+ width: config.width,
4870
+ height: config.height,
4871
+ tileSize: config.tileSize,
4872
+ samplesPerPixel: config.samplesPerPixel,
4873
+ tilePixelCapacity: config.tilePixelCapacity,
4874
+ maxDepth: config.maxDepth,
4875
+ sceneObjectCapacity: config.sceneObjectCapacity,
4876
+ triangleCapacity: config.triangleCapacity,
4877
+ bvhNodeCapacity: config.bvhNodeCapacity,
4878
+ bvhLeafSortCapacity: config.bvhLeafSortCapacity,
4879
+ emissiveTriangleCapacity: config.emissiveTriangleCapacity,
4880
+ environmentPortalCapacity: config.environmentPortalCapacity,
4881
+ meshVertexCount: config.gpuMeshSource.vertices.count,
4882
+ meshIndexCount: config.gpuMeshSource.indices.count,
4883
+ meshRangeCount: config.gpuMeshSource.meshes.count,
4884
+ bvhSortStageCount: config.bvhSortStages.length,
4885
+ bvhBuildLevelCount: config.bvhBuildLevels.length,
4886
+ denoise: config.denoise,
4887
+ deferredPathResolve: config.deferredPathResolve,
4888
+ minUniformBufferOffsetAlignment: limits.minUniformBufferOffsetAlignment ?? WEBGPU_DEFAULT_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT
4767
4889
  });
4768
4890
  }
4769
4891
  function createWavefrontPathTracingComputeConfig(options = {}) {
@@ -4908,15 +5030,25 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
4908
5030
  denoise: options.denoise !== false,
4909
5031
  frameIndex: readNonNegativeInteger2("frameIndex", options.frameIndex, 0),
4910
5032
  memory: estimateWavefrontPathTracingMemory({
5033
+ width,
5034
+ height,
5035
+ tileSize,
5036
+ samplesPerPixel,
4911
5037
  tilePixelCapacity,
4912
5038
  maxDepth,
4913
5039
  sceneObjectCapacity,
4914
5040
  triangleCapacity,
4915
- materialCapacity: gpuMaterialSource.count,
4916
5041
  bvhNodeCapacity,
4917
5042
  bvhLeafSortCapacity,
4918
5043
  emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
4919
- environmentPortalCapacity
5044
+ environmentPortalCapacity,
5045
+ meshVertexCount: gpuMeshSource.vertices.count,
5046
+ meshIndexCount: gpuMeshSource.indices.count,
5047
+ meshRangeCount: gpuMeshSource.meshes.count,
5048
+ bvhSortStageCount: bvhSortStages.length,
5049
+ bvhBuildLevelCount: bvhBuildLevels.length,
5050
+ denoise: options.denoise !== false,
5051
+ deferredPathResolve
4920
5052
  })
4921
5053
  });
4922
5054
  }
@@ -5761,6 +5893,32 @@ function createWavefrontPresentBindGroup({
5761
5893
  }
5762
5894
 
5763
5895
  // src/wavefront-runtime-support.js
5896
+ var WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE = 128 * 1024 * 1024;
5897
+ var WEBGPU_DEFAULT_MAX_BUFFER_SIZE = 256 * 1024 * 1024;
5898
+ function readRequestedDeviceLimit(source, name) {
5899
+ const rawValue = source?.[name];
5900
+ if (rawValue === void 0 || rawValue === null) {
5901
+ return 0;
5902
+ }
5903
+ const value = Number(rawValue);
5904
+ if (!Number.isSafeInteger(value) || value < 0) {
5905
+ throw new Error(`${name} must be a non-negative safe integer.`);
5906
+ }
5907
+ return value;
5908
+ }
5909
+ function assertAdapterSupportsRequiredLimit(adapter, name, requiredValue) {
5910
+ const availableValue = Number(adapter?.limits?.[name]);
5911
+ if (!Number.isFinite(availableValue)) {
5912
+ throw new Error(
5913
+ `Wavefront scene buffers require ${name}>=${requiredValue} bytes, but this adapter does not expose that limit.`
5914
+ );
5915
+ }
5916
+ if (availableValue < requiredValue) {
5917
+ throw new Error(
5918
+ `Wavefront scene buffers require ${name}>=${requiredValue} bytes, but this adapter exposes ${availableValue} bytes.`
5919
+ );
5920
+ }
5921
+ }
5764
5922
  function getGpuUsageConstants() {
5765
5923
  if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
5766
5924
  throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
@@ -5858,8 +6016,13 @@ async function createRenderPipeline(device, descriptor) {
5858
6016
  }
5859
6017
  return device.createRenderPipeline(descriptor);
5860
6018
  }
5861
- function createWavefrontDeviceDescriptor(adapter, options = {}) {
5862
- const requiredLimits = { ...options.requiredLimits ?? {} };
6019
+ function createWavefrontDeviceDescriptor(adapter, options = {}, memory = null) {
6020
+ const descriptor = { ...options.deviceDescriptor ?? {} };
6021
+ const descriptorRequiredLimits = { ...descriptor.requiredLimits ?? {} };
6022
+ const requiredLimits = {
6023
+ ...descriptorRequiredLimits,
6024
+ ...options.requiredLimits ?? {}
6025
+ };
5863
6026
  const exposedStorageBufferLimit = Number(adapter?.limits?.maxStorageBuffersPerShaderStage);
5864
6027
  if (Number.isFinite(exposedStorageBufferLimit)) {
5865
6028
  if (exposedStorageBufferLimit < TRACE_STORAGE_BUFFER_BINDINGS) {
@@ -5884,7 +6047,32 @@ function createWavefrontDeviceDescriptor(adapter, options = {}) {
5884
6047
  TRACE_SAMPLED_TEXTURE_BINDINGS
5885
6048
  );
5886
6049
  }
5887
- const descriptor = { ...options.deviceDescriptor ?? {} };
6050
+ const sceneStorageBufferBindingBytes = Number(
6051
+ memory?.sceneStorageBufferBindingBytes ?? 0
6052
+ );
6053
+ const requestedStorageBufferBindingSize = Math.max(
6054
+ readRequestedDeviceLimit(descriptorRequiredLimits, "maxStorageBufferBindingSize"),
6055
+ readRequestedDeviceLimit(options.requiredLimits, "maxStorageBufferBindingSize"),
6056
+ sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE ? sceneStorageBufferBindingBytes : 0
6057
+ );
6058
+ if (requestedStorageBufferBindingSize > 0) {
6059
+ assertAdapterSupportsRequiredLimit(
6060
+ adapter,
6061
+ "maxStorageBufferBindingSize",
6062
+ requestedStorageBufferBindingSize
6063
+ );
6064
+ requiredLimits.maxStorageBufferBindingSize = requestedStorageBufferBindingSize;
6065
+ }
6066
+ const requestedMaxBufferSize = Math.max(
6067
+ readRequestedDeviceLimit(descriptorRequiredLimits, "maxBufferSize"),
6068
+ readRequestedDeviceLimit(options.requiredLimits, "maxBufferSize"),
6069
+ sceneStorageBufferBindingBytes > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? sceneStorageBufferBindingBytes : 0,
6070
+ requestedStorageBufferBindingSize > WEBGPU_DEFAULT_MAX_BUFFER_SIZE ? requestedStorageBufferBindingSize : 0
6071
+ );
6072
+ if (requestedMaxBufferSize > 0) {
6073
+ assertAdapterSupportsRequiredLimit(adapter, "maxBufferSize", requestedMaxBufferSize);
6074
+ requiredLimits.maxBufferSize = requestedMaxBufferSize;
6075
+ }
5888
6076
  const requiredFeatures = Array.from(descriptor.requiredFeatures ?? []);
5889
6077
  const adapterFeatures = adapter?.features;
5890
6078
  const timestampQuerySupported = typeof adapterFeatures?.has === "function" ? adapterFeatures.has("timestamp-query") : adapterFeatures && typeof adapterFeatures[Symbol.iterator] === "function" ? Array.from(adapterFeatures).includes("timestamp-query") : false;
@@ -5935,7 +6123,8 @@ function createGpuAdapterParallelismDiagnostics(adapter, device) {
5935
6123
  maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
5936
6124
  maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
5937
6125
  maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
5938
- maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
6126
+ maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize"),
6127
+ maxBufferSize: readGpuLimit(adapter, device, "maxBufferSize")
5939
6128
  }),
5940
6129
  configuredWorkgroupSize: WORKGROUP_SIZE
5941
6130
  });
@@ -6345,7 +6534,11 @@ function createWavefrontFrameTelemetryResources({
6345
6534
  const rayCounts = new Uint32Array(rayCopy, 0, recordCount);
6346
6535
  rayCountReadbackBuffer.unmap();
6347
6536
  rayReadbackMapped = false;
6348
- const bounceHistogram = Array.from({ length: Math.max(0, ...bounceIndices) + 1 }, () => 0);
6537
+ let maximumBounceIndex = 0;
6538
+ for (const bounceIndex of bounceIndices) {
6539
+ maximumBounceIndex = Math.max(maximumBounceIndex, bounceIndex);
6540
+ }
6541
+ const bounceHistogram = Array.from({ length: maximumBounceIndex + 1 }, () => 0);
6349
6542
  let observedPrimaryRays = 0;
6350
6543
  let secondaryRays = 0;
6351
6544
  let totalPathSegments = 0;
@@ -11237,7 +11430,9 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11237
11430
  if (!adapter) {
11238
11431
  throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
11239
11432
  }
11240
- const device = await adapter.requestDevice(createWavefrontDeviceDescriptor(adapter, options));
11433
+ const device = await adapter.requestDevice(
11434
+ createWavefrontDeviceDescriptor(adapter, options, initialConfig.memory)
11435
+ );
11241
11436
  const gpuAdapterParallelism = createGpuAdapterParallelismDiagnostics(adapter, device);
11242
11437
  const context = canvas.getContext("webgpu");
11243
11438
  if (!context || typeof context.configure !== "function") {
@@ -11255,6 +11450,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11255
11450
  tileSize: safeTileSize
11256
11451
  });
11257
11452
  }
11453
+ config = Object.freeze({
11454
+ ...config,
11455
+ memory: estimateWavefrontPathTracingMemoryForConfig(config, device.limits)
11456
+ });
11258
11457
  canvas.width = config.width;
11259
11458
  canvas.height = config.height;
11260
11459
  context.configure({
@@ -11263,79 +11462,72 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11263
11462
  alphaMode: options.alpha === true ? "premultiplied" : "opaque"
11264
11463
  });
11265
11464
  const bufferUsage = constants.buffer.STORAGE | constants.buffer.COPY_DST;
11266
- const rayQueueBytes = config.tilePixelCapacity * RAY_RECORD_BYTES;
11267
- const hitBytes = config.tilePixelCapacity * HIT_RECORD_BYTES;
11268
- const accumulationBytes = config.tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
11269
- const pathVertexBytes = config.tilePixelCapacity * (config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
11270
- const activeQueue = createBuffer(device, bufferUsage, rayQueueBytes, "plasius.wavefront.activeQueue");
11271
- const nextQueue = createBuffer(device, bufferUsage, rayQueueBytes, "plasius.wavefront.nextQueue");
11272
- const hitBuffer = createBuffer(device, bufferUsage, hitBytes, "plasius.wavefront.hitBuffer");
11465
+ const memory = config.memory;
11466
+ const activeQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.activeQueue");
11467
+ const nextQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.nextQueue");
11468
+ const hitBuffer = createBuffer(device, bufferUsage, memory.hitBytes, "plasius.wavefront.hitBuffer");
11273
11469
  const accumulationBuffer = createBuffer(
11274
11470
  device,
11275
11471
  bufferUsage,
11276
- accumulationBytes,
11472
+ memory.accumulationBytes,
11277
11473
  "plasius.wavefront.accumulation"
11278
11474
  );
11279
11475
  const pathVertexBuffer = createBuffer(
11280
11476
  device,
11281
11477
  bufferUsage,
11282
- pathVertexBytes,
11478
+ memory.pathVertexBytes,
11283
11479
  "plasius.wavefront.pathVertices"
11284
11480
  );
11285
11481
  const sceneObjectBuffer = createBuffer(
11286
11482
  device,
11287
11483
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
11288
- config.sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES,
11484
+ memory.sceneObjectBytes,
11289
11485
  "plasius.wavefront.sceneObjects"
11290
11486
  );
11291
11487
  const triangleBuffer = createBuffer(
11292
11488
  device,
11293
11489
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
11294
- Math.max(1, config.triangleCapacity) * TRIANGLE_RECORD_BYTES,
11490
+ memory.triangleBytes,
11295
11491
  "plasius.wavefront.triangles"
11296
11492
  );
11297
11493
  const bvhNodeBuffer = createBuffer(
11298
11494
  device,
11299
11495
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
11300
- Math.max(1, config.bvhNodeCapacity + config.emissiveTriangleCapacity) * BVH_NODE_RECORD_BYTES,
11496
+ memory.bvhCombinedBytes,
11301
11497
  "plasius.wavefront.bvhNodes"
11302
11498
  );
11303
11499
  const meshVertexBuffer = createBuffer(
11304
11500
  device,
11305
11501
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
11306
- Math.max(1, config.gpuMeshSource.vertices.count) * MESH_VERTEX_RECORD_BYTES,
11502
+ memory.meshVertexBytes,
11307
11503
  "plasius.wavefront.meshVertices"
11308
11504
  );
11309
11505
  const meshIndexBuffer = createBuffer(
11310
11506
  device,
11311
11507
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
11312
- Math.max(1, config.gpuMeshSource.indices.count) * 4,
11508
+ memory.meshIndexBytes,
11313
11509
  "plasius.wavefront.meshIndices"
11314
11510
  );
11315
11511
  const meshRangeBuffer = createBuffer(
11316
11512
  device,
11317
11513
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
11318
- Math.max(1, config.gpuMeshSource.meshes.count) * MESH_RANGE_RECORD_BYTES,
11514
+ memory.meshRangeBytes,
11319
11515
  "plasius.wavefront.meshRanges"
11320
11516
  );
11321
11517
  const environmentPortalBuffer = createBuffer(
11322
11518
  device,
11323
11519
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
11324
- Math.max(1, config.environmentPortalCapacity) * ENVIRONMENT_PORTAL_RECORD_BYTES,
11520
+ memory.environmentPortalBytes,
11325
11521
  "plasius.wavefront.environmentPortals"
11326
11522
  );
11327
11523
  const bvhLeafRefBuffer = createBuffer(
11328
11524
  device,
11329
11525
  constants.buffer.STORAGE | constants.buffer.COPY_DST,
11330
- Math.max(1, config.bvhLeafSortCapacity) * BVH_LEAF_REF_RECORD_BYTES,
11526
+ memory.bvhLeafReferenceBytes,
11331
11527
  "plasius.wavefront.bvhLeafRefs"
11332
11528
  );
11333
11529
  const tiles = createTiles(config.width, config.height, config.tileSize);
11334
- const uniformOffsetAlignment = Number(device?.limits?.minUniformBufferOffsetAlignment);
11335
- const configBufferStride = alignTo(
11336
- CONFIG_BUFFER_BYTES,
11337
- Number.isFinite(uniformOffsetAlignment) && uniformOffsetAlignment > 0 ? uniformOffsetAlignment : CONFIG_BUFFER_BYTES
11338
- );
11530
+ const configBufferStride = memory.configBufferStride;
11339
11531
  const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
11340
11532
  const frameConfigSlotCount = Math.max(
11341
11533
  1,
@@ -11344,26 +11536,25 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11344
11536
  const configBuffer = createBuffer(
11345
11537
  device,
11346
11538
  constants.buffer.UNIFORM | constants.buffer.COPY_DST,
11347
- frameConfigSlotCount * configBufferStride,
11539
+ memory.frameConfigBytes,
11348
11540
  "plasius.wavefront.frameConfig"
11349
11541
  );
11350
- const bvhBuildConfigSlots = 1 + config.bvhSortStages.length + config.bvhBuildLevels.length;
11351
11542
  const bvhBuildConfigBuffer = createBuffer(
11352
11543
  device,
11353
11544
  constants.buffer.UNIFORM | constants.buffer.COPY_DST,
11354
- Math.max(1, bvhBuildConfigSlots) * configBufferStride,
11545
+ memory.bvhBuildConfigBytes,
11355
11546
  "plasius.wavefront.bvhBuildConfig"
11356
11547
  );
11357
11548
  const counterBuffer = createBuffer(
11358
11549
  device,
11359
11550
  constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
11360
- COUNTER_BUFFER_BYTES,
11551
+ memory.counterBytes,
11361
11552
  "plasius.wavefront.counters"
11362
11553
  );
11363
11554
  const activeDispatchBuffer = createBuffer(
11364
11555
  device,
11365
11556
  constants.buffer.INDIRECT | constants.buffer.COPY_DST,
11366
- INDIRECT_DISPATCH_ARGS_BYTES,
11557
+ memory.indirectDispatchBytes,
11367
11558
  "plasius.wavefront.activeDispatchArgs"
11368
11559
  );
11369
11560
  let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);