@plasius/gpu-renderer 0.1.15 → 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,
@@ -167,6 +174,25 @@ function normalize(value, fallback = [0, 0, 1]) {
167
174
  }
168
175
  return [value[0] / length, value[1] / length, value[2] / length];
169
176
  }
177
+ function hashUint32(value) {
178
+ let x = value >>> 0;
179
+ x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
180
+ x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
181
+ return (x >>> 16 ^ x) >>> 0;
182
+ }
183
+ function mixSeed(pixelId, sampleId, bounce, frameIndex, dimension) {
184
+ let x = (pixelId >>> 0) * 747796405 ^ (sampleId >>> 0) * 2891336453 ^ (bounce >>> 0) * 277803737 ^ (frameIndex >>> 0) * 1442695041 ^ (dimension >>> 0) * 1597334677;
185
+ x >>>= 0;
186
+ x ^= x >>> 16;
187
+ x = x * 2146121005 >>> 0;
188
+ x ^= x >>> 15;
189
+ x = x * 2221713035 >>> 0;
190
+ x ^= x >>> 16;
191
+ return x >>> 0;
192
+ }
193
+ function random01FromSeed(seed) {
194
+ return (hashUint32(seed) & 16777215) / 16777215;
195
+ }
170
196
  function getArrayLikeLength(value) {
171
197
  return Array.isArray(value) || ArrayBuffer.isView(value) ? value.length : 0;
172
198
  }
@@ -765,6 +791,29 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
765
791
  exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure))
766
792
  });
767
793
  }
794
+ function evaluateReferenceEnvironmentRadiance(config, origin, direction) {
795
+ void origin;
796
+ const rayDirection = normalize(direction, [0, 1, 0]);
797
+ const upFactor = clamp(rayDirection[1] * 0.5 + 0.5, 0, 1);
798
+ const sunDirection = normalize(
799
+ config.environmentLighting?.sunDirection ?? DEFAULT_ENVIRONMENT_LIGHTING.sunDirection,
800
+ DEFAULT_ENVIRONMENT_LIGHTING.sunDirection
801
+ );
802
+ const sunGlow = Math.pow(clamp(dot(rayDirection, sunDirection), 0, 1), 192);
803
+ const horizonColor = config.environmentLighting?.horizonColor ?? DEFAULT_ENVIRONMENT_LIGHTING.horizonColor;
804
+ const zenithColor = config.environmentLighting?.zenithColor ?? DEFAULT_ENVIRONMENT_LIGHTING.zenithColor;
805
+ const sunColor = config.environmentLighting?.sunColor ?? DEFAULT_ENVIRONMENT_LIGHTING.sunColor;
806
+ const intensity = Math.max(
807
+ 1e-4,
808
+ Number(config.environmentLighting?.intensity ?? DEFAULT_ENVIRONMENT_LIGHTING.intensity)
809
+ );
810
+ return Object.freeze([
811
+ (horizonColor[0] * (1 - upFactor) + zenithColor[0] * upFactor + sunColor[0] * sunGlow) * intensity,
812
+ (horizonColor[1] * (1 - upFactor) + zenithColor[1] * upFactor + sunColor[1] * sunGlow) * intensity,
813
+ (horizonColor[2] * (1 - upFactor) + zenithColor[2] * upFactor + sunColor[2] * sunGlow) * intensity,
814
+ 1
815
+ ]);
816
+ }
768
817
  function resolveEnvironmentPortalMode(value, hasPortals) {
769
818
  if (value === void 0 || value === null) {
770
819
  return hasPortals ? 2 : 0;
@@ -968,7 +1017,8 @@ function estimateWavefrontPathTracingMemory(options = {}) {
968
1017
  environmentPortalBytes,
969
1018
  configBytes: CONFIG_BUFFER_BYTES,
970
1019
  counterBytes: COUNTER_BUFFER_BYTES,
971
- 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
972
1022
  });
973
1023
  }
974
1024
  function createWavefrontPathTracingComputeConfig(options = {}) {
@@ -1045,6 +1095,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
1045
1095
  environmentPortals.length > 0
1046
1096
  );
1047
1097
  return Object.freeze({
1098
+ mode: rendererWavefrontComputeMode,
1048
1099
  width,
1049
1100
  height,
1050
1101
  maxDepth,
@@ -1099,6 +1150,9 @@ function getGpuUsageConstants() {
1099
1150
  if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
1100
1151
  throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
1101
1152
  }
1153
+ if (typeof GPUBufferUsage.INDIRECT !== "number") {
1154
+ throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
1155
+ }
1102
1156
  return {
1103
1157
  buffer: GPUBufferUsage,
1104
1158
  texture: GPUTextureUsage,
@@ -1293,6 +1347,209 @@ function createTiles(width, height, tileSize) {
1293
1347
  }
1294
1348
  return Object.freeze(tiles);
1295
1349
  }
1350
+ function normalizeReferenceTile(config, tileInput = {}) {
1351
+ const tileX = clamp(
1352
+ readNonNegativeInteger("tile.x", tileInput.x, 0),
1353
+ 0,
1354
+ Math.max(0, config.width - 1)
1355
+ );
1356
+ const tileY = clamp(
1357
+ readNonNegativeInteger("tile.y", tileInput.y, 0),
1358
+ 0,
1359
+ Math.max(0, config.height - 1)
1360
+ );
1361
+ const tileWidth = clamp(
1362
+ readPositiveInteger("tile.width", tileInput.width, config.width - tileX),
1363
+ 1,
1364
+ config.width - tileX
1365
+ );
1366
+ const tileHeight = clamp(
1367
+ readPositiveInteger("tile.height", tileInput.height, config.height - tileY),
1368
+ 1,
1369
+ config.height - tileY
1370
+ );
1371
+ return Object.freeze({
1372
+ x: tileX,
1373
+ y: tileY,
1374
+ width: tileWidth,
1375
+ height: tileHeight
1376
+ });
1377
+ }
1378
+ function repairReferenceShadingNormal(geometricNormal, shadingNormal) {
1379
+ const normal = normalize(shadingNormal, geometricNormal);
1380
+ return dot(normal, geometricNormal) < 0 ? scale(normal, -1) : normal;
1381
+ }
1382
+ function readOptionalMaxDistance(value) {
1383
+ if (value === void 0 || value === null) {
1384
+ return Number.POSITIVE_INFINITY;
1385
+ }
1386
+ const numeric = Number(value);
1387
+ if (!Number.isFinite(numeric) || numeric <= 0) {
1388
+ throw new Error("maxDistance must be a positive finite number when provided.");
1389
+ }
1390
+ return numeric;
1391
+ }
1392
+ function createWavefrontReferenceRay(config, options = {}) {
1393
+ if (!config || typeof config !== "object") {
1394
+ throw new Error("config must be a wavefront path tracing config.");
1395
+ }
1396
+ const tile = normalizeReferenceTile(config, options.tile);
1397
+ const tilePixelCount = tile.width * tile.height;
1398
+ const pixelIndex = readNonNegativeInteger("pixelIndex", options.pixelIndex, 0);
1399
+ if (pixelIndex >= tilePixelCount) {
1400
+ throw new Error(`pixelIndex ${pixelIndex} exceeds tile capacity ${tilePixelCount}.`);
1401
+ }
1402
+ const sampleIndex = readNonNegativeInteger("sampleIndex", options.sampleIndex, 0);
1403
+ const frameIndex = readNonNegativeInteger("frameIndex", options.frameIndex, config.frameIndex ?? 0);
1404
+ const jitterScale = clamp(readFiniteNumber("jitterScale", options.jitterScale, 0.35), 0, 1);
1405
+ const localX = pixelIndex % tile.width;
1406
+ const localY = Math.floor(pixelIndex / tile.width);
1407
+ const pixelX = tile.x + localX;
1408
+ const pixelY = tile.y + localY;
1409
+ const sourcePixelId = pixelY * config.width + pixelX;
1410
+ const jitterX = random01FromSeed(mixSeed(sourcePixelId, sampleIndex, 0, frameIndex, 1)) - 0.5;
1411
+ const jitterY = random01FromSeed(mixSeed(sourcePixelId, sampleIndex, 0, frameIndex, 2)) - 0.5;
1412
+ const ndcX = (pixelX + 0.5 + jitterX * jitterScale) / config.width * 2 - 1;
1413
+ const ndcY = 1 - (pixelY + 0.5 + jitterY * jitterScale) / config.height * 2;
1414
+ const viewX = ndcX * config.camera.tanHalfFovY * config.camera.aspect;
1415
+ const viewY = ndcY * config.camera.tanHalfFovY;
1416
+ const direction = normalize(
1417
+ add(
1418
+ add(config.camera.forward, scale(config.camera.right, viewX)),
1419
+ scale(config.camera.up, viewY)
1420
+ ),
1421
+ config.camera.forward
1422
+ );
1423
+ return Object.freeze({
1424
+ rayId: pixelIndex,
1425
+ parentRayId: 4294967295,
1426
+ sourcePixelId,
1427
+ sampleId: sampleIndex,
1428
+ bounce: 0,
1429
+ mediumRefId: 0,
1430
+ flags: 0,
1431
+ origin: Object.freeze([...config.camera.position]),
1432
+ direction: Object.freeze(direction),
1433
+ throughput: Object.freeze([1, 1, 1, 1]),
1434
+ pixelX,
1435
+ pixelY
1436
+ });
1437
+ }
1438
+ function intersectWavefrontReferenceTriangle(ray, triangle, options = {}) {
1439
+ if (!ray || typeof ray !== "object") {
1440
+ throw new Error("ray must be a wavefront reference ray.");
1441
+ }
1442
+ if (!triangle || typeof triangle !== "object") {
1443
+ throw new Error("triangle must be a wavefront triangle record.");
1444
+ }
1445
+ const maxDistance = readOptionalMaxDistance(options.maxDistance);
1446
+ const triangleIndex = readNonNegativeInteger("triangleIndex", options.triangleIndex, 0);
1447
+ const edge1 = subtract(triangle.v1, triangle.v0);
1448
+ const edge2 = subtract(triangle.v2, triangle.v0);
1449
+ const pvec = cross(ray.direction, edge2);
1450
+ const determinant = dot(edge1, pvec);
1451
+ if (Math.abs(determinant) < 1e-7) {
1452
+ return null;
1453
+ }
1454
+ const invDet = 1 / determinant;
1455
+ const tvec = subtract(ray.origin, triangle.v0);
1456
+ const u = dot(tvec, pvec) * invDet;
1457
+ if (u < 0 || u > 1) {
1458
+ return null;
1459
+ }
1460
+ const qvec = cross(tvec, edge1);
1461
+ const v = dot(ray.direction, qvec) * invDet;
1462
+ if (v < 0 || u + v > 1) {
1463
+ return null;
1464
+ }
1465
+ const distance = dot(edge2, qvec) * invDet;
1466
+ if (distance <= 1e-3 || distance > maxDistance) {
1467
+ return null;
1468
+ }
1469
+ const geometric = normalize(cross(edge1, edge2), [0, 1, 0]);
1470
+ const frontFace = dot(ray.direction, geometric) < 0;
1471
+ const orientedGeometric = frontFace ? geometric : scale(geometric, -1);
1472
+ const w = 1 - u - v;
1473
+ const interpolated = [
1474
+ triangle.n0[0] * w + triangle.n1[0] * u + triangle.n2[0] * v,
1475
+ triangle.n0[1] * w + triangle.n1[1] * u + triangle.n2[1] * v,
1476
+ triangle.n0[2] * w + triangle.n1[2] * u + triangle.n2[2] * v
1477
+ ];
1478
+ const shadingNormal = repairReferenceShadingNormal(orientedGeometric, interpolated);
1479
+ const uv = [
1480
+ triangle.uv0[0] * w + triangle.uv1[0] * u + triangle.uv2[0] * v,
1481
+ triangle.uv0[1] * w + triangle.uv1[1] * u + triangle.uv2[1] * v
1482
+ ];
1483
+ const position = add(ray.origin, scale(ray.direction, distance));
1484
+ return Object.freeze({
1485
+ hitType: "surface",
1486
+ rayId: ray.rayId,
1487
+ sourcePixelId: ray.sourcePixelId,
1488
+ distance,
1489
+ entityId: triangle.meshId,
1490
+ instanceId: 0,
1491
+ primitiveId: triangle.triangleId,
1492
+ materialId: triangle.materialKind,
1493
+ materialRefId: triangle.materialRefId,
1494
+ mediumRefId: triangle.mediumRefId,
1495
+ barycentrics: Object.freeze([w, u, v]),
1496
+ uv: Object.freeze(uv),
1497
+ geometricNormal: Object.freeze(orientedGeometric),
1498
+ shadingNormal: Object.freeze(shadingNormal),
1499
+ frontFace,
1500
+ triangleIndex,
1501
+ triangleId: triangle.triangleId,
1502
+ position: Object.freeze(position),
1503
+ color: triangle.color,
1504
+ emission: triangle.emission,
1505
+ material: triangle.material
1506
+ });
1507
+ }
1508
+ function createWavefrontReferenceEnvironmentHit(config, ray) {
1509
+ const radiance = evaluateReferenceEnvironmentRadiance(config, ray.origin, ray.direction);
1510
+ return Object.freeze({
1511
+ hitType: "environment",
1512
+ rayId: ray.rayId,
1513
+ sourcePixelId: ray.sourcePixelId,
1514
+ distance: -1,
1515
+ entityId: 0,
1516
+ instanceId: 0,
1517
+ primitiveId: 0,
1518
+ materialId: 0,
1519
+ materialRefId: 0,
1520
+ mediumRefId: 0,
1521
+ barycentrics: Object.freeze([0, 0, 0]),
1522
+ uv: Object.freeze([0, 0]),
1523
+ geometricNormal: Object.freeze(scale(ray.direction, -1)),
1524
+ shadingNormal: Object.freeze(scale(ray.direction, -1)),
1525
+ frontFace: true,
1526
+ triangleIndex: -1,
1527
+ triangleId: -1,
1528
+ position: Object.freeze(add(ray.origin, scale(ray.direction, 1e3))),
1529
+ color: Object.freeze([0, 0, 0, 0]),
1530
+ emission: radiance,
1531
+ material: Object.freeze([1, 0, 1, 1])
1532
+ });
1533
+ }
1534
+ function traceWavefrontReferenceTriangles(config, ray, triangles, options = {}) {
1535
+ if (!config || typeof config !== "object") {
1536
+ throw new Error("config must be a wavefront path tracing config.");
1537
+ }
1538
+ const source = Array.isArray(triangles) ? triangles : [];
1539
+ let nearestHit = null;
1540
+ let nearestDistance = readOptionalMaxDistance(options.maxDistance);
1541
+ source.forEach((triangle, index) => {
1542
+ const hit = intersectWavefrontReferenceTriangle(ray, triangle, {
1543
+ maxDistance: Number.isFinite(nearestDistance) ? nearestDistance : void 0,
1544
+ triangleIndex: index
1545
+ });
1546
+ if (hit && hit.distance < nearestDistance) {
1547
+ nearestDistance = hit.distance;
1548
+ nearestHit = hit;
1549
+ }
1550
+ });
1551
+ return nearestHit ?? createWavefrontReferenceEnvironmentHit(config, ray);
1552
+ }
1296
1553
  function clampTileSizeForDevice(config, device) {
1297
1554
  const limit = Number(device?.limits?.maxStorageBufferBindingSize);
1298
1555
  if (!Number.isFinite(limit) || limit <= 0) {
@@ -1532,6 +1789,10 @@ struct Counters {
1532
1789
  nextCount: atomic<u32>,
1533
1790
  terminatedCount: atomic<u32>,
1534
1791
  hitCount: atomic<u32>,
1792
+ dispatchX: u32,
1793
+ dispatchY: u32,
1794
+ dispatchZ: u32,
1795
+ dispatchPad: u32,
1535
1796
  };
1536
1797
 
1537
1798
  struct Candidate {
@@ -1694,6 +1955,18 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
1694
1955
  return environment_radiance(origin, direction);
1695
1956
  }
1696
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
+
1697
1970
  fn default_mesh_range() -> MeshRange {
1698
1971
  return MeshRange(
1699
1972
  0u,
@@ -2265,6 +2538,17 @@ fn tone_map_radiance(value: vec3<f32>) -> vec3<f32> {
2265
2538
  return pow(clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0)), vec3<f32>(1.0 / 2.2));
2266
2539
  }
2267
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
+
2268
2552
  fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
2269
2553
  return value / (vec3<f32>(1.0) + value);
2270
2554
  }
@@ -2277,6 +2561,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
2277
2561
  atomicStore(&counters.nextCount, 0u);
2278
2562
  atomicStore(&counters.terminatedCount, 0u);
2279
2563
  atomicStore(&counters.hitCount, 0u);
2564
+ write_active_dispatch_args(config.tilePixelCount);
2280
2565
  }
2281
2566
  if (index >= config.tilePixelCount) {
2282
2567
  return;
@@ -2552,9 +2837,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
2552
2837
  }
2553
2838
 
2554
2839
  if (ray.bounce + 1u >= config.maxDepth) {
2840
+ let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
2555
2841
  accumulation[ray.rayId] =
2556
- accumulation[ray.rayId] +
2557
- vec4<f32>(ray.throughput.xyz * config.ambientColor.xyz * sample_weight(), 1.0);
2842
+ accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
2558
2843
  atomicAdd(&counters.terminatedCount, 1u);
2559
2844
  return;
2560
2845
  }
@@ -2563,6 +2848,10 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
2563
2848
  let scatter = scatter_direction(ray, hit, seed);
2564
2849
  let nextIndex = atomicAdd(&counters.nextCount, 1u);
2565
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);
2566
2855
  return;
2567
2856
  }
2568
2857
  let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
@@ -2591,8 +2880,10 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
2591
2880
  return;
2592
2881
  }
2593
2882
  let nextCount = atomicLoad(&counters.nextCount);
2594
- atomicStore(&counters.activeCount, min(nextCount, config.tilePixelCount));
2883
+ let activeCount = min(nextCount, config.tilePixelCount);
2884
+ atomicStore(&counters.activeCount, activeCount);
2595
2885
  atomicStore(&counters.nextCount, 0u);
2886
+ write_active_dispatch_args(activeCount);
2596
2887
  }
2597
2888
 
2598
2889
  @compute @workgroup_size(64)
@@ -2870,10 +3161,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
2870
3161
  );
2871
3162
  const counterBuffer = createBuffer(
2872
3163
  device,
2873
- constants.buffer.STORAGE | constants.buffer.COPY_DST,
3164
+ constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
2874
3165
  COUNTER_BUFFER_BYTES,
2875
3166
  "plasius.wavefront.counters"
2876
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
+ );
2877
3174
  let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
2878
3175
  device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
2879
3176
  const packedTriangles = packWavefrontTriangles(
@@ -3295,24 +3592,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3295
3592
  return true;
3296
3593
  }
3297
3594
  function encodeTileSample(encoder, tile, configOffset) {
3298
- const passEncoder = encoder.beginComputePass({
3299
- label: "plasius.wavefront.computePass"
3595
+ const generatePass = encoder.beginComputePass({
3596
+ label: "plasius.wavefront.generatePrimaryRaysPass"
3300
3597
  });
3301
3598
  const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
3302
- const capacityWorkgroups = Math.ceil(config.tilePixelCapacity / WORKGROUP_SIZE);
3303
- passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
3304
- passEncoder.setPipeline(pipelines.generatePrimaryRays);
3305
- passEncoder.dispatchWorkgroups(tileWorkgroups);
3599
+ generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
3600
+ generatePass.setPipeline(pipelines.generatePrimaryRays);
3601
+ generatePass.dispatchWorkgroups(tileWorkgroups);
3602
+ generatePass.end();
3306
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
+ });
3307
3614
  passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
3308
3615
  passEncoder.setPipeline(pipelines.intersectActiveQueue);
3309
- passEncoder.dispatchWorkgroups(capacityWorkgroups);
3616
+ passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
3310
3617
  passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
3311
- passEncoder.dispatchWorkgroups(capacityWorkgroups);
3618
+ passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
3312
3619
  passEncoder.setPipeline(pipelines.compactAndSwapQueues);
3313
3620
  passEncoder.dispatchWorkgroups(1);
3621
+ passEncoder.end();
3314
3622
  }
3315
- passEncoder.end();
3316
3623
  }
3317
3624
  function encodeTileOutput(encoder, tile, configOffset) {
3318
3625
  const passEncoder = encoder.beginComputePass({
@@ -3455,6 +3762,28 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3455
3762
  luminance: (0.2126 * bytes[0] + 0.7152 * bytes[1] + 0.0722 * bytes[2]) / 255
3456
3763
  });
3457
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
+ }
3458
3787
  function updateSceneObjects(sceneObjects) {
3459
3788
  const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
3460
3789
  packedScene = nextPackedScene;
@@ -3513,6 +3842,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3513
3842
  configBuffer.destroy?.();
3514
3843
  bvhBuildConfigBuffer.destroy?.();
3515
3844
  counterBuffer.destroy?.();
3845
+ activeDispatchBuffer.destroy?.();
3516
3846
  radianceTexture.destroy?.();
3517
3847
  denoiseScratchTexture.destroy?.();
3518
3848
  outputTexture.destroy?.();
@@ -3525,12 +3855,32 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
3525
3855
  format,
3526
3856
  config,
3527
3857
  renderOnce,
3858
+ renderFrame,
3528
3859
  readOutputProbe,
3529
3860
  updateSceneObjects,
3530
3861
  getSnapshot,
3531
3862
  destroy
3532
3863
  });
3533
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
+ }
3534
3884
 
3535
3885
  // src/index.js
3536
3886
  var DEFAULT_CLEAR_COLOR = Object.freeze([0.07, 0.11, 0.18, 1]);
@@ -4830,22 +5180,29 @@ export {
4830
5180
  createWavefrontMeshAcceleration,
4831
5181
  createWavefrontPathTracingComputeConfig,
4832
5182
  createWavefrontPathTracingComputeRenderer,
5183
+ createWavefrontPathTracingComputeShaderSource,
4833
5184
  createWavefrontPathTracingPlan,
5185
+ createWavefrontReferenceRay,
4834
5186
  defaultRendererClearColor,
4835
5187
  defaultRendererWorkerProfile,
4836
5188
  estimateWavefrontPathTracingMemory,
4837
5189
  getRendererWorkerManifest,
4838
5190
  getRendererWorkerProfile,
5191
+ intersectWavefrontReferenceTriangle,
4839
5192
  normalizeWavefrontMesh,
4840
5193
  normalizeWavefrontSceneObject,
4841
5194
  packWavefrontBvhNodes,
4842
5195
  packWavefrontSceneObjects,
4843
5196
  packWavefrontTriangles,
5197
+ renderWavefrontPathTracingComputeFrame,
4844
5198
  rendererAccelerationStructureUpdateClasses,
4845
5199
  rendererDebugOwner,
4846
5200
  rendererRayTracingStageOrder,
4847
5201
  rendererRepresentationBands,
4848
5202
  rendererWavefrontBufferSchemaVersion,
5203
+ rendererWavefrontComputeMode,
5204
+ rendererWavefrontComputeStatsStride,
5205
+ rendererWavefrontComputeWorkgroupSize,
4849
5206
  rendererWavefrontHitTypes,
4850
5207
  rendererWavefrontPassOrder,
4851
5208
  rendererWavefrontQueuePairStrategy,
@@ -4855,6 +5212,7 @@ export {
4855
5212
  rendererWorkerQueueClass,
4856
5213
  supportsWavefrontPathTracingCompute,
4857
5214
  supportsWebGpu,
5215
+ traceWavefrontReferenceTriangles,
4858
5216
  wavefrontMaterialKinds,
4859
5217
  wavefrontPathTracingComputeLimits,
4860
5218
  wavefrontSceneObjectKinds