@plasius/gpu-renderer 0.2.7 → 0.2.8

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
@@ -165,6 +165,7 @@ var PATH_VERTEX_RECORD_BYTES = 16;
165
165
  var GPU_SUBMITTED_WORK_TIMEOUT_MS = 5e3;
166
166
  var GPU_READBACK_COMPLETION_TIMEOUT_MS = 6e4;
167
167
  var GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 6e4;
168
+ var GPU_MAX_SUBMITTED_WORK_DEADLINE_MS = 18e4;
168
169
  var CONFIG_BUFFER_BYTES = 320;
169
170
  var COUNTER_DISPATCH_ARGS_OFFSET = 16;
170
171
  var INDIRECT_DISPATCH_ARGS_BYTES = 12;
@@ -870,141 +871,9 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
870
871
  function clampUnit(value) {
871
872
  return clamp(Number(value) || 0, 0, 1);
872
873
  }
873
- function srgbToLinear(value) {
874
- const channel = clampUnit(value);
875
- if (channel <= 0.04045) {
876
- return channel / 12.92;
877
- }
878
- return ((channel + 0.055) / 1.055) ** 2.4;
879
- }
880
- function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
881
- if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || !texture.data || texture.width <= 0 || texture.height <= 0) {
882
- return [1, 1, 1, 1];
883
- }
884
- const u = (uv[0] % 1 + 1) % 1;
885
- const v = (uv[1] % 1 + 1) % 1;
886
- const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
887
- const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
888
- const offset = (y * texture.width + x) * 4;
889
- const data = texture.data;
890
- const scale2 = resolveTextureSampleScale(data);
891
- const defaultChannel = scale2 === 1 ? 1 : Math.round(1 / scale2);
892
- const color = [
893
- (data[offset] ?? defaultChannel) * scale2,
894
- (data[offset + 1] ?? defaultChannel) * scale2,
895
- (data[offset + 2] ?? defaultChannel) * scale2,
896
- (data[offset + 3] ?? defaultChannel) * scale2
897
- ];
898
- if (colorSpace === "srgb") {
899
- return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
900
- }
901
- return color;
902
- }
903
- function resolveTextureSampleScale(data) {
904
- if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
905
- return 1 / 255;
906
- }
907
- if (data instanceof Uint16Array) {
908
- return 1 / 65535;
909
- }
910
- if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
911
- return 1 / 255;
912
- }
913
- return 1;
914
- }
915
- function normalizeVectorOrFallback(vector, fallback) {
916
- return normalize(Array.isArray(vector) ? vector : fallback, fallback);
917
- }
918
- function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
919
- const edge1 = subtract(v1, v0);
920
- const edge2 = subtract(v2, v0);
921
- const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
922
- const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
923
- const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
924
- if (Math.abs(determinant) < 1e-6) {
925
- const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
926
- const tangent2 = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
927
- const bitangent2 = normalize(cross(fallbackNormal, tangent2), [0, 0, 1]);
928
- return { tangent: tangent2, bitangent: bitangent2 };
929
- }
930
- const inverse = 1 / determinant;
931
- const tangent = normalize(
932
- [
933
- inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
934
- inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
935
- inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1])
936
- ],
937
- [1, 0, 0]
938
- );
939
- const bitangent = normalize(
940
- [
941
- inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
942
- inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
943
- inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0])
944
- ],
945
- [0, 0, 1]
946
- );
947
- return { tangent, bitangent };
948
- }
949
- function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
950
- if (!normalTexture) {
951
- return normalizeVectorOrFallback(normal, [0, 1, 0]);
952
- }
953
- const sample = sampleTextureRgba(normalTexture, uv, "linear");
954
- const strength = clampUnit(normalTexture.scale ?? 1);
955
- const tangentNormal = normalize(
956
- [
957
- (sample[0] * 2 - 1) * strength,
958
- (sample[1] * 2 - 1) * strength,
959
- 1 + (sample[2] * 2 - 1 - 1) * strength
960
- ],
961
- [0, 0, 1]
962
- );
963
- return normalize(
964
- [
965
- tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
966
- tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
967
- tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2]
968
- ],
969
- normal
970
- );
971
- }
972
- function sampleBaseColor(mesh, uv) {
973
- const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
974
- return [
975
- clampUnit(mesh.color[0] * sample[0]),
976
- clampUnit(mesh.color[1] * sample[1]),
977
- clampUnit(mesh.color[2] * sample[2]),
978
- clampUnit((mesh.color[3] ?? 1) * sample[3])
979
- ];
980
- }
981
- function sampleSurfaceMaterial(mesh, uv) {
982
- const textureSample = mesh.metallicRoughnessTexture ? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear") : [1, 1, 1, 1];
983
- return {
984
- roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
985
- metallic: clamp(mesh.metallic * textureSample[2], 0, 1)
986
- };
987
- }
988
- function averageColors(colors) {
989
- const count = Math.max(colors.length, 1);
990
- return colors.reduce(
991
- (accumulator, color) => [
992
- accumulator[0] + color[0] / count,
993
- accumulator[1] + color[1] / count,
994
- accumulator[2] + color[2] / count,
995
- accumulator[3] + color[3] / count
996
- ],
997
- [0, 0, 0, 0]
998
- );
999
- }
1000
- function averageNumbers(values, fallback = 0) {
1001
- if (!Array.isArray(values) || values.length === 0) {
1002
- return fallback;
1003
- }
1004
- return values.reduce((sum, value) => sum + value, 0) / values.length;
1005
- }
1006
- function createMeshTriangleRecords(meshes) {
874
+ function createMeshTriangleRecords(meshes, gpuMaterialSource = null) {
1007
875
  const source = Array.isArray(meshes) ? meshes : [];
876
+ const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
1008
877
  let nextTriangleId = 0;
1009
878
  return source.flatMap((meshInput, meshIndex) => {
1010
879
  const mesh = normalizeWavefrontMesh(meshInput, meshIndex);
@@ -1023,16 +892,6 @@ function createMeshTriangleRecords(meshes) {
1023
892
  const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
1024
893
  const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
1025
894
  const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
1026
- const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
1027
- const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
1028
- const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
1029
- const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
1030
- const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
1031
- const sampledMaterials = [
1032
- sampleSurfaceMaterial(mesh, uv0),
1033
- sampleSurfaceMaterial(mesh, uv1),
1034
- sampleSurfaceMaterial(mesh, uv2)
1035
- ];
1036
895
  const bounds = triangleBounds(v0, v1, v2);
1037
896
  triangles.push(
1038
897
  Object.freeze({
@@ -1046,17 +905,17 @@ function createMeshTriangleRecords(meshes) {
1046
905
  v0: Object.freeze(v0),
1047
906
  v1: Object.freeze(v1),
1048
907
  v2: Object.freeze(v2),
1049
- n0: Object.freeze(shadedN0),
1050
- n1: Object.freeze(shadedN1),
1051
- n2: Object.freeze(shadedN2),
908
+ n0: Object.freeze(n0),
909
+ n1: Object.freeze(n1),
910
+ n2: Object.freeze(n2),
1052
911
  uv0: Object.freeze(uv0),
1053
912
  uv1: Object.freeze(uv1),
1054
913
  uv2: Object.freeze(uv2),
1055
- color: Object.freeze(averageColors(sampledColors)),
914
+ color: mesh.color,
1056
915
  emission: mesh.emission,
1057
916
  material: Object.freeze([
1058
- averageNumbers(sampledMaterials.map((sample) => sample.roughness), mesh.roughness),
1059
- averageNumbers(sampledMaterials.map((sample) => sample.metallic), mesh.metallic),
917
+ mesh.roughness,
918
+ mesh.metallic,
1060
919
  mesh.opacity,
1061
920
  mesh.ior
1062
921
  ]),
@@ -1078,6 +937,27 @@ function createMeshTriangleRecords(meshes) {
1078
937
  mesh.specularColor[2] ?? 1,
1079
938
  1
1080
939
  ]),
940
+ baseColorAtlas: Object.freeze(
941
+ resolvedMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
942
+ ),
943
+ metallicRoughnessAtlas: Object.freeze(
944
+ resolvedMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
945
+ ),
946
+ normalAtlas: Object.freeze(
947
+ resolvedMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
948
+ ),
949
+ occlusionAtlas: Object.freeze(
950
+ resolvedMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
951
+ ),
952
+ emissiveAtlas: Object.freeze(
953
+ resolvedMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
954
+ ),
955
+ textureSettings: Object.freeze([
956
+ clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
957
+ clampUnit(mesh.occlusionTexture?.strength ?? 1),
958
+ clampUnit(mesh.emissiveTexture?.strength ?? 1),
959
+ 0
960
+ ]),
1081
961
  bounds: Object.freeze({
1082
962
  min: Object.freeze(bounds.min),
1083
963
  max: Object.freeze(bounds.max)
@@ -1152,9 +1032,10 @@ function buildBvh(triangles, maxLeafTriangles = 4) {
1152
1032
  triangles: Object.freeze(orderedTriangles)
1153
1033
  });
1154
1034
  }
1155
- function createWavefrontMeshAcceleration(meshes = []) {
1035
+ function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource = null) {
1156
1036
  const source = Array.isArray(meshes) ? meshes : [meshes];
1157
- const triangles = createMeshTriangleRecords(source);
1037
+ const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
1038
+ const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
1158
1039
  return buildBvh(triangles);
1159
1040
  }
1160
1041
  function estimateMeshSourceShape(meshes) {
@@ -1448,12 +1329,12 @@ function createWavefrontBvhBuildLevels(triangleCountInput) {
1448
1329
  return Object.freeze(levels);
1449
1330
  }
1450
1331
  function resolveAccelerationBuildMode(options = {}) {
1451
- const mode = options.accelerationBuildMode ?? (options.displayQuality === true ? "gpu" : "cpu-debug");
1452
- if (mode !== "gpu" && mode !== "cpu-debug") {
1453
- throw new Error('accelerationBuildMode must be either "gpu" or "cpu-debug".');
1454
- }
1455
- if (options.displayQuality === true && mode !== "gpu") {
1456
- throw new Error("Display-quality path tracing requires GPU-built mesh acceleration.");
1332
+ const requestedMode = options.accelerationBuildMode ?? (options.displayQuality === true ? "cpu-upload" : "cpu-debug");
1333
+ const mode = requestedMode === "cpu-debug" ? "cpu-upload" : requestedMode;
1334
+ if (mode !== "gpu" && mode !== "cpu-upload") {
1335
+ throw new Error(
1336
+ 'accelerationBuildMode must be either "gpu", "cpu-upload", or the legacy alias "cpu-debug".'
1337
+ );
1457
1338
  }
1458
1339
  return mode;
1459
1340
  }
@@ -1939,7 +1820,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
1939
1820
  const meshSourceShape = estimateMeshSourceShape(meshes);
1940
1821
  const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
1941
1822
  const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
1942
- const meshAcceleration = accelerationBuildMode === "cpu-debug" ? createWavefrontMeshAcceleration(meshes) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
1823
+ const meshAcceleration = accelerationBuildMode === "cpu-upload" ? createWavefrontMeshAcceleration(meshes, gpuMaterialSource) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
1943
1824
  const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
1944
1825
  meshes,
1945
1826
  options.emissiveTriangleCapacity
@@ -5751,9 +5632,22 @@ function nowMs() {
5751
5632
  }
5752
5633
  return Date.now();
5753
5634
  }
5754
- function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs = null) {
5635
+ function estimateAccelerationBuildWaitFactor(config) {
5636
+ if (config?.gpuAccelerationBuildRequired !== true) {
5637
+ return 1;
5638
+ }
5639
+ const bvhSortStageCount = Array.isArray(config?.bvhSortStages) ? config.bvhSortStages.length : 0;
5640
+ const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
5641
+ const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
5642
+ return Math.max(1, 1 + accelerationStageCount / 96);
5643
+ }
5644
+ function estimateSubmittedGpuWorkTiming(config, tileCount, overrideTimeoutMs = null, options = {}) {
5755
5645
  if (Number.isFinite(overrideTimeoutMs)) {
5756
- return Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
5646
+ const overrideMs = Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
5647
+ return Object.freeze({
5648
+ timeoutMs: overrideMs,
5649
+ maxWaitMs: overrideMs
5650
+ });
5757
5651
  }
5758
5652
  const samplesPerPixel = Math.max(
5759
5653
  1,
@@ -5764,10 +5658,26 @@ function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs
5764
5658
  const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
5765
5659
  const tiles = Math.max(1, Number(tileCount ?? 1));
5766
5660
  const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
5767
- return Math.min(
5661
+ const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
5662
+ const geometryFactor = Math.max(1, triangleCount / 131072);
5663
+ const includeAccelerationBuild = options.includeAccelerationBuild === true;
5664
+ const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
5665
+ const estimatedWindowMs = Math.round(
5666
+ (GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
5667
+ );
5668
+ const timeoutMs = Math.min(
5768
5669
  GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
5769
- GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5
5670
+ Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
5770
5671
  );
5672
+ const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
5673
+ const maxWaitMs = Math.min(
5674
+ GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
5675
+ Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
5676
+ );
5677
+ return Object.freeze({
5678
+ timeoutMs,
5679
+ maxWaitMs
5680
+ });
5771
5681
  }
5772
5682
  async function createWavefrontPathTracingComputeRenderer(options = {}) {
5773
5683
  assertAnalyticDisplayQualityPolicy(options);
@@ -6743,6 +6653,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6743
6653
  1,
6744
6654
  Number.isFinite(options2.timeoutMs) ? Number(options2.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
6745
6655
  );
6656
+ const maxWaitMs = Math.max(
6657
+ timeoutMs,
6658
+ Number.isFinite(options2.maxWaitMs) ? Number(options2.maxWaitMs) : timeoutMs
6659
+ );
6746
6660
  const allowTimeout = options2.allowTimeout !== false;
6747
6661
  const completionPromise = device.queue.onSubmittedWorkDone().then(
6748
6662
  () => ({ status: "done" }),
@@ -6755,43 +6669,57 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6755
6669
  `WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
6756
6670
  );
6757
6671
  }) : null;
6758
- let timeoutHandle = null;
6759
- let resolveTimeoutPromise = null;
6760
- let timeoutSettled = false;
6761
- const settleTimeoutPromise = (value) => {
6762
- if (timeoutSettled) {
6763
- return;
6672
+ const startedAtMs = nowMs();
6673
+ while (true) {
6674
+ const elapsedMs = Math.max(0, nowMs() - startedAtMs);
6675
+ const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
6676
+ if (remainingMs <= 0) {
6677
+ if (!allowTimeout) {
6678
+ throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
6679
+ }
6680
+ console.warn(
6681
+ `[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
6682
+ );
6683
+ return false;
6764
6684
  }
6765
- timeoutSettled = true;
6766
- resolveTimeoutPromise?.(value);
6767
- };
6768
- const timeoutPromise = new Promise((resolve) => {
6769
- resolveTimeoutPromise = resolve;
6770
- timeoutHandle = setTimeout(() => settleTimeoutPromise({ status: "timeout" }), timeoutMs);
6771
- });
6772
- let result;
6773
- try {
6774
- result = await Promise.race(
6775
- [completionPromise, timeoutPromise, lossPromise].filter(Boolean)
6776
- );
6777
- } finally {
6778
- if (timeoutHandle !== null) {
6779
- clearTimeout(timeoutHandle);
6780
- settleTimeoutPromise({ status: "cancelled" });
6685
+ const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
6686
+ let timeoutHandle = null;
6687
+ let resolveTimeoutPromise = null;
6688
+ let timeoutSettled = false;
6689
+ const settleTimeoutPromise = (value) => {
6690
+ if (timeoutSettled) {
6691
+ return;
6692
+ }
6693
+ timeoutSettled = true;
6694
+ resolveTimeoutPromise?.(value);
6695
+ };
6696
+ const timeoutPromise = new Promise((resolve) => {
6697
+ resolveTimeoutPromise = resolve;
6698
+ timeoutHandle = setTimeout(
6699
+ () => settleTimeoutPromise({ status: "timeout" }),
6700
+ waitWindowMs
6701
+ );
6702
+ });
6703
+ let result;
6704
+ try {
6705
+ result = await Promise.race(
6706
+ [completionPromise, timeoutPromise, lossPromise].filter(Boolean)
6707
+ );
6708
+ } finally {
6709
+ if (timeoutHandle !== null) {
6710
+ clearTimeout(timeoutHandle);
6711
+ settleTimeoutPromise({ status: "cancelled" });
6712
+ }
6781
6713
  }
6782
- }
6783
- if (result?.status === "timeout") {
6784
- if (!allowTimeout) {
6785
- throw new Error(`Timed out after ${timeoutMs} ms waiting for submitted GPU work.`);
6714
+ if (result?.status === "done") {
6715
+ return true;
6716
+ }
6717
+ if (result?.status !== "timeout") {
6718
+ return true;
6786
6719
  }
6787
- console.warn(
6788
- `[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
6789
- );
6790
- return false;
6791
6720
  }
6792
- return true;
6793
6721
  }
6794
- function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
6722
+ function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
6795
6723
  const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
6796
6724
  const denoisePassCount = config.denoise ? renderedSamplesPerPixel < 4 ? 2 : 1 : 0;
6797
6725
  const tailPassCount = denoisePassCount + 1;
@@ -6801,17 +6729,42 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6801
6729
  Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) / Math.max(samplePassesPerSample, 1)
6802
6730
  )
6803
6731
  );
6804
- let submissionCount = 0;
6805
- for (const tile of tiles) {
6806
- for (let sampleStart = 0; sampleStart < renderedSamplesPerPixel; sampleStart += sampleBatchSize) {
6807
- const sampleEnd = Math.min(renderedSamplesPerPixel, sampleStart + sampleBatchSize);
6732
+ const sampleRangeStart = clamp(
6733
+ readNonNegativeInteger("sampleRangeStart", optionsForFrame.sampleRangeStart, 0),
6734
+ 0,
6735
+ renderedSamplesPerPixel
6736
+ );
6737
+ const sampleRangeEnd = clamp(
6738
+ readPositiveInteger("sampleRangeEnd", optionsForFrame.sampleRangeEnd, renderedSamplesPerPixel),
6739
+ sampleRangeStart,
6740
+ renderedSamplesPerPixel
6741
+ );
6742
+ const includeDenoise = optionsForFrame.includeDenoise === true;
6743
+ const includePresent = optionsForFrame.includePresent === true;
6744
+ const tileStartIndex = clamp(
6745
+ readNonNegativeInteger("tileStartIndex", optionsForFrame.tileStartIndex, 0),
6746
+ 0,
6747
+ tiles.length
6748
+ );
6749
+ const tileEndIndex = clamp(
6750
+ readPositiveInteger("tileEndIndex", optionsForFrame.tileEndIndex, tiles.length),
6751
+ tileStartIndex,
6752
+ tiles.length
6753
+ );
6754
+ let submissionCount = Math.max(
6755
+ 0,
6756
+ readNonNegativeInteger("startingSubmissionCount", optionsForFrame.startingSubmissionCount, 0)
6757
+ );
6758
+ let slot = Math.max(0, readNonNegativeInteger("startingSlot", optionsForFrame.startingSlot, 0));
6759
+ for (const tile of tiles.slice(tileStartIndex, tileEndIndex)) {
6760
+ for (let sampleStart = sampleRangeStart; sampleStart < sampleRangeEnd; sampleStart += sampleBatchSize) {
6761
+ const sampleEnd = Math.min(sampleRangeEnd, sampleStart + sampleBatchSize);
6808
6762
  const batch = createGpuSubmissionBatcher({
6809
6763
  device,
6810
6764
  frameIndex,
6811
6765
  maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6812
6766
  startingSubmissionCount: submissionCount
6813
6767
  });
6814
- let slot = 0;
6815
6768
  for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
6816
6769
  const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
6817
6770
  sampleIndex,
@@ -6828,41 +6781,50 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6828
6781
  encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
6829
6782
  }
6830
6783
  }
6831
- if (!config.deferredPathResolve && sampleEnd >= renderedSamplesPerPixel) {
6784
+ if (!config.deferredPathResolve && sampleRangeEnd >= renderedSamplesPerPixel) {
6832
6785
  const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
6833
6786
  sampleIndex: 0,
6834
6787
  sampleWeight: 1 / renderedSamplesPerPixel
6835
6788
  });
6789
+ slot += 1;
6836
6790
  encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
6837
6791
  }
6838
6792
  batch.flush();
6839
6793
  submissionCount += batch.getSubmissionCount();
6840
6794
  }
6841
6795
  }
6842
- const tail = createGpuSubmissionBatcher({
6843
- device,
6844
- frameIndex,
6845
- maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6846
- startingSubmissionCount: submissionCount
6847
- });
6848
- if (config.denoise) {
6849
- const denoiseConfigOffset = writeFrameConfigSlot(
6850
- 0,
6851
- { x: 0, y: 0, width: config.width, height: config.height },
6796
+ if (includeDenoise || includePresent) {
6797
+ const tail = createGpuSubmissionBatcher({
6798
+ device,
6852
6799
  frameIndex,
6853
- { sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
6854
- );
6855
- encodeDenoise(
6856
- tail.reserve(denoisePassCount),
6857
- denoiseConfigOffset,
6858
- parallelism,
6859
- renderedSamplesPerPixel
6860
- );
6800
+ maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6801
+ startingSubmissionCount: submissionCount
6802
+ });
6803
+ if (includeDenoise && config.denoise) {
6804
+ const denoiseConfigOffset = writeFrameConfigSlot(
6805
+ slot,
6806
+ { x: 0, y: 0, width: config.width, height: config.height },
6807
+ frameIndex,
6808
+ { sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
6809
+ );
6810
+ slot += 1;
6811
+ encodeDenoise(
6812
+ tail.reserve(denoisePassCount),
6813
+ denoiseConfigOffset,
6814
+ parallelism,
6815
+ renderedSamplesPerPixel
6816
+ );
6817
+ }
6818
+ if (includePresent) {
6819
+ encodePresent(tail.reserve(1));
6820
+ }
6821
+ tail.flush();
6822
+ submissionCount += tail.getSubmissionCount();
6861
6823
  }
6862
- encodePresent(tail.reserve(1));
6863
- tail.flush();
6864
- submissionCount += tail.getSubmissionCount();
6865
- return submissionCount;
6824
+ return Object.freeze({
6825
+ submissionCount,
6826
+ slot
6827
+ });
6866
6828
  }
6867
6829
  async function readOutputProbe(optionsForProbe = {}) {
6868
6830
  const mapMode = constants.map;
@@ -6908,24 +6870,59 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6908
6870
  const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
6909
6871
  const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
6910
6872
  const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
6911
- const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
6912
- { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
6913
- tiles.length,
6914
- renderOptions.submittedWorkTimeoutMs
6915
- );
6916
6873
  const frameStartTimeMs = nowMs();
6917
- const submissionWaitOptions = awaitGPUCompletion ? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false } : { timeoutMs: submittedWorkTimeoutMs };
6918
6874
  let frameStats;
6919
6875
  if (useThrottledHighSamplePath) {
6920
6876
  frame += 1;
6921
6877
  const frameIndex = frame + config.frameIndex;
6922
6878
  const parallelismCounters = createGpuParallelismCounters();
6923
6879
  const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
6924
- const frameSubmissionCount = dispatchFrameAwaitingGpu(
6925
- frameIndex,
6926
- parallelismCounters,
6927
- samplingPlan.renderedSamplesPerPixel
6928
- );
6880
+ let frameSubmissionCount = 0;
6881
+ let frameConfigSlot = 0;
6882
+ if (accelerationBuildSubmitted) {
6883
+ const accelerationWaitOptions = {
6884
+ ...estimateSubmittedGpuWorkTiming(
6885
+ { ...config, renderedSamplesPerPixel: 1 },
6886
+ 1,
6887
+ renderOptions.submittedWorkTimeoutMs,
6888
+ { includeAccelerationBuild: true }
6889
+ ),
6890
+ allowTimeout: false
6891
+ };
6892
+ await waitForSubmittedGpuWork(accelerationWaitOptions);
6893
+ }
6894
+ for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
6895
+ const tileRangeDispatch = dispatchFrameAwaitingGpu(
6896
+ frameIndex,
6897
+ parallelismCounters,
6898
+ samplingPlan.renderedSamplesPerPixel,
6899
+ {
6900
+ sampleRangeStart: 0,
6901
+ sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
6902
+ tileStartIndex: tileIndex,
6903
+ tileEndIndex: tileIndex + 1,
6904
+ startingSubmissionCount: frameSubmissionCount,
6905
+ startingSlot: frameConfigSlot,
6906
+ includeDenoise: tileIndex + 1 >= tiles.length,
6907
+ includePresent: tileIndex + 1 >= tiles.length
6908
+ }
6909
+ );
6910
+ frameSubmissionCount = tileRangeDispatch.submissionCount;
6911
+ frameConfigSlot = tileRangeDispatch.slot;
6912
+ const tileWaitOptions = {
6913
+ ...estimateSubmittedGpuWorkTiming(
6914
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
6915
+ 1,
6916
+ renderOptions.submittedWorkTimeoutMs,
6917
+ {
6918
+ includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
6919
+ includePresent: tileIndex + 1 >= tiles.length
6920
+ }
6921
+ ),
6922
+ allowTimeout: false
6923
+ };
6924
+ await waitForSubmittedGpuWork(tileWaitOptions);
6925
+ }
6929
6926
  frameStats = createFrameStats({
6930
6927
  frameIndex,
6931
6928
  accelerationBuildSubmitted,
@@ -6937,10 +6934,24 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6937
6934
  budgetConstrained: samplingPlan.budgetConstrained
6938
6935
  });
6939
6936
  } else {
6937
+ const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
6938
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
6939
+ tiles.length,
6940
+ renderOptions.submittedWorkTimeoutMs,
6941
+ { includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
6942
+ );
6943
+ const submissionWaitOptions = awaitGPUCompletion ? {
6944
+ timeoutMs: submittedWorkTiming.timeoutMs,
6945
+ maxWaitMs: submittedWorkTiming.maxWaitMs,
6946
+ allowTimeout: false
6947
+ } : {
6948
+ timeoutMs: submittedWorkTiming.timeoutMs,
6949
+ maxWaitMs: submittedWorkTiming.maxWaitMs
6950
+ };
6940
6951
  frameStats = renderOnce(renderOptions, samplingPlan);
6941
- }
6942
- if (awaitGPUCompletion) {
6943
- await waitForSubmittedGpuWork(submissionWaitOptions);
6952
+ if (awaitGPUCompletion) {
6953
+ await waitForSubmittedGpuWork(submissionWaitOptions);
6954
+ }
6944
6955
  }
6945
6956
  const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
6946
6957
  if (awaitGPUCompletion) {