@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/dist/index.js CHANGED
@@ -7,6 +7,9 @@ var DEFAULT_SAMPLES_PER_PIXEL = 1;
7
7
  var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
8
8
  var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
9
9
  var WORKGROUP_SIZE = 64;
10
+ var rendererWavefrontComputeMode = "webgpu-compute";
11
+ var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
12
+ var rendererWavefrontComputeStatsStride = 8;
10
13
  var RAY_RECORD_BYTES = 80;
11
14
  var HIT_RECORD_BYTES = 208;
12
15
  var SCENE_OBJECT_RECORD_BYTES = 96;
@@ -19,7 +22,9 @@ var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
19
22
  var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
20
23
  var ACCUMULATION_RECORD_BYTES = 16;
21
24
  var CONFIG_BUFFER_BYTES = 272;
22
- var COUNTER_BUFFER_BYTES = 16;
25
+ var COUNTER_DISPATCH_ARGS_OFFSET = 16;
26
+ var INDIRECT_DISPATCH_ARGS_BYTES = 12;
27
+ var COUNTER_BUFFER_BYTES = 32;
23
28
  var TRACE_STORAGE_BUFFER_BINDINGS = 9;
24
29
  var MATERIAL_DIFFUSE = 0;
25
30
  var MATERIAL_METAL = 1;
@@ -59,7 +64,9 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
59
64
  emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
60
65
  emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
61
66
  environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
62
- accumulationRecordBytes: ACCUMULATION_RECORD_BYTES
67
+ accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
68
+ counterRecordBytes: COUNTER_BUFFER_BYTES,
69
+ indirectDispatchRecordBytes: INDIRECT_DISPATCH_ARGS_BYTES
63
70
  });
64
71
  var wavefrontSceneObjectKinds = Object.freeze({
65
72
  sphere: OBJECT_KIND_SPHERE,
@@ -1010,7 +1017,8 @@ function estimateWavefrontPathTracingMemory(options = {}) {
1010
1017
  environmentPortalBytes,
1011
1018
  configBytes: CONFIG_BUFFER_BYTES,
1012
1019
  counterBytes: COUNTER_BUFFER_BYTES,
1013
- totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES
1020
+ indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
1021
+ totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
1014
1022
  });
1015
1023
  }
1016
1024
  function createWavefrontPathTracingComputeConfig(options = {}) {
@@ -1087,6 +1095,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
1087
1095
  environmentPortals.length > 0
1088
1096
  );
1089
1097
  return Object.freeze({
1098
+ mode: rendererWavefrontComputeMode,
1090
1099
  width,
1091
1100
  height,
1092
1101
  maxDepth,
@@ -1141,6 +1150,9 @@ function getGpuUsageConstants() {
1141
1150
  if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
1142
1151
  throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
1143
1152
  }
1153
+ if (typeof GPUBufferUsage.INDIRECT !== "number") {
1154
+ throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
1155
+ }
1144
1156
  return {
1145
1157
  buffer: GPUBufferUsage,
1146
1158
  texture: GPUTextureUsage,
@@ -1777,6 +1789,10 @@ struct Counters {
1777
1789
  nextCount: atomic<u32>,
1778
1790
  terminatedCount: atomic<u32>,
1779
1791
  hitCount: atomic<u32>,
1792
+ dispatchX: u32,
1793
+ dispatchY: u32,
1794
+ dispatchZ: u32,
1795
+ dispatchPad: u32,
1780
1796
  };
1781
1797
 
1782
1798
  struct Candidate {
@@ -1939,6 +1955,18 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
1939
1955
  return environment_radiance(origin, direction);
1940
1956
  }
1941
1957
 
1958
+ fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
1959
+ let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
1960
+ let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
1961
+ let normalEnvironment = gated_environment_radiance(
1962
+ hit.position.xyz + normal * 0.003,
1963
+ normal
1964
+ );
1965
+ let environmentFloor = max(config.ambientColor.xyz, normalEnvironment * 0.12);
1966
+ let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
1967
+ return clamp_sample_radiance(ray.throughput.xyz * surfaceColor * environmentFloor * materialFloor);
1968
+ }
1969
+
1942
1970
  fn default_mesh_range() -> MeshRange {
1943
1971
  return MeshRange(
1944
1972
  0u,
@@ -2510,6 +2538,17 @@ fn tone_map_radiance(value: vec3<f32>) -> vec3<f32> {
2510
2538
  return pow(clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0)), vec3<f32>(1.0 / 2.2));
2511
2539
  }
2512
2540
 
2541
+ fn ray_workgroups_for_count(rayCount: u32) -> u32 {
2542
+ return max(1u, (rayCount + 63u) / 64u);
2543
+ }
2544
+
2545
+ fn write_active_dispatch_args(activeCount: u32) {
2546
+ counters.dispatchX = ray_workgroups_for_count(activeCount);
2547
+ counters.dispatchY = 1u;
2548
+ counters.dispatchZ = 1u;
2549
+ counters.dispatchPad = 0u;
2550
+ }
2551
+
2513
2552
  fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
2514
2553
  return value / (vec3<f32>(1.0) + value);
2515
2554
  }
@@ -2522,6 +2561,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
2522
2561
  atomicStore(&counters.nextCount, 0u);
2523
2562
  atomicStore(&counters.terminatedCount, 0u);
2524
2563
  atomicStore(&counters.hitCount, 0u);
2564
+ write_active_dispatch_args(config.tilePixelCount);
2525
2565
  }
2526
2566
  if (index >= config.tilePixelCount) {
2527
2567
  return;
@@ -2797,9 +2837,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
2797
2837
  }
2798
2838
 
2799
2839
  if (ray.bounce + 1u >= config.maxDepth) {
2840
+ let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
2800
2841
  accumulation[ray.rayId] =
2801
- accumulation[ray.rayId] +
2802
- vec4<f32>(ray.throughput.xyz * config.ambientColor.xyz * sample_weight(), 1.0);
2842
+ accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
2803
2843
  atomicAdd(&counters.terminatedCount, 1u);
2804
2844
  return;
2805
2845
  }
@@ -2808,6 +2848,10 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
2808
2848
  let scatter = scatter_direction(ray, hit, seed);
2809
2849
  let nextIndex = atomicAdd(&counters.nextCount, 1u);
2810
2850
  if (nextIndex >= config.tilePixelCount) {
2851
+ let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
2852
+ accumulation[ray.rayId] =
2853
+ accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
2854
+ atomicAdd(&counters.terminatedCount, 1u);
2811
2855
  return;
2812
2856
  }
2813
2857
  let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
@@ -2836,8 +2880,10 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
2836
2880
  return;
2837
2881
  }
2838
2882
  let nextCount = atomicLoad(&counters.nextCount);
2839
- atomicStore(&counters.activeCount, min(nextCount, config.tilePixelCount));
2883
+ let activeCount = min(nextCount, config.tilePixelCount);
2884
+ atomicStore(&counters.activeCount, activeCount);
2840
2885
  atomicStore(&counters.nextCount, 0u);
2886
+ write_active_dispatch_args(activeCount);
2841
2887
  }
2842
2888
 
2843
2889
  @compute @workgroup_size(64)
@@ -3115,10 +3161,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3115
3161
  );
3116
3162
  const counterBuffer = createBuffer(
3117
3163
  device,
3118
- constants.buffer.STORAGE | constants.buffer.COPY_DST,
3164
+ constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
3119
3165
  COUNTER_BUFFER_BYTES,
3120
3166
  "plasius.wavefront.counters"
3121
3167
  );
3168
+ const activeDispatchBuffer = createBuffer(
3169
+ device,
3170
+ constants.buffer.INDIRECT | constants.buffer.COPY_DST,
3171
+ INDIRECT_DISPATCH_ARGS_BYTES,
3172
+ "plasius.wavefront.activeDispatchArgs"
3173
+ );
3122
3174
  let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
3123
3175
  device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
3124
3176
  const packedTriangles = packWavefrontTriangles(
@@ -3540,24 +3592,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3540
3592
  return true;
3541
3593
  }
3542
3594
  function encodeTileSample(encoder, tile, configOffset) {
3543
- const passEncoder = encoder.beginComputePass({
3544
- label: "plasius.wavefront.computePass"
3595
+ const generatePass = encoder.beginComputePass({
3596
+ label: "plasius.wavefront.generatePrimaryRaysPass"
3545
3597
  });
3546
3598
  const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
3547
- const capacityWorkgroups = Math.ceil(config.tilePixelCapacity / WORKGROUP_SIZE);
3548
- passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
3549
- passEncoder.setPipeline(pipelines.generatePrimaryRays);
3550
- passEncoder.dispatchWorkgroups(tileWorkgroups);
3599
+ generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
3600
+ generatePass.setPipeline(pipelines.generatePrimaryRays);
3601
+ generatePass.dispatchWorkgroups(tileWorkgroups);
3602
+ generatePass.end();
3551
3603
  for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
3604
+ encoder.copyBufferToBuffer(
3605
+ counterBuffer,
3606
+ COUNTER_DISPATCH_ARGS_OFFSET,
3607
+ activeDispatchBuffer,
3608
+ 0,
3609
+ INDIRECT_DISPATCH_ARGS_BYTES
3610
+ );
3611
+ const passEncoder = encoder.beginComputePass({
3612
+ label: `plasius.wavefront.bounce.${bounceIndex}`
3613
+ });
3552
3614
  passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
3553
3615
  passEncoder.setPipeline(pipelines.intersectActiveQueue);
3554
- passEncoder.dispatchWorkgroups(capacityWorkgroups);
3616
+ passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
3555
3617
  passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
3556
- passEncoder.dispatchWorkgroups(capacityWorkgroups);
3618
+ passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
3557
3619
  passEncoder.setPipeline(pipelines.compactAndSwapQueues);
3558
3620
  passEncoder.dispatchWorkgroups(1);
3621
+ passEncoder.end();
3559
3622
  }
3560
- passEncoder.end();
3561
3623
  }
3562
3624
  function encodeTileOutput(encoder, tile, configOffset) {
3563
3625
  const passEncoder = encoder.beginComputePass({
@@ -3700,6 +3762,28 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3700
3762
  luminance: (0.2126 * bytes[0] + 0.7152 * bytes[1] + 0.0722 * bytes[2]) / 255
3701
3763
  });
3702
3764
  }
3765
+ async function renderFrame(renderOptions = {}) {
3766
+ const frameStats = renderOnce();
3767
+ const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
3768
+ const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
3769
+ return Object.freeze({
3770
+ ...frameStats,
3771
+ outputProbe: probe ? Object.freeze({
3772
+ ...probe,
3773
+ sampledPixels: 1,
3774
+ nonZeroSamples: maxChannel > 0 ? 1 : 0,
3775
+ maxChannel
3776
+ }) : null,
3777
+ bounces: [],
3778
+ termination: Object.freeze({
3779
+ emissive: 0,
3780
+ environment: 0,
3781
+ ambientFallback: 0,
3782
+ maxDepth: 0
3783
+ }),
3784
+ queueOverflow: 0
3785
+ });
3786
+ }
3703
3787
  function updateSceneObjects(sceneObjects) {
3704
3788
  const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
3705
3789
  packedScene = nextPackedScene;
@@ -3758,6 +3842,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3758
3842
  configBuffer.destroy?.();
3759
3843
  bvhBuildConfigBuffer.destroy?.();
3760
3844
  counterBuffer.destroy?.();
3845
+ activeDispatchBuffer.destroy?.();
3761
3846
  radianceTexture.destroy?.();
3762
3847
  denoiseScratchTexture.destroy?.();
3763
3848
  outputTexture.destroy?.();
@@ -3770,12 +3855,32 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3770
3855
  format,
3771
3856
  config,
3772
3857
  renderOnce,
3858
+ renderFrame,
3773
3859
  readOutputProbe,
3774
3860
  updateSceneObjects,
3775
3861
  getSnapshot,
3776
3862
  destroy
3777
3863
  });
3778
3864
  }
3865
+ async function renderWavefrontPathTracingComputeFrame(options = {}) {
3866
+ const renderer = await createWavefrontPathTracingComputeRenderer(options);
3867
+ try {
3868
+ return await renderer.renderFrame(options);
3869
+ } finally {
3870
+ renderer.destroy();
3871
+ }
3872
+ }
3873
+ function createWavefrontPathTracingComputeShaderSource(options = {}) {
3874
+ const workgroupSize = readPositiveInteger(
3875
+ "workgroupSize",
3876
+ options.workgroupSize ?? rendererWavefrontComputeWorkgroupSize,
3877
+ rendererWavefrontComputeWorkgroupSize
3878
+ );
3879
+ if (workgroupSize !== rendererWavefrontComputeWorkgroupSize) {
3880
+ throw new Error(`wavefront mesh compute currently requires workgroupSize=${rendererWavefrontComputeWorkgroupSize}.`);
3881
+ }
3882
+ return WAVEFRONT_COMPUTE_WGSL;
3883
+ }
3779
3884
 
3780
3885
  // src/index.js
3781
3886
  var DEFAULT_CLEAR_COLOR = Object.freeze([0.07, 0.11, 0.18, 1]);
@@ -5075,6 +5180,7 @@ export {
5075
5180
  createWavefrontMeshAcceleration,
5076
5181
  createWavefrontPathTracingComputeConfig,
5077
5182
  createWavefrontPathTracingComputeRenderer,
5183
+ createWavefrontPathTracingComputeShaderSource,
5078
5184
  createWavefrontPathTracingPlan,
5079
5185
  createWavefrontReferenceRay,
5080
5186
  defaultRendererClearColor,
@@ -5088,11 +5194,15 @@ export {
5088
5194
  packWavefrontBvhNodes,
5089
5195
  packWavefrontSceneObjects,
5090
5196
  packWavefrontTriangles,
5197
+ renderWavefrontPathTracingComputeFrame,
5091
5198
  rendererAccelerationStructureUpdateClasses,
5092
5199
  rendererDebugOwner,
5093
5200
  rendererRayTracingStageOrder,
5094
5201
  rendererRepresentationBands,
5095
5202
  rendererWavefrontBufferSchemaVersion,
5203
+ rendererWavefrontComputeMode,
5204
+ rendererWavefrontComputeStatsStride,
5205
+ rendererWavefrontComputeWorkgroupSize,
5096
5206
  rendererWavefrontHitTypes,
5097
5207
  rendererWavefrontPassOrder,
5098
5208
  rendererWavefrontQueuePairStrategy,