@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.cjs CHANGED
@@ -239,6 +239,7 @@ var PATH_VERTEX_RECORD_BYTES = 16;
239
239
  var GPU_SUBMITTED_WORK_TIMEOUT_MS = 5e3;
240
240
  var GPU_READBACK_COMPLETION_TIMEOUT_MS = 6e4;
241
241
  var GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 6e4;
242
+ var GPU_MAX_SUBMITTED_WORK_DEADLINE_MS = 18e4;
242
243
  var CONFIG_BUFFER_BYTES = 320;
243
244
  var COUNTER_DISPATCH_ARGS_OFFSET = 16;
244
245
  var INDIRECT_DISPATCH_ARGS_BYTES = 12;
@@ -944,141 +945,9 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
944
945
  function clampUnit(value) {
945
946
  return clamp(Number(value) || 0, 0, 1);
946
947
  }
947
- function srgbToLinear(value) {
948
- const channel = clampUnit(value);
949
- if (channel <= 0.04045) {
950
- return channel / 12.92;
951
- }
952
- return ((channel + 0.055) / 1.055) ** 2.4;
953
- }
954
- function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
955
- if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || !texture.data || texture.width <= 0 || texture.height <= 0) {
956
- return [1, 1, 1, 1];
957
- }
958
- const u = (uv[0] % 1 + 1) % 1;
959
- const v = (uv[1] % 1 + 1) % 1;
960
- const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
961
- const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
962
- const offset = (y * texture.width + x) * 4;
963
- const data = texture.data;
964
- const scale2 = resolveTextureSampleScale(data);
965
- const defaultChannel = scale2 === 1 ? 1 : Math.round(1 / scale2);
966
- const color = [
967
- (data[offset] ?? defaultChannel) * scale2,
968
- (data[offset + 1] ?? defaultChannel) * scale2,
969
- (data[offset + 2] ?? defaultChannel) * scale2,
970
- (data[offset + 3] ?? defaultChannel) * scale2
971
- ];
972
- if (colorSpace === "srgb") {
973
- return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
974
- }
975
- return color;
976
- }
977
- function resolveTextureSampleScale(data) {
978
- if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
979
- return 1 / 255;
980
- }
981
- if (data instanceof Uint16Array) {
982
- return 1 / 65535;
983
- }
984
- if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
985
- return 1 / 255;
986
- }
987
- return 1;
988
- }
989
- function normalizeVectorOrFallback(vector, fallback) {
990
- return normalize(Array.isArray(vector) ? vector : fallback, fallback);
991
- }
992
- function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
993
- const edge1 = subtract(v1, v0);
994
- const edge2 = subtract(v2, v0);
995
- const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
996
- const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
997
- const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
998
- if (Math.abs(determinant) < 1e-6) {
999
- const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
1000
- const tangent2 = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
1001
- const bitangent2 = normalize(cross(fallbackNormal, tangent2), [0, 0, 1]);
1002
- return { tangent: tangent2, bitangent: bitangent2 };
1003
- }
1004
- const inverse = 1 / determinant;
1005
- const tangent = normalize(
1006
- [
1007
- inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
1008
- inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
1009
- inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1])
1010
- ],
1011
- [1, 0, 0]
1012
- );
1013
- const bitangent = normalize(
1014
- [
1015
- inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
1016
- inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
1017
- inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0])
1018
- ],
1019
- [0, 0, 1]
1020
- );
1021
- return { tangent, bitangent };
1022
- }
1023
- function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
1024
- if (!normalTexture) {
1025
- return normalizeVectorOrFallback(normal, [0, 1, 0]);
1026
- }
1027
- const sample = sampleTextureRgba(normalTexture, uv, "linear");
1028
- const strength = clampUnit(normalTexture.scale ?? 1);
1029
- const tangentNormal = normalize(
1030
- [
1031
- (sample[0] * 2 - 1) * strength,
1032
- (sample[1] * 2 - 1) * strength,
1033
- 1 + (sample[2] * 2 - 1 - 1) * strength
1034
- ],
1035
- [0, 0, 1]
1036
- );
1037
- return normalize(
1038
- [
1039
- tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
1040
- tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
1041
- tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2]
1042
- ],
1043
- normal
1044
- );
1045
- }
1046
- function sampleBaseColor(mesh, uv) {
1047
- const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
1048
- return [
1049
- clampUnit(mesh.color[0] * sample[0]),
1050
- clampUnit(mesh.color[1] * sample[1]),
1051
- clampUnit(mesh.color[2] * sample[2]),
1052
- clampUnit((mesh.color[3] ?? 1) * sample[3])
1053
- ];
1054
- }
1055
- function sampleSurfaceMaterial(mesh, uv) {
1056
- const textureSample = mesh.metallicRoughnessTexture ? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear") : [1, 1, 1, 1];
1057
- return {
1058
- roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
1059
- metallic: clamp(mesh.metallic * textureSample[2], 0, 1)
1060
- };
1061
- }
1062
- function averageColors(colors) {
1063
- const count = Math.max(colors.length, 1);
1064
- return colors.reduce(
1065
- (accumulator, color) => [
1066
- accumulator[0] + color[0] / count,
1067
- accumulator[1] + color[1] / count,
1068
- accumulator[2] + color[2] / count,
1069
- accumulator[3] + color[3] / count
1070
- ],
1071
- [0, 0, 0, 0]
1072
- );
1073
- }
1074
- function averageNumbers(values, fallback = 0) {
1075
- if (!Array.isArray(values) || values.length === 0) {
1076
- return fallback;
1077
- }
1078
- return values.reduce((sum, value) => sum + value, 0) / values.length;
1079
- }
1080
- function createMeshTriangleRecords(meshes) {
948
+ function createMeshTriangleRecords(meshes, gpuMaterialSource = null) {
1081
949
  const source = Array.isArray(meshes) ? meshes : [];
950
+ const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
1082
951
  let nextTriangleId = 0;
1083
952
  return source.flatMap((meshInput, meshIndex) => {
1084
953
  const mesh = normalizeWavefrontMesh(meshInput, meshIndex);
@@ -1097,16 +966,6 @@ function createMeshTriangleRecords(meshes) {
1097
966
  const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
1098
967
  const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
1099
968
  const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
1100
- const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
1101
- const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
1102
- const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
1103
- const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
1104
- const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
1105
- const sampledMaterials = [
1106
- sampleSurfaceMaterial(mesh, uv0),
1107
- sampleSurfaceMaterial(mesh, uv1),
1108
- sampleSurfaceMaterial(mesh, uv2)
1109
- ];
1110
969
  const bounds = triangleBounds(v0, v1, v2);
1111
970
  triangles.push(
1112
971
  Object.freeze({
@@ -1120,17 +979,17 @@ function createMeshTriangleRecords(meshes) {
1120
979
  v0: Object.freeze(v0),
1121
980
  v1: Object.freeze(v1),
1122
981
  v2: Object.freeze(v2),
1123
- n0: Object.freeze(shadedN0),
1124
- n1: Object.freeze(shadedN1),
1125
- n2: Object.freeze(shadedN2),
982
+ n0: Object.freeze(n0),
983
+ n1: Object.freeze(n1),
984
+ n2: Object.freeze(n2),
1126
985
  uv0: Object.freeze(uv0),
1127
986
  uv1: Object.freeze(uv1),
1128
987
  uv2: Object.freeze(uv2),
1129
- color: Object.freeze(averageColors(sampledColors)),
988
+ color: mesh.color,
1130
989
  emission: mesh.emission,
1131
990
  material: Object.freeze([
1132
- averageNumbers(sampledMaterials.map((sample) => sample.roughness), mesh.roughness),
1133
- averageNumbers(sampledMaterials.map((sample) => sample.metallic), mesh.metallic),
991
+ mesh.roughness,
992
+ mesh.metallic,
1134
993
  mesh.opacity,
1135
994
  mesh.ior
1136
995
  ]),
@@ -1152,6 +1011,27 @@ function createMeshTriangleRecords(meshes) {
1152
1011
  mesh.specularColor[2] ?? 1,
1153
1012
  1
1154
1013
  ]),
1014
+ baseColorAtlas: Object.freeze(
1015
+ resolvedMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
1016
+ ),
1017
+ metallicRoughnessAtlas: Object.freeze(
1018
+ resolvedMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
1019
+ ),
1020
+ normalAtlas: Object.freeze(
1021
+ resolvedMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
1022
+ ),
1023
+ occlusionAtlas: Object.freeze(
1024
+ resolvedMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
1025
+ ),
1026
+ emissiveAtlas: Object.freeze(
1027
+ resolvedMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
1028
+ ),
1029
+ textureSettings: Object.freeze([
1030
+ clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
1031
+ clampUnit(mesh.occlusionTexture?.strength ?? 1),
1032
+ clampUnit(mesh.emissiveTexture?.strength ?? 1),
1033
+ 0
1034
+ ]),
1155
1035
  bounds: Object.freeze({
1156
1036
  min: Object.freeze(bounds.min),
1157
1037
  max: Object.freeze(bounds.max)
@@ -1226,9 +1106,10 @@ function buildBvh(triangles, maxLeafTriangles = 4) {
1226
1106
  triangles: Object.freeze(orderedTriangles)
1227
1107
  });
1228
1108
  }
1229
- function createWavefrontMeshAcceleration(meshes = []) {
1109
+ function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource = null) {
1230
1110
  const source = Array.isArray(meshes) ? meshes : [meshes];
1231
- const triangles = createMeshTriangleRecords(source);
1111
+ const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
1112
+ const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
1232
1113
  return buildBvh(triangles);
1233
1114
  }
1234
1115
  function estimateMeshSourceShape(meshes) {
@@ -1522,12 +1403,12 @@ function createWavefrontBvhBuildLevels(triangleCountInput) {
1522
1403
  return Object.freeze(levels);
1523
1404
  }
1524
1405
  function resolveAccelerationBuildMode(options = {}) {
1525
- const mode = options.accelerationBuildMode ?? (options.displayQuality === true ? "gpu" : "cpu-debug");
1526
- if (mode !== "gpu" && mode !== "cpu-debug") {
1527
- throw new Error('accelerationBuildMode must be either "gpu" or "cpu-debug".');
1528
- }
1529
- if (options.displayQuality === true && mode !== "gpu") {
1530
- throw new Error("Display-quality path tracing requires GPU-built mesh acceleration.");
1406
+ const requestedMode = options.accelerationBuildMode ?? (options.displayQuality === true ? "cpu-upload" : "cpu-debug");
1407
+ const mode = requestedMode === "cpu-debug" ? "cpu-upload" : requestedMode;
1408
+ if (mode !== "gpu" && mode !== "cpu-upload") {
1409
+ throw new Error(
1410
+ 'accelerationBuildMode must be either "gpu", "cpu-upload", or the legacy alias "cpu-debug".'
1411
+ );
1531
1412
  }
1532
1413
  return mode;
1533
1414
  }
@@ -2013,7 +1894,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
2013
1894
  const meshSourceShape = estimateMeshSourceShape(meshes);
2014
1895
  const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
2015
1896
  const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
2016
- const meshAcceleration = accelerationBuildMode === "cpu-debug" ? createWavefrontMeshAcceleration(meshes) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
1897
+ const meshAcceleration = accelerationBuildMode === "cpu-upload" ? createWavefrontMeshAcceleration(meshes, gpuMaterialSource) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
2017
1898
  const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
2018
1899
  meshes,
2019
1900
  options.emissiveTriangleCapacity
@@ -5825,9 +5706,22 @@ function nowMs() {
5825
5706
  }
5826
5707
  return Date.now();
5827
5708
  }
5828
- function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs = null) {
5709
+ function estimateAccelerationBuildWaitFactor(config) {
5710
+ if (config?.gpuAccelerationBuildRequired !== true) {
5711
+ return 1;
5712
+ }
5713
+ const bvhSortStageCount = Array.isArray(config?.bvhSortStages) ? config.bvhSortStages.length : 0;
5714
+ const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
5715
+ const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
5716
+ return Math.max(1, 1 + accelerationStageCount / 96);
5717
+ }
5718
+ function estimateSubmittedGpuWorkTiming(config, tileCount, overrideTimeoutMs = null, options = {}) {
5829
5719
  if (Number.isFinite(overrideTimeoutMs)) {
5830
- return Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
5720
+ const overrideMs = Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
5721
+ return Object.freeze({
5722
+ timeoutMs: overrideMs,
5723
+ maxWaitMs: overrideMs
5724
+ });
5831
5725
  }
5832
5726
  const samplesPerPixel = Math.max(
5833
5727
  1,
@@ -5838,10 +5732,26 @@ function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs
5838
5732
  const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
5839
5733
  const tiles = Math.max(1, Number(tileCount ?? 1));
5840
5734
  const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
5841
- return Math.min(
5735
+ const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
5736
+ const geometryFactor = Math.max(1, triangleCount / 131072);
5737
+ const includeAccelerationBuild = options.includeAccelerationBuild === true;
5738
+ const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
5739
+ const estimatedWindowMs = Math.round(
5740
+ (GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
5741
+ );
5742
+ const timeoutMs = Math.min(
5842
5743
  GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
5843
- GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5
5744
+ Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
5844
5745
  );
5746
+ const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
5747
+ const maxWaitMs = Math.min(
5748
+ GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
5749
+ Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
5750
+ );
5751
+ return Object.freeze({
5752
+ timeoutMs,
5753
+ maxWaitMs
5754
+ });
5845
5755
  }
5846
5756
  async function createWavefrontPathTracingComputeRenderer(options = {}) {
5847
5757
  assertAnalyticDisplayQualityPolicy(options);
@@ -6817,6 +6727,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6817
6727
  1,
6818
6728
  Number.isFinite(options2.timeoutMs) ? Number(options2.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
6819
6729
  );
6730
+ const maxWaitMs = Math.max(
6731
+ timeoutMs,
6732
+ Number.isFinite(options2.maxWaitMs) ? Number(options2.maxWaitMs) : timeoutMs
6733
+ );
6820
6734
  const allowTimeout = options2.allowTimeout !== false;
6821
6735
  const completionPromise = device.queue.onSubmittedWorkDone().then(
6822
6736
  () => ({ status: "done" }),
@@ -6829,43 +6743,57 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6829
6743
  `WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
6830
6744
  );
6831
6745
  }) : null;
6832
- let timeoutHandle = null;
6833
- let resolveTimeoutPromise = null;
6834
- let timeoutSettled = false;
6835
- const settleTimeoutPromise = (value) => {
6836
- if (timeoutSettled) {
6837
- return;
6746
+ const startedAtMs = nowMs();
6747
+ while (true) {
6748
+ const elapsedMs = Math.max(0, nowMs() - startedAtMs);
6749
+ const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
6750
+ if (remainingMs <= 0) {
6751
+ if (!allowTimeout) {
6752
+ throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
6753
+ }
6754
+ console.warn(
6755
+ `[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
6756
+ );
6757
+ return false;
6838
6758
  }
6839
- timeoutSettled = true;
6840
- resolveTimeoutPromise?.(value);
6841
- };
6842
- const timeoutPromise = new Promise((resolve) => {
6843
- resolveTimeoutPromise = resolve;
6844
- timeoutHandle = setTimeout(() => settleTimeoutPromise({ status: "timeout" }), timeoutMs);
6845
- });
6846
- let result;
6847
- try {
6848
- result = await Promise.race(
6849
- [completionPromise, timeoutPromise, lossPromise].filter(Boolean)
6850
- );
6851
- } finally {
6852
- if (timeoutHandle !== null) {
6853
- clearTimeout(timeoutHandle);
6854
- settleTimeoutPromise({ status: "cancelled" });
6759
+ const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
6760
+ let timeoutHandle = null;
6761
+ let resolveTimeoutPromise = null;
6762
+ let timeoutSettled = false;
6763
+ const settleTimeoutPromise = (value) => {
6764
+ if (timeoutSettled) {
6765
+ return;
6766
+ }
6767
+ timeoutSettled = true;
6768
+ resolveTimeoutPromise?.(value);
6769
+ };
6770
+ const timeoutPromise = new Promise((resolve) => {
6771
+ resolveTimeoutPromise = resolve;
6772
+ timeoutHandle = setTimeout(
6773
+ () => settleTimeoutPromise({ status: "timeout" }),
6774
+ waitWindowMs
6775
+ );
6776
+ });
6777
+ let result;
6778
+ try {
6779
+ result = await Promise.race(
6780
+ [completionPromise, timeoutPromise, lossPromise].filter(Boolean)
6781
+ );
6782
+ } finally {
6783
+ if (timeoutHandle !== null) {
6784
+ clearTimeout(timeoutHandle);
6785
+ settleTimeoutPromise({ status: "cancelled" });
6786
+ }
6855
6787
  }
6856
- }
6857
- if (result?.status === "timeout") {
6858
- if (!allowTimeout) {
6859
- throw new Error(`Timed out after ${timeoutMs} ms waiting for submitted GPU work.`);
6788
+ if (result?.status === "done") {
6789
+ return true;
6790
+ }
6791
+ if (result?.status !== "timeout") {
6792
+ return true;
6860
6793
  }
6861
- console.warn(
6862
- `[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
6863
- );
6864
- return false;
6865
6794
  }
6866
- return true;
6867
6795
  }
6868
- function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
6796
+ function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
6869
6797
  const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
6870
6798
  const denoisePassCount = config.denoise ? renderedSamplesPerPixel < 4 ? 2 : 1 : 0;
6871
6799
  const tailPassCount = denoisePassCount + 1;
@@ -6875,17 +6803,42 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6875
6803
  Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) / Math.max(samplePassesPerSample, 1)
6876
6804
  )
6877
6805
  );
6878
- let submissionCount = 0;
6879
- for (const tile of tiles) {
6880
- for (let sampleStart = 0; sampleStart < renderedSamplesPerPixel; sampleStart += sampleBatchSize) {
6881
- const sampleEnd = Math.min(renderedSamplesPerPixel, sampleStart + sampleBatchSize);
6806
+ const sampleRangeStart = clamp(
6807
+ readNonNegativeInteger("sampleRangeStart", optionsForFrame.sampleRangeStart, 0),
6808
+ 0,
6809
+ renderedSamplesPerPixel
6810
+ );
6811
+ const sampleRangeEnd = clamp(
6812
+ readPositiveInteger("sampleRangeEnd", optionsForFrame.sampleRangeEnd, renderedSamplesPerPixel),
6813
+ sampleRangeStart,
6814
+ renderedSamplesPerPixel
6815
+ );
6816
+ const includeDenoise = optionsForFrame.includeDenoise === true;
6817
+ const includePresent = optionsForFrame.includePresent === true;
6818
+ const tileStartIndex = clamp(
6819
+ readNonNegativeInteger("tileStartIndex", optionsForFrame.tileStartIndex, 0),
6820
+ 0,
6821
+ tiles.length
6822
+ );
6823
+ const tileEndIndex = clamp(
6824
+ readPositiveInteger("tileEndIndex", optionsForFrame.tileEndIndex, tiles.length),
6825
+ tileStartIndex,
6826
+ tiles.length
6827
+ );
6828
+ let submissionCount = Math.max(
6829
+ 0,
6830
+ readNonNegativeInteger("startingSubmissionCount", optionsForFrame.startingSubmissionCount, 0)
6831
+ );
6832
+ let slot = Math.max(0, readNonNegativeInteger("startingSlot", optionsForFrame.startingSlot, 0));
6833
+ for (const tile of tiles.slice(tileStartIndex, tileEndIndex)) {
6834
+ for (let sampleStart = sampleRangeStart; sampleStart < sampleRangeEnd; sampleStart += sampleBatchSize) {
6835
+ const sampleEnd = Math.min(sampleRangeEnd, sampleStart + sampleBatchSize);
6882
6836
  const batch = createGpuSubmissionBatcher({
6883
6837
  device,
6884
6838
  frameIndex,
6885
6839
  maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6886
6840
  startingSubmissionCount: submissionCount
6887
6841
  });
6888
- let slot = 0;
6889
6842
  for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
6890
6843
  const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
6891
6844
  sampleIndex,
@@ -6902,41 +6855,50 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6902
6855
  encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
6903
6856
  }
6904
6857
  }
6905
- if (!config.deferredPathResolve && sampleEnd >= renderedSamplesPerPixel) {
6858
+ if (!config.deferredPathResolve && sampleRangeEnd >= renderedSamplesPerPixel) {
6906
6859
  const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
6907
6860
  sampleIndex: 0,
6908
6861
  sampleWeight: 1 / renderedSamplesPerPixel
6909
6862
  });
6863
+ slot += 1;
6910
6864
  encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
6911
6865
  }
6912
6866
  batch.flush();
6913
6867
  submissionCount += batch.getSubmissionCount();
6914
6868
  }
6915
6869
  }
6916
- const tail = createGpuSubmissionBatcher({
6917
- device,
6918
- frameIndex,
6919
- maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6920
- startingSubmissionCount: submissionCount
6921
- });
6922
- if (config.denoise) {
6923
- const denoiseConfigOffset = writeFrameConfigSlot(
6924
- 0,
6925
- { x: 0, y: 0, width: config.width, height: config.height },
6870
+ if (includeDenoise || includePresent) {
6871
+ const tail = createGpuSubmissionBatcher({
6872
+ device,
6926
6873
  frameIndex,
6927
- { sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
6928
- );
6929
- encodeDenoise(
6930
- tail.reserve(denoisePassCount),
6931
- denoiseConfigOffset,
6932
- parallelism,
6933
- renderedSamplesPerPixel
6934
- );
6874
+ maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6875
+ startingSubmissionCount: submissionCount
6876
+ });
6877
+ if (includeDenoise && config.denoise) {
6878
+ const denoiseConfigOffset = writeFrameConfigSlot(
6879
+ slot,
6880
+ { x: 0, y: 0, width: config.width, height: config.height },
6881
+ frameIndex,
6882
+ { sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
6883
+ );
6884
+ slot += 1;
6885
+ encodeDenoise(
6886
+ tail.reserve(denoisePassCount),
6887
+ denoiseConfigOffset,
6888
+ parallelism,
6889
+ renderedSamplesPerPixel
6890
+ );
6891
+ }
6892
+ if (includePresent) {
6893
+ encodePresent(tail.reserve(1));
6894
+ }
6895
+ tail.flush();
6896
+ submissionCount += tail.getSubmissionCount();
6935
6897
  }
6936
- encodePresent(tail.reserve(1));
6937
- tail.flush();
6938
- submissionCount += tail.getSubmissionCount();
6939
- return submissionCount;
6898
+ return Object.freeze({
6899
+ submissionCount,
6900
+ slot
6901
+ });
6940
6902
  }
6941
6903
  async function readOutputProbe(optionsForProbe = {}) {
6942
6904
  const mapMode = constants.map;
@@ -6982,24 +6944,59 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
6982
6944
  const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
6983
6945
  const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
6984
6946
  const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
6985
- const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
6986
- { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
6987
- tiles.length,
6988
- renderOptions.submittedWorkTimeoutMs
6989
- );
6990
6947
  const frameStartTimeMs = nowMs();
6991
- const submissionWaitOptions = awaitGPUCompletion ? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false } : { timeoutMs: submittedWorkTimeoutMs };
6992
6948
  let frameStats;
6993
6949
  if (useThrottledHighSamplePath) {
6994
6950
  frame += 1;
6995
6951
  const frameIndex = frame + config.frameIndex;
6996
6952
  const parallelismCounters = createGpuParallelismCounters();
6997
6953
  const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
6998
- const frameSubmissionCount = dispatchFrameAwaitingGpu(
6999
- frameIndex,
7000
- parallelismCounters,
7001
- samplingPlan.renderedSamplesPerPixel
7002
- );
6954
+ let frameSubmissionCount = 0;
6955
+ let frameConfigSlot = 0;
6956
+ if (accelerationBuildSubmitted) {
6957
+ const accelerationWaitOptions = {
6958
+ ...estimateSubmittedGpuWorkTiming(
6959
+ { ...config, renderedSamplesPerPixel: 1 },
6960
+ 1,
6961
+ renderOptions.submittedWorkTimeoutMs,
6962
+ { includeAccelerationBuild: true }
6963
+ ),
6964
+ allowTimeout: false
6965
+ };
6966
+ await waitForSubmittedGpuWork(accelerationWaitOptions);
6967
+ }
6968
+ for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
6969
+ const tileRangeDispatch = dispatchFrameAwaitingGpu(
6970
+ frameIndex,
6971
+ parallelismCounters,
6972
+ samplingPlan.renderedSamplesPerPixel,
6973
+ {
6974
+ sampleRangeStart: 0,
6975
+ sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
6976
+ tileStartIndex: tileIndex,
6977
+ tileEndIndex: tileIndex + 1,
6978
+ startingSubmissionCount: frameSubmissionCount,
6979
+ startingSlot: frameConfigSlot,
6980
+ includeDenoise: tileIndex + 1 >= tiles.length,
6981
+ includePresent: tileIndex + 1 >= tiles.length
6982
+ }
6983
+ );
6984
+ frameSubmissionCount = tileRangeDispatch.submissionCount;
6985
+ frameConfigSlot = tileRangeDispatch.slot;
6986
+ const tileWaitOptions = {
6987
+ ...estimateSubmittedGpuWorkTiming(
6988
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
6989
+ 1,
6990
+ renderOptions.submittedWorkTimeoutMs,
6991
+ {
6992
+ includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
6993
+ includePresent: tileIndex + 1 >= tiles.length
6994
+ }
6995
+ ),
6996
+ allowTimeout: false
6997
+ };
6998
+ await waitForSubmittedGpuWork(tileWaitOptions);
6999
+ }
7003
7000
  frameStats = createFrameStats({
7004
7001
  frameIndex,
7005
7002
  accelerationBuildSubmitted,
@@ -7011,10 +7008,24 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
7011
7008
  budgetConstrained: samplingPlan.budgetConstrained
7012
7009
  });
7013
7010
  } else {
7011
+ const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
7012
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
7013
+ tiles.length,
7014
+ renderOptions.submittedWorkTimeoutMs,
7015
+ { includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
7016
+ );
7017
+ const submissionWaitOptions = awaitGPUCompletion ? {
7018
+ timeoutMs: submittedWorkTiming.timeoutMs,
7019
+ maxWaitMs: submittedWorkTiming.maxWaitMs,
7020
+ allowTimeout: false
7021
+ } : {
7022
+ timeoutMs: submittedWorkTiming.timeoutMs,
7023
+ maxWaitMs: submittedWorkTiming.maxWaitMs
7024
+ };
7014
7025
  frameStats = renderOnce(renderOptions, samplingPlan);
7015
- }
7016
- if (awaitGPUCompletion) {
7017
- await waitForSubmittedGpuWork(submissionWaitOptions);
7026
+ if (awaitGPUCompletion) {
7027
+ await waitForSubmittedGpuWork(submissionWaitOptions);
7028
+ }
7018
7029
  }
7019
7030
  const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
7020
7031
  if (awaitGPUCompletion) {