@meimu/mutracer 0.2.0 → 0.3.0

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.
Files changed (211) hide show
  1. package/README.md +3 -0
  2. package/dist/assets/bvh.worker.js +1 -1
  3. package/dist/mutracer.umd.js +8949 -7915
  4. package/docs/api.md +42 -21
  5. package/docs/caustics.md +80 -0
  6. package/docs/materials.md +5 -4
  7. package/docs/progressive-rendering.md +50 -9
  8. package/docs/technical/continuous-spectrum.md +93 -0
  9. package/docs/updates.md +3 -1
  10. package/docs/viewer-checklist.md +65 -0
  11. package/docs/viewer.md +7 -5
  12. package/lib/Display.js +1 -1
  13. package/lib/Display.js.map +1 -1
  14. package/lib/Engine.d.ts +2 -0
  15. package/lib/Engine.js +47 -12
  16. package/lib/Engine.js.map +1 -1
  17. package/lib/Render.d.ts +61 -13
  18. package/lib/Render.js +169 -19
  19. package/lib/Render.js.map +1 -1
  20. package/lib/RenderLoop.d.ts +7 -3
  21. package/lib/RenderLoop.js +68 -44
  22. package/lib/RenderLoop.js.map +1 -1
  23. package/lib/main.d.ts +1 -1
  24. package/lib/main.js.map +1 -1
  25. package/lib/oidn.d.ts +1 -1
  26. package/lib/oidnBundledWeights.cdn.d.ts +2 -2
  27. package/lib/renderer/GPUTraceQueue.d.ts +2 -1
  28. package/lib/renderer/GPUTraceQueue.js +3 -1
  29. package/lib/renderer/GPUTraceQueue.js.map +1 -1
  30. package/lib/renderer/RenderingPipeline.d.ts +4 -0
  31. package/lib/renderer/RenderingPipeline.js.map +1 -1
  32. package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
  33. package/lib/renderer/WGPUCausticLightPass.js +217 -0
  34. package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
  35. package/lib/renderer/WGPUEnvMapDistribution.js +18 -18
  36. package/lib/renderer/WGPUFullQuadPass.js +3 -3
  37. package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
  38. package/lib/renderer/WGPUPhotonMap.js +191 -0
  39. package/lib/renderer/WGPUPhotonMap.js.map +1 -0
  40. package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
  41. package/lib/renderer/WGPURayTraceOutput.js +122 -28
  42. package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
  43. package/lib/renderer/WGPURayTracePass.d.ts +48 -17
  44. package/lib/renderer/WGPURayTracePass.js +538 -340
  45. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  46. package/lib/renderer/WGPURenderingPipeline.js +83 -47
  47. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  48. package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
  49. package/lib/renderer/WebGLFullQuadPass.js +4 -0
  50. package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
  51. package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
  52. package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
  53. package/lib/renderer/WebGLRayTracePass.d.ts +6 -1
  54. package/lib/renderer/WebGLRayTracePass.js +43 -6
  55. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  56. package/lib/renderer/WebGLRenderingPipeline.js +10 -14
  57. package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
  58. package/lib/renderer/WebGLSkyBaker.js +60 -60
  59. package/lib/renderer/accumulationTarget.d.ts +2 -1
  60. package/lib/renderer/accumulationTarget.js.map +1 -1
  61. package/lib/renderer/causticDiagnostics.d.ts +31 -0
  62. package/lib/renderer/causticDiagnostics.js +5 -0
  63. package/lib/renderer/causticDiagnostics.js.map +1 -0
  64. package/lib/renderer/causticEmission.d.ts +10 -0
  65. package/lib/renderer/causticEmission.js +40 -0
  66. package/lib/renderer/causticEmission.js.map +1 -0
  67. package/lib/renderer/causticUniform.d.ts +12 -0
  68. package/lib/renderer/causticUniform.js +22 -0
  69. package/lib/renderer/causticUniform.js.map +1 -0
  70. package/lib/renderer/caustics.d.ts +16 -0
  71. package/lib/renderer/caustics.js +93 -0
  72. package/lib/renderer/caustics.js.map +1 -0
  73. package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
  74. package/lib/renderer/glsl/aovReflectance.glsl.js +53 -53
  75. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
  76. package/lib/renderer/glsl/aovTrace.frag.js +33 -33
  77. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -9
  78. package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
  79. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
  80. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
  81. package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -73
  82. package/lib/renderer/glsl/chunks/intersect.glsl.js +426 -428
  83. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
  84. package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
  85. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +156 -153
  86. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
  87. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +771 -771
  88. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  89. package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
  90. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
  91. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +68 -63
  92. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
  93. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +94 -76
  94. package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
  95. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
  96. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +140 -117
  97. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
  98. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +14 -14
  99. package/lib/renderer/glsl/chunks/sampling.glsl.js +89 -92
  100. package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
  101. package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
  102. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  103. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
  104. package/lib/renderer/glsl/depthQuery.frag.js +2 -2
  105. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
  106. package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
  107. package/lib/renderer/glsl/rayTrace.frag.js +260 -237
  108. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  109. package/lib/renderer/glsl/shadowCatcher.frag.js +26 -26
  110. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
  111. package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
  112. package/lib/renderer/lightSampling.d.ts +3 -2
  113. package/lib/renderer/lightSampling.js +14 -8
  114. package/lib/renderer/lightSampling.js.map +1 -1
  115. package/lib/renderer/progressiveScheduler.js +4 -3
  116. package/lib/renderer/progressiveScheduler.js.map +1 -1
  117. package/lib/renderer/renderPipeline.d.ts +1 -0
  118. package/lib/renderer/renderPipeline.js.map +1 -1
  119. package/lib/renderer/researchTransport.d.ts +31 -0
  120. package/lib/renderer/researchTransport.js +5 -0
  121. package/lib/renderer/researchTransport.js.map +1 -0
  122. package/lib/renderer/sceneLayoutSchema.d.ts +1 -0
  123. package/lib/renderer/sceneLayoutSchema.js +2 -2
  124. package/lib/renderer/sceneLayoutSchema.js.map +1 -1
  125. package/lib/renderer/scratchSampling.js +6 -6
  126. package/lib/renderer/spectrum.d.ts +21 -0
  127. package/lib/renderer/spectrum.js +154 -0
  128. package/lib/renderer/spectrum.js.map +1 -0
  129. package/lib/renderer/webglLightSampling.d.ts +39 -0
  130. package/lib/renderer/webglLightSampling.js +15 -0
  131. package/lib/renderer/webglLightSampling.js.map +1 -0
  132. package/lib/renderer/wgpuBindlessSource.js +3 -3
  133. package/lib/renderer/wgpuShaderSource.d.ts +7 -0
  134. package/lib/renderer/wgpuShaderSource.js +2 -0
  135. package/lib/renderer/wgpuShaderSource.js.map +1 -1
  136. package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
  137. package/lib/renderer/wgsl/aovReflectance.wgsl.js +55 -55
  138. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
  139. package/lib/renderer/wgsl/aovResolve.wgsl.js +2 -2
  140. package/lib/renderer/wgsl/aovSnapshot.wgsl.js +1 -1
  141. package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
  142. package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
  143. package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
  144. package/lib/renderer/wgsl/causticLight.compute.js +537 -0
  145. package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
  146. package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
  147. package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
  148. package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
  149. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
  150. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
  151. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
  152. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
  153. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
  154. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
  155. package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
  156. package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
  157. package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
  158. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  159. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +37 -37
  160. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +598 -592
  161. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  162. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +14 -14
  163. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +2 -1
  164. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +75 -73
  165. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
  166. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +794 -794
  167. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +139 -139
  168. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  169. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +2 -1
  170. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +35 -25
  171. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
  172. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
  173. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +142 -138
  174. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
  175. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
  176. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +204 -187
  177. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
  178. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
  179. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +11 -11
  180. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
  181. package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
  182. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +89 -93
  183. package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
  184. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
  185. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
  186. package/lib/renderer/wgsl/compose.frag.wgsl.js +98 -98
  187. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  188. package/lib/renderer/wgsl/depthQuery.wgsl.js +2 -2
  189. package/lib/renderer/wgsl/jnlm.compute.js +74 -74
  190. package/lib/renderer/wgsl/offlineAdaptive.compute.js +25 -25
  191. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
  192. package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
  193. package/lib/renderer/wgsl/photonMap.compute.js +352 -0
  194. package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
  195. package/lib/renderer/wgsl/proceduralSky.compute.js +64 -64
  196. package/lib/renderer/wgsl/rayTrace.compute.d.ts +9 -1
  197. package/lib/renderer/wgsl/rayTrace.compute.js +448 -522
  198. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  199. package/lib/scene/MaterialState.d.ts +2 -2
  200. package/lib/scene/MaterialState.js +2 -2
  201. package/lib/scene/MaterialState.js.map +1 -1
  202. package/lib/settings/render.d.ts +11 -0
  203. package/lib/settings/render.js +1 -0
  204. package/lib/settings/render.js.map +1 -1
  205. package/lib/util/fromTHREE.js +2 -2
  206. package/lib/util/fromTHREE.js.map +1 -1
  207. package/lib/version.d.ts +1 -1
  208. package/lib/version.js +1 -1
  209. package/lib/version.js.map +1 -1
  210. package/llms.txt +2 -1
  211. package/package.json +3 -3
@@ -3,7 +3,6 @@ import { oidnAuxiliaryDefine } from './wgsl/oidnAuxiliary.wgsl.js';
3
3
  import { displayAovKind, DEFAULT_AOV_DISPLAY } from './aovDisplay.js';
4
4
  import { WGPUAov } from './WGPUAov.js';
5
5
  import { aovMask } from './aov.js';
6
- import { scheduleFrame } from '../util/frame.js';
7
6
  import { DisplayPostProcess } from './DisplayPostProcess.js';
8
7
  import { customDenoiseLayersMain, customDenoiseCoverageMain } from './wgsl/customDenoise.wgsl.js';
9
8
  import { WGPUCanvasSurface } from './WGPUCanvasSurface.js';
@@ -28,16 +27,16 @@ export const oidnAlbedoSrgbEncodeMain = /* wgsl */ `
28
27
  if (globalId.x >= u32(imageSize.x) || globalId.y >= u32(imageSize.y)) { return; }
29
28
  let index = globalId.x + globalId.y * u32(imageSize.x);
30
29
  let raw = textureLoad(in_albedo, vec2u(globalId.xy), 0);
31
- let encoded = sT(raw.rgb);
30
+ let encoded = sQ(raw.rgb);
32
31
  out_albedo[index] = vec4f(encoded, raw.a);
33
32
  `;
34
33
  export const oidnAlbedoSrgbDecodeMain = /* wgsl */ `
35
34
  if (globalId.x >= u32(imageSize.x) || globalId.y >= u32(imageSize.y)) { return; }
36
35
  let index = globalId.x + globalId.y * u32(imageSize.x);
37
36
  let filtered = in_albedo[index];
38
- let linear = sS(filtered.rgb);
37
+ let linear = sP(filtered.rgb);
39
38
  var result = linear;
40
- let jD = textureLoad(in_rawAlbedo, vec2i(globalId.xy), 0).a;
39
+ let jj = textureLoad(in_rawAlbedo, vec2i(globalId.xy), 0).a;
41
40
  if (preserveSurfaceTexture != 0u) {
42
41
 
43
42
 
@@ -45,33 +44,33 @@ if (preserveSurfaceTexture != 0u) {
45
44
  let pixel = vec2i(globalId.xy);
46
45
  let center = textureLoad(in_rawNormal, pixel, 0);
47
46
  let n = normalize(center.rgb * 2.0 - 1.0 + vec3f(1e-8));
48
- var oc = 0.0;
47
+ var nS = 0.0;
49
48
  for (var y = -1; y <= 1; y++) {
50
49
  for (var x = -1; x <= 1; x++) {
51
- let vw = textureLoad(in_rawNormal,
50
+ let vI = textureLoad(in_rawNormal,
52
51
  clamp(pixel + vec2i(x, y), vec2i(0), imageSize - 1), 0);
53
- let nn = normalize(vw.rgb * 2.0 - 1.0 + vec3f(1e-8));
54
- oc = max(oc,
55
- max(1.0 - dot(n, nn), abs(center.a - vw.a)));
52
+ let nn = normalize(vI.rgb * 2.0 - 1.0 + vec3f(1e-8));
53
+ nS = max(nS,
54
+ max(1.0 - dot(n, nn), abs(center.a - vI.a)));
56
55
  }
57
56
  }
58
- let vO = smoothstep(0.02, 0.1, oc);
57
+ let wc = smoothstep(0.02, 0.1, nS);
59
58
  let raw = clamp(textureLoad(in_rawAlbedo, pixel, 0).rgb,
60
59
  vec3f(0.0), vec3f(1.0));
61
- result = mix(raw, linear, vO);
60
+ result = mix(raw, linear, wc);
62
61
  }
63
- out_albedo[index] = vec4f(result, jD);
62
+ out_albedo[index] = vec4f(result, jj);
64
63
  `;
65
64
  export const oidnNormalSanitizeMain = /* wgsl */ `
66
65
  if (globalId.x >= u32(imageSize.x) || globalId.y >= u32(imageSize.y)) { return; }
67
66
  let index = globalId.x + globalId.y * u32(imageSize.x);
68
- out_normal[index] = vec4f(rL(in_normal[index].rgb), 1.0);
67
+ out_normal[index] = vec4f(rG(in_normal[index].rgb), 1.0);
69
68
  `;
70
69
  function createTimeQuery(device) {
71
70
  try {
72
71
  const querySet = device.createQuerySet({
73
72
  type: 'timestamp',
74
- count: 2
73
+ count: 16
75
74
  });
76
75
  const resolveBuffer = device.createBuffer({
77
76
  size: querySet.count * 8,
@@ -100,6 +99,9 @@ function makeWGPUAccumulationTarget(canvas, device, rayTracePass, surface, scrat
100
99
  // the next presentation or read resolves them once.
101
100
  let realtimeDeferPostProcess = false;
102
101
  let postProcessPending = false;
102
+ // Resolve uses the configuration that produced these buffers, even if another
103
+ // target has since changed the shared trace pass's caustic settings.
104
+ let sampledOutput;
103
105
  let offlineSamplesPerBatch = 0;
104
106
  let deferPostProcess = false;
105
107
  let sampleSequenceOffset = 0;
@@ -109,6 +111,7 @@ function makeWGPUAccumulationTarget(canvas, device, rayTracePass, surface, scrat
109
111
  let elapsedFrameTime = 32;
110
112
  let elapsedTileTime;
111
113
  let tileTimingGeneration = 0;
114
+ let causticBatchTimeMs = 0, photonBatchTimeMs = 0;
112
115
  let lastGpuTraceTiming;
113
116
  let displayTrace;
114
117
  let diagnosticsReadbackPromise = Promise.resolve();
@@ -235,6 +238,8 @@ function makeWGPUAccumulationTarget(canvas, device, rayTracePass, surface, scrat
235
238
  tileTimingGeneration++;
236
239
  tileRenderWithPredict?.needsUpdatePredicting();
237
240
  lastGpuTraceTiming = undefined;
241
+ causticBatchTimeMs = 0;
242
+ photonBatchTimeMs = 0;
238
243
  // A changed render configuration needs fresh timing. Refresh
239
244
  // its estimator without submitting a hidden probe in that mode.
240
245
  needsUpdatePredicting = offlineSamplesPerBatch === 0;
@@ -261,9 +266,9 @@ function makeWGPUAccumulationTarget(canvas, device, rayTracePass, surface, scrat
261
266
  var color = load_colorTex(uv, 0);
262
267
  let coverage = clamp(load_coverageTex(uv, 0).a, 0.0, 1.0);
263
268
  color = vec4f(
264
- jQ(color.r, coverage, flatBackgroundColor.a > 0.5, true),
265
- jQ(color.g, coverage, flatBackgroundColor.a > 0.5, true),
266
- jQ(color.b, coverage, flatBackgroundColor.a > 0.5, true), color.a);
269
+ jx(color.r, coverage, flatBackgroundColor.a > 0.5, true),
270
+ jx(color.g, coverage, flatBackgroundColor.a > 0.5, true),
271
+ jx(color.b, coverage, flatBackgroundColor.a > 0.5, true), color.a);
267
272
  output.color = color;
268
273
  `
269
274
  }));
@@ -387,11 +392,11 @@ output.color = color;
387
392
  oidnNormalSanitizePass.setOutputParams({ normal: { channels: 4 } });
388
393
  const outputPass = scratch.get('outputPass', () => new WGPUFullQuadPass('output', device, {
389
394
  fsMain: /* wgsl */ `
390
- let xs = min(
395
+ let xW = min(
391
396
  vec2i(uv2 * vec2f(colorTexRegion())),
392
397
  vec2i(colorTexRegion()) - vec2i(1)
393
398
  );
394
- var color = load_colorTex(xs, 0);
399
+ var color = load_colorTex(xW, 0);
395
400
  color = vec4(color.rgb * color.a, color.a);
396
401
  output.color = color;
397
402
  `,
@@ -424,7 +429,8 @@ output.color = color;
424
429
  _updateSeed(renderWidth, renderHeight);
425
430
  const activeTimeQuery = timeQuery;
426
431
  const isUnmapped = activeTimeQuery?.resultBuffer.mapState === 'unmapped';
427
- rayTracePass.createPass(encoder, clear, tiles, activeTimeQuery ? { querySet: activeTimeQuery.querySet } : undefined);
432
+ const measuredCausticBatch = rayTracePass.createPass(encoder, clear, tiles, activeTimeQuery ? { querySet: activeTimeQuery.querySet } : undefined);
433
+ sampledOutput = rayTracePass.getOutputBuffer();
428
434
  if (activeTimeQuery) {
429
435
  encoder.resolveQuerySet(activeTimeQuery.querySet, 0, activeTimeQuery.querySet.count, activeTimeQuery.resolveBuffer, 0);
430
436
  if (isUnmapped) {
@@ -440,14 +446,25 @@ output.color = color;
440
446
  diagnosticsReadbackPromise = activeTimeQuery.resultBuffer
441
447
  .mapAsync(GPUMapMode.READ).then(() => {
442
448
  const mappedRange = activeTimeQuery.resultBuffer.getMappedRange();
443
- const times = new BigInt64Array(mappedRange, 0, 2);
444
- const traceTime = Math.max(Number(times[1] - times[0]) / 1_000_000, 0.1);
449
+ const times = new BigInt64Array(mappedRange, 0, 16);
450
+ const causticTime = (measuredCausticBatch & 1) ? Math.max(Number(times[3] - times[2] + times[5] - times[4]) / 1_000_000, 0) : 0;
451
+ // Independent timestamp pairs avoid Metal's cross-pass counter bug.
452
+ const photonTime = (measuredCausticBatch & 2) ? Math.max(Number(times[7] - times[6] + times[9] - times[8] + times[11] - times[10] + times[13] - times[12] + times[15] - times[14]) / 1_000_000, 0) : 0;
453
+ const ptTime = Math.max(Number(times[1] - times[0]) / 1_000_000, 0);
445
454
  // A readback may finish after scene preparation or a mode switch.
446
455
  // It must not overwrite throughput learned for the current scene.
447
456
  const timingIsCurrent = !disposed && submittedTimingGeneration === tileTimingGeneration &&
448
457
  !isFirstFrame;
449
458
  if (timingIsCurrent) {
459
+ if (measuredCausticBatch & 1)
460
+ causticBatchTimeMs = causticTime;
461
+ if (measuredCausticBatch & 2)
462
+ photonBatchTimeMs = photonTime;
463
+ const traceTime = Math.max(ptTime + (causticBatchTimeMs + photonBatchTimeMs) / submittedTileCount, .1);
450
464
  lastGpuTraceTiming = {
465
+ causticTimeMs: causticBatchTimeMs, photonMappingTimeMs: photonBatchTimeMs,
466
+ photonStagesMs: (measuredCausticBatch & 2) ? [6, 8, 10, 12, 14].map(i => Math.max(Number(times[i + 1] - times[i]) / 1_000_000, 0)) : undefined,
467
+ pathTracingTimeMs: ptTime,
451
468
  tileTimeMs: traceTime,
452
469
  tileTimePerSampleMs: traceTime,
453
470
  estimatedFullFrameTimeMs: traceTime * submittedTileCount,
@@ -556,7 +573,7 @@ output.color = color;
556
573
  denoisedTexture = prepareDenoisedTexture(encoder, denoisedBuffer, denoisedTexture);
557
574
  if (resolve) {
558
575
  rayTraceOutputPass.setSampleCount(sampleCount);
559
- rayTraceOutputPass.createPass(encoder, rayTracePass.getOutputBuffer(), resolvedHasShadowCatcher);
576
+ rayTraceOutputPass.createPass(encoder, sampledOutput ?? rayTracePass.getOutputBuffer(), resolvedHasShadowCatcher);
560
577
  }
561
578
  composePass.setUniform('shadowCatcherEnabled', +resolvedHasShadowCatcher);
562
579
  let foregroundTexture = getFrameInput(resolvedHasShadowCatcher ? 'foreground' : 'color');
@@ -674,19 +691,25 @@ output.color = color;
674
691
  drawPostProcess(device.createCommandEncoder(), false, true);
675
692
  }
676
693
  function trace(camera, maxSamples) {
694
+ applyTraceQuality();
677
695
  // A call to draw means accumulation has been invalidated. Cancel OIDN
678
696
  // before checking shader compilation: pipeline-affecting parameter changes
679
697
  // otherwise leave the previous asynchronous denoise alive while resources
680
698
  // and bind groups are being replaced.
681
699
  cancelDenoise();
682
- if (rayTracePass.isCompilingShader()) {
683
- isFirstFrame = true;
684
- sampleCount = 0;
685
- return true;
686
- }
687
700
  // Update the camera before compiling and sampling.
688
701
  rayTracePass.setCamera(camera);
689
702
  aovCamera = camera;
703
+ rayTracePass.updatePipeline();
704
+ const pipelineError = rayTracePass.getPipelineError();
705
+ if (pipelineError)
706
+ throw pipelineError;
707
+ // Waiting for a shader is not an input change: this render's samples and
708
+ // complete image stay valid, and only Render's restart discards them.
709
+ if (rayTracePass.isCompilingShader() || !rayTracePass.isPipelineReady())
710
+ return true;
711
+ if (execution?.aovs?.length && !rayTracePass.prepareAovs(execution.aovs))
712
+ return true;
690
713
  const resetAccumulation = isFirstFrame;
691
714
  if (resetAccumulation)
692
715
  aov.reset();
@@ -696,20 +719,11 @@ output.color = color;
696
719
  denoiseValid = false;
697
720
  tileTimingGeneration++;
698
721
  elapsedTileTime = undefined;
699
- rayTracePass.setCamera(camera);
700
722
  (tileRenderWithPredict || tileRender).reset();
701
723
  // Reset
702
724
  isFirstFrame = false;
703
725
  sampleCount = 0;
704
726
  }
705
- rayTracePass.updatePipeline();
706
- if (rayTracePass.isCompilingShader() || !rayTracePass.isPipelineReady()) {
707
- isFirstFrame = true;
708
- sampleCount = 0;
709
- return true;
710
- }
711
- if (execution?.aovs?.length && !rayTracePass.prepareAovs(execution.aovs))
712
- return true;
713
727
  // Keep the last completed denoise visible while a static while-converging batch
714
728
  // accumulates, including the incomplete first sample after a scene edit.
715
729
  if (!preserveDenoisedOutput || isFirstFrame) {
@@ -840,6 +854,7 @@ output.color = color;
840
854
  // The target retains its complete presentation texture through the resize.
841
855
  clearDenoisedOutput();
842
856
  tileTimingGeneration++;
857
+ sampledOutput = undefined;
843
858
  renderWidth = nextRenderWidth;
844
859
  renderHeight = nextRenderHeight;
845
860
  (tileRenderWithPredict || tileRender).setSize(renderWidth, renderHeight);
@@ -1289,24 +1304,30 @@ output.color = color;
1289
1304
  rayTraceOutputPass.dispose();
1290
1305
  });
1291
1306
  }
1307
+ // Keep tracing quality out of presentation: an old fallback must not
1308
+ // invalidate a new caustic pipeline while it is compiling.
1309
+ function applyTraceQuality() {
1310
+ if (execution) {
1311
+ rayTracePass.setQuality(execution);
1312
+ rayTracePass.setBevelSamples(execution.bevelSamples);
1313
+ rayTracePass.setAlphaBlendStrategy(execution.alphaBlendStrategy);
1314
+ rayTracePass.setSamplingPattern(execution.samplingPattern, execution.maxSamples);
1315
+ }
1316
+ }
1292
1317
  function activate() {
1293
1318
  rayTracePass.useTarget(targetKey);
1294
1319
  if (renderWidth && renderHeight)
1295
1320
  rayTracePass.setSize(renderWidth, renderHeight, screenWidth / screenHeight);
1296
1321
  if (execution) {
1297
- rayTracePass.setQuality(execution);
1298
1322
  rayTracePass.setClamping(execution.clamping);
1299
1323
  rayTracePass.setTransmissiveOutputAlphaMin(execution.transmissiveOutputAlphaMin);
1300
- rayTracePass.setBevelSamples(execution.bevelSamples);
1301
- rayTracePass.setAlphaBlendStrategy(execution.alphaBlendStrategy);
1302
- rayTracePass.setSamplingPattern(execution.samplingPattern, execution.maxSamples);
1303
1324
  }
1304
1325
  }
1305
1326
  return {
1306
1327
  invalidateTiming: invalidateTilePrediction,
1307
1328
  invalidateDenoisedOutput() { cancelDenoise(); denoiseValid = false; preserveDenoisedOutput = false; },
1308
1329
  configure(settings) {
1309
- const timingKeys = ['bounces', 'bevel', 'bevelSamples', 'directLightCandidates', 'dispersionWavelengths', 'alphaBlendStrategy', 'samplingPattern', 'clamping', 'transmissiveOutputAlphaMin'];
1330
+ const timingKeys = ['bounces', 'bevel', 'bevelSamples', 'directLightCandidates', 'dispersionWavelengths', 'alphaBlendStrategy', 'samplingPattern', 'clamping', 'transmissiveOutputAlphaMin', 'caustics'];
1310
1331
  if (!execution || timingKeys.some(key => execution[key] !== settings[key]))
1311
1332
  invalidateTilePrediction();
1312
1333
  if (settings.aovs?.length && (!Number.isInteger(settings.aovSamples ?? 16) || (settings.aovSamples ?? 16) < 1))
@@ -1341,7 +1362,8 @@ output.color = color;
1341
1362
  },
1342
1363
  setRealtimeBatchBudget(milliseconds) { realtimeBatchBudgetMs = milliseconds; },
1343
1364
  setRealtimeDeferPostProcess(defer) { realtimeDeferPostProcess = defer; },
1344
- getRealtimeSampleTime() { return lastGpuTraceTiming?.tileCount === 1 ? lastGpuTraceTiming.estimatedFullFrameTimeMs : undefined; },
1365
+ getRealtimeSampleTime() { return lastGpuTraceTiming?.estimatedFullFrameTimeMs; },
1366
+ getRealtimeTileTime() { return lastGpuTraceTiming?.tileTimePerSampleMs; },
1345
1367
  whenTraceAvailable() { return traceQueue.whenAvailable(); },
1346
1368
  get presentationComplete() { return !!presentationTexture; },
1347
1369
  get presentationDenoised() { return presentationDenoised; },
@@ -1434,9 +1456,13 @@ output.color = color;
1434
1456
  refineAovs(camera, samples = sampleCount) {
1435
1457
  if (samples < 1 || !execution?.aovs?.length)
1436
1458
  return true;
1459
+ applyTraceQuality();
1437
1460
  rayTracePass.setCamera(camera);
1438
1461
  aovCamera = camera;
1439
1462
  rayTracePass.updatePipeline();
1463
+ const pipelineError = rayTracePass.getPipelineError();
1464
+ if (pipelineError)
1465
+ throw pipelineError;
1440
1466
  if (rayTracePass.isCompilingShader() || !rayTracePass.isPipelineReady())
1441
1467
  return false;
1442
1468
  return rayTracePass.renderAovs(aov, camera, samples, execution.aovSamples ?? 16, execution.aovs);
@@ -1444,11 +1470,21 @@ output.color = color;
1444
1470
  async readAov(kind) {
1445
1471
  if (!execution?.aovs?.includes(kind) || !aovCamera || (sampleCount < 1 && aov.samples < 1))
1446
1472
  throw new Error('AOV output is not ready');
1447
- rayTracePass.setCamera(aovCamera);
1448
- while (!rayTracePass.renderAovs(aov, aovCamera, Math.max(sampleCount, aov.samples), execution.aovSamples ?? 16, execution.aovs)) {
1473
+ for (;;) {
1449
1474
  if (disposed)
1450
1475
  throw new Error('AOV compilation was interrupted');
1451
- await new Promise(resolve => scheduleFrame(() => resolve()));
1476
+ activate();
1477
+ applyTraceQuality();
1478
+ rayTracePass.setCamera(aovCamera);
1479
+ rayTracePass.updatePipeline();
1480
+ const pipelineError = rayTracePass.getPipelineError();
1481
+ if (pipelineError)
1482
+ throw pipelineError;
1483
+ if (!rayTracePass.isCompilingShader() && rayTracePass.isPipelineReady() &&
1484
+ rayTracePass.renderAovs(aov, aovCamera, Math.max(sampleCount, aov.samples), execution.aovSamples ?? 16, execution.aovs))
1485
+ break;
1486
+ // Display frames stop on a hidden page, but compilation still completes.
1487
+ await new Promise(resolve => setTimeout(resolve, 16));
1452
1488
  }
1453
1489
  return aov.read(kind);
1454
1490
  },
@@ -1532,7 +1568,7 @@ output.color = color;
1532
1568
  setSize(input.width, input.height);
1533
1569
  rayTracePass.useTarget(targetKey);
1534
1570
  rayTracePass.setSize(renderWidth, renderHeight, screenWidth / screenHeight);
1535
- const buffers = rayTracePass.getOutputBuffer();
1571
+ const buffers = sampledOutput = rayTracePass.getOutputBuffer();
1536
1572
  const shadow = new Float32Array(input.width * input.height * 12);
1537
1573
  for (let i = 0; i < input.width * input.height; i++) {
1538
1574
  shadow.set(input.shadow.subarray(i * 8, i * 8 + 8), i * 12);