@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.
- package/README.md +3 -0
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +8949 -7915
- package/docs/api.md +42 -21
- package/docs/caustics.md +80 -0
- package/docs/materials.md +5 -4
- package/docs/progressive-rendering.md +50 -9
- package/docs/technical/continuous-spectrum.md +93 -0
- package/docs/updates.md +3 -1
- package/docs/viewer-checklist.md +65 -0
- package/docs/viewer.md +7 -5
- package/lib/Display.js +1 -1
- package/lib/Display.js.map +1 -1
- package/lib/Engine.d.ts +2 -0
- package/lib/Engine.js +47 -12
- package/lib/Engine.js.map +1 -1
- package/lib/Render.d.ts +61 -13
- package/lib/Render.js +169 -19
- package/lib/Render.js.map +1 -1
- package/lib/RenderLoop.d.ts +7 -3
- package/lib/RenderLoop.js +68 -44
- package/lib/RenderLoop.js.map +1 -1
- package/lib/main.d.ts +1 -1
- package/lib/main.js.map +1 -1
- package/lib/oidn.d.ts +1 -1
- package/lib/oidnBundledWeights.cdn.d.ts +2 -2
- package/lib/renderer/GPUTraceQueue.d.ts +2 -1
- package/lib/renderer/GPUTraceQueue.js +3 -1
- package/lib/renderer/GPUTraceQueue.js.map +1 -1
- package/lib/renderer/RenderingPipeline.d.ts +4 -0
- package/lib/renderer/RenderingPipeline.js.map +1 -1
- package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
- package/lib/renderer/WGPUCausticLightPass.js +217 -0
- package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
- package/lib/renderer/WGPUEnvMapDistribution.js +18 -18
- package/lib/renderer/WGPUFullQuadPass.js +3 -3
- package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
- package/lib/renderer/WGPUPhotonMap.js +191 -0
- package/lib/renderer/WGPUPhotonMap.js.map +1 -0
- package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
- package/lib/renderer/WGPURayTraceOutput.js +122 -28
- package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
- package/lib/renderer/WGPURayTracePass.d.ts +48 -17
- package/lib/renderer/WGPURayTracePass.js +538 -340
- package/lib/renderer/WGPURayTracePass.js.map +1 -1
- package/lib/renderer/WGPURenderingPipeline.js +83 -47
- package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
- package/lib/renderer/WebGLFullQuadPass.js +4 -0
- package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
- package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
- package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
- package/lib/renderer/WebGLRayTracePass.d.ts +6 -1
- package/lib/renderer/WebGLRayTracePass.js +43 -6
- package/lib/renderer/WebGLRayTracePass.js.map +1 -1
- package/lib/renderer/WebGLRenderingPipeline.js +10 -14
- package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLSkyBaker.js +60 -60
- package/lib/renderer/accumulationTarget.d.ts +2 -1
- package/lib/renderer/accumulationTarget.js.map +1 -1
- package/lib/renderer/causticDiagnostics.d.ts +31 -0
- package/lib/renderer/causticDiagnostics.js +5 -0
- package/lib/renderer/causticDiagnostics.js.map +1 -0
- package/lib/renderer/causticEmission.d.ts +10 -0
- package/lib/renderer/causticEmission.js +40 -0
- package/lib/renderer/causticEmission.js.map +1 -0
- package/lib/renderer/causticUniform.d.ts +12 -0
- package/lib/renderer/causticUniform.js +22 -0
- package/lib/renderer/causticUniform.js.map +1 -0
- package/lib/renderer/caustics.d.ts +16 -0
- package/lib/renderer/caustics.js +93 -0
- package/lib/renderer/caustics.js.map +1 -0
- package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
- package/lib/renderer/glsl/aovReflectance.glsl.js +53 -53
- package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
- package/lib/renderer/glsl/aovTrace.frag.js +33 -33
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -9
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -73
- package/lib/renderer/glsl/chunks/intersect.glsl.js +426 -428
- package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +156 -153
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +771 -771
- package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
- package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +68 -63
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js +94 -76
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +140 -117
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +14 -14
- package/lib/renderer/glsl/chunks/sampling.glsl.js +89 -92
- package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
- package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
- package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
- package/lib/renderer/glsl/depthQuery.frag.js +2 -2
- package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
- package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
- package/lib/renderer/glsl/rayTrace.frag.js +260 -237
- package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/shadowCatcher.frag.js +26 -26
- package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
- package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
- package/lib/renderer/lightSampling.d.ts +3 -2
- package/lib/renderer/lightSampling.js +14 -8
- package/lib/renderer/lightSampling.js.map +1 -1
- package/lib/renderer/progressiveScheduler.js +4 -3
- package/lib/renderer/progressiveScheduler.js.map +1 -1
- package/lib/renderer/renderPipeline.d.ts +1 -0
- package/lib/renderer/renderPipeline.js.map +1 -1
- package/lib/renderer/researchTransport.d.ts +31 -0
- package/lib/renderer/researchTransport.js +5 -0
- package/lib/renderer/researchTransport.js.map +1 -0
- package/lib/renderer/sceneLayoutSchema.d.ts +1 -0
- package/lib/renderer/sceneLayoutSchema.js +2 -2
- package/lib/renderer/sceneLayoutSchema.js.map +1 -1
- package/lib/renderer/scratchSampling.js +6 -6
- package/lib/renderer/spectrum.d.ts +21 -0
- package/lib/renderer/spectrum.js +154 -0
- package/lib/renderer/spectrum.js.map +1 -0
- package/lib/renderer/webglLightSampling.d.ts +39 -0
- package/lib/renderer/webglLightSampling.js +15 -0
- package/lib/renderer/webglLightSampling.js.map +1 -0
- package/lib/renderer/wgpuBindlessSource.js +3 -3
- package/lib/renderer/wgpuShaderSource.d.ts +7 -0
- package/lib/renderer/wgpuShaderSource.js +2 -0
- package/lib/renderer/wgpuShaderSource.js.map +1 -1
- package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
- package/lib/renderer/wgsl/aovReflectance.wgsl.js +55 -55
- package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
- package/lib/renderer/wgsl/aovResolve.wgsl.js +2 -2
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js +1 -1
- package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
- package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
- package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
- package/lib/renderer/wgsl/causticLight.compute.js +537 -0
- package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
- package/lib/renderer/wgsl/chunks/envMap.wgsl.js +37 -37
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js +598 -592
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +14 -14
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +2 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +75 -73
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +794 -794
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +139 -139
- package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +2 -1
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +35 -25
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +142 -138
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +204 -187
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +11 -11
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js +89 -93
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
- package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
- package/lib/renderer/wgsl/compose.frag.wgsl.js +98 -98
- package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
- package/lib/renderer/wgsl/depthQuery.wgsl.js +2 -2
- package/lib/renderer/wgsl/jnlm.compute.js +74 -74
- package/lib/renderer/wgsl/offlineAdaptive.compute.js +25 -25
- package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
- package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
- package/lib/renderer/wgsl/photonMap.compute.js +352 -0
- package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
- package/lib/renderer/wgsl/proceduralSky.compute.js +64 -64
- package/lib/renderer/wgsl/rayTrace.compute.d.ts +9 -1
- package/lib/renderer/wgsl/rayTrace.compute.js +448 -522
- package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
- package/lib/scene/MaterialState.d.ts +2 -2
- package/lib/scene/MaterialState.js +2 -2
- package/lib/scene/MaterialState.js.map +1 -1
- package/lib/settings/render.d.ts +11 -0
- package/lib/settings/render.js +1 -0
- package/lib/settings/render.js.map +1 -1
- package/lib/util/fromTHREE.js +2 -2
- package/lib/util/fromTHREE.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/llms.txt +2 -1
- package/package.json +3 -3
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { causticDiagnostics } from './causticDiagnostics.js';
|
|
2
|
+
import { WGPUPhotonMap, photonRadius } from './WGPUPhotonMap.js';
|
|
3
|
+
import { defaultPhotonMapPhotons } from './wgsl/photonMap.compute.js';
|
|
4
|
+
import { researchTransport } from './researchTransport.js';
|
|
5
|
+
import { causticEmissionTable } from './causticEmission.js';
|
|
6
|
+
import { WGPUCausticLightPass } from './WGPUCausticLightPass.js';
|
|
7
|
+
import { causticLightCompute } from './wgsl/causticLight.compute.js';
|
|
8
|
+
import { causticCasterSpheres, hasStochasticCausticCaster, causticCasterSphere, isCausticSpecular } from './caustics.js';
|
|
1
9
|
import { AovPrimitiveCache } from './AovPrimitiveCache.js';
|
|
2
10
|
import { packAovScene } from './aovScene.js';
|
|
3
11
|
import { aovMask } from './aov.js';
|
|
@@ -46,7 +54,7 @@ import { updateMeshInstances, updateMeshInstanceTransforms } from './prepareMesh
|
|
|
46
54
|
import { buildTLAS, concatBLASBuffers, getTraversalStackSizes, refitBLAS, refitTLAS } from './bvhAccel.js';
|
|
47
55
|
import { isTextureDataSource } from '../scene/Texture.js';
|
|
48
56
|
import { DEFAULT_BEVEL_SAMPLES, DEFAULT_DIRECT_LIGHT_CANDIDATES, normalizeBevelSamples, normalizeDirectLightCandidates } from './RenderingPipeline.js';
|
|
49
|
-
import { EmissiveTriangleCache, LightChoiceBuffer, estimateEnvironmentPower
|
|
57
|
+
import { EmissiveTriangleCache, LightChoiceBuffer, estimateEnvironmentPower } from './lightSampling.js';
|
|
50
58
|
import { getOpenPbrEnergyLutData, OPENPBR_ENERGY_LUT_HEIGHT, OPENPBR_ENERGY_LUT_WIDTH } from './openPbrEnergyLut.js';
|
|
51
59
|
import { offlineAdaptiveCompute } from './wgsl/offlineAdaptive.compute.js';
|
|
52
60
|
import { normalizeOfflineAdaptiveSamplingOptions } from './OfflineAdaptiveSampling.js';
|
|
@@ -78,6 +86,10 @@ function joinWebGPUErrors(...errors) {
|
|
|
78
86
|
.filter(Boolean))
|
|
79
87
|
].join('\n');
|
|
80
88
|
}
|
|
89
|
+
const MAX_TRACE_VARIANTS = 32;
|
|
90
|
+
// Requests are cheap but drivers are not, and a running compilation cannot be cancelled.
|
|
91
|
+
const MAX_CONCURRENT_COMPILES = 2;
|
|
92
|
+
const MAX_QUEUED_COMPILES = 4;
|
|
81
93
|
function shaderCompilationNow() {
|
|
82
94
|
return typeof performance === 'undefined' ? Date.now() : performance.now();
|
|
83
95
|
}
|
|
@@ -113,6 +125,16 @@ class WGPURayTracePass {
|
|
|
113
125
|
_width = 0;
|
|
114
126
|
_height = 0;
|
|
115
127
|
_meshes = [];
|
|
128
|
+
_causticKey = '';
|
|
129
|
+
_photonMapping = false;
|
|
130
|
+
_photonMap;
|
|
131
|
+
_causticLight;
|
|
132
|
+
_causticSettings;
|
|
133
|
+
_causticPerspective = true;
|
|
134
|
+
_causticCameraScale = 1;
|
|
135
|
+
_causticBounds = [0, 0, 0, 0];
|
|
136
|
+
_transport;
|
|
137
|
+
_lightTracing = false;
|
|
116
138
|
_sceneEnvironment = new SceneEnvironmentCache();
|
|
117
139
|
_rectLightTextures = new RectLightTextureTracker();
|
|
118
140
|
_analyticLights = [];
|
|
@@ -147,6 +169,8 @@ class WGPURayTracePass {
|
|
|
147
169
|
if (!output)
|
|
148
170
|
return;
|
|
149
171
|
this._targetOutputs.delete(target);
|
|
172
|
+
this._causticLight?.release(target);
|
|
173
|
+
this._photonMap?.release(target);
|
|
150
174
|
for (const buffer of output.buffers)
|
|
151
175
|
buffer.destroy();
|
|
152
176
|
if (this._activeTarget === target) {
|
|
@@ -155,17 +179,23 @@ class WGPURayTracePass {
|
|
|
155
179
|
}
|
|
156
180
|
}
|
|
157
181
|
getTargetMemoryBytes(target) {
|
|
158
|
-
return this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0
|
|
182
|
+
return ((this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0) +
|
|
183
|
+
(this._causticLight?.bytes(target) ?? 0) +
|
|
184
|
+
(this._photonMap?.bytes(target) ?? 0));
|
|
159
185
|
}
|
|
160
186
|
useTarget(target) {
|
|
187
|
+
if (this._photonMapping && this._activeTarget !== target)
|
|
188
|
+
this._needsPhotonTargetBindGroups = true;
|
|
161
189
|
this._activeTarget = target;
|
|
162
190
|
const output = this._targetOutputs.get(target);
|
|
163
|
-
[this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] =
|
|
191
|
+
[this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] =
|
|
192
|
+
output?.buffers ?? [];
|
|
164
193
|
if (output?.bindGroup && output.pipeline === this._rayTracePipeline && this._rayTraceBindGroups) {
|
|
165
194
|
this._rayTraceBindGroups[this._bindless ? 2 : 3] = output.bindGroup;
|
|
195
|
+
this._needsUpdateOutputBindGroup = false;
|
|
166
196
|
}
|
|
167
197
|
else {
|
|
168
|
-
this.
|
|
198
|
+
this._needsUpdateOutputBindGroup = true;
|
|
169
199
|
}
|
|
170
200
|
}
|
|
171
201
|
_outColorBuffer;
|
|
@@ -212,22 +242,25 @@ class WGPURayTracePass {
|
|
|
212
242
|
_skyBaker;
|
|
213
243
|
_backgroundImageSource;
|
|
214
244
|
_backgroundTex;
|
|
215
|
-
_rayTracePipelineLayout;
|
|
216
245
|
_rayTraceBindGroups;
|
|
246
|
+
_photonEmissionGroups;
|
|
217
247
|
_blasLayoutMask = 1;
|
|
218
|
-
_compiledBlasLayoutMask = 0;
|
|
219
|
-
_compiledAnisotropyDirections = false;
|
|
220
|
-
_compiledCoatNormals = false;
|
|
221
|
-
_compiledHasAlpha = true;
|
|
222
|
-
_compiledSceneHasAlpha = true;
|
|
223
248
|
_alphaBlendStrategy = 'stochastic';
|
|
224
249
|
_constants = initialSceneConstants();
|
|
225
250
|
_retainedShaderCapabilities;
|
|
226
251
|
_retainedShaderStructure;
|
|
227
252
|
_shaderCapabilities = new WGPUShaderCapabilities();
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
253
|
+
_targetTransport = new WeakMap();
|
|
254
|
+
_traceVariants = new Map();
|
|
255
|
+
_queuedCompiles = [];
|
|
256
|
+
_activeCompiles = 0;
|
|
257
|
+
_traceModules = new Map();
|
|
258
|
+
_requestedVariant;
|
|
259
|
+
_activeVariant;
|
|
260
|
+
_activeInputs;
|
|
261
|
+
_awaitingRetirement;
|
|
262
|
+
_resourceError;
|
|
263
|
+
_aovPipelinesFailed = false;
|
|
231
264
|
_hasTransmission = false;
|
|
232
265
|
_hasShadowCatcher = false;
|
|
233
266
|
_hasDisplacement = false;
|
|
@@ -242,19 +275,20 @@ class WGPURayTracePass {
|
|
|
242
275
|
_apertureRadius = 0;
|
|
243
276
|
_focusDistance = 10;
|
|
244
277
|
_needsUpdateBindGroups = true;
|
|
245
|
-
|
|
278
|
+
// Photon maps are per target. Switching targets only rebinds them, which
|
|
279
|
+
// must not send steady frames through the asynchronous pipeline path.
|
|
280
|
+
_needsPhotonTargetBindGroups = false;
|
|
281
|
+
// Target selection is not a scene-resource edit. A stale fallback must not
|
|
282
|
+
// leave global bindings dirty after switching back to a prepared target.
|
|
283
|
+
_needsUpdateOutputBindGroup = false;
|
|
246
284
|
_aovPipelines = new WeakMap();
|
|
247
285
|
_depthCompute;
|
|
248
286
|
_depthTracePipeline;
|
|
249
287
|
_depthPipeline;
|
|
250
288
|
_shaderCache;
|
|
251
289
|
_debugBVHBuffer;
|
|
252
|
-
_isCompilingShader = false;
|
|
253
|
-
_shaderCompilationDiagnostics;
|
|
254
|
-
_pipelineError;
|
|
255
290
|
// Fold scene invariants before backend inlining; runtime edits use uniforms.
|
|
256
291
|
_bakePipelineConstants = true;
|
|
257
|
-
_failedPipelineSignature;
|
|
258
292
|
constructor(device, _textureSelection = { requested: 'atlas' }) {
|
|
259
293
|
this._textureSelection = _textureSelection;
|
|
260
294
|
this._device = device;
|
|
@@ -298,8 +332,14 @@ class WGPURayTracePass {
|
|
|
298
332
|
device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, new Float32Array([Number.MAX_SAFE_INTEGER]), { bytesPerRow: 4 }, [1, 1]);
|
|
299
333
|
}
|
|
300
334
|
getOutputBuffer() {
|
|
335
|
+
// Another render may have traced since this target's samples were written.
|
|
336
|
+
const transport = this._targetTransport.get(this._activeTarget) ?? 0;
|
|
301
337
|
return {
|
|
302
338
|
color: this._outColorBuffer,
|
|
339
|
+
...(transport & 2 || ((this._transport?.debugGroup ?? -1) >= 0 || causticDiagnostics.debugGroup >= 0) ? { wideHdr: true } : {}),
|
|
340
|
+
...(transport & 1 && this._causticLight?.get(this._activeTarget)
|
|
341
|
+
? { caustic: this._causticLight.get(this._activeTarget) }
|
|
342
|
+
: {}),
|
|
303
343
|
normal: this._outNormalBuffer,
|
|
304
344
|
albedo: this._outAlbedoBuffer,
|
|
305
345
|
shadowCatcher: this._outShadowCatcherBuffer
|
|
@@ -494,12 +534,26 @@ class WGPURayTracePass {
|
|
|
494
534
|
varianceBuffer.destroy();
|
|
495
535
|
}
|
|
496
536
|
}
|
|
497
|
-
_createPipelineLayout() {
|
|
537
|
+
_createPipelineLayout(bindless, photonMapping) {
|
|
498
538
|
const descriptor = {
|
|
499
539
|
label: 'Ray Trace Pipeline Layout',
|
|
500
|
-
bindGroupLayouts: (
|
|
540
|
+
bindGroupLayouts: (bindless ? bindlessBindGroupLayouts() : rayTraceBindGroupLayouts()).map((layout, index) => {
|
|
541
|
+
if (photonMapping && index === (bindless ? 0 : 1))
|
|
542
|
+
layout = {
|
|
543
|
+
...layout,
|
|
544
|
+
entries: [
|
|
545
|
+
...layout.entries,
|
|
546
|
+
...[11, 12].map((binding) => ({
|
|
547
|
+
binding: binding + (bindless ? 4 : 0),
|
|
548
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
549
|
+
texture: { sampleType: 'uint' }
|
|
550
|
+
}))
|
|
551
|
+
]
|
|
552
|
+
};
|
|
553
|
+
return this._shaderCache.createBindGroupLayout(layout);
|
|
554
|
+
})
|
|
501
555
|
};
|
|
502
|
-
|
|
556
|
+
return this._shaderCache.createPipelineLayout(bindless ? resourceTableLayout(descriptor) : descriptor);
|
|
503
557
|
}
|
|
504
558
|
_updateGPUBuffer(label, gpuBuffer, data, minSize = WGPU_SCENE_BUFFER_MIN_BYTES) {
|
|
505
559
|
const device = this._device;
|
|
@@ -527,22 +581,46 @@ class WGPURayTracePass {
|
|
|
527
581
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
528
582
|
});
|
|
529
583
|
}
|
|
530
|
-
|
|
531
|
-
if (this.
|
|
584
|
+
_preparePhotonTarget() {
|
|
585
|
+
if (!this._photonMapping)
|
|
532
586
|
return;
|
|
587
|
+
this._photonMap ??= new WGPUPhotonMap(this._device);
|
|
588
|
+
const before = this._photonMap.get(this._activeTarget)?.data;
|
|
589
|
+
const requested = this._causticSettings?.photonMapping;
|
|
590
|
+
const n = typeof requested === 'object' ? requested.photonsPerSample : undefined;
|
|
591
|
+
const after = this._photonMap.ensure(this._activeTarget, Number.isFinite(n) && n > 0 ? n : defaultPhotonMapPhotons(this._width, this._height, causticDiagnostics.photonCountDivisor));
|
|
592
|
+
if (before !== after.data)
|
|
593
|
+
this._needsUpdateBindGroups = true;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Prepares the pipeline for the settings currently written to this pass.
|
|
597
|
+
* Everything except compilation runs synchronously, and compilation only
|
|
598
|
+
* writes to its own variant. Renders with different settings can therefore
|
|
599
|
+
* interleave without invalidating each other. Returns a promise while the
|
|
600
|
+
* requested state waits on the GPU; undefined means nothing is outstanding.
|
|
601
|
+
*/
|
|
602
|
+
updatePipeline() {
|
|
603
|
+
if (this._disposed)
|
|
604
|
+
return;
|
|
605
|
+
if (this._needsUpdateMeshData || this._needsUpdateMeshMembership || this._pendingGeometryUpdates.size ||
|
|
606
|
+
this._needsRefitTLAS || this._dirtyColorMeshes.size || this._pendingMaterialUpdate || this._needsUpdateLightSamplingData) {
|
|
607
|
+
// A failure can have the same shader signature as its repaired scene.
|
|
608
|
+
// Retry after authored updates, while keeping an unchanged failure idle.
|
|
609
|
+
this._resourceError = undefined;
|
|
610
|
+
for (const [signature, variant] of this._traceVariants)
|
|
611
|
+
if (variant.status === 'failed')
|
|
612
|
+
this._traceVariants.delete(signature);
|
|
613
|
+
if (this._aovPipelinesFailed) {
|
|
614
|
+
this._aovPipelines = new WeakMap();
|
|
615
|
+
this._aovPipelinesFailed = false;
|
|
616
|
+
}
|
|
533
617
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
this.
|
|
538
|
-
|
|
539
|
-
let compilationStartedAt;
|
|
540
|
-
let pipelineCompilationStartedAt;
|
|
618
|
+
this._awaitingRetirement = this._bindless?.retirement;
|
|
619
|
+
if (this._awaitingRetirement)
|
|
620
|
+
return this._awaitingRetirement;
|
|
621
|
+
if (this._resourceError)
|
|
622
|
+
return;
|
|
541
623
|
try {
|
|
542
|
-
if (this._bindless?.retirement)
|
|
543
|
-
await this._bindless.retirement;
|
|
544
|
-
if (this._disposed)
|
|
545
|
-
return;
|
|
546
624
|
if (this._needsUpdateMeshData) {
|
|
547
625
|
this._updateMeshData();
|
|
548
626
|
}
|
|
@@ -557,29 +635,49 @@ class WGPURayTracePass {
|
|
|
557
635
|
this._updateMeshColors();
|
|
558
636
|
if (this._pendingMaterialUpdate)
|
|
559
637
|
this._updateMaterialData();
|
|
638
|
+
this._setCaustics(this._causticSettings);
|
|
560
639
|
if (this._needsUpdateLightSamplingData) {
|
|
561
640
|
this._updateLightSamplingData();
|
|
562
641
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
this.
|
|
642
|
+
this._setCaustics(this._causticSettings);
|
|
643
|
+
if (this._lightTracing || this._photonMapping)
|
|
644
|
+
this._causticBounds = causticCasterSphere(this._meshes);
|
|
645
|
+
this._targetTransport.set(this._activeTarget, (+this._lightTracing) | (+this._photonMapping << 1));
|
|
646
|
+
this._preparePhotonTarget();
|
|
647
|
+
const pipelineInputs = JSON.stringify([
|
|
648
|
+
this._constants,
|
|
649
|
+
this._hasDisplacement,
|
|
650
|
+
this._alphaBlendStrategy,
|
|
651
|
+
this._blasLayoutMask,
|
|
652
|
+
this._bakePipelineConstants,
|
|
653
|
+
this._causticKey,
|
|
654
|
+
!!this._bindless
|
|
655
|
+
]);
|
|
566
656
|
// Steady rendering must not repeat capability selection and cache-key
|
|
567
657
|
// preparation. Dirty resources still take the normal bind-group path.
|
|
568
|
-
if (pipelineInputs === this.
|
|
569
|
-
this.
|
|
658
|
+
if (pipelineInputs === this._activeInputs &&
|
|
659
|
+
this._rayTracePipeline &&
|
|
660
|
+
this._rayTraceBindGroups &&
|
|
661
|
+
!this._needsUpdateBindGroups) {
|
|
662
|
+
if (this._needsPhotonTargetBindGroups || this._needsUpdateOutputBindGroup) {
|
|
663
|
+
this._needsPhotonTargetBindGroups = false;
|
|
664
|
+
this._needsUpdateOutputBindGroup = false;
|
|
665
|
+
this._updateBindGroups();
|
|
666
|
+
}
|
|
667
|
+
this._requestedVariant = this._activeVariant;
|
|
570
668
|
return;
|
|
669
|
+
}
|
|
571
670
|
this._rtUniform.views.sceneFeatureFlags[0] = wgpuSceneFeatureFlags(this._constants);
|
|
572
671
|
const requiredCapabilities = retainedShaderCapabilities(this._constants);
|
|
573
672
|
const constants = { ...this._constants, ...requiredCapabilities };
|
|
574
|
-
const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) &&
|
|
575
|
-
this._alphaBlendStrategy === 'deterministic-source-over';
|
|
673
|
+
const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) && this._alphaBlendStrategy === 'deterministic-source-over';
|
|
576
674
|
const hasSceneAlpha = Boolean(constants.HAS_ALPHA);
|
|
577
675
|
const pipelineConstantsKeys = wgpuPipelineConstantKeys(constants);
|
|
578
|
-
const defineCode = WGPU_SHADER_DEFINE_KEYS
|
|
579
|
-
.map((key) => `const ${key} = ${constants[key]};`)
|
|
580
|
-
.join('\n');
|
|
676
|
+
const defineCode = WGPU_SHADER_DEFINE_KEYS.map((key) => `const ${key} = ${constants[key]};`).join('\n');
|
|
581
677
|
const structure = {
|
|
582
678
|
hasDisplacement: this._hasDisplacement,
|
|
679
|
+
caustics: this._causticKey,
|
|
680
|
+
bindless: !!this._bindless,
|
|
583
681
|
hasAlpha,
|
|
584
682
|
hasSceneAlpha,
|
|
585
683
|
hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
|
|
@@ -588,10 +686,11 @@ class WGPURayTracePass {
|
|
|
588
686
|
bakePipelineConstants: this._bakePipelineConstants
|
|
589
687
|
};
|
|
590
688
|
const structureKey = JSON.stringify({
|
|
591
|
-
...structure,
|
|
689
|
+
...structure,
|
|
690
|
+
defineCode,
|
|
592
691
|
constants: pipelineConstantsKeys
|
|
593
|
-
.filter(key => !(key in requiredCapabilities))
|
|
594
|
-
.map(key => [key, constants[key]])
|
|
692
|
+
.filter((key) => !(key in requiredCapabilities))
|
|
693
|
+
.map((key) => [key, constants[key]])
|
|
595
694
|
});
|
|
596
695
|
// Prefer an existing compatible pipeline, including an earlier lean
|
|
597
696
|
// variant. Only grow the current variant when it cannot cover an edit;
|
|
@@ -599,265 +698,255 @@ class WGPURayTracePass {
|
|
|
599
698
|
const capabilities = this._shaderCapabilities.find(structureKey, requiredCapabilities) ??
|
|
600
699
|
retainedShaderCapabilities(this._constants, structureKey === this._retainedShaderStructure ? this._retainedShaderCapabilities : undefined);
|
|
601
700
|
Object.assign(constants, capabilities);
|
|
602
|
-
|
|
701
|
+
// Runtime feature values live in uniforms, so edits can share a pipeline
|
|
702
|
+
// as long as its compiled defines and overrides are the same.
|
|
703
|
+
const signature = JSON.stringify({
|
|
603
704
|
...structure,
|
|
604
|
-
|
|
705
|
+
defineCode,
|
|
706
|
+
constants: pipelineConstantsKeys.map((key) => [key, constants[key]])
|
|
605
707
|
});
|
|
606
|
-
|
|
607
|
-
|
|
708
|
+
const variant = this._traceVariants.get(signature) ??
|
|
709
|
+
this._startTraceVariant(signature, constants, { hasAlpha, hasSceneAlpha, defineCode, pipelineConstantsKeys, structureKey, capabilities });
|
|
710
|
+
this._requestedVariant = variant;
|
|
711
|
+
if (variant.status === 'pending') {
|
|
712
|
+
this._requestQueuedCompile(variant);
|
|
713
|
+
return variant.settled;
|
|
714
|
+
}
|
|
715
|
+
if (variant.status === 'failed')
|
|
608
716
|
return;
|
|
717
|
+
this._installTraceVariant(variant, pipelineInputs);
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
this._resourceError = error;
|
|
721
|
+
this._needsUpdateBindGroups = true;
|
|
722
|
+
console.error('Failed to update WebGPU ray tracing resources.', error);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
/** Runs only for a ready variant, without yielding, so tracing can start in this call. */
|
|
726
|
+
_installTraceVariant(variant, pipelineInputs) {
|
|
727
|
+
if (this._activeVariant !== variant) {
|
|
728
|
+
this._activeVariant = variant;
|
|
729
|
+
this._rayTracePipeline = variant.pipeline;
|
|
730
|
+
this._depthCompute = variant.depthCompute;
|
|
731
|
+
this._needsUpdateBindGroups = true;
|
|
732
|
+
}
|
|
733
|
+
const emission = () => causticEmissionTable(this._analyticLights, this._emissiveTriangles, this._causticBounds[3], this._envImageSource && 'sky' in this._envImageSource
|
|
734
|
+
? proceduralEnvironmentPower(this._envImageSource)
|
|
735
|
+
: estimateEnvironmentPower(this._envImageSource));
|
|
736
|
+
const casters = () => causticDiagnostics.aggregateCasters ? [this._causticBounds] : causticCasterSpheres(this._meshes);
|
|
737
|
+
if (this._lightTracing && this._causticLight) {
|
|
738
|
+
this._causticLight.setDistribution(casters(), emission());
|
|
739
|
+
this._causticLight.use(variant.lightEmitter);
|
|
740
|
+
}
|
|
741
|
+
if (this._photonMapping && this._photonMap) {
|
|
742
|
+
this._photonMap.emission.setDistribution(casters(), emission());
|
|
743
|
+
this._photonMap.emission.use(variant.photonEmitter);
|
|
744
|
+
}
|
|
745
|
+
// TODO rebind when lights, materials, bvh changed.
|
|
746
|
+
if (this._needsUpdateBindGroups || this._needsPhotonTargetBindGroups || this._needsUpdateOutputBindGroup) {
|
|
747
|
+
this._needsUpdateBindGroups = false;
|
|
748
|
+
this._needsPhotonTargetBindGroups = false;
|
|
749
|
+
this._needsUpdateOutputBindGroup = false;
|
|
750
|
+
this._updateBindGroups();
|
|
751
|
+
}
|
|
752
|
+
this._retainedShaderCapabilities = variant.capabilities;
|
|
753
|
+
this._retainedShaderStructure = variant.structureKey;
|
|
754
|
+
this._shaderCapabilities.remember(variant.structureKey, variant.capabilities);
|
|
755
|
+
this._activeInputs = pipelineInputs;
|
|
756
|
+
}
|
|
757
|
+
/** Snapshots everything compilation needs. The task must not read mutable pass state afterwards. */
|
|
758
|
+
_startTraceVariant(signature, constants, shader) {
|
|
759
|
+
const bake = this._bakePipelineConstants, bindless = this._bindless;
|
|
760
|
+
const hasDisplacement = this._hasDisplacement, blasLayoutMask = this._blasLayoutMask;
|
|
761
|
+
const lightTracing = this._lightTracing, photonMapping = this._photonMapping;
|
|
762
|
+
const causticKey = this._causticKey, transport = this._transport;
|
|
763
|
+
const { hasAlpha, hasSceneAlpha, defineCode, pipelineConstantsKeys } = shader;
|
|
764
|
+
const emitterSource = (mode) => {
|
|
765
|
+
const code = causticLightCompute(constants, blasLayoutMask, hasDisplacement, mode, '', causticDiagnostics.fullEmissionRandom !== false);
|
|
766
|
+
return bindless ? bindlessShaderSource(code, this._textureSelection.support.enable) : code;
|
|
767
|
+
};
|
|
768
|
+
const lightEmitter = lightTracing ? this._causticLight ??= new WGPUCausticLightPass(this._device) : undefined;
|
|
769
|
+
const photonEmitter = photonMapping ? this._photonMap?.emission : undefined;
|
|
770
|
+
const pipelineDiagnostic = {
|
|
771
|
+
label: 'Ray Trace', entryPoint: 'main', firstFrameRequired: true, status: 'compiling',
|
|
772
|
+
startedAtMs: 0, concurrentCompilationCount: 1
|
|
773
|
+
};
|
|
774
|
+
const variant = {
|
|
775
|
+
status: 'pending', capabilities: shader.capabilities, structureKey: shader.structureKey,
|
|
776
|
+
settled: undefined,
|
|
777
|
+
diagnostics: {
|
|
778
|
+
status: 'compiling', sourceBytes: 0, sourceLines: 0, pipelineCount: 1,
|
|
779
|
+
completedPipelineCount: 0, sourceGenerationTimeMs: 0, moduleCreationTimeMs: 0,
|
|
780
|
+
modules: [], pipelines: [{ ...pipelineDiagnostic }], maximumConcurrentPipelineCount: 1, peakConcurrentPipelineCount: 1
|
|
609
781
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
(
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
let sourceFingerprint = this._shaderCompilationDiagnostics?.modules[0]?.sourceFingerprint ?? '';
|
|
634
|
-
if (needsCreateShaderModule) {
|
|
635
|
-
const sourceGenerationStartedAt = shaderCompilationNow();
|
|
636
|
-
let rayTraceShaderCode = wgpuShaderSource(constants, {
|
|
637
|
-
hasDisplacement: this._hasDisplacement, hasAlpha, hasSceneAlpha,
|
|
782
|
+
};
|
|
783
|
+
const device = this._device, groupIndices = bindless ? [0, 1] : [0, 1, 2];
|
|
784
|
+
let startedAt = 0;
|
|
785
|
+
const compile = async () => {
|
|
786
|
+
startedAt = shaderCompilationNow();
|
|
787
|
+
// Override values are not part of the WGSL unless they are baked, so a
|
|
788
|
+
// quality edit can share the module that the driver has already parsed.
|
|
789
|
+
const moduleKey = JSON.stringify([causticKey, !!bindless, hasAlpha, hasSceneAlpha, hasDisplacement,
|
|
790
|
+
constants.USE_ANISOTROPY_DIRECTIONS, constants.USE_COAT_NORMAL_FRAMES, blasLayoutMask, defineCode, pipelineConstantsKeys,
|
|
791
|
+
bake && pipelineConstantsKeys.map((key) => constants[key])]);
|
|
792
|
+
let source = this._traceModules.get(moduleKey);
|
|
793
|
+
if (!source) {
|
|
794
|
+
let code = wgpuShaderSource(constants, {
|
|
795
|
+
hasDisplacement,
|
|
796
|
+
hasAlpha,
|
|
797
|
+
hasSceneAlpha,
|
|
798
|
+
...transport?.features,
|
|
799
|
+
lightTracing,
|
|
800
|
+
photonMapping,
|
|
801
|
+
photonMaxDepth: causticDiagnostics.photonMaxDepth ?? 20,
|
|
802
|
+
photonDirectSubset: causticDiagnostics.photonDirectSubset,
|
|
803
|
+
debugGroup: causticDiagnostics.debugGroup,
|
|
804
|
+
workgroupSize: transport?.workgroupSize ?? WORKGROUP_SIZE,
|
|
638
805
|
hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
|
|
639
806
|
hasCoatNormals: Boolean(constants.USE_COAT_NORMAL_FRAMES),
|
|
640
|
-
blasLayoutMask
|
|
807
|
+
blasLayoutMask,
|
|
641
808
|
dynamicEnvironment: true
|
|
642
|
-
},
|
|
643
|
-
if (
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
moduleCreationTimeMs =
|
|
656
|
-
shaderCompilationNow() - moduleCreationStartedAt;
|
|
657
|
-
this._compiledHasAlpha = hasAlpha;
|
|
658
|
-
this._compiledSceneHasAlpha = hasSceneAlpha;
|
|
659
|
-
this._compiledAnisotropyDirections = Boolean(constants.USE_ANISOTROPY_DIRECTIONS);
|
|
660
|
-
this._compiledCoatNormals = Boolean(constants.USE_COAT_NORMAL_FRAMES);
|
|
661
|
-
this._compiledBlasLayoutMask = this._blasLayoutMask;
|
|
809
|
+
}, bake);
|
|
810
|
+
if (bindless)
|
|
811
|
+
code = bindlessShaderSource(code, this._textureSelection.support.enable);
|
|
812
|
+
variant.diagnostics.sourceGenerationTimeMs = shaderCompilationNow() - startedAt;
|
|
813
|
+
const moduleStartedAt = shaderCompilationNow();
|
|
814
|
+
source = {
|
|
815
|
+
module: this._shaderCache.createShaderModule({ label: 'Ray Trace', code }),
|
|
816
|
+
bytes: code.length, lines: shaderSourceLineCount(code), fingerprint: shaderSourceFingerprint(code)
|
|
817
|
+
};
|
|
818
|
+
variant.diagnostics.moduleCreationTimeMs = shaderCompilationNow() - moduleStartedAt;
|
|
819
|
+
this._traceModules.set(moduleKey, source);
|
|
820
|
+
if (this._traceModules.size > 8)
|
|
821
|
+
this._traceModules.delete(this._traceModules.keys().next().value);
|
|
662
822
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
};
|
|
704
|
-
pipelineCompilationStartedAt = shaderCompilationNow();
|
|
705
|
-
diagnostic.startedAtMs = 0;
|
|
706
|
-
diagnostic.concurrentCompilationCount = 1;
|
|
707
|
-
this._shaderCompilationDiagnostics = {
|
|
708
|
-
status: 'compiling',
|
|
709
|
-
sourceBytes,
|
|
710
|
-
sourceLines,
|
|
711
|
-
pipelineCount: 1,
|
|
712
|
-
completedPipelineCount: 0,
|
|
713
|
-
sourceGenerationTimeMs,
|
|
714
|
-
moduleCreationTimeMs,
|
|
715
|
-
modules: [
|
|
716
|
-
{
|
|
717
|
-
label: 'Ray Trace',
|
|
718
|
-
sourceBytes,
|
|
719
|
-
sourceLines,
|
|
720
|
-
sourceFingerprint
|
|
721
|
-
}
|
|
722
|
-
],
|
|
723
|
-
pipelines: [{ ...diagnostic }],
|
|
724
|
-
maximumConcurrentPipelineCount: 1,
|
|
725
|
-
peakConcurrentPipelineCount: 1
|
|
726
|
-
};
|
|
727
|
-
let rayTracePipeline;
|
|
728
|
-
try {
|
|
729
|
-
rayTracePipeline = await this._shaderCache.createComputePipelineAsync({
|
|
730
|
-
label: 'Ray Trace',
|
|
731
|
-
layout: this._rayTracePipelineLayout,
|
|
732
|
-
compute: {
|
|
733
|
-
module: this._computeShaderModule,
|
|
734
|
-
entryPoint: 'main',
|
|
735
|
-
constants: pipelineConstants
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
diagnostic.status = 'ready';
|
|
739
|
-
}
|
|
740
|
-
catch (error) {
|
|
741
|
-
diagnostic.status = 'failed';
|
|
742
|
-
diagnostic.error = getWebGPUErrorMessage(error);
|
|
743
|
-
// Pipeline creation already reports WGSL failures. Pull the much
|
|
744
|
-
// heavier module diagnostics only on that failure path so normal
|
|
745
|
-
// startup does not schedule a second full-module inspection.
|
|
746
|
-
const info = await this._computeShaderModule
|
|
747
|
-
.getCompilationInfo()
|
|
748
|
-
.catch(() => undefined);
|
|
749
|
-
const shaderErrors = (info?.messages ?? [])
|
|
750
|
-
.filter((message) => message.type === 'error');
|
|
751
|
-
if (shaderErrors.length) {
|
|
752
|
-
throw new Error(shaderErrors
|
|
753
|
-
.map((message) => `Ray Trace WGSL ${message.lineNum}:${message.linePos} ${message.message}`)
|
|
754
|
-
.join('\n'));
|
|
755
|
-
}
|
|
756
|
-
throw error;
|
|
757
|
-
}
|
|
758
|
-
finally {
|
|
759
|
-
diagnostic.endedAtMs =
|
|
760
|
-
shaderCompilationNow() - pipelineCompilationStartedAt;
|
|
761
|
-
diagnostic.durationMs = diagnostic.endedAtMs;
|
|
762
|
-
}
|
|
763
|
-
this._rayTracePipeline = rayTracePipeline;
|
|
764
|
-
this._depthCompute = { module: this._computeShaderModule, constants: pipelineConstants };
|
|
765
|
-
const pipelineError = await device.popErrorScope();
|
|
766
|
-
pipelineScopeOpen = false;
|
|
767
|
-
if (pipelineError)
|
|
768
|
-
throw pipelineError;
|
|
769
|
-
const compilationCompletedAt = shaderCompilationNow();
|
|
770
|
-
this._shaderCompilationDiagnostics = {
|
|
771
|
-
...this._shaderCompilationDiagnostics,
|
|
772
|
-
status: 'ready',
|
|
773
|
-
completedPipelineCount: 1,
|
|
774
|
-
pipelines: [{ ...diagnostic }],
|
|
775
|
-
pipelineCompilationTimeMs: compilationCompletedAt - pipelineCompilationStartedAt,
|
|
776
|
-
totalTimeMs: compilationCompletedAt - compilationStartedAt
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
catch (error) {
|
|
780
|
-
const scopedError = pipelineScopeOpen
|
|
781
|
-
? await device.popErrorScope().catch(() => null)
|
|
782
|
-
: null;
|
|
783
|
-
const detail = joinWebGPUErrors(error, scopedError);
|
|
784
|
-
if (!this._bakePipelineConstants &&
|
|
785
|
-
isWebGPUPipelineSpecializationFailure(error, scopedError)) {
|
|
786
|
-
const failedAt = shaderCompilationNow();
|
|
787
|
-
if (this._shaderCompilationDiagnostics?.status === 'compiling') {
|
|
788
|
-
this._shaderCompilationDiagnostics = {
|
|
789
|
-
...this._shaderCompilationDiagnostics,
|
|
790
|
-
status: 'failed',
|
|
791
|
-
pipelineCompilationTimeMs: pipelineCompilationStartedAt == null
|
|
792
|
-
? undefined
|
|
793
|
-
: failedAt - pipelineCompilationStartedAt,
|
|
794
|
-
totalTimeMs: compilationStartedAt == null
|
|
795
|
-
? undefined
|
|
796
|
-
: failedAt - compilationStartedAt,
|
|
797
|
-
error: detail
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
// WebKit can accept a large WGSL module but fail while translating
|
|
801
|
-
// its specialization-constant variant to Metal. Baking the values
|
|
802
|
-
// into WGSL lets its front end eliminate disabled feature branches
|
|
803
|
-
// before creating the Metal library.
|
|
804
|
-
this._bakePipelineConstants = true;
|
|
805
|
-
this._rayTracePipeline = undefined;
|
|
806
|
-
this._rayTraceBindGroups = undefined;
|
|
807
|
-
this._computeShaderModule = undefined;
|
|
808
|
-
this._prevPipelineConstants = {};
|
|
809
|
-
console.warn('WebGPU pipeline specialization failed; retrying with constants baked into WGSL.');
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
throw new Error(detail);
|
|
823
|
+
Object.assign(variant.diagnostics, {
|
|
824
|
+
sourceBytes: source.bytes, sourceLines: source.lines,
|
|
825
|
+
modules: [{ label: 'Ray Trace', sourceBytes: source.bytes, sourceLines: source.lines, sourceFingerprint: source.fingerprint }]
|
|
826
|
+
});
|
|
827
|
+
const { module } = source;
|
|
828
|
+
const layout = this._createPipelineLayout(!!bindless, photonMapping);
|
|
829
|
+
const pipelineConstants = !bake
|
|
830
|
+
? pipelineConstantsKeys.reduce((obj, key) => {
|
|
831
|
+
obj[key] = +constants[key];
|
|
832
|
+
return obj;
|
|
833
|
+
}, {})
|
|
834
|
+
: undefined;
|
|
835
|
+
const lightSource = lightEmitter && emitterSource('lightTracing');
|
|
836
|
+
const photonSource = photonEmitter && emitterSource('photonMapping');
|
|
837
|
+
// Scopes capture all device errors while open, so one must never span an
|
|
838
|
+
// await: frames presented meanwhile would be blamed on this pipeline.
|
|
839
|
+
device.pushErrorScope('validation');
|
|
840
|
+
const request = this._shaderCache.createComputePipelineAsync({
|
|
841
|
+
label: 'Ray Trace', layout,
|
|
842
|
+
compute: { module, entryPoint: 'main', constants: pipelineConstants }
|
|
843
|
+
});
|
|
844
|
+
const scope = device.popErrorScope().catch(() => null);
|
|
845
|
+
let pipeline;
|
|
846
|
+
try {
|
|
847
|
+
pipeline = await request;
|
|
848
|
+
const scopedError = await scope;
|
|
849
|
+
if (scopedError)
|
|
850
|
+
throw scopedError;
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
const scopedError = await scope;
|
|
854
|
+
// Pipeline creation already reports WGSL failures. Pull the much
|
|
855
|
+
// heavier module diagnostics only on that failure path so normal
|
|
856
|
+
// startup does not schedule a second full-module inspection.
|
|
857
|
+
const info = await module.getCompilationInfo().catch(() => undefined);
|
|
858
|
+
const shaderErrors = (info?.messages ?? []).filter((message) => message.type === 'error');
|
|
859
|
+
if (shaderErrors.length) {
|
|
860
|
+
throw new Error(shaderErrors
|
|
861
|
+
.map((message) => `Ray Trace WGSL ${message.lineNum}:${message.linePos} ${message.message}`)
|
|
862
|
+
.join('\n'));
|
|
813
863
|
}
|
|
864
|
+
if (isWebGPUPipelineSpecializationFailure(error, scopedError))
|
|
865
|
+
throw error;
|
|
866
|
+
throw new Error(joinWebGPUErrors(error, scopedError));
|
|
814
867
|
}
|
|
815
|
-
|
|
816
|
-
|
|
868
|
+
const groups = lightEmitter || photonEmitter ? groupIndices.map((i) => pipeline.getBindGroupLayout(i)) : [];
|
|
869
|
+
const [light, photon] = await Promise.all([
|
|
870
|
+
lightEmitter?.prepare(lightSource, groups, !!bindless),
|
|
871
|
+
photonEmitter?.prepare(photonSource, groups, !!bindless)
|
|
872
|
+
]);
|
|
873
|
+
Object.assign(variant, { pipeline, lightEmitter: light, photonEmitter: photon,
|
|
874
|
+
depthCompute: { module, constants: pipelineConstants } });
|
|
875
|
+
};
|
|
876
|
+
const finishDiagnostics = (status, error) => {
|
|
877
|
+
const totalTimeMs = shaderCompilationNow() - startedAt;
|
|
878
|
+
Object.assign(variant.diagnostics, {
|
|
879
|
+
status, completedPipelineCount: status === 'ready' ? 1 : 0, error,
|
|
880
|
+
pipelines: [{ ...pipelineDiagnostic, status, error, endedAtMs: totalTimeMs, durationMs: totalTimeMs }],
|
|
881
|
+
pipelineCompilationTimeMs: totalTimeMs, totalTimeMs
|
|
882
|
+
});
|
|
883
|
+
};
|
|
884
|
+
const run = () => compile().then(() => {
|
|
885
|
+
variant.status = 'ready';
|
|
886
|
+
finishDiagnostics('ready');
|
|
887
|
+
}, (error) => {
|
|
888
|
+
finishDiagnostics('failed', getWebGPUErrorMessage(error));
|
|
817
889
|
if (this._disposed)
|
|
818
890
|
return;
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
891
|
+
if (!bake && isWebGPUPipelineSpecializationFailure(error)) {
|
|
892
|
+
// WebKit can accept a large WGSL module but fail while translating
|
|
893
|
+
// its specialization-constant variant to Metal. Baking the values
|
|
894
|
+
// into WGSL lets its front end eliminate disabled feature branches
|
|
895
|
+
// before creating the Metal library. The next request has a new
|
|
896
|
+
// signature, so this one stays pending until it is replaced.
|
|
897
|
+
this._bakePipelineConstants = true;
|
|
898
|
+
this._traceVariants.delete(signature);
|
|
899
|
+
console.warn('WebGPU pipeline specialization failed; retrying with constants baked into WGSL.');
|
|
900
|
+
return;
|
|
823
901
|
}
|
|
824
|
-
|
|
825
|
-
this.
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
this._failedPipelineSignature = undefined;
|
|
829
|
-
}
|
|
830
|
-
catch (error) {
|
|
831
|
-
if (this._disposed)
|
|
902
|
+
// Nobody needs an obsolete result, and a later request should try again.
|
|
903
|
+
if (this._requestedVariant !== variant) {
|
|
904
|
+
this._traceVariants.delete(signature);
|
|
905
|
+
variant.status = 'failed';
|
|
832
906
|
return;
|
|
833
|
-
this._pipelineError = error;
|
|
834
|
-
const failedAt = shaderCompilationNow();
|
|
835
|
-
if (this._shaderCompilationDiagnostics?.status === 'compiling') {
|
|
836
|
-
this._shaderCompilationDiagnostics = {
|
|
837
|
-
...this._shaderCompilationDiagnostics,
|
|
838
|
-
status: 'failed',
|
|
839
|
-
pipelineCompilationTimeMs: pipelineCompilationStartedAt == null
|
|
840
|
-
? undefined
|
|
841
|
-
: failedAt - pipelineCompilationStartedAt,
|
|
842
|
-
totalTimeMs: compilationStartedAt == null
|
|
843
|
-
? undefined
|
|
844
|
-
: failedAt - compilationStartedAt,
|
|
845
|
-
error: getWebGPUErrorMessage(error)
|
|
846
|
-
};
|
|
847
907
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
// The failed signature prevents retrying the same shader every frame;
|
|
851
|
-
// changing a pipeline-affecting setting permits a new attempt.
|
|
852
|
-
this._rayTracePipeline = undefined;
|
|
853
|
-
this._rayTraceBindGroups = undefined;
|
|
854
|
-
this._prevPipelineConstants = {};
|
|
855
|
-
this._needsUpdateBindGroups = true;
|
|
908
|
+
variant.status = 'failed';
|
|
909
|
+
variant.error = error;
|
|
856
910
|
console.error('Failed to update WebGPU ray tracing pipeline.', error);
|
|
911
|
+
});
|
|
912
|
+
variant.settled = new Promise((resolve) => {
|
|
913
|
+
this._queuedCompiles.push({ variant, signature, run, release: resolve });
|
|
914
|
+
});
|
|
915
|
+
this._traceVariants.set(signature, variant);
|
|
916
|
+
if (this._queuedCompiles.length > MAX_QUEUED_COMPILES) {
|
|
917
|
+
// A dropped variant stays pending, because a render that still wants it
|
|
918
|
+
// replaces it with a new request on its next frame.
|
|
919
|
+
const obsolete = this._queuedCompiles.shift();
|
|
920
|
+
if (this._traceVariants.get(obsolete.signature) === obsolete.variant)
|
|
921
|
+
this._traceVariants.delete(obsolete.signature);
|
|
922
|
+
obsolete.release();
|
|
923
|
+
}
|
|
924
|
+
for (const [key, retained] of this._traceVariants) {
|
|
925
|
+
if (this._traceVariants.size <= MAX_TRACE_VARIANTS)
|
|
926
|
+
break;
|
|
927
|
+
if (retained.status !== 'pending' && retained !== this._activeVariant)
|
|
928
|
+
this._traceVariants.delete(key);
|
|
929
|
+
}
|
|
930
|
+
this._pumpCompiles();
|
|
931
|
+
return variant;
|
|
932
|
+
}
|
|
933
|
+
/** Runs the most recently requested variants first: a dragged slider's last value is the one in use. */
|
|
934
|
+
_pumpCompiles() {
|
|
935
|
+
while (this._activeCompiles < MAX_CONCURRENT_COMPILES && this._queuedCompiles.length) {
|
|
936
|
+
const job = this._queuedCompiles.pop();
|
|
937
|
+
this._activeCompiles++;
|
|
938
|
+
void job.run().finally(() => {
|
|
939
|
+
this._activeCompiles--;
|
|
940
|
+
job.release();
|
|
941
|
+
if (!this._disposed)
|
|
942
|
+
this._pumpCompiles();
|
|
943
|
+
});
|
|
857
944
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
945
|
+
}
|
|
946
|
+
_requestQueuedCompile(variant) {
|
|
947
|
+
const index = this._queuedCompiles.findIndex((job) => job.variant === variant);
|
|
948
|
+
if (index >= 0)
|
|
949
|
+
this._queuedCompiles.push(...this._queuedCompiles.splice(index, 1));
|
|
861
950
|
}
|
|
862
951
|
_prepareAovPipelines(kinds) {
|
|
863
952
|
const trace = this._rayTracePipeline;
|
|
@@ -890,7 +979,7 @@ class WGPURayTracePass {
|
|
|
890
979
|
void Promise.all([
|
|
891
980
|
this._shaderCache.createComputePipelineAsync({ label: 'AOV accumulation', layout, compute: { ...compute, entryPoint: 'aovTrace' } }),
|
|
892
981
|
this._shaderCache.createComputePipelineAsync({ label: 'AOV center', layout, compute: { ...compute, entryPoint: 'aovTraceCenter' } })
|
|
893
|
-
]).then(([accumulation, center]) => { pending.accumulation = accumulation; pending.center = center; }, error => { pending.error = error; });
|
|
982
|
+
]).then(([accumulation, center]) => { pending.accumulation = accumulation; pending.center = center; }, error => { pending.error = error; this._aovPipelinesFailed = true; });
|
|
894
983
|
}
|
|
895
984
|
if (pipelines.error)
|
|
896
985
|
throw pipelines.error;
|
|
@@ -959,7 +1048,7 @@ class WGPURayTracePass {
|
|
|
959
1048
|
const tracePipeline = this._rayTracePipeline;
|
|
960
1049
|
const groups = this._rayTraceBindGroups;
|
|
961
1050
|
const textures = this._bindless, textureRevision = textures?.revision;
|
|
962
|
-
if (!tracePipeline || !groups || !this._depthCompute || this.
|
|
1051
|
+
if (!tracePipeline || !groups || !this._depthCompute || this.isCompilingShader())
|
|
963
1052
|
return;
|
|
964
1053
|
const packed = packDepthSampleArea(area, aspect ?? this._width / this._height);
|
|
965
1054
|
if (this._depthTracePipeline !== tracePipeline || !this._depthPipeline) {
|
|
@@ -1023,15 +1112,17 @@ class WGPURayTracePass {
|
|
|
1023
1112
|
params.destroy();
|
|
1024
1113
|
}
|
|
1025
1114
|
}
|
|
1115
|
+
/** Whether the latest request waits on GPU work that will settle on its own. */
|
|
1026
1116
|
isCompilingShader() {
|
|
1027
|
-
return this.
|
|
1117
|
+
return !!this._awaitingRetirement || this._requestedVariant?.status === 'pending';
|
|
1028
1118
|
}
|
|
1029
1119
|
isPipelineReady() {
|
|
1030
1120
|
return ((!!this._rayTracePipeline) &&
|
|
1031
1121
|
!!this._rayTraceBindGroups);
|
|
1032
1122
|
}
|
|
1123
|
+
/** Failure of the latest request. A failed variant does not affect other renders. */
|
|
1033
1124
|
getPipelineError() {
|
|
1034
|
-
return this.
|
|
1125
|
+
return this._resourceError ?? this._requestedVariant?.error;
|
|
1035
1126
|
}
|
|
1036
1127
|
_updateBindGroups() {
|
|
1037
1128
|
const { _device } = this;
|
|
@@ -1054,14 +1145,18 @@ class WGPURayTracePass {
|
|
|
1054
1145
|
}
|
|
1055
1146
|
}));
|
|
1056
1147
|
const textureEntries = [
|
|
1057
|
-
...(this._bindless
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1148
|
+
...(this._bindless
|
|
1149
|
+
? []
|
|
1150
|
+
: [
|
|
1151
|
+
{
|
|
1152
|
+
binding: 0,
|
|
1153
|
+
resource: this._mapsSampler
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
binding: 1,
|
|
1157
|
+
resource: this._maps.createView()
|
|
1158
|
+
}
|
|
1159
|
+
]),
|
|
1065
1160
|
{
|
|
1066
1161
|
binding: 3,
|
|
1067
1162
|
resource: {
|
|
@@ -1071,6 +1166,12 @@ class WGPURayTracePass {
|
|
|
1071
1166
|
{ binding: 4, resource: this._envTex.createView() },
|
|
1072
1167
|
{ binding: 5, resource: this._envDistributionTex.createView() },
|
|
1073
1168
|
{ binding: 6, resource: this._backgroundTex.createView() },
|
|
1169
|
+
...(this._photonMapping
|
|
1170
|
+
? [
|
|
1171
|
+
{ binding: 11, resource: this._photonMap.get(this._activeTarget).data.createView() },
|
|
1172
|
+
{ binding: 12, resource: this._photonMap.get(this._activeTarget).headers.createView() }
|
|
1173
|
+
]
|
|
1174
|
+
: []),
|
|
1074
1175
|
{ binding: 8, resource: this._lightSamplingTexture.createView() },
|
|
1075
1176
|
{ binding: 9, resource: this._openPbrEnergyLut.createView() },
|
|
1076
1177
|
{
|
|
@@ -1078,11 +1179,18 @@ class WGPURayTracePass {
|
|
|
1078
1179
|
resource: this._adaptiveSamplingTexture.createView()
|
|
1079
1180
|
}
|
|
1080
1181
|
];
|
|
1081
|
-
const bufferBindGroup = _device.createBindGroup({
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1182
|
+
const bufferBindGroup = _device.createBindGroup({
|
|
1183
|
+
layout: bindGroupPipeline.getBindGroupLayout(0),
|
|
1184
|
+
entries: this._bindless
|
|
1185
|
+
? bufferEntries.concat(textureEntries.map((e) => ({ ...e, binding: e.binding + 4 })))
|
|
1186
|
+
: bufferEntries
|
|
1085
1187
|
});
|
|
1188
|
+
const texturesBindGroup = this._bindless
|
|
1189
|
+
? undefined
|
|
1190
|
+
: _device.createBindGroup({
|
|
1191
|
+
layout: bindGroupPipeline.getBindGroupLayout(1),
|
|
1192
|
+
entries: textureEntries
|
|
1193
|
+
});
|
|
1086
1194
|
const uniformBindGroup = _device.createBindGroup({
|
|
1087
1195
|
layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 1 : 2),
|
|
1088
1196
|
entries: Object.keys(this._uniforms).map((key, i) => ({
|
|
@@ -1092,19 +1200,36 @@ class WGPURayTracePass {
|
|
|
1092
1200
|
}
|
|
1093
1201
|
}))
|
|
1094
1202
|
});
|
|
1203
|
+
if (this._photonMapping) {
|
|
1204
|
+
// Explicit layouts still track unused textures. Bind a placeholder while
|
|
1205
|
+
// emission writes the real packed map through its storage view.
|
|
1206
|
+
const emissionEntries = textureEntries.map((entry) => entry.binding === 11 || entry.binding === 12
|
|
1207
|
+
? { ...entry, resource: this._photonMap.placeholder.createView() }
|
|
1208
|
+
: entry);
|
|
1209
|
+
const sceneGroup = _device.createBindGroup({
|
|
1210
|
+
layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 0 : 1),
|
|
1211
|
+
entries: this._bindless
|
|
1212
|
+
? bufferEntries.concat(emissionEntries.map((e) => ({ ...e, binding: e.binding + 4 })))
|
|
1213
|
+
: emissionEntries
|
|
1214
|
+
});
|
|
1215
|
+
this._photonEmissionGroups = this._bindless
|
|
1216
|
+
? [sceneGroup, uniformBindGroup]
|
|
1217
|
+
: [bufferBindGroup, sceneGroup, uniformBindGroup];
|
|
1218
|
+
}
|
|
1095
1219
|
const target = this._targetOutputs.get(this._activeTarget);
|
|
1096
|
-
const outputBindGroup = target.pipeline === bindGroupPipeline && target.bindGroup ||
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1220
|
+
const outputBindGroup = (target.pipeline === bindGroupPipeline && target.bindGroup) ||
|
|
1221
|
+
_device.createBindGroup({
|
|
1222
|
+
layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 2 : 3),
|
|
1223
|
+
entries: [
|
|
1224
|
+
this._outColorBuffer,
|
|
1225
|
+
this._outNormalBuffer,
|
|
1226
|
+
this._outAlbedoBuffer,
|
|
1227
|
+
this._outShadowCatcherBuffer
|
|
1228
|
+
].map((buffer, i) => ({
|
|
1229
|
+
binding: i,
|
|
1230
|
+
resource: { buffer }
|
|
1231
|
+
}))
|
|
1232
|
+
});
|
|
1108
1233
|
target.bindGroup = outputBindGroup;
|
|
1109
1234
|
target.pipeline = bindGroupPipeline;
|
|
1110
1235
|
this._rayTraceBindGroups = [
|
|
@@ -1131,6 +1256,36 @@ class WGPURayTracePass {
|
|
|
1131
1256
|
encoder.clearBuffer(this._outAlbedoBuffer);
|
|
1132
1257
|
encoder.clearBuffer(this._outShadowCatcherBuffer);
|
|
1133
1258
|
}
|
|
1259
|
+
const offset = this._rtUniform.views.scaleOffset;
|
|
1260
|
+
const causticBatch = this._lightTracing && offset[2] === tiles[0] - 1 && offset[3] === tiles[1] - 1;
|
|
1261
|
+
const photonBatch = this._photonMapping && offset[2] === 0 && offset[3] === 0;
|
|
1262
|
+
if (photonBatch && this._photonMap) {
|
|
1263
|
+
const requested = this._causticSettings?.photonMapping, initial = typeof requested === 'object' ? requested.initialRadius : undefined;
|
|
1264
|
+
const radius = Number.isFinite(initial) && initial > 0 ? initial : Math.max((causticDiagnostics.photonPerCasterRadius
|
|
1265
|
+
? causticCasterSpheres(this._meshes).reduce((maximum, s) => Math.max(maximum, s[3]), 0)
|
|
1266
|
+
: this._causticBounds[3]) * 0.03, 1e-4);
|
|
1267
|
+
const batch = Math.max(1, this._rtUniform.views.sampleCount[0] - this._rtUniform.views.sampleSequenceOffset[0] + 1);
|
|
1268
|
+
const photonViewProjection = mat4.create();
|
|
1269
|
+
mat4.invert(photonViewProjection, this._viewProjInv);
|
|
1270
|
+
this._photonMap.encode(encoder, this._photonEmissionGroups, this._activeTarget, photonRadius(radius, causticDiagnostics.photonDebugBatch ?? batch, causticDiagnostics.photonAlpha ?? 2 / 3,
|
|
1271
|
+
// Allocation limits may reduce the requested count.
|
|
1272
|
+
this._photonMap.get(this._activeTarget).count, causticDiagnostics.photonRadiusScaling), {
|
|
1273
|
+
querySet: timing?.querySet,
|
|
1274
|
+
resourceTable: this._bindless?.table,
|
|
1275
|
+
orthographic: !this._causticPerspective,
|
|
1276
|
+
progressiveBatch: batch,
|
|
1277
|
+
width: this._width,
|
|
1278
|
+
height: this._height,
|
|
1279
|
+
clear,
|
|
1280
|
+
run: true,
|
|
1281
|
+
photons: 1,
|
|
1282
|
+
batch: Math.floor(this._rtUniform.views.sampleCount[0]),
|
|
1283
|
+
...this._transport?.uniform,
|
|
1284
|
+
viewProjection: photonViewProjection,
|
|
1285
|
+
casterSphere: this._causticBounds,
|
|
1286
|
+
cameraScale: this._causticCameraScale
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1134
1289
|
const descriptor = {};
|
|
1135
1290
|
const querySet = timing?.querySet;
|
|
1136
1291
|
if (querySet) {
|
|
@@ -1150,8 +1305,32 @@ class WGPURayTracePass {
|
|
|
1150
1305
|
this._rayTraceBindGroups.forEach((bindGroup, idx) => {
|
|
1151
1306
|
pass.setBindGroup(idx, bindGroup);
|
|
1152
1307
|
});
|
|
1153
|
-
|
|
1308
|
+
researchTransport()?.capture(this);
|
|
1309
|
+
pass.dispatchWorkgroups(Math.ceil(dispatchWidth / (this._transport?.workgroupSize ?? WORKGROUP_SIZE)), Math.ceil(dispatchHeight / (this._transport?.workgroupSize ?? WORKGROUP_SIZE)), 1);
|
|
1154
1310
|
pass.end();
|
|
1311
|
+
if (this._lightTracing && this._causticLight) {
|
|
1312
|
+
const viewProjection = mat4.create();
|
|
1313
|
+
mat4.invert(viewProjection, this._viewProjInv);
|
|
1314
|
+
const requested = this._causticSettings?.lightTracing;
|
|
1315
|
+
const count = typeof requested === 'object' ? requested.photonsPerSample : undefined;
|
|
1316
|
+
const photons = Math.min(this._device.limits.maxComputeWorkgroupsPerDimension * 64, Math.max(1, Math.round(Number.isFinite(count) ? count : (this._width * this._height) / 4)));
|
|
1317
|
+
this._causticLight.encode(encoder, this._rayTraceBindGroups.slice(0, this._bindless ? 2 : 3), this._activeTarget, {
|
|
1318
|
+
querySet: causticBatch ? querySet : undefined,
|
|
1319
|
+
width: this._width,
|
|
1320
|
+
height: this._height,
|
|
1321
|
+
clear,
|
|
1322
|
+
run: !!causticBatch,
|
|
1323
|
+
photons,
|
|
1324
|
+
batch: Math.floor(this._rtUniform.views.sampleCount[0]),
|
|
1325
|
+
...this._transport?.uniform,
|
|
1326
|
+
resourceTable: this._bindless?.table,
|
|
1327
|
+
orthographic: !this._causticPerspective,
|
|
1328
|
+
viewProjection,
|
|
1329
|
+
casterSphere: this._causticBounds,
|
|
1330
|
+
cameraScale: this._causticCameraScale
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
return (causticBatch ? 1 : 0) | (photonBatch ? 2 : 0);
|
|
1155
1334
|
}
|
|
1156
1335
|
async getDebugBVHData() {
|
|
1157
1336
|
if (!this._debugBVHBuffer) {
|
|
@@ -1174,7 +1353,7 @@ class WGPURayTracePass {
|
|
|
1174
1353
|
scale: atlas?.scale ?? 1, metadataBytes: 0, resourceTableCapacity: 0, retiredBytes: 0
|
|
1175
1354
|
})
|
|
1176
1355
|
},
|
|
1177
|
-
shaderCompilation: this.
|
|
1356
|
+
shaderCompilation: this._requestedVariant?.diagnostics,
|
|
1178
1357
|
lightSampling: {
|
|
1179
1358
|
emissiveTriangles: this._emissiveTriangles.length,
|
|
1180
1359
|
activeChoices: this._rtUniform.views.lightChoiceCount[0],
|
|
@@ -1194,15 +1373,6 @@ class WGPURayTracePass {
|
|
|
1194
1373
|
if (next === this._alphaBlendStrategy)
|
|
1195
1374
|
return false;
|
|
1196
1375
|
this._alphaBlendStrategy = next;
|
|
1197
|
-
if (this._constants.HAS_ALPHA_BLEND) {
|
|
1198
|
-
this._computeShaderModule = undefined;
|
|
1199
|
-
this._rayTracePipeline = undefined;
|
|
1200
|
-
this._rayTracePipelineLayout = undefined;
|
|
1201
|
-
this._rayTraceBindGroups = undefined;
|
|
1202
|
-
this._prevPipelineConstants = {};
|
|
1203
|
-
this._failedPipelineSignature = undefined;
|
|
1204
|
-
this._needsUpdateBindGroups = true;
|
|
1205
|
-
}
|
|
1206
1376
|
return true;
|
|
1207
1377
|
}
|
|
1208
1378
|
hasDepthOfField() {
|
|
@@ -1266,8 +1436,28 @@ class WGPURayTracePass {
|
|
|
1266
1436
|
this._device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, data, { bytesPerRow: width * Float32Array.BYTES_PER_ELEMENT }, [width, height]);
|
|
1267
1437
|
}
|
|
1268
1438
|
setQuality(options) {
|
|
1439
|
+
WGPURayTracePass.prototype._setCaustics.call(this, options.caustics);
|
|
1269
1440
|
writeTraceQuality(this._rtUniform.views, options);
|
|
1270
1441
|
}
|
|
1442
|
+
_setCaustics(settings) {
|
|
1443
|
+
this._causticSettings = settings;
|
|
1444
|
+
const transport = researchTransport()?.configure(this, 'webgpu');
|
|
1445
|
+
if (!!transport?.enabled !== !!this._transport?.enabled)
|
|
1446
|
+
this._needsUpdateLightSamplingData = true;
|
|
1447
|
+
this._transport = transport;
|
|
1448
|
+
const hasCaster = !!(settings?.lightTracing || settings?.photonMapping) &&
|
|
1449
|
+
this._meshes.some((mesh) => mesh.materials.some(isCausticSpecular));
|
|
1450
|
+
const photonSupported = !(settings?.lightTracing || settings?.photonMapping) ||
|
|
1451
|
+
!hasCaster ||
|
|
1452
|
+
!hasStochasticCausticCaster(this._meshes);
|
|
1453
|
+
this._lightTracing = !!settings?.lightTracing && hasCaster && photonSupported;
|
|
1454
|
+
const photonMapping = !!settings?.photonMapping && hasCaster && photonSupported;
|
|
1455
|
+
this._photonMapping = photonMapping;
|
|
1456
|
+
this._causticKey =
|
|
1457
|
+
this._transport?.enabled || this._lightTracing || this._photonMapping
|
|
1458
|
+
? `${this._lightTracing}/${this._photonMapping}/${causticDiagnostics.fullEmissionRandom !== false}/${causticDiagnostics.photonMaxDepth ?? 20}/${causticDiagnostics.debugGroup}/${causticDiagnostics.photonDirectSubset !== false}/${this._transport?.key ?? ''}`
|
|
1459
|
+
: '';
|
|
1460
|
+
}
|
|
1271
1461
|
_setEnvMap(envImage) {
|
|
1272
1462
|
const device = this._device;
|
|
1273
1463
|
// Always create env tex
|
|
@@ -1621,14 +1811,19 @@ class WGPURayTracePass {
|
|
|
1621
1811
|
this._needsUpdateBindGroups ||= resized;
|
|
1622
1812
|
}
|
|
1623
1813
|
_updateLightSamplingData() {
|
|
1624
|
-
this._constants.LIGHT_CAPABILITIES = lightShaderCapabilities(this._analyticLights.map(light => light.type), this._emissiveTriangles.length);
|
|
1625
|
-
const lighting = prepareSceneLightSampling(this._analyticLights, this._emissiveTriangles, this._envImageSource && 'sky' in this._envImageSource
|
|
1814
|
+
this._constants.LIGHT_CAPABILITIES = lightShaderCapabilities(this._analyticLights.map((light) => light.type), this._emissiveTriangles.length);
|
|
1815
|
+
const lighting = prepareSceneLightSampling(this._analyticLights, this._emissiveTriangles, this._envImageSource && 'sky' in this._envImageSource
|
|
1816
|
+
? proceduralEnvironmentPower(this._envImageSource)
|
|
1817
|
+
: estimateEnvironmentPower(this._envImageSource), this._device.limits.maxTextureDimension2D, this._lightChoices, this._lightSamplingScratch, this._shadowCatcherLightChoices);
|
|
1818
|
+
researchTransport()?.prepare(this, 'webgpu', lighting);
|
|
1626
1819
|
this._analyticLightSelectionPmfs = lighting.analyticPmfs;
|
|
1627
1820
|
this._iesProfileTexelOffsets = lighting.profiles.offsets;
|
|
1628
1821
|
this._lightDomainDiagnostics = lighting.domains;
|
|
1629
1822
|
const { lightWidth: width, lightHeight: height, lightData: textureData } = lighting;
|
|
1630
1823
|
writeTraceLighting(this._rtUniform.views, lighting);
|
|
1631
|
-
const resized = !this._lightSamplingTexture ||
|
|
1824
|
+
const resized = !this._lightSamplingTexture ||
|
|
1825
|
+
width !== this._lightSamplingTextureSize[0] ||
|
|
1826
|
+
height !== this._lightSamplingTextureSize[1];
|
|
1632
1827
|
if (!this._lightSamplingTexture ||
|
|
1633
1828
|
width !== this._lightSamplingTextureSize[0] ||
|
|
1634
1829
|
height !== this._lightSamplingTextureSize[1]) {
|
|
@@ -1770,11 +1965,7 @@ class WGPURayTracePass {
|
|
|
1770
1965
|
}
|
|
1771
1966
|
this._hasShadowCatcher = materials.some((m) => m.shadowCatcher);
|
|
1772
1967
|
const hasDisplacement = materialData.getDisplacementMaps().length > 0;
|
|
1773
|
-
|
|
1774
|
-
this._hasDisplacement = hasDisplacement;
|
|
1775
|
-
// Needs to recreate pipeline layout
|
|
1776
|
-
this._rayTracePipelineLayout = undefined;
|
|
1777
|
-
}
|
|
1968
|
+
this._hasDisplacement = hasDisplacement;
|
|
1778
1969
|
Object.assign(this._constants, wgpuMaterialConstants(materials, materialData, mergedGeometry, this._bevelSamples, Number(this._constants.NUM_MAPS ?? 0)));
|
|
1779
1970
|
// Runtime storage metadata has no texture-count specialization.
|
|
1780
1971
|
if (this._bindless)
|
|
@@ -1945,8 +2136,6 @@ class WGPURayTracePass {
|
|
|
1945
2136
|
this._retiredBindless.add(retired);
|
|
1946
2137
|
void this._device.queue.onSubmittedWorkDone().catch(() => { }).then(() => { retired.dispose(); this._retiredBindless.delete(retired); });
|
|
1947
2138
|
this._bindless = undefined;
|
|
1948
|
-
this._computeShaderModule = undefined;
|
|
1949
|
-
this._rayTracePipelineLayout = undefined;
|
|
1950
2139
|
}
|
|
1951
2140
|
return new MaterialTable(...args);
|
|
1952
2141
|
}
|
|
@@ -2058,6 +2247,10 @@ class WGPURayTracePass {
|
|
|
2058
2247
|
}
|
|
2059
2248
|
// TODO reuse the code of RayTraceNode
|
|
2060
2249
|
setCamera(camera) {
|
|
2250
|
+
this._causticPerspective = camera.projection === 'perspective';
|
|
2251
|
+
const projection = camera.getProjectionMatrix(this._targetAspect);
|
|
2252
|
+
this._causticCameraScale = Math.abs(projection[0] * projection[5]) / 4;
|
|
2253
|
+
this._setCaustics(this._causticSettings);
|
|
2061
2254
|
const viewProjectionInvMat = this._viewProjInv;
|
|
2062
2255
|
this._apertureRadius = cameraApertureRadius(camera);
|
|
2063
2256
|
this._focusDistance = camera.focusDistance;
|
|
@@ -2147,12 +2340,14 @@ class WGPURayTracePass {
|
|
|
2147
2340
|
this._envDistribution?.dispose();
|
|
2148
2341
|
this._skyBaker?.dispose();
|
|
2149
2342
|
this._bindless?.dispose();
|
|
2150
|
-
this._retiredBindless.forEach(textures => textures.dispose());
|
|
2343
|
+
this._retiredBindless.forEach((textures) => textures.dispose());
|
|
2151
2344
|
this._retiredBindless.clear();
|
|
2345
|
+
this._causticLight?.dispose();
|
|
2346
|
+
this._photonMap?.dispose();
|
|
2152
2347
|
this._shaderCache.clear();
|
|
2153
2348
|
this._shaderCapabilities.clear();
|
|
2154
2349
|
const buffers = new Set([
|
|
2155
|
-
...Array.from(this._targetOutputs.values()).flatMap(target => target.buffers),
|
|
2350
|
+
...Array.from(this._targetOutputs.values()).flatMap((target) => target.buffers),
|
|
2156
2351
|
this._mapInfoUniform,
|
|
2157
2352
|
this._attrBuffers,
|
|
2158
2353
|
this._materialsBuffer,
|
|
@@ -2172,6 +2367,9 @@ class WGPURayTracePass {
|
|
|
2172
2367
|
this._adaptiveSamplingTexture
|
|
2173
2368
|
]);
|
|
2174
2369
|
textures.forEach((texture) => texture?.destroy());
|
|
2370
|
+
for (const job of this._queuedCompiles.splice(0))
|
|
2371
|
+
job.release();
|
|
2372
|
+
this._traceVariants.clear();
|
|
2175
2373
|
this._rayTracePipeline = undefined;
|
|
2176
2374
|
this._rayTraceBindGroups = undefined;
|
|
2177
2375
|
}
|