@plasius/gpu-renderer 0.2.0 → 0.2.1

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,18 +23,40 @@ All notable changes to this project will be documented in this file.
23
23
  - **Security**
24
24
  - (placeholder)
25
25
 
26
- ## [0.2.0] - 2026-06-05
26
+ ## [0.2.1] - 2026-06-06
27
27
 
28
28
  - **Added**
29
29
  - Added deterministic wavefront reference helpers for primary-ray creation,
30
30
  triangle-hit evaluation, nearest-hit selection, and environment-miss
31
31
  fallback alongside regression tests for task-level acceptance coverage.
32
+ - Added ADR coverage for mesh BVH wavefront path tracing as the display-quality
33
+ baseline.
32
34
 
33
35
  - **Changed**
34
- - (placeholder)
36
+ - Promoted the mesh BVH wavefront renderer source into the canonical
37
+ `src/wavefront-compute.js` module so source, typings, tests, and package
38
+ builds no longer drift behind generated artifacts.
39
+ - Changed wavefront mesh tracing to dispatch bounce intersection and
40
+ surface-resolution work from GPU-authored active-ray counts instead of the
41
+ fixed tile capacity after queue compaction.
42
+ - Changed the npm CD workflow to prepare package version and changelog
43
+ metadata locally, then publish the validated tag while persisting release
44
+ metadata through an automation PR branch instead of direct protected-main
45
+ pushes.
35
46
 
36
47
  - **Fixed**
37
- - (placeholder)
48
+ - Fixed wavefront source ownership so display-quality mesh BVH code is not
49
+ hidden behind a legacy module split.
50
+ - Preserved the wavefront mode/workgroup constants, shader-source helper,
51
+ one-shot frame helper, and `renderFrame(...)` compatibility wrapper while
52
+ promoting the mesh BVH renderer source.
53
+ - Fixed a production performance regression where compacted continuation
54
+ queues still scheduled full-capacity per-bounce workgroups.
55
+ - Fixed low-sample wavefront surface traces so terminal non-emissive surface
56
+ collisions use an environment-derived ambient floor instead of a flat or
57
+ missing fallback.
58
+ - Fixed release metadata versioning, demo dependencies, and one-shot
59
+ wavefront cleanup blockers found during PR validation.
38
60
 
39
61
  - **Security**
40
62
  - (placeholder)
@@ -289,4 +311,4 @@ All notable changes to this project will be documented in this file.
289
311
  [0.1.11]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.1.11
290
312
  [0.1.12]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.1.12
291
313
  [0.1.14]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.1.14
292
- [0.2.0]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.0
314
+ [0.2.1]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.1
package/README.md CHANGED
@@ -141,6 +141,10 @@ const renderer = await createWavefrontPathTracingComputeRenderer({
141
141
  renderer.renderOnce();
142
142
  ```
143
143
 
144
+ Existing consumers that still call `renderFrame(...)` or
145
+ `renderWavefrontPathTracingComputeFrame(...)` remain supported as compatibility
146
+ wrappers around the canonical mesh renderer.
147
+
144
148
  Analytic scene objects remain available for debug fixtures:
145
149
 
146
150
  ```js
@@ -203,7 +207,12 @@ sampling, temporal accumulation, and better material PDFs are hardened.
203
207
  For static mesh scenes, the GPU acceleration build is submitted once and then
204
208
  reused by subsequent frames. Per-frame tracing writes one dynamic uniform slot
205
209
  per tile/sample or post-process pass and batches tile tracing, tile output,
206
- optional denoise, and presentation into a single command submission. WebGPU
210
+ optional denoise, and presentation into a single command submission. After each
211
+ primary-ray or compaction pass, the GPU writes the active-ray workgroup count
212
+ into the counter buffer and the encoder copies it into an indirect-dispatch
213
+ argument buffer. Intersection and surface-resolution passes therefore scale
214
+ with active continuation rays instead of the maximum tile capacity, while still
215
+ avoiding CPU readback between bounces. WebGPU
207
216
  still preserves ordering between dependent bounce passes, but the renderer no
208
217
  longer forces one CPU queue submission per tile/sample.
209
218
  Environment-light portals can additionally guide and gate sky/HDRI contribution
@@ -242,6 +251,8 @@ renderer.bindXrManager(xr, {
242
251
  - `createRayTracingRenderPlan(options)`
243
252
  - `createWavefrontPathTracingComputeRenderer(options)`
244
253
  - `createWavefrontPathTracingComputeConfig(options)`
254
+ - `createWavefrontPathTracingComputeShaderSource(options?)`
255
+ - `renderWavefrontPathTracingComputeFrame(options)`
245
256
  - `createWavefrontReferenceRay(config, options?)`
246
257
  - `intersectWavefrontReferenceTriangle(ray, triangle, options?)`
247
258
  - `traceWavefrontReferenceTriangles(config, ray, triangles, options?)`
@@ -254,6 +265,9 @@ renderer.bindXrManager(xr, {
254
265
  - `packWavefrontSceneObjects(sceneObjects, capacity?)`
255
266
  - `packWavefrontTriangles(triangles, capacity?)`
256
267
  - `packWavefrontBvhNodes(nodes, capacity?)`
268
+ - `rendererWavefrontComputeMode`
269
+ - `rendererWavefrontComputeWorkgroupSize`
270
+ - `rendererWavefrontComputeStatsStride`
257
271
  - `bindRendererToXrManager(renderer, xrManager, options)`
258
272
  - `defaultRendererClearColor`
259
273
  - `rendererDebugOwner`
@@ -298,6 +312,8 @@ npm run pack:check
298
312
  ## Files
299
313
 
300
314
  - `src/index.js`: WebGPU renderer runtime and XR binding helper.
315
+ - `src/wavefront-compute.js`: Canonical WebGPU mesh BVH wavefront renderer,
316
+ debug scene-object fixtures, and deterministic reference helpers.
301
317
  - `src/index.d.ts`: public API typings.
302
318
  - `tests/package.test.js`: unit tests for renderer lifecycle behavior.
303
319
  - `docs/design/worker-manifest-integration.md`: renderer frame-stage DAG model.
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  createWavefrontMeshAcceleration: () => createWavefrontMeshAcceleration,
32
32
  createWavefrontPathTracingComputeConfig: () => createWavefrontPathTracingComputeConfig,
33
33
  createWavefrontPathTracingComputeRenderer: () => createWavefrontPathTracingComputeRenderer,
34
+ createWavefrontPathTracingComputeShaderSource: () => createWavefrontPathTracingComputeShaderSource,
34
35
  createWavefrontPathTracingPlan: () => createWavefrontPathTracingPlan,
35
36
  createWavefrontReferenceRay: () => createWavefrontReferenceRay,
36
37
  defaultRendererClearColor: () => defaultRendererClearColor,
@@ -44,11 +45,15 @@ __export(index_exports, {
44
45
  packWavefrontBvhNodes: () => packWavefrontBvhNodes,
45
46
  packWavefrontSceneObjects: () => packWavefrontSceneObjects,
46
47
  packWavefrontTriangles: () => packWavefrontTriangles,
48
+ renderWavefrontPathTracingComputeFrame: () => renderWavefrontPathTracingComputeFrame,
47
49
  rendererAccelerationStructureUpdateClasses: () => rendererAccelerationStructureUpdateClasses,
48
50
  rendererDebugOwner: () => rendererDebugOwner,
49
51
  rendererRayTracingStageOrder: () => rendererRayTracingStageOrder,
50
52
  rendererRepresentationBands: () => rendererRepresentationBands,
51
53
  rendererWavefrontBufferSchemaVersion: () => rendererWavefrontBufferSchemaVersion,
54
+ rendererWavefrontComputeMode: () => rendererWavefrontComputeMode,
55
+ rendererWavefrontComputeStatsStride: () => rendererWavefrontComputeStatsStride,
56
+ rendererWavefrontComputeWorkgroupSize: () => rendererWavefrontComputeWorkgroupSize,
52
57
  rendererWavefrontHitTypes: () => rendererWavefrontHitTypes,
53
58
  rendererWavefrontPassOrder: () => rendererWavefrontPassOrder,
54
59
  rendererWavefrontQueuePairStrategy: () => rendererWavefrontQueuePairStrategy,
@@ -74,6 +79,9 @@ var DEFAULT_SAMPLES_PER_PIXEL = 1;
74
79
  var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
75
80
  var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
76
81
  var WORKGROUP_SIZE = 64;
82
+ var rendererWavefrontComputeMode = "webgpu-compute";
83
+ var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
84
+ var rendererWavefrontComputeStatsStride = 8;
77
85
  var RAY_RECORD_BYTES = 80;
78
86
  var HIT_RECORD_BYTES = 208;
79
87
  var SCENE_OBJECT_RECORD_BYTES = 96;
@@ -86,7 +94,9 @@ var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
86
94
  var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
87
95
  var ACCUMULATION_RECORD_BYTES = 16;
88
96
  var CONFIG_BUFFER_BYTES = 272;
89
- var COUNTER_BUFFER_BYTES = 16;
97
+ var COUNTER_DISPATCH_ARGS_OFFSET = 16;
98
+ var INDIRECT_DISPATCH_ARGS_BYTES = 12;
99
+ var COUNTER_BUFFER_BYTES = 32;
90
100
  var TRACE_STORAGE_BUFFER_BINDINGS = 9;
91
101
  var MATERIAL_DIFFUSE = 0;
92
102
  var MATERIAL_METAL = 1;
@@ -126,7 +136,9 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
126
136
  emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
127
137
  emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
128
138
  environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
129
- accumulationRecordBytes: ACCUMULATION_RECORD_BYTES
139
+ accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
140
+ counterRecordBytes: COUNTER_BUFFER_BYTES,
141
+ indirectDispatchRecordBytes: INDIRECT_DISPATCH_ARGS_BYTES
130
142
  });
131
143
  var wavefrontSceneObjectKinds = Object.freeze({
132
144
  sphere: OBJECT_KIND_SPHERE,
@@ -1077,7 +1089,8 @@ function estimateWavefrontPathTracingMemory(options = {}) {
1077
1089
  environmentPortalBytes,
1078
1090
  configBytes: CONFIG_BUFFER_BYTES,
1079
1091
  counterBytes: COUNTER_BUFFER_BYTES,
1080
- totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES
1092
+ indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
1093
+ totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
1081
1094
  });
1082
1095
  }
1083
1096
  function createWavefrontPathTracingComputeConfig(options = {}) {
@@ -1154,6 +1167,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
1154
1167
  environmentPortals.length > 0
1155
1168
  );
1156
1169
  return Object.freeze({
1170
+ mode: rendererWavefrontComputeMode,
1157
1171
  width,
1158
1172
  height,
1159
1173
  maxDepth,
@@ -1208,6 +1222,9 @@ function getGpuUsageConstants() {
1208
1222
  if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
1209
1223
  throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
1210
1224
  }
1225
+ if (typeof GPUBufferUsage.INDIRECT !== "number") {
1226
+ throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
1227
+ }
1211
1228
  return {
1212
1229
  buffer: GPUBufferUsage,
1213
1230
  texture: GPUTextureUsage,
@@ -1844,6 +1861,10 @@ struct Counters {
1844
1861
  nextCount: atomic<u32>,
1845
1862
  terminatedCount: atomic<u32>,
1846
1863
  hitCount: atomic<u32>,
1864
+ dispatchX: u32,
1865
+ dispatchY: u32,
1866
+ dispatchZ: u32,
1867
+ dispatchPad: u32,
1847
1868
  };
1848
1869
 
1849
1870
  struct Candidate {
@@ -2006,6 +2027,18 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
2006
2027
  return environment_radiance(origin, direction);
2007
2028
  }
2008
2029
 
2030
+ fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
2031
+ let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
2032
+ let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
2033
+ let normalEnvironment = gated_environment_radiance(
2034
+ hit.position.xyz + normal * 0.003,
2035
+ normal
2036
+ );
2037
+ let environmentFloor = max(config.ambientColor.xyz, normalEnvironment * 0.12);
2038
+ let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
2039
+ return clamp_sample_radiance(ray.throughput.xyz * surfaceColor * environmentFloor * materialFloor);
2040
+ }
2041
+
2009
2042
  fn default_mesh_range() -> MeshRange {
2010
2043
  return MeshRange(
2011
2044
  0u,
@@ -2577,6 +2610,17 @@ fn tone_map_radiance(value: vec3<f32>) -> vec3<f32> {
2577
2610
  return pow(clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0)), vec3<f32>(1.0 / 2.2));
2578
2611
  }
2579
2612
 
2613
+ fn ray_workgroups_for_count(rayCount: u32) -> u32 {
2614
+ return max(1u, (rayCount + 63u) / 64u);
2615
+ }
2616
+
2617
+ fn write_active_dispatch_args(activeCount: u32) {
2618
+ counters.dispatchX = ray_workgroups_for_count(activeCount);
2619
+ counters.dispatchY = 1u;
2620
+ counters.dispatchZ = 1u;
2621
+ counters.dispatchPad = 0u;
2622
+ }
2623
+
2580
2624
  fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
2581
2625
  return value / (vec3<f32>(1.0) + value);
2582
2626
  }
@@ -2589,6 +2633,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
2589
2633
  atomicStore(&counters.nextCount, 0u);
2590
2634
  atomicStore(&counters.terminatedCount, 0u);
2591
2635
  atomicStore(&counters.hitCount, 0u);
2636
+ write_active_dispatch_args(config.tilePixelCount);
2592
2637
  }
2593
2638
  if (index >= config.tilePixelCount) {
2594
2639
  return;
@@ -2864,9 +2909,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
2864
2909
  }
2865
2910
 
2866
2911
  if (ray.bounce + 1u >= config.maxDepth) {
2912
+ let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
2867
2913
  accumulation[ray.rayId] =
2868
- accumulation[ray.rayId] +
2869
- vec4<f32>(ray.throughput.xyz * config.ambientColor.xyz * sample_weight(), 1.0);
2914
+ accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
2870
2915
  atomicAdd(&counters.terminatedCount, 1u);
2871
2916
  return;
2872
2917
  }
@@ -2875,6 +2920,10 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
2875
2920
  let scatter = scatter_direction(ray, hit, seed);
2876
2921
  let nextIndex = atomicAdd(&counters.nextCount, 1u);
2877
2922
  if (nextIndex >= config.tilePixelCount) {
2923
+ let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
2924
+ accumulation[ray.rayId] =
2925
+ accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
2926
+ atomicAdd(&counters.terminatedCount, 1u);
2878
2927
  return;
2879
2928
  }
2880
2929
  let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
@@ -2903,8 +2952,10 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
2903
2952
  return;
2904
2953
  }
2905
2954
  let nextCount = atomicLoad(&counters.nextCount);
2906
- atomicStore(&counters.activeCount, min(nextCount, config.tilePixelCount));
2955
+ let activeCount = min(nextCount, config.tilePixelCount);
2956
+ atomicStore(&counters.activeCount, activeCount);
2907
2957
  atomicStore(&counters.nextCount, 0u);
2958
+ write_active_dispatch_args(activeCount);
2908
2959
  }
2909
2960
 
2910
2961
  @compute @workgroup_size(64)
@@ -3182,10 +3233,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3182
3233
  );
3183
3234
  const counterBuffer = createBuffer(
3184
3235
  device,
3185
- constants.buffer.STORAGE | constants.buffer.COPY_DST,
3236
+ constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
3186
3237
  COUNTER_BUFFER_BYTES,
3187
3238
  "plasius.wavefront.counters"
3188
3239
  );
3240
+ const activeDispatchBuffer = createBuffer(
3241
+ device,
3242
+ constants.buffer.INDIRECT | constants.buffer.COPY_DST,
3243
+ INDIRECT_DISPATCH_ARGS_BYTES,
3244
+ "plasius.wavefront.activeDispatchArgs"
3245
+ );
3189
3246
  let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
3190
3247
  device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
3191
3248
  const packedTriangles = packWavefrontTriangles(
@@ -3607,24 +3664,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3607
3664
  return true;
3608
3665
  }
3609
3666
  function encodeTileSample(encoder, tile, configOffset) {
3610
- const passEncoder = encoder.beginComputePass({
3611
- label: "plasius.wavefront.computePass"
3667
+ const generatePass = encoder.beginComputePass({
3668
+ label: "plasius.wavefront.generatePrimaryRaysPass"
3612
3669
  });
3613
3670
  const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
3614
- const capacityWorkgroups = Math.ceil(config.tilePixelCapacity / WORKGROUP_SIZE);
3615
- passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
3616
- passEncoder.setPipeline(pipelines.generatePrimaryRays);
3617
- passEncoder.dispatchWorkgroups(tileWorkgroups);
3671
+ generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
3672
+ generatePass.setPipeline(pipelines.generatePrimaryRays);
3673
+ generatePass.dispatchWorkgroups(tileWorkgroups);
3674
+ generatePass.end();
3618
3675
  for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
3676
+ encoder.copyBufferToBuffer(
3677
+ counterBuffer,
3678
+ COUNTER_DISPATCH_ARGS_OFFSET,
3679
+ activeDispatchBuffer,
3680
+ 0,
3681
+ INDIRECT_DISPATCH_ARGS_BYTES
3682
+ );
3683
+ const passEncoder = encoder.beginComputePass({
3684
+ label: `plasius.wavefront.bounce.${bounceIndex}`
3685
+ });
3619
3686
  passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
3620
3687
  passEncoder.setPipeline(pipelines.intersectActiveQueue);
3621
- passEncoder.dispatchWorkgroups(capacityWorkgroups);
3688
+ passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
3622
3689
  passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
3623
- passEncoder.dispatchWorkgroups(capacityWorkgroups);
3690
+ passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
3624
3691
  passEncoder.setPipeline(pipelines.compactAndSwapQueues);
3625
3692
  passEncoder.dispatchWorkgroups(1);
3693
+ passEncoder.end();
3626
3694
  }
3627
- passEncoder.end();
3628
3695
  }
3629
3696
  function encodeTileOutput(encoder, tile, configOffset) {
3630
3697
  const passEncoder = encoder.beginComputePass({
@@ -3767,6 +3834,28 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3767
3834
  luminance: (0.2126 * bytes[0] + 0.7152 * bytes[1] + 0.0722 * bytes[2]) / 255
3768
3835
  });
3769
3836
  }
3837
+ async function renderFrame(renderOptions = {}) {
3838
+ const frameStats = renderOnce();
3839
+ const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
3840
+ const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
3841
+ return Object.freeze({
3842
+ ...frameStats,
3843
+ outputProbe: probe ? Object.freeze({
3844
+ ...probe,
3845
+ sampledPixels: 1,
3846
+ nonZeroSamples: maxChannel > 0 ? 1 : 0,
3847
+ maxChannel
3848
+ }) : null,
3849
+ bounces: [],
3850
+ termination: Object.freeze({
3851
+ emissive: 0,
3852
+ environment: 0,
3853
+ ambientFallback: 0,
3854
+ maxDepth: 0
3855
+ }),
3856
+ queueOverflow: 0
3857
+ });
3858
+ }
3770
3859
  function updateSceneObjects(sceneObjects) {
3771
3860
  const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
3772
3861
  packedScene = nextPackedScene;
@@ -3825,6 +3914,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3825
3914
  configBuffer.destroy?.();
3826
3915
  bvhBuildConfigBuffer.destroy?.();
3827
3916
  counterBuffer.destroy?.();
3917
+ activeDispatchBuffer.destroy?.();
3828
3918
  radianceTexture.destroy?.();
3829
3919
  denoiseScratchTexture.destroy?.();
3830
3920
  outputTexture.destroy?.();
@@ -3837,12 +3927,32 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3837
3927
  format,
3838
3928
  config,
3839
3929
  renderOnce,
3930
+ renderFrame,
3840
3931
  readOutputProbe,
3841
3932
  updateSceneObjects,
3842
3933
  getSnapshot,
3843
3934
  destroy
3844
3935
  });
3845
3936
  }
3937
+ async function renderWavefrontPathTracingComputeFrame(options = {}) {
3938
+ const renderer = await createWavefrontPathTracingComputeRenderer(options);
3939
+ try {
3940
+ return await renderer.renderFrame(options);
3941
+ } finally {
3942
+ renderer.destroy();
3943
+ }
3944
+ }
3945
+ function createWavefrontPathTracingComputeShaderSource(options = {}) {
3946
+ const workgroupSize = readPositiveInteger(
3947
+ "workgroupSize",
3948
+ options.workgroupSize ?? rendererWavefrontComputeWorkgroupSize,
3949
+ rendererWavefrontComputeWorkgroupSize
3950
+ );
3951
+ if (workgroupSize !== rendererWavefrontComputeWorkgroupSize) {
3952
+ throw new Error(`wavefront mesh compute currently requires workgroupSize=${rendererWavefrontComputeWorkgroupSize}.`);
3953
+ }
3954
+ return WAVEFRONT_COMPUTE_WGSL;
3955
+ }
3846
3956
 
3847
3957
  // src/index.js
3848
3958
  var DEFAULT_CLEAR_COLOR = Object.freeze([0.07, 0.11, 0.18, 1]);
@@ -5143,6 +5253,7 @@ var defaultRendererClearColor = DEFAULT_CLEAR_COLOR;
5143
5253
  createWavefrontMeshAcceleration,
5144
5254
  createWavefrontPathTracingComputeConfig,
5145
5255
  createWavefrontPathTracingComputeRenderer,
5256
+ createWavefrontPathTracingComputeShaderSource,
5146
5257
  createWavefrontPathTracingPlan,
5147
5258
  createWavefrontReferenceRay,
5148
5259
  defaultRendererClearColor,
@@ -5156,11 +5267,15 @@ var defaultRendererClearColor = DEFAULT_CLEAR_COLOR;
5156
5267
  packWavefrontBvhNodes,
5157
5268
  packWavefrontSceneObjects,
5158
5269
  packWavefrontTriangles,
5270
+ renderWavefrontPathTracingComputeFrame,
5159
5271
  rendererAccelerationStructureUpdateClasses,
5160
5272
  rendererDebugOwner,
5161
5273
  rendererRayTracingStageOrder,
5162
5274
  rendererRepresentationBands,
5163
5275
  rendererWavefrontBufferSchemaVersion,
5276
+ rendererWavefrontComputeMode,
5277
+ rendererWavefrontComputeStatsStride,
5278
+ rendererWavefrontComputeWorkgroupSize,
5164
5279
  rendererWavefrontHitTypes,
5165
5280
  rendererWavefrontPassOrder,
5166
5281
  rendererWavefrontQueuePairStrategy,