@meimu/mutracer 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +222 -0
- package/LICENSES/FidelityFX-FSR.txt +19 -0
- package/LICENSES/pbrt-v4-Apache-2.0.txt +202 -0
- package/README.md +3 -0
- package/THIRD_PARTY_NOTICES.md +18 -4
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +9328 -7614
- package/docs/api.md +83 -35
- package/docs/caustics.md +80 -0
- package/docs/materials.md +9 -6
- package/docs/performance.md +7 -0
- package/docs/post-processing.md +62 -9
- package/docs/progressive-rendering.md +53 -9
- package/docs/sampling.md +4 -0
- package/docs/technical/acceleration.md +52 -0
- package/docs/technical/aovs.md +79 -0
- package/docs/technical/caustics.md +109 -0
- package/docs/technical/continuous-spectrum.md +122 -0
- package/docs/technical/display-upscale.md +82 -0
- package/docs/technical/openpbr.md +99 -0
- package/docs/technical/pbrt-import.md +168 -0
- package/docs/technical/tiling.md +74 -0
- package/docs/technical/webgl.md +53 -0
- package/docs/updates.md +3 -1
- package/docs/viewer-checklist.md +65 -0
- package/docs/viewer.md +18 -6
- package/lib/Display.d.ts +19 -5
- package/lib/Display.js +10 -7
- package/lib/Display.js.map +1 -1
- package/lib/Engine.d.ts +4 -1
- package/lib/Engine.js +77 -14
- package/lib/Engine.js.map +1 -1
- package/lib/Render.d.ts +81 -24
- package/lib/Render.js +201 -26
- 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 +4 -2
- package/lib/main.js +1 -0
- package/lib/main.js.map +1 -1
- package/lib/oidn.d.ts +1 -1
- package/lib/renderer/AovPrimitiveCache.js +39 -8
- package/lib/renderer/AovPrimitiveCache.js.map +1 -1
- package/lib/renderer/DisplayPostProcess.d.ts +8 -3
- package/lib/renderer/DisplayPostProcess.js +51 -18
- package/lib/renderer/DisplayPostProcess.js.map +1 -1
- 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/MaterialData.js +7 -2
- package/lib/renderer/MaterialData.js.map +1 -1
- package/lib/renderer/RenderingPipeline.d.ts +11 -1
- package/lib/renderer/RenderingPipeline.js.map +1 -1
- package/lib/renderer/TileRender.d.ts +2 -2
- package/lib/renderer/TileRender.js +128 -7
- package/lib/renderer/TileRender.js.map +1 -1
- package/lib/renderer/WGPUAov.d.ts +6 -1
- package/lib/renderer/WGPUAov.js +108 -14
- package/lib/renderer/WGPUAov.js.map +1 -1
- package/lib/renderer/WGPUCanvasSurface.js +12 -1
- package/lib/renderer/WGPUCanvasSurface.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 +19 -19
- 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 +65 -21
- package/lib/renderer/WGPURayTracePass.js +730 -409
- package/lib/renderer/WGPURayTracePass.js.map +1 -1
- package/lib/renderer/WGPURenderingPipeline.js +110 -68
- package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLAov.js +3 -3
- package/lib/renderer/WebGLAov.js.map +1 -1
- package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
- package/lib/renderer/WebGLFullQuadPass.js +10 -0
- package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
- package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
- package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -1
- package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
- package/lib/renderer/WebGLRayTracePass.d.ts +20 -11
- package/lib/renderer/WebGLRayTracePass.js +157 -116
- package/lib/renderer/WebGLRayTracePass.js.map +1 -1
- package/lib/renderer/WebGLRenderingPipeline.js +17 -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/aov.d.ts +13 -1
- package/lib/renderer/aov.js +21 -2
- package/lib/renderer/aov.js.map +1 -1
- package/lib/renderer/aovDisplay.js +2 -2
- package/lib/renderer/aovDisplay.js.map +1 -1
- package/lib/renderer/binnedSahBvh.d.ts +2 -1
- package/lib/renderer/binnedSahBvh.js +24 -17
- package/lib/renderer/binnedSahBvh.js.map +1 -1
- package/lib/renderer/bvhAccel.d.ts +3 -1
- package/lib/renderer/bvhAccel.js +36 -6
- package/lib/renderer/bvhAccel.js.map +1 -1
- package/lib/renderer/cameraUniform.d.ts +1 -1
- package/lib/renderer/cameraUniform.js +6 -2
- package/lib/renderer/cameraUniform.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/compactVertices.d.ts +14 -0
- package/lib/renderer/compactVertices.js +124 -0
- package/lib/renderer/compactVertices.js.map +1 -0
- package/lib/renderer/easu.d.ts +11 -0
- package/lib/renderer/easu.js +156 -0
- package/lib/renderer/easu.js.map +1 -0
- package/lib/renderer/geometryUpdateState.d.ts +1 -0
- package/lib/renderer/geometryUpdateState.js.map +1 -1
- package/lib/renderer/geometryUpdates.js +3 -2
- package/lib/renderer/geometryUpdates.js.map +1 -1
- 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 +45 -37
- package/lib/renderer/glsl/aovTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.d.ts +2 -0
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.js +16 -0
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/cameraRay.glsl.d.ts +1 -1
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -16
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js.map +1 -1
- 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 +478 -465
- 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 +70 -63
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js +89 -75
- 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 +160 -118
- 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 +88 -91
- 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/spectralPrefix.glsl.d.ts +6 -0
- package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js +47 -0
- package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js.map +1 -0
- 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 +13 -7
- package/lib/renderer/glsl/depthQuery.frag.js.map +1 -1
- 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 +338 -252
- package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/shadowCatcher.frag.js +27 -26
- package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
- package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
- package/lib/renderer/gpuMemory.js +1 -1
- package/lib/renderer/gpuMemory.js.map +1 -1
- 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/openPbrEnergyLut.d.ts +2 -1
- package/lib/renderer/openPbrEnergyLut.js +18 -1
- package/lib/renderer/openPbrEnergyLut.js.map +1 -1
- package/lib/renderer/pathLimits.d.ts +5 -4
- package/lib/renderer/pathLimits.js +6 -5
- package/lib/renderer/pathLimits.js.map +1 -1
- package/lib/renderer/prepareMeshBVH.d.ts +2 -0
- package/lib/renderer/prepareMeshBVH.js +5 -3
- package/lib/renderer/prepareMeshBVH.js.map +1 -1
- package/lib/renderer/progressiveScheduler.js +5 -4
- package/lib/renderer/progressiveScheduler.js.map +1 -1
- package/lib/renderer/renderPipeline.d.ts +5 -0
- package/lib/renderer/renderPipeline.js +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/sceneAssembly.d.ts +1 -1
- package/lib/renderer/sceneAssembly.js +4 -3
- package/lib/renderer/sceneAssembly.js.map +1 -1
- package/lib/renderer/sceneChangeDetector.d.ts +1 -1
- package/lib/renderer/sceneChangeDetector.js +3 -1
- package/lib/renderer/sceneChangeDetector.js.map +1 -1
- package/lib/renderer/sceneLayoutSchema.d.ts +2 -0
- package/lib/renderer/sceneLayoutSchema.js +3 -3
- package/lib/renderer/sceneLayoutSchema.js.map +1 -1
- package/lib/renderer/scenePreparation.d.ts +3 -2
- package/lib/renderer/scenePreparation.js +18 -2
- package/lib/renderer/scenePreparation.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/staticBvhPreparation.d.ts +9 -0
- package/lib/renderer/staticBvhPreparation.js +8 -2
- package/lib/renderer/staticBvhPreparation.js.map +1 -1
- package/lib/renderer/surfaceHit.d.ts +10 -0
- package/lib/renderer/surfaceHit.js +22 -0
- package/lib/renderer/surfaceHit.js.map +1 -0
- package/lib/renderer/tangentRequirements.d.ts +2 -0
- package/lib/renderer/tangentRequirements.js +7 -0
- package/lib/renderer/tangentRequirements.js.map +1 -0
- package/lib/renderer/textureAtlas.d.ts +4 -0
- package/lib/renderer/textureAtlas.js +48 -2
- package/lib/renderer/textureAtlas.js.map +1 -1
- package/lib/renderer/webgl.js +4 -0
- package/lib/renderer/webgl.js.map +1 -1
- 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/webglVertexAttributes.d.ts +17 -0
- package/lib/renderer/webglVertexAttributes.js +44 -0
- package/lib/renderer/webglVertexAttributes.js.map +1 -0
- package/lib/renderer/wgpuBindlessSource.js +3 -3
- package/lib/renderer/wgpuMaterialConstants.js +4 -3
- package/lib/renderer/wgpuMaterialConstants.js.map +1 -1
- package/lib/renderer/wgpuSceneUniform.js +2 -2
- package/lib/renderer/wgpuSceneUniform.js.map +1 -1
- package/lib/renderer/wgpuShaderSource.d.ts +22 -0
- package/lib/renderer/wgpuShaderSource.js +3 -1
- package/lib/renderer/wgpuShaderSource.js.map +1 -1
- package/lib/renderer/wgsl/aovCompact.wgsl.d.ts +4 -0
- package/lib/renderer/wgsl/aovCompact.wgsl.js +26 -0
- package/lib/renderer/wgsl/aovCompact.wgsl.js.map +1 -0
- 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.d.ts +4 -0
- package/lib/renderer/wgsl/aovResolve.wgsl.js +21 -2
- package/lib/renderer/wgsl/aovResolve.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js +2 -2
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js +56 -0
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js.map +1 -0
- 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 +539 -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.d.ts +6 -2
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js +634 -620
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +23 -21
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +3 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +77 -74
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +795 -795
- 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 +5 -1
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +45 -29
- 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 +228 -189
- 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 +88 -92
- 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/spectralPrefix.wgsl.d.ts +6 -0
- package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js +66 -0
- package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js +61 -0
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/compose.frag.wgsl.js +100 -98
- package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
- package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/depthQuery.wgsl.js +12 -3
- package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/easu.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/easu.wgsl.js +80 -0
- package/lib/renderer/wgsl/easu.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.d.ts +21 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.js +230 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/jnlm.compute.js +74 -74
- package/lib/renderer/wgsl/jnlm.compute.js.map +1 -1
- package/lib/renderer/wgsl/offlineAdaptive.compute.js +26 -26
- 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 +23 -1
- package/lib/renderer/wgsl/rayTrace.compute.js +531 -543
- 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/scene/Scene.js.map +1 -1
- package/lib/settings/render.d.ts +22 -0
- package/lib/settings/render.js +2 -0
- package/lib/settings/render.js.map +1 -1
- package/lib/util/fromTHREE.js +23 -16
- 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 +7 -4
- package/lib/oidnBundledWeights.cdn.d.ts +0 -2
- package/lib/oidnBundledWeights.cdn.js +0 -5
- package/lib/oidnBundledWeights.cdn.js.map +0 -1
- package/lib/renderer/bvh.worker.d.ts +0 -1
- package/lib/renderer/bvh.worker.js +0 -16
- package/lib/renderer/bvh.worker.js.map +0 -1
- package/lib/renderer/bvhWorkerBuild.d.ts +0 -13
- package/lib/renderer/bvhWorkerBuild.js +0 -17
- package/lib/renderer/bvhWorkerBuild.js.map +0 -1
- package/lib/renderer/lightCore.d.ts +0 -12
- package/lib/renderer/lightCore.js +0 -13
- package/lib/renderer/lightCore.js.map +0 -1
- package/lib/renderer/materialCore.d.ts +0 -8
- package/lib/renderer/materialCore.js +0 -9
- package/lib/renderer/materialCore.js.map +0 -1
- package/lib/scene/PixelTexture.d.ts +0 -6
- package/lib/scene/PixelTexture.js +0 -10
- package/lib/scene/PixelTexture.js.map +0 -1
- package/lib/util/studioLighting.d.ts +0 -18
- package/lib/util/studioLighting.js +0 -36
- package/lib/util/studioLighting.js.map +0 -1
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import { resolveSurfaceHit } from './surfaceHit.js';
|
|
2
|
+
import { compactVertexLayout, packCompactVertices, packTriangleMaterials } from './compactVertices.js';
|
|
3
|
+
import { causticDiagnostics } from './causticDiagnostics.js';
|
|
4
|
+
import { WGPUPhotonMap, photonRadius } from './WGPUPhotonMap.js';
|
|
5
|
+
import { defaultPhotonMapPhotons } from './wgsl/photonMap.compute.js';
|
|
6
|
+
import { researchTransport } from './researchTransport.js';
|
|
7
|
+
import { causticEmissionTable } from './causticEmission.js';
|
|
8
|
+
import { WGPUCausticLightPass } from './WGPUCausticLightPass.js';
|
|
9
|
+
import { causticLightCompute } from './wgsl/causticLight.compute.js';
|
|
10
|
+
import { causticCasterSpheres, hasStochasticCausticCaster, causticCasterSphere, isCausticSpecular } from './caustics.js';
|
|
1
11
|
import { AovPrimitiveCache } from './AovPrimitiveCache.js';
|
|
2
12
|
import { packAovScene } from './aovScene.js';
|
|
3
|
-
import { aovMask } from './aov.js';
|
|
13
|
+
import { aovMask, AOV_ACCUMULATED_MASK, AOV_REFLECTANCE_MASK, COMPACT_AOV_KINDS } from './aov.js';
|
|
4
14
|
import { proceduralEnvironmentPower } from './proceduralEnvironmentPower.js';
|
|
5
15
|
import { WGPUSkyBaker } from './WGPUSkyBaker.js';
|
|
6
|
-
import { initialSceneConstants,
|
|
16
|
+
import { initialSceneConstants, sceneTraversalConstants, prepareSceneLightSampling, writeTraceLighting, packSceneAnalyticLights, writeTraceQuality, traceSampling, writeTraceSampling } from './sceneAssembly.js';
|
|
7
17
|
import { rayTraceBindGroupLayouts } from './sceneLayoutSchema.js';
|
|
8
18
|
import { WGPUBindlessMaterialData } from './WGPUBindlessMaterialData.js';
|
|
9
19
|
import { WGPUBindlessTextures } from './WGPUBindlessTextures.js';
|
|
@@ -14,7 +24,7 @@ import { wgpuShaderSource, wgpuPipelineConstantKeys, WGPU_SHADER_DEFINE_KEYS } f
|
|
|
14
24
|
import { makeRayTraceUniform, WGPU_SCENE_BUFFER_MIN_BYTES } from './wgpuSceneUniform.js';
|
|
15
25
|
import { constants } from './webgl.js';
|
|
16
26
|
import { MATERIAL_FLOAT_COUNT } from './materialLayoutSchema.js';
|
|
17
|
-
import { normalizeBounces } from './pathLimits.js';
|
|
27
|
+
import { normalizeBounces, MAX_PATH_EVENTS_WGPU } from './pathLimits.js';
|
|
18
28
|
import { updateMeshInstanceColors } from './prepareMeshBVH.js';
|
|
19
29
|
import { updateTLASInstanceColors } from './bvhAccel.js';
|
|
20
30
|
import { getMaterialHashKey } from '../scene/openPbrSchema.js';
|
|
@@ -27,8 +37,7 @@ import { packCameraUniform } from './cameraUniform.js';
|
|
|
27
37
|
import { SHADOW_CATCHER_ACCUMULATION_BYTES } from './wgsl/chunks/shadowCatcherAccumulation.wgsl.js';
|
|
28
38
|
import { DEPTH_SAMPLE_COUNT, packDepthSampleArea, unpackDepthSamples } from './depthQuery.js';
|
|
29
39
|
import { MeshMembershipCache } from './meshMembership.js';
|
|
30
|
-
import {
|
|
31
|
-
import { updatePreparedGeometries, canUpdateGeometries, geometryTopologyChanged, packVertexAttributes } from './geometryUpdates.js';
|
|
40
|
+
import { updatePreparedGeometries, canUpdateGeometries, geometryTopologyChanged, } from './geometryUpdates.js';
|
|
32
41
|
import { SceneEnvironmentCache, RectLightTextureTracker } from './sceneLightChanges.js';
|
|
33
42
|
import { MaterialChangeTracker, materialIsEmissive } from './materialChanges.js';
|
|
34
43
|
import { WGPUEnvMapDistribution } from './WGPUEnvMapDistribution.js';
|
|
@@ -43,10 +52,10 @@ import { generateBackgroundMapFromSceneBackground, environmentRotation, backgrou
|
|
|
43
52
|
import { WORKGROUP_SIZE } from './wgsl/rayTrace.compute.js';
|
|
44
53
|
import { envMapDistribution, environmentMapSourcesMatch } from './envMapDistribution.js';
|
|
45
54
|
import { updateMeshInstances, updateMeshInstanceTransforms } from './prepareMeshBVH.js';
|
|
46
|
-
import {
|
|
55
|
+
import { buildWebTLAS, getTraversalStackSizes, refitBLAS, refitTLAS } from './bvhAccel.js';
|
|
47
56
|
import { isTextureDataSource } from '../scene/Texture.js';
|
|
48
57
|
import { DEFAULT_BEVEL_SAMPLES, DEFAULT_DIRECT_LIGHT_CANDIDATES, normalizeBevelSamples, normalizeDirectLightCandidates } from './RenderingPipeline.js';
|
|
49
|
-
import { EmissiveTriangleCache, LightChoiceBuffer, estimateEnvironmentPower
|
|
58
|
+
import { EmissiveTriangleCache, LightChoiceBuffer, estimateEnvironmentPower } from './lightSampling.js';
|
|
50
59
|
import { getOpenPbrEnergyLutData, OPENPBR_ENERGY_LUT_HEIGHT, OPENPBR_ENERGY_LUT_WIDTH } from './openPbrEnergyLut.js';
|
|
51
60
|
import { offlineAdaptiveCompute } from './wgsl/offlineAdaptive.compute.js';
|
|
52
61
|
import { normalizeOfflineAdaptiveSamplingOptions } from './OfflineAdaptiveSampling.js';
|
|
@@ -78,6 +87,10 @@ function joinWebGPUErrors(...errors) {
|
|
|
78
87
|
.filter(Boolean))
|
|
79
88
|
].join('\n');
|
|
80
89
|
}
|
|
90
|
+
const MAX_TRACE_VARIANTS = 32;
|
|
91
|
+
// Requests are cheap but drivers are not, and a running compilation cannot be cancelled.
|
|
92
|
+
const MAX_CONCURRENT_COMPILES = 2;
|
|
93
|
+
const MAX_QUEUED_COMPILES = 4;
|
|
81
94
|
function shaderCompilationNow() {
|
|
82
95
|
return typeof performance === 'undefined' ? Date.now() : performance.now();
|
|
83
96
|
}
|
|
@@ -113,6 +126,16 @@ class WGPURayTracePass {
|
|
|
113
126
|
_width = 0;
|
|
114
127
|
_height = 0;
|
|
115
128
|
_meshes = [];
|
|
129
|
+
_causticKey = '';
|
|
130
|
+
_photonMapping = false;
|
|
131
|
+
_photonMap;
|
|
132
|
+
_causticLight;
|
|
133
|
+
_causticSettings;
|
|
134
|
+
_causticPerspective = true;
|
|
135
|
+
_causticCameraScale = 1;
|
|
136
|
+
_causticBounds = [0, 0, 0, 0];
|
|
137
|
+
_transport;
|
|
138
|
+
_lightTracing = false;
|
|
116
139
|
_sceneEnvironment = new SceneEnvironmentCache();
|
|
117
140
|
_rectLightTextures = new RectLightTextureTracker();
|
|
118
141
|
_analyticLights = [];
|
|
@@ -147,6 +170,8 @@ class WGPURayTracePass {
|
|
|
147
170
|
if (!output)
|
|
148
171
|
return;
|
|
149
172
|
this._targetOutputs.delete(target);
|
|
173
|
+
this._causticLight?.release(target);
|
|
174
|
+
this._photonMap?.release(target);
|
|
150
175
|
for (const buffer of output.buffers)
|
|
151
176
|
buffer.destroy();
|
|
152
177
|
if (this._activeTarget === target) {
|
|
@@ -155,17 +180,23 @@ class WGPURayTracePass {
|
|
|
155
180
|
}
|
|
156
181
|
}
|
|
157
182
|
getTargetMemoryBytes(target) {
|
|
158
|
-
return this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0
|
|
183
|
+
return ((this._targetOutputs.get(target)?.buffers.reduce((sum, buffer) => sum + buffer.size, 0) ?? 0) +
|
|
184
|
+
(this._causticLight?.bytes(target) ?? 0) +
|
|
185
|
+
(this._photonMap?.bytes(target) ?? 0));
|
|
159
186
|
}
|
|
160
187
|
useTarget(target) {
|
|
188
|
+
if (this._photonMapping && this._activeTarget !== target)
|
|
189
|
+
this._needsPhotonTargetBindGroups = true;
|
|
161
190
|
this._activeTarget = target;
|
|
162
191
|
const output = this._targetOutputs.get(target);
|
|
163
|
-
[this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] =
|
|
192
|
+
[this._outColorBuffer, this._outNormalBuffer, this._outAlbedoBuffer, this._outShadowCatcherBuffer] =
|
|
193
|
+
output?.buffers ?? [];
|
|
164
194
|
if (output?.bindGroup && output.pipeline === this._rayTracePipeline && this._rayTraceBindGroups) {
|
|
165
195
|
this._rayTraceBindGroups[this._bindless ? 2 : 3] = output.bindGroup;
|
|
196
|
+
this._needsUpdateOutputBindGroup = false;
|
|
166
197
|
}
|
|
167
198
|
else {
|
|
168
|
-
this.
|
|
199
|
+
this._needsUpdateOutputBindGroup = true;
|
|
169
200
|
}
|
|
170
201
|
}
|
|
171
202
|
_outColorBuffer;
|
|
@@ -212,22 +243,25 @@ class WGPURayTracePass {
|
|
|
212
243
|
_skyBaker;
|
|
213
244
|
_backgroundImageSource;
|
|
214
245
|
_backgroundTex;
|
|
215
|
-
_rayTracePipelineLayout;
|
|
216
246
|
_rayTraceBindGroups;
|
|
247
|
+
_photonEmissionGroups;
|
|
217
248
|
_blasLayoutMask = 1;
|
|
218
|
-
_compiledBlasLayoutMask = 0;
|
|
219
|
-
_compiledAnisotropyDirections = false;
|
|
220
|
-
_compiledCoatNormals = false;
|
|
221
|
-
_compiledHasAlpha = true;
|
|
222
|
-
_compiledSceneHasAlpha = true;
|
|
223
249
|
_alphaBlendStrategy = 'stochastic';
|
|
224
250
|
_constants = initialSceneConstants();
|
|
225
251
|
_retainedShaderCapabilities;
|
|
226
252
|
_retainedShaderStructure;
|
|
227
253
|
_shaderCapabilities = new WGPUShaderCapabilities();
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
254
|
+
_targetTransport = new WeakMap();
|
|
255
|
+
_traceVariants = new Map();
|
|
256
|
+
_queuedCompiles = [];
|
|
257
|
+
_activeCompiles = 0;
|
|
258
|
+
_traceModules = new Map();
|
|
259
|
+
_requestedVariant;
|
|
260
|
+
_activeVariant;
|
|
261
|
+
_activeInputs;
|
|
262
|
+
_awaitingRetirement;
|
|
263
|
+
_resourceError;
|
|
264
|
+
_aovPipelinesFailed = false;
|
|
231
265
|
_hasTransmission = false;
|
|
232
266
|
_hasShadowCatcher = false;
|
|
233
267
|
_hasDisplacement = false;
|
|
@@ -238,23 +272,26 @@ class WGPURayTracePass {
|
|
|
238
272
|
_clamping;
|
|
239
273
|
_transmissiveAlphaMin = 1;
|
|
240
274
|
_viewProjInv = mat4.create();
|
|
275
|
+
_projectionInv = mat4.create();
|
|
241
276
|
_cameraWorld = mat4.create();
|
|
242
277
|
_apertureRadius = 0;
|
|
243
278
|
_focusDistance = 10;
|
|
244
279
|
_needsUpdateBindGroups = true;
|
|
245
|
-
|
|
280
|
+
// Photon maps are per target. Switching targets only rebinds them, which
|
|
281
|
+
// must not send steady frames through the asynchronous pipeline path.
|
|
282
|
+
_needsPhotonTargetBindGroups = false;
|
|
283
|
+
// Target selection is not a scene-resource edit. A stale fallback must not
|
|
284
|
+
// leave global bindings dirty after switching back to a prepared target.
|
|
285
|
+
_needsUpdateOutputBindGroup = false;
|
|
246
286
|
_aovPipelines = new WeakMap();
|
|
247
287
|
_depthCompute;
|
|
248
288
|
_depthTracePipeline;
|
|
249
289
|
_depthPipeline;
|
|
250
290
|
_shaderCache;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
_shaderCompilationDiagnostics;
|
|
254
|
-
_pipelineError;
|
|
291
|
+
_bvhCPU;
|
|
292
|
+
_bvhDataBytes = 0;
|
|
255
293
|
// Fold scene invariants before backend inlining; runtime edits use uniforms.
|
|
256
294
|
_bakePipelineConstants = true;
|
|
257
|
-
_failedPipelineSignature;
|
|
258
295
|
constructor(device, _textureSelection = { requested: 'atlas' }) {
|
|
259
296
|
this._textureSelection = _textureSelection;
|
|
260
297
|
this._device = device;
|
|
@@ -262,7 +299,7 @@ class WGPURayTracePass {
|
|
|
262
299
|
if (_textureSelection.support)
|
|
263
300
|
this._bindless = new WGPUBindlessTextures(device);
|
|
264
301
|
this._uniforms = {
|
|
265
|
-
cameraUniform: this._uniformBuffer(
|
|
302
|
+
cameraUniform: this._uniformBuffer(52 * 4),
|
|
266
303
|
rtUniform: this._uniformBuffer(this._rtUniform.buffer.byteLength),
|
|
267
304
|
analyticLights: this._uniformBuffer(FIXED_LIGHT_COUNT * PIXELS_PER_LIGHT * 4 * 4)
|
|
268
305
|
};
|
|
@@ -298,8 +335,14 @@ class WGPURayTracePass {
|
|
|
298
335
|
device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, new Float32Array([Number.MAX_SAFE_INTEGER]), { bytesPerRow: 4 }, [1, 1]);
|
|
299
336
|
}
|
|
300
337
|
getOutputBuffer() {
|
|
338
|
+
// Another render may have traced since this target's samples were written.
|
|
339
|
+
const transport = this._targetTransport.get(this._activeTarget) ?? 0;
|
|
301
340
|
return {
|
|
302
341
|
color: this._outColorBuffer,
|
|
342
|
+
...(transport & 2 || ((this._transport?.debugGroup ?? -1) >= 0 || causticDiagnostics.debugGroup >= 0) ? { wideHdr: true } : {}),
|
|
343
|
+
...(transport & 1 && this._causticLight?.get(this._activeTarget)
|
|
344
|
+
? { caustic: this._causticLight.get(this._activeTarget) }
|
|
345
|
+
: {}),
|
|
303
346
|
normal: this._outNormalBuffer,
|
|
304
347
|
albedo: this._outAlbedoBuffer,
|
|
305
348
|
shadowCatcher: this._outShadowCatcherBuffer
|
|
@@ -494,16 +537,33 @@ class WGPURayTracePass {
|
|
|
494
537
|
varianceBuffer.destroy();
|
|
495
538
|
}
|
|
496
539
|
}
|
|
497
|
-
_createPipelineLayout() {
|
|
540
|
+
_createPipelineLayout(bindless, photonMapping) {
|
|
498
541
|
const descriptor = {
|
|
499
542
|
label: 'Ray Trace Pipeline Layout',
|
|
500
|
-
bindGroupLayouts: (
|
|
543
|
+
bindGroupLayouts: (bindless ? bindlessBindGroupLayouts() : rayTraceBindGroupLayouts()).map((layout, index) => {
|
|
544
|
+
if (photonMapping && index === (bindless ? 0 : 1))
|
|
545
|
+
layout = {
|
|
546
|
+
...layout,
|
|
547
|
+
entries: [
|
|
548
|
+
...layout.entries,
|
|
549
|
+
...[11, 12].map((binding) => ({
|
|
550
|
+
binding: binding + (bindless ? 4 : 0),
|
|
551
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
552
|
+
texture: { sampleType: 'uint' }
|
|
553
|
+
}))
|
|
554
|
+
]
|
|
555
|
+
};
|
|
556
|
+
return this._shaderCache.createBindGroupLayout(layout);
|
|
557
|
+
})
|
|
501
558
|
};
|
|
502
|
-
|
|
559
|
+
return this._shaderCache.createPipelineLayout(bindless ? resourceTableLayout(descriptor) : descriptor);
|
|
503
560
|
}
|
|
504
561
|
_updateGPUBuffer(label, gpuBuffer, data, minSize = WGPU_SCENE_BUFFER_MIN_BYTES) {
|
|
505
562
|
const device = this._device;
|
|
506
563
|
const size = Math.max(data.byteLength, minSize);
|
|
564
|
+
const limit = Math.min(device.limits?.maxBufferSize ?? Infinity, device.limits?.maxStorageBufferBindingSize ?? Infinity);
|
|
565
|
+
if (size > limit)
|
|
566
|
+
throw new RangeError(`${label} buffer requires ${size} bytes; device storage buffer limit is ${limit} bytes.`);
|
|
507
567
|
if (gpuBuffer == null || gpuBuffer.size < size) {
|
|
508
568
|
this._needsUpdateBindGroups = true;
|
|
509
569
|
if (gpuBuffer) {
|
|
@@ -514,10 +574,22 @@ class WGPURayTracePass {
|
|
|
514
574
|
size,
|
|
515
575
|
usage: GPUBufferUsage.STORAGE |
|
|
516
576
|
GPUBufferUsage.COPY_DST |
|
|
517
|
-
GPUBufferUsage.COPY_SRC
|
|
577
|
+
GPUBufferUsage.COPY_SRC,
|
|
578
|
+
// Chromium's mapped-creation IPC payload is limited to a signed 32-bit
|
|
579
|
+
// size even when the adapter supports larger storage buffers.
|
|
580
|
+
mappedAtCreation: size < 0x80000000
|
|
518
581
|
});
|
|
582
|
+
if (size < 0x80000000) {
|
|
583
|
+
new Uint8Array(gpuBuffer.getMappedRange()).set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
584
|
+
gpuBuffer.unmap();
|
|
585
|
+
return gpuBuffer;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Bound each staging allocation/IPC payload for large or reused buffers.
|
|
589
|
+
const chunkBytes = 64 * 1024 * 1024;
|
|
590
|
+
for (let offset = 0; offset < data.byteLength; offset += chunkBytes) {
|
|
591
|
+
this._device.queue.writeBuffer(gpuBuffer, offset, data.buffer, data.byteOffset + offset, Math.min(chunkBytes, data.byteLength - offset));
|
|
519
592
|
}
|
|
520
|
-
this._device.queue.writeBuffer(gpuBuffer, 0, data.buffer);
|
|
521
593
|
return gpuBuffer;
|
|
522
594
|
}
|
|
523
595
|
_uniformBuffer(size) {
|
|
@@ -527,22 +599,46 @@ class WGPURayTracePass {
|
|
|
527
599
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
528
600
|
});
|
|
529
601
|
}
|
|
530
|
-
|
|
531
|
-
if (this.
|
|
602
|
+
_preparePhotonTarget() {
|
|
603
|
+
if (!this._photonMapping)
|
|
532
604
|
return;
|
|
605
|
+
this._photonMap ??= new WGPUPhotonMap(this._device);
|
|
606
|
+
const before = this._photonMap.get(this._activeTarget)?.data;
|
|
607
|
+
const requested = this._causticSettings?.photonMapping;
|
|
608
|
+
const n = typeof requested === 'object' ? requested.photonsPerSample : undefined;
|
|
609
|
+
const after = this._photonMap.ensure(this._activeTarget, Number.isFinite(n) && n > 0 ? n : defaultPhotonMapPhotons(this._width, this._height, causticDiagnostics.photonCountDivisor));
|
|
610
|
+
if (before !== after.data)
|
|
611
|
+
this._needsUpdateBindGroups = true;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Prepares the pipeline for the settings currently written to this pass.
|
|
615
|
+
* Everything except compilation runs synchronously, and compilation only
|
|
616
|
+
* writes to its own variant. Renders with different settings can therefore
|
|
617
|
+
* interleave without invalidating each other. Returns a promise while the
|
|
618
|
+
* requested state waits on the GPU; undefined means nothing is outstanding.
|
|
619
|
+
*/
|
|
620
|
+
updatePipeline() {
|
|
621
|
+
if (this._disposed)
|
|
622
|
+
return;
|
|
623
|
+
if (this._needsUpdateMeshData || this._needsUpdateMeshMembership || this._pendingGeometryUpdates.size ||
|
|
624
|
+
this._needsRefitTLAS || this._dirtyColorMeshes.size || this._pendingMaterialUpdate || this._needsUpdateLightSamplingData) {
|
|
625
|
+
// A failure can have the same shader signature as its repaired scene.
|
|
626
|
+
// Retry after authored updates, while keeping an unchanged failure idle.
|
|
627
|
+
this._resourceError = undefined;
|
|
628
|
+
for (const [signature, variant] of this._traceVariants)
|
|
629
|
+
if (variant.status === 'failed')
|
|
630
|
+
this._traceVariants.delete(signature);
|
|
631
|
+
if (this._aovPipelinesFailed) {
|
|
632
|
+
this._aovPipelines = new WeakMap();
|
|
633
|
+
this._aovPipelinesFailed = false;
|
|
634
|
+
}
|
|
533
635
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
this.
|
|
538
|
-
|
|
539
|
-
let compilationStartedAt;
|
|
540
|
-
let pipelineCompilationStartedAt;
|
|
636
|
+
this._awaitingRetirement = this._bindless?.retirement;
|
|
637
|
+
if (this._awaitingRetirement)
|
|
638
|
+
return this._awaitingRetirement;
|
|
639
|
+
if (this._resourceError)
|
|
640
|
+
return;
|
|
541
641
|
try {
|
|
542
|
-
if (this._bindless?.retirement)
|
|
543
|
-
await this._bindless.retirement;
|
|
544
|
-
if (this._disposed)
|
|
545
|
-
return;
|
|
546
642
|
if (this._needsUpdateMeshData) {
|
|
547
643
|
this._updateMeshData();
|
|
548
644
|
}
|
|
@@ -557,29 +653,51 @@ class WGPURayTracePass {
|
|
|
557
653
|
this._updateMeshColors();
|
|
558
654
|
if (this._pendingMaterialUpdate)
|
|
559
655
|
this._updateMaterialData();
|
|
656
|
+
this._setCaustics(this._causticSettings);
|
|
560
657
|
if (this._needsUpdateLightSamplingData) {
|
|
561
658
|
this._updateLightSamplingData();
|
|
562
659
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
this.
|
|
660
|
+
this._setCaustics(this._causticSettings);
|
|
661
|
+
if (this._lightTracing || this._photonMapping)
|
|
662
|
+
this._causticBounds = causticCasterSphere(this._meshes);
|
|
663
|
+
this._targetTransport.set(this._activeTarget, (+this._lightTracing) | (+this._photonMapping << 1));
|
|
664
|
+
this._preparePhotonTarget();
|
|
665
|
+
const pipelineInputs = JSON.stringify([
|
|
666
|
+
this._constants,
|
|
667
|
+
Boolean(this._constants.SPECTRAL) && this._shareSpectralPrefix,
|
|
668
|
+
this._hasDisplacement,
|
|
669
|
+
this._alphaBlendStrategy,
|
|
670
|
+
this._blasLayoutMask,
|
|
671
|
+
this._bakePipelineConstants,
|
|
672
|
+
this._causticKey,
|
|
673
|
+
!!this._bindless
|
|
674
|
+
]);
|
|
566
675
|
// Steady rendering must not repeat capability selection and cache-key
|
|
567
676
|
// preparation. Dirty resources still take the normal bind-group path.
|
|
568
|
-
if (pipelineInputs === this.
|
|
569
|
-
this.
|
|
677
|
+
if (pipelineInputs === this._activeInputs &&
|
|
678
|
+
this._rayTracePipeline &&
|
|
679
|
+
this._rayTraceBindGroups &&
|
|
680
|
+
!this._needsUpdateBindGroups) {
|
|
681
|
+
if (this._needsPhotonTargetBindGroups || this._needsUpdateOutputBindGroup) {
|
|
682
|
+
this._needsPhotonTargetBindGroups = false;
|
|
683
|
+
this._needsUpdateOutputBindGroup = false;
|
|
684
|
+
this._updateBindGroups();
|
|
685
|
+
}
|
|
686
|
+
this._requestedVariant = this._activeVariant;
|
|
570
687
|
return;
|
|
688
|
+
}
|
|
571
689
|
this._rtUniform.views.sceneFeatureFlags[0] = wgpuSceneFeatureFlags(this._constants);
|
|
572
690
|
const requiredCapabilities = retainedShaderCapabilities(this._constants);
|
|
573
691
|
const constants = { ...this._constants, ...requiredCapabilities };
|
|
574
|
-
const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) &&
|
|
575
|
-
this._alphaBlendStrategy === 'deterministic-source-over';
|
|
692
|
+
const hasAlpha = Boolean(constants.HAS_ALPHA_BLEND) && this._alphaBlendStrategy === 'deterministic-source-over';
|
|
576
693
|
const hasSceneAlpha = Boolean(constants.HAS_ALPHA);
|
|
577
694
|
const pipelineConstantsKeys = wgpuPipelineConstantKeys(constants);
|
|
578
|
-
const defineCode = WGPU_SHADER_DEFINE_KEYS
|
|
579
|
-
.map((key) => `const ${key} = ${constants[key]};`)
|
|
580
|
-
.join('\n');
|
|
695
|
+
const defineCode = WGPU_SHADER_DEFINE_KEYS.map((key) => `const ${key} = ${constants[key]};`).join('\n');
|
|
581
696
|
const structure = {
|
|
697
|
+
shareSpectralPrefix: Boolean(constants.SPECTRAL) && this._shareSpectralPrefix,
|
|
582
698
|
hasDisplacement: this._hasDisplacement,
|
|
699
|
+
caustics: this._causticKey,
|
|
700
|
+
bindless: !!this._bindless,
|
|
583
701
|
hasAlpha,
|
|
584
702
|
hasSceneAlpha,
|
|
585
703
|
hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
|
|
@@ -588,10 +706,11 @@ class WGPURayTracePass {
|
|
|
588
706
|
bakePipelineConstants: this._bakePipelineConstants
|
|
589
707
|
};
|
|
590
708
|
const structureKey = JSON.stringify({
|
|
591
|
-
...structure,
|
|
709
|
+
...structure,
|
|
710
|
+
defineCode,
|
|
592
711
|
constants: pipelineConstantsKeys
|
|
593
|
-
.filter(key => !(key in requiredCapabilities))
|
|
594
|
-
.map(key => [key, constants[key]])
|
|
712
|
+
.filter((key) => !(key in requiredCapabilities))
|
|
713
|
+
.map((key) => [key, constants[key]])
|
|
595
714
|
});
|
|
596
715
|
// Prefer an existing compatible pipeline, including an earlier lean
|
|
597
716
|
// variant. Only grow the current variant when it cannot cover an edit;
|
|
@@ -599,280 +718,279 @@ class WGPURayTracePass {
|
|
|
599
718
|
const capabilities = this._shaderCapabilities.find(structureKey, requiredCapabilities) ??
|
|
600
719
|
retainedShaderCapabilities(this._constants, structureKey === this._retainedShaderStructure ? this._retainedShaderCapabilities : undefined);
|
|
601
720
|
Object.assign(constants, capabilities);
|
|
602
|
-
|
|
721
|
+
// Runtime feature values live in uniforms, so edits can share a pipeline
|
|
722
|
+
// as long as its compiled defines and overrides are the same.
|
|
723
|
+
const signature = JSON.stringify({
|
|
603
724
|
...structure,
|
|
604
|
-
|
|
725
|
+
defineCode,
|
|
726
|
+
constants: pipelineConstantsKeys.map((key) => [key, constants[key]])
|
|
605
727
|
});
|
|
606
|
-
|
|
607
|
-
|
|
728
|
+
const variant = this._traceVariants.get(signature) ??
|
|
729
|
+
this._startTraceVariant(signature, constants, { hasAlpha, hasSceneAlpha, defineCode, pipelineConstantsKeys, structureKey, capabilities });
|
|
730
|
+
this._requestedVariant = variant;
|
|
731
|
+
if (variant.status === 'pending') {
|
|
732
|
+
this._requestQueuedCompile(variant);
|
|
733
|
+
return variant.settled;
|
|
734
|
+
}
|
|
735
|
+
if (variant.status === 'failed')
|
|
608
736
|
return;
|
|
737
|
+
this._installTraceVariant(variant, pipelineInputs);
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
this._resourceError = error;
|
|
741
|
+
this._needsUpdateBindGroups = true;
|
|
742
|
+
console.error('Failed to update WebGPU ray tracing resources.', error);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/** Runs only for a ready variant, without yielding, so tracing can start in this call. */
|
|
746
|
+
_installTraceVariant(variant, pipelineInputs) {
|
|
747
|
+
if (this._activeVariant !== variant) {
|
|
748
|
+
this._activeVariant = variant;
|
|
749
|
+
this._rayTracePipeline = variant.pipeline;
|
|
750
|
+
this._depthCompute = variant.depthCompute;
|
|
751
|
+
this._needsUpdateBindGroups = true;
|
|
752
|
+
}
|
|
753
|
+
const emission = () => causticEmissionTable(this._analyticLights, this._emissiveTriangles, this._causticBounds[3], this._envImageSource && 'sky' in this._envImageSource
|
|
754
|
+
? proceduralEnvironmentPower(this._envImageSource)
|
|
755
|
+
: estimateEnvironmentPower(this._envImageSource));
|
|
756
|
+
const casters = () => causticDiagnostics.aggregateCasters ? [this._causticBounds] : causticCasterSpheres(this._meshes);
|
|
757
|
+
if (this._lightTracing && this._causticLight) {
|
|
758
|
+
this._causticLight.setDistribution(casters(), emission());
|
|
759
|
+
this._causticLight.use(variant.lightEmitter);
|
|
760
|
+
}
|
|
761
|
+
if (this._photonMapping && this._photonMap) {
|
|
762
|
+
this._photonMap.emission.setDistribution(casters(), emission());
|
|
763
|
+
this._photonMap.emission.use(variant.photonEmitter);
|
|
764
|
+
}
|
|
765
|
+
// TODO rebind when lights, materials, bvh changed.
|
|
766
|
+
if (this._needsUpdateBindGroups || this._needsPhotonTargetBindGroups || this._needsUpdateOutputBindGroup) {
|
|
767
|
+
this._needsUpdateBindGroups = false;
|
|
768
|
+
this._needsPhotonTargetBindGroups = false;
|
|
769
|
+
this._needsUpdateOutputBindGroup = false;
|
|
770
|
+
this._updateBindGroups();
|
|
771
|
+
}
|
|
772
|
+
this._retainedShaderCapabilities = variant.capabilities;
|
|
773
|
+
this._retainedShaderStructure = variant.structureKey;
|
|
774
|
+
this._shaderCapabilities.remember(variant.structureKey, variant.capabilities);
|
|
775
|
+
this._activeInputs = pipelineInputs;
|
|
776
|
+
}
|
|
777
|
+
/** Snapshots everything compilation needs. The task must not read mutable pass state afterwards. */
|
|
778
|
+
_startTraceVariant(signature, constants, shader) {
|
|
779
|
+
const shareSpectralPrefix = Boolean(constants.SPECTRAL) && this._shareSpectralPrefix;
|
|
780
|
+
const bake = this._bakePipelineConstants, bindless = this._bindless;
|
|
781
|
+
const hasDisplacement = this._hasDisplacement, blasLayoutMask = this._blasLayoutMask;
|
|
782
|
+
const lightTracing = this._lightTracing, photonMapping = this._photonMapping;
|
|
783
|
+
const causticKey = this._causticKey, transport = this._transport;
|
|
784
|
+
const { hasAlpha, hasSceneAlpha, defineCode, pipelineConstantsKeys } = shader;
|
|
785
|
+
const emitterSource = (mode) => {
|
|
786
|
+
const code = causticLightCompute(constants, blasLayoutMask, hasDisplacement, mode, '', causticDiagnostics.fullEmissionRandom !== false, true, true);
|
|
787
|
+
return bindless ? bindlessShaderSource(code, this._textureSelection.support.enable) : code;
|
|
788
|
+
};
|
|
789
|
+
const lightEmitter = lightTracing ? this._causticLight ??= new WGPUCausticLightPass(this._device) : undefined;
|
|
790
|
+
const photonEmitter = photonMapping ? this._photonMap?.emission : undefined;
|
|
791
|
+
const pipelineDiagnostic = {
|
|
792
|
+
label: 'Ray Trace', entryPoint: 'main', firstFrameRequired: true, status: 'compiling',
|
|
793
|
+
startedAtMs: 0, concurrentCompilationCount: 1
|
|
794
|
+
};
|
|
795
|
+
const variant = {
|
|
796
|
+
status: 'pending', capabilities: shader.capabilities, structureKey: shader.structureKey,
|
|
797
|
+
settled: undefined,
|
|
798
|
+
diagnostics: {
|
|
799
|
+
status: 'compiling', sourceBytes: 0, sourceLines: 0, pipelineCount: 1,
|
|
800
|
+
completedPipelineCount: 0, sourceGenerationTimeMs: 0, moduleCreationTimeMs: 0,
|
|
801
|
+
modules: [], pipelines: [{ ...pipelineDiagnostic }], maximumConcurrentPipelineCount: 1, peakConcurrentPipelineCount: 1
|
|
609
802
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
(
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
let rayTraceShaderCode = wgpuShaderSource(constants, {
|
|
637
|
-
hasDisplacement: this._hasDisplacement, hasAlpha, hasSceneAlpha,
|
|
803
|
+
};
|
|
804
|
+
const device = this._device, groupIndices = bindless ? [0, 1] : [0, 1, 2];
|
|
805
|
+
let startedAt = 0;
|
|
806
|
+
const compile = async () => {
|
|
807
|
+
startedAt = shaderCompilationNow();
|
|
808
|
+
// Override values are not part of the WGSL unless they are baked, so a
|
|
809
|
+
// quality edit can share the module that the driver has already parsed.
|
|
810
|
+
const moduleKey = JSON.stringify([causticKey, !!bindless, shareSpectralPrefix, Boolean(constants.SPECTRAL), hasAlpha, hasSceneAlpha, hasDisplacement,
|
|
811
|
+
constants.USE_ANISOTROPY_DIRECTIONS, constants.USE_COAT_NORMAL_FRAMES, blasLayoutMask, defineCode, pipelineConstantsKeys,
|
|
812
|
+
bake && pipelineConstantsKeys.map((key) => constants[key])]);
|
|
813
|
+
let source = this._traceModules.get(moduleKey);
|
|
814
|
+
if (!source) {
|
|
815
|
+
let code = wgpuShaderSource(constants, {
|
|
816
|
+
maxPathEvents: MAX_PATH_EVENTS_WGPU,
|
|
817
|
+
hasDisplacement,
|
|
818
|
+
channelIndependentRis: Boolean(constants.SPECTRAL),
|
|
819
|
+
shareSpectralPrefix,
|
|
820
|
+
hasAlpha,
|
|
821
|
+
hasSceneAlpha,
|
|
822
|
+
...transport?.features,
|
|
823
|
+
lightTracing,
|
|
824
|
+
photonMapping,
|
|
825
|
+
photonMaxDepth: causticDiagnostics.photonMaxDepth ?? 20,
|
|
826
|
+
photonDirectSubset: causticDiagnostics.photonDirectSubset,
|
|
827
|
+
debugGroup: causticDiagnostics.debugGroup,
|
|
828
|
+
workgroupSize: transport?.workgroupSize ?? WORKGROUP_SIZE,
|
|
638
829
|
hasAnisotropyDirections: Boolean(constants.USE_ANISOTROPY_DIRECTIONS),
|
|
639
830
|
hasCoatNormals: Boolean(constants.USE_COAT_NORMAL_FRAMES),
|
|
640
|
-
blasLayoutMask
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
label: 'Ray Trace',
|
|
653
|
-
code:
|
|
654
|
-
}
|
|
655
|
-
moduleCreationTimeMs =
|
|
656
|
-
|
|
657
|
-
this.
|
|
658
|
-
|
|
659
|
-
this._compiledAnisotropyDirections = Boolean(constants.USE_ANISOTROPY_DIRECTIONS);
|
|
660
|
-
this._compiledCoatNormals = Boolean(constants.USE_COAT_NORMAL_FRAMES);
|
|
661
|
-
this._compiledBlasLayoutMask = this._blasLayoutMask;
|
|
831
|
+
blasLayoutMask,
|
|
832
|
+
cameraProjectionInverse: true,
|
|
833
|
+
perInstanceBounds: true,
|
|
834
|
+
compactVertices: true,
|
|
835
|
+
dynamicEnvironment: true,
|
|
836
|
+
displayAovStorage: true
|
|
837
|
+
}, bake);
|
|
838
|
+
if (bindless)
|
|
839
|
+
code = bindlessShaderSource(code, this._textureSelection.support.enable);
|
|
840
|
+
variant.diagnostics.sourceGenerationTimeMs = shaderCompilationNow() - startedAt;
|
|
841
|
+
const moduleStartedAt = shaderCompilationNow();
|
|
842
|
+
source = {
|
|
843
|
+
module: this._shaderCache.createShaderModule({ label: 'Ray Trace', code }),
|
|
844
|
+
bytes: code.length, lines: shaderSourceLineCount(code), fingerprint: shaderSourceFingerprint(code)
|
|
845
|
+
};
|
|
846
|
+
variant.diagnostics.moduleCreationTimeMs = shaderCompilationNow() - moduleStartedAt;
|
|
847
|
+
this._traceModules.set(moduleKey, source);
|
|
848
|
+
if (this._traceModules.size > 8)
|
|
849
|
+
this._traceModules.delete(this._traceModules.keys().next().value);
|
|
662
850
|
}
|
|
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);
|
|
851
|
+
Object.assign(variant.diagnostics, {
|
|
852
|
+
sourceBytes: source.bytes, sourceLines: source.lines,
|
|
853
|
+
modules: [{ label: 'Ray Trace', sourceBytes: source.bytes, sourceLines: source.lines, sourceFingerprint: source.fingerprint }]
|
|
854
|
+
});
|
|
855
|
+
const { module } = source;
|
|
856
|
+
const layout = this._createPipelineLayout(!!bindless, photonMapping);
|
|
857
|
+
const pipelineConstants = !bake
|
|
858
|
+
? pipelineConstantsKeys.reduce((obj, key) => {
|
|
859
|
+
obj[key] = +constants[key];
|
|
860
|
+
return obj;
|
|
861
|
+
}, {})
|
|
862
|
+
: undefined;
|
|
863
|
+
const lightSource = lightEmitter && emitterSource('lightTracing');
|
|
864
|
+
const photonSource = photonEmitter && emitterSource('photonMapping');
|
|
865
|
+
// Scopes capture all device errors while open, so one must never span an
|
|
866
|
+
// await: frames presented meanwhile would be blamed on this pipeline.
|
|
867
|
+
device.pushErrorScope('validation');
|
|
868
|
+
const request = this._shaderCache.createComputePipelineAsync({
|
|
869
|
+
label: 'Ray Trace', layout,
|
|
870
|
+
compute: { module, entryPoint: 'main', constants: pipelineConstants }
|
|
871
|
+
});
|
|
872
|
+
const scope = device.popErrorScope().catch(() => null);
|
|
873
|
+
let pipeline;
|
|
874
|
+
try {
|
|
875
|
+
pipeline = await request;
|
|
876
|
+
const scopedError = await scope;
|
|
877
|
+
if (scopedError)
|
|
878
|
+
throw scopedError;
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
const scopedError = await scope;
|
|
882
|
+
// Pipeline creation already reports WGSL failures. Pull the much
|
|
883
|
+
// heavier module diagnostics only on that failure path so normal
|
|
884
|
+
// startup does not schedule a second full-module inspection.
|
|
885
|
+
const info = await module.getCompilationInfo().catch(() => undefined);
|
|
886
|
+
const shaderErrors = (info?.messages ?? []).filter((message) => message.type === 'error');
|
|
887
|
+
if (shaderErrors.length) {
|
|
888
|
+
throw new Error(shaderErrors
|
|
889
|
+
.map((message) => `Ray Trace WGSL ${message.lineNum}:${message.linePos} ${message.message}`)
|
|
890
|
+
.join('\n'));
|
|
813
891
|
}
|
|
892
|
+
if (isWebGPUPipelineSpecializationFailure(error, scopedError))
|
|
893
|
+
throw error;
|
|
894
|
+
throw new Error(joinWebGPUErrors(error, scopedError));
|
|
814
895
|
}
|
|
815
|
-
|
|
816
|
-
|
|
896
|
+
const groups = lightEmitter || photonEmitter ? groupIndices.map((i) => pipeline.getBindGroupLayout(i)) : [];
|
|
897
|
+
const [light, photon] = await Promise.all([
|
|
898
|
+
lightEmitter?.prepare(lightSource, groups, !!bindless),
|
|
899
|
+
photonEmitter?.prepare(photonSource, groups, !!bindless)
|
|
900
|
+
]);
|
|
901
|
+
Object.assign(variant, { pipeline, lightEmitter: light, photonEmitter: photon,
|
|
902
|
+
depthCompute: { module, constants: pipelineConstants } });
|
|
903
|
+
};
|
|
904
|
+
const finishDiagnostics = (status, error) => {
|
|
905
|
+
const totalTimeMs = shaderCompilationNow() - startedAt;
|
|
906
|
+
Object.assign(variant.diagnostics, {
|
|
907
|
+
status, completedPipelineCount: status === 'ready' ? 1 : 0, error,
|
|
908
|
+
pipelines: [{ ...pipelineDiagnostic, status, error, endedAtMs: totalTimeMs, durationMs: totalTimeMs }],
|
|
909
|
+
pipelineCompilationTimeMs: totalTimeMs, totalTimeMs
|
|
910
|
+
});
|
|
911
|
+
};
|
|
912
|
+
const run = () => compile().then(() => {
|
|
913
|
+
variant.status = 'ready';
|
|
914
|
+
finishDiagnostics('ready');
|
|
915
|
+
}, (error) => {
|
|
916
|
+
finishDiagnostics('failed', getWebGPUErrorMessage(error));
|
|
817
917
|
if (this._disposed)
|
|
818
918
|
return;
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
919
|
+
if (!bake && isWebGPUPipelineSpecializationFailure(error)) {
|
|
920
|
+
// WebKit can accept a large WGSL module but fail while translating
|
|
921
|
+
// its specialization-constant variant to Metal. Baking the values
|
|
922
|
+
// into WGSL lets its front end eliminate disabled feature branches
|
|
923
|
+
// before creating the Metal library. The next request has a new
|
|
924
|
+
// signature, so this one stays pending until it is replaced.
|
|
925
|
+
this._bakePipelineConstants = true;
|
|
926
|
+
this._traceVariants.delete(signature);
|
|
927
|
+
console.warn('WebGPU pipeline specialization failed; retrying with constants baked into WGSL.');
|
|
928
|
+
return;
|
|
823
929
|
}
|
|
824
|
-
|
|
825
|
-
this.
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
this._failedPipelineSignature = undefined;
|
|
829
|
-
}
|
|
830
|
-
catch (error) {
|
|
831
|
-
if (this._disposed)
|
|
930
|
+
// Nobody needs an obsolete result, and a later request should try again.
|
|
931
|
+
if (this._requestedVariant !== variant) {
|
|
932
|
+
this._traceVariants.delete(signature);
|
|
933
|
+
variant.status = 'failed';
|
|
832
934
|
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
935
|
}
|
|
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;
|
|
936
|
+
variant.status = 'failed';
|
|
937
|
+
variant.error = error;
|
|
856
938
|
console.error('Failed to update WebGPU ray tracing pipeline.', error);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
this.
|
|
939
|
+
});
|
|
940
|
+
variant.settled = new Promise((resolve) => {
|
|
941
|
+
this._queuedCompiles.push({ variant, signature, run, release: resolve });
|
|
942
|
+
});
|
|
943
|
+
this._traceVariants.set(signature, variant);
|
|
944
|
+
if (this._queuedCompiles.length > MAX_QUEUED_COMPILES) {
|
|
945
|
+
// A dropped variant stays pending, because a render that still wants it
|
|
946
|
+
// replaces it with a new request on its next frame.
|
|
947
|
+
const obsolete = this._queuedCompiles.shift();
|
|
948
|
+
if (this._traceVariants.get(obsolete.signature) === obsolete.variant)
|
|
949
|
+
this._traceVariants.delete(obsolete.signature);
|
|
950
|
+
obsolete.release();
|
|
951
|
+
}
|
|
952
|
+
for (const [key, retained] of this._traceVariants) {
|
|
953
|
+
if (this._traceVariants.size <= MAX_TRACE_VARIANTS)
|
|
954
|
+
break;
|
|
955
|
+
if (retained.status !== 'pending' && retained !== this._activeVariant)
|
|
956
|
+
this._traceVariants.delete(key);
|
|
957
|
+
}
|
|
958
|
+
this._pumpCompiles();
|
|
959
|
+
return variant;
|
|
960
|
+
}
|
|
961
|
+
/** Runs the most recently requested variants first: a dragged slider's last value is the one in use. */
|
|
962
|
+
_pumpCompiles() {
|
|
963
|
+
while (this._activeCompiles < MAX_CONCURRENT_COMPILES && this._queuedCompiles.length) {
|
|
964
|
+
const job = this._queuedCompiles.pop();
|
|
965
|
+
this._activeCompiles++;
|
|
966
|
+
void job.run().finally(() => {
|
|
967
|
+
this._activeCompiles--;
|
|
968
|
+
job.release();
|
|
969
|
+
if (!this._disposed)
|
|
970
|
+
this._pumpCompiles();
|
|
971
|
+
});
|
|
860
972
|
}
|
|
861
973
|
}
|
|
862
|
-
|
|
974
|
+
_requestQueuedCompile(variant) {
|
|
975
|
+
const index = this._queuedCompiles.findIndex((job) => job.variant === variant);
|
|
976
|
+
if (index >= 0)
|
|
977
|
+
this._queuedCompiles.push(...this._queuedCompiles.splice(index, 1));
|
|
978
|
+
}
|
|
979
|
+
_prepareAovPipelines(kinds, compact = false) {
|
|
863
980
|
const trace = this._rayTracePipeline;
|
|
864
981
|
if (!trace || !this._depthCompute)
|
|
865
982
|
return;
|
|
866
|
-
const reflectance = (aovMask(kinds) &
|
|
983
|
+
const reflectance = (aovMask(kinds) & AOV_REFLECTANCE_MASK) !== 0;
|
|
867
984
|
let variants = this._aovPipelines.get(trace);
|
|
868
985
|
if (!variants) {
|
|
869
986
|
variants = new Map();
|
|
870
987
|
this._aovPipelines.set(trace, variants);
|
|
871
988
|
}
|
|
872
|
-
|
|
989
|
+
const key = Number(reflectance) + Number(compact) * 2;
|
|
990
|
+
let pipelines = variants.get(key);
|
|
873
991
|
if (!pipelines) {
|
|
874
992
|
pipelines = {};
|
|
875
|
-
variants.set(
|
|
993
|
+
variants.set(key, pipelines);
|
|
876
994
|
const pending = pipelines;
|
|
877
995
|
const textures = this._bindless, groupIndex = textures ? 2 : 3;
|
|
878
996
|
const output = this._device.createBindGroupLayout({ entries: [
|
|
@@ -884,42 +1002,45 @@ class WGPURayTracePass {
|
|
|
884
1002
|
] });
|
|
885
1003
|
const descriptor = { bindGroupLayouts: Array.from({ length: groupIndex }, (_, i) => trace.getBindGroupLayout(i)).concat(output) };
|
|
886
1004
|
const layout = this._device.createPipelineLayout(textures ? resourceTableLayout(descriptor) : descriptor);
|
|
887
|
-
const compute = { ...this._depthCompute, constants: { ...this._depthCompute.constants, DEPTH_QUERY_MODE: 1, AOV_REFLECTANCE: reflectance ? 1 : 0 } };
|
|
1005
|
+
const compute = { ...this._depthCompute, constants: { ...this._depthCompute.constants, DEPTH_QUERY_MODE: 1, AOV_REFLECTANCE: reflectance ? 1 : 0, AOV_COMPACT: compact ? 1 : 0 } };
|
|
888
1006
|
// Keep the UI responsive during first use, and reuse both variants across
|
|
889
1007
|
// preview/final target changes rather than recreating synchronous pipelines.
|
|
890
1008
|
void Promise.all([
|
|
891
1009
|
this._shaderCache.createComputePipelineAsync({ label: 'AOV accumulation', layout, compute: { ...compute, entryPoint: 'aovTrace' } }),
|
|
892
1010
|
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; });
|
|
1011
|
+
]).then(([accumulation, center]) => { pending.accumulation = accumulation; pending.center = center; }, error => { pending.error = error; this._aovPipelinesFailed = true; });
|
|
894
1012
|
}
|
|
895
1013
|
if (pipelines.error)
|
|
896
1014
|
throw pipelines.error;
|
|
897
1015
|
return pipelines;
|
|
898
1016
|
}
|
|
899
|
-
prepareAovs(kinds) {
|
|
900
|
-
const pipelines = this._prepareAovPipelines(kinds);
|
|
1017
|
+
prepareAovs(kinds, compact = false) {
|
|
1018
|
+
const pipelines = this._prepareAovPipelines(kinds, compact);
|
|
901
1019
|
return !!pipelines?.accumulation && !!pipelines.center;
|
|
902
1020
|
}
|
|
903
|
-
renderAovs(state, camera, samples, cap, kinds) {
|
|
1021
|
+
renderAovs(state, camera, samples, cap, kinds, resolution) {
|
|
904
1022
|
const groups = this._rayTraceBindGroups;
|
|
905
1023
|
if (!groups || !this._tlas || !this._preparedMeshData || !kinds.length)
|
|
906
1024
|
return false;
|
|
907
|
-
const
|
|
1025
|
+
const compact = !!resolution && kinds.every(kind => COMPACT_AOV_KINDS.includes(kind));
|
|
1026
|
+
const pipelines = this._prepareAovPipelines(kinds, compact);
|
|
908
1027
|
if (!pipelines?.accumulation || !pipelines.center)
|
|
909
1028
|
return false;
|
|
910
|
-
state.resize(this._width, this._height);
|
|
1029
|
+
state.resize(resolution?.width ?? this._width, resolution?.height ?? this._height, compact);
|
|
911
1030
|
const textures = this._bindless, groupIndex = textures ? 2 : 3;
|
|
912
1031
|
const target = Math.min(samples, cap);
|
|
913
1032
|
if (state.centerReady && state.samples >= target)
|
|
914
1033
|
return true;
|
|
915
1034
|
if (!state.frame) {
|
|
916
|
-
|
|
917
|
-
|
|
1035
|
+
// Only the center pass reads primitive IDs and compact display AOVs skip
|
|
1036
|
+
// it; building the map walks every BVH leaf on the main thread.
|
|
1037
|
+
const primitiveIds = compact ? undefined : this._aovPrimitiveCache.get(this._preparedMeshData, this._tlas);
|
|
1038
|
+
const packed = packAovScene(this._preparedMeshData, this._tlas, camera, state.width, state.height, state.history, primitiveIds, false);
|
|
918
1039
|
state.frame = packed.frame;
|
|
919
1040
|
state.setScene(packed.words, primitiveIds);
|
|
920
1041
|
}
|
|
921
1042
|
this._device.queue.writeBuffer(this._uniforms.rtUniform, 0, this._rtUniform.buffer);
|
|
922
|
-
this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance));
|
|
1043
|
+
this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance, this._projectionInv));
|
|
923
1044
|
const device = this._device;
|
|
924
1045
|
const run = (pipeline, sample) => {
|
|
925
1046
|
// A fresh uniform for each queued dispatch keeps batch samples independent.
|
|
@@ -943,11 +1064,12 @@ class WGPURayTracePass {
|
|
|
943
1064
|
parameters.destroy();
|
|
944
1065
|
};
|
|
945
1066
|
if (!state.centerReady) {
|
|
946
|
-
|
|
1067
|
+
if (!compact)
|
|
1068
|
+
run(pipelines.center, 0);
|
|
947
1069
|
state.centerReady = true;
|
|
948
1070
|
state.history.accept(state.frame);
|
|
949
1071
|
}
|
|
950
|
-
if ((aovMask(kinds) &
|
|
1072
|
+
if ((aovMask(kinds) & AOV_ACCUMULATED_MASK) === 0)
|
|
951
1073
|
state.samples = target;
|
|
952
1074
|
while (state.samples < target) {
|
|
953
1075
|
run(pipelines.accumulation, state.samples);
|
|
@@ -955,13 +1077,27 @@ class WGPURayTracePass {
|
|
|
955
1077
|
}
|
|
956
1078
|
return true;
|
|
957
1079
|
}
|
|
958
|
-
async
|
|
1080
|
+
async pickSurface(x, y, aspect) {
|
|
1081
|
+
const source = this._preparedMeshData, tlas = this._tlas;
|
|
1082
|
+
if (!source || !tlas)
|
|
1083
|
+
return;
|
|
1084
|
+
const values = await this._readDepthSamples({ x, y, size: 0 }, aspect, true);
|
|
1085
|
+
if (!values || source !== this._preparedMeshData || tlas !== this._tlas)
|
|
1086
|
+
return;
|
|
1087
|
+
return resolveSurfaceHit(source, tlas, values);
|
|
1088
|
+
}
|
|
1089
|
+
readDepthSamples(area, aspect) {
|
|
1090
|
+
return this._readDepthSamples(area, aspect);
|
|
1091
|
+
}
|
|
1092
|
+
async _readDepthSamples(area, aspect, picking = false) {
|
|
959
1093
|
const tracePipeline = this._rayTracePipeline;
|
|
960
1094
|
const groups = this._rayTraceBindGroups;
|
|
961
1095
|
const textures = this._bindless, textureRevision = textures?.revision;
|
|
962
|
-
if (!tracePipeline || !groups || !this._depthCompute || this.
|
|
1096
|
+
if (!tracePipeline || !groups || !this._depthCompute || this.isCompilingShader())
|
|
963
1097
|
return;
|
|
964
1098
|
const packed = packDepthSampleArea(area, aspect ?? this._width / this._height);
|
|
1099
|
+
if (picking)
|
|
1100
|
+
packed.set([-1, -1], 2);
|
|
965
1101
|
if (this._depthTracePipeline !== tracePipeline || !this._depthPipeline) {
|
|
966
1102
|
const outputLayout = this._shaderCache.createBindGroupLayout({
|
|
967
1103
|
label: 'Depth query output',
|
|
@@ -1013,7 +1149,8 @@ class WGPURayTracePass {
|
|
|
1013
1149
|
encoder.copyBufferToBuffer(output, 0, readback, 0, bytes);
|
|
1014
1150
|
device.queue.submit([encoder.finish()]);
|
|
1015
1151
|
await readback.mapAsync(GPUMapMode.READ);
|
|
1016
|
-
|
|
1152
|
+
const rgba = new Float32Array(readback.getMappedRange());
|
|
1153
|
+
return picking ? rgba.slice(0, 4) : unpackDepthSamples(rgba);
|
|
1017
1154
|
}
|
|
1018
1155
|
finally {
|
|
1019
1156
|
if (readback.mapState === 'mapped')
|
|
@@ -1023,15 +1160,17 @@ class WGPURayTracePass {
|
|
|
1023
1160
|
params.destroy();
|
|
1024
1161
|
}
|
|
1025
1162
|
}
|
|
1163
|
+
/** Whether the latest request waits on GPU work that will settle on its own. */
|
|
1026
1164
|
isCompilingShader() {
|
|
1027
|
-
return this.
|
|
1165
|
+
return !!this._awaitingRetirement || this._requestedVariant?.status === 'pending';
|
|
1028
1166
|
}
|
|
1029
1167
|
isPipelineReady() {
|
|
1030
1168
|
return ((!!this._rayTracePipeline) &&
|
|
1031
1169
|
!!this._rayTraceBindGroups);
|
|
1032
1170
|
}
|
|
1171
|
+
/** Failure of the latest request. A failed variant does not affect other renders. */
|
|
1033
1172
|
getPipelineError() {
|
|
1034
|
-
return this.
|
|
1173
|
+
return this._resourceError ?? this._requestedVariant?.error;
|
|
1035
1174
|
}
|
|
1036
1175
|
_updateBindGroups() {
|
|
1037
1176
|
const { _device } = this;
|
|
@@ -1054,14 +1193,18 @@ class WGPURayTracePass {
|
|
|
1054
1193
|
}
|
|
1055
1194
|
}));
|
|
1056
1195
|
const textureEntries = [
|
|
1057
|
-
...(this._bindless
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1196
|
+
...(this._bindless
|
|
1197
|
+
? []
|
|
1198
|
+
: [
|
|
1199
|
+
{
|
|
1200
|
+
binding: 0,
|
|
1201
|
+
resource: this._mapsSampler
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
binding: 1,
|
|
1205
|
+
resource: this._maps.createView()
|
|
1206
|
+
}
|
|
1207
|
+
]),
|
|
1065
1208
|
{
|
|
1066
1209
|
binding: 3,
|
|
1067
1210
|
resource: {
|
|
@@ -1071,6 +1214,12 @@ class WGPURayTracePass {
|
|
|
1071
1214
|
{ binding: 4, resource: this._envTex.createView() },
|
|
1072
1215
|
{ binding: 5, resource: this._envDistributionTex.createView() },
|
|
1073
1216
|
{ binding: 6, resource: this._backgroundTex.createView() },
|
|
1217
|
+
...(this._photonMapping
|
|
1218
|
+
? [
|
|
1219
|
+
{ binding: 11, resource: this._photonMap.get(this._activeTarget).data.createView() },
|
|
1220
|
+
{ binding: 12, resource: this._photonMap.get(this._activeTarget).headers.createView() }
|
|
1221
|
+
]
|
|
1222
|
+
: []),
|
|
1074
1223
|
{ binding: 8, resource: this._lightSamplingTexture.createView() },
|
|
1075
1224
|
{ binding: 9, resource: this._openPbrEnergyLut.createView() },
|
|
1076
1225
|
{
|
|
@@ -1078,11 +1227,18 @@ class WGPURayTracePass {
|
|
|
1078
1227
|
resource: this._adaptiveSamplingTexture.createView()
|
|
1079
1228
|
}
|
|
1080
1229
|
];
|
|
1081
|
-
const bufferBindGroup = _device.createBindGroup({
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1230
|
+
const bufferBindGroup = _device.createBindGroup({
|
|
1231
|
+
layout: bindGroupPipeline.getBindGroupLayout(0),
|
|
1232
|
+
entries: this._bindless
|
|
1233
|
+
? bufferEntries.concat(textureEntries.map((e) => ({ ...e, binding: e.binding + 4 })))
|
|
1234
|
+
: bufferEntries
|
|
1085
1235
|
});
|
|
1236
|
+
const texturesBindGroup = this._bindless
|
|
1237
|
+
? undefined
|
|
1238
|
+
: _device.createBindGroup({
|
|
1239
|
+
layout: bindGroupPipeline.getBindGroupLayout(1),
|
|
1240
|
+
entries: textureEntries
|
|
1241
|
+
});
|
|
1086
1242
|
const uniformBindGroup = _device.createBindGroup({
|
|
1087
1243
|
layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 1 : 2),
|
|
1088
1244
|
entries: Object.keys(this._uniforms).map((key, i) => ({
|
|
@@ -1092,19 +1248,36 @@ class WGPURayTracePass {
|
|
|
1092
1248
|
}
|
|
1093
1249
|
}))
|
|
1094
1250
|
});
|
|
1251
|
+
if (this._photonMapping) {
|
|
1252
|
+
// Explicit layouts still track unused textures. Bind a placeholder while
|
|
1253
|
+
// emission writes the real packed map through its storage view.
|
|
1254
|
+
const emissionEntries = textureEntries.map((entry) => entry.binding === 11 || entry.binding === 12
|
|
1255
|
+
? { ...entry, resource: this._photonMap.placeholder.createView() }
|
|
1256
|
+
: entry);
|
|
1257
|
+
const sceneGroup = _device.createBindGroup({
|
|
1258
|
+
layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 0 : 1),
|
|
1259
|
+
entries: this._bindless
|
|
1260
|
+
? bufferEntries.concat(emissionEntries.map((e) => ({ ...e, binding: e.binding + 4 })))
|
|
1261
|
+
: emissionEntries
|
|
1262
|
+
});
|
|
1263
|
+
this._photonEmissionGroups = this._bindless
|
|
1264
|
+
? [sceneGroup, uniformBindGroup]
|
|
1265
|
+
: [bufferBindGroup, sceneGroup, uniformBindGroup];
|
|
1266
|
+
}
|
|
1095
1267
|
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
|
-
|
|
1268
|
+
const outputBindGroup = (target.pipeline === bindGroupPipeline && target.bindGroup) ||
|
|
1269
|
+
_device.createBindGroup({
|
|
1270
|
+
layout: bindGroupPipeline.getBindGroupLayout(this._bindless ? 2 : 3),
|
|
1271
|
+
entries: [
|
|
1272
|
+
this._outColorBuffer,
|
|
1273
|
+
this._outNormalBuffer,
|
|
1274
|
+
this._outAlbedoBuffer,
|
|
1275
|
+
this._outShadowCatcherBuffer
|
|
1276
|
+
].map((buffer, i) => ({
|
|
1277
|
+
binding: i,
|
|
1278
|
+
resource: { buffer }
|
|
1279
|
+
}))
|
|
1280
|
+
});
|
|
1108
1281
|
target.bindGroup = outputBindGroup;
|
|
1109
1282
|
target.pipeline = bindGroupPipeline;
|
|
1110
1283
|
this._rayTraceBindGroups = [
|
|
@@ -1131,6 +1304,36 @@ class WGPURayTracePass {
|
|
|
1131
1304
|
encoder.clearBuffer(this._outAlbedoBuffer);
|
|
1132
1305
|
encoder.clearBuffer(this._outShadowCatcherBuffer);
|
|
1133
1306
|
}
|
|
1307
|
+
const offset = this._rtUniform.views.scaleOffset;
|
|
1308
|
+
const causticBatch = this._lightTracing && offset[2] === tiles[0] - 1 && offset[3] === tiles[1] - 1;
|
|
1309
|
+
const photonBatch = this._photonMapping && offset[2] === 0 && offset[3] === 0;
|
|
1310
|
+
if (photonBatch && this._photonMap) {
|
|
1311
|
+
const requested = this._causticSettings?.photonMapping, initial = typeof requested === 'object' ? requested.initialRadius : undefined;
|
|
1312
|
+
const radius = Number.isFinite(initial) && initial > 0 ? initial : Math.max((causticDiagnostics.photonPerCasterRadius
|
|
1313
|
+
? causticCasterSpheres(this._meshes).reduce((maximum, s) => Math.max(maximum, s[3]), 0)
|
|
1314
|
+
: this._causticBounds[3]) * 0.03, 1e-4);
|
|
1315
|
+
const batch = Math.max(1, this._rtUniform.views.sampleCount[0] - this._rtUniform.views.sampleSequenceOffset[0] + 1);
|
|
1316
|
+
const photonViewProjection = mat4.create();
|
|
1317
|
+
mat4.invert(photonViewProjection, this._viewProjInv);
|
|
1318
|
+
this._photonMap.encode(encoder, this._photonEmissionGroups, this._activeTarget, photonRadius(radius, causticDiagnostics.photonDebugBatch ?? batch, causticDiagnostics.photonAlpha ?? 2 / 3,
|
|
1319
|
+
// Allocation limits may reduce the requested count.
|
|
1320
|
+
this._photonMap.get(this._activeTarget).count, causticDiagnostics.photonRadiusScaling), {
|
|
1321
|
+
querySet: timing?.querySet,
|
|
1322
|
+
resourceTable: this._bindless?.table,
|
|
1323
|
+
orthographic: !this._causticPerspective,
|
|
1324
|
+
progressiveBatch: batch,
|
|
1325
|
+
width: this._width,
|
|
1326
|
+
height: this._height,
|
|
1327
|
+
clear,
|
|
1328
|
+
run: true,
|
|
1329
|
+
photons: 1,
|
|
1330
|
+
batch: Math.floor(this._rtUniform.views.sampleCount[0]),
|
|
1331
|
+
...this._transport?.uniform,
|
|
1332
|
+
viewProjection: photonViewProjection,
|
|
1333
|
+
casterSphere: this._causticBounds,
|
|
1334
|
+
cameraScale: this._causticCameraScale
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1134
1337
|
const descriptor = {};
|
|
1135
1338
|
const querySet = timing?.querySet;
|
|
1136
1339
|
if (querySet) {
|
|
@@ -1150,16 +1353,40 @@ class WGPURayTracePass {
|
|
|
1150
1353
|
this._rayTraceBindGroups.forEach((bindGroup, idx) => {
|
|
1151
1354
|
pass.setBindGroup(idx, bindGroup);
|
|
1152
1355
|
});
|
|
1153
|
-
|
|
1356
|
+
researchTransport()?.capture(this);
|
|
1357
|
+
pass.dispatchWorkgroups(Math.ceil(dispatchWidth / (this._transport?.workgroupSize ?? WORKGROUP_SIZE)), Math.ceil(dispatchHeight / (this._transport?.workgroupSize ?? WORKGROUP_SIZE)), 1);
|
|
1154
1358
|
pass.end();
|
|
1359
|
+
if (this._lightTracing && this._causticLight) {
|
|
1360
|
+
const viewProjection = mat4.create();
|
|
1361
|
+
mat4.invert(viewProjection, this._viewProjInv);
|
|
1362
|
+
const requested = this._causticSettings?.lightTracing;
|
|
1363
|
+
const count = typeof requested === 'object' ? requested.photonsPerSample : undefined;
|
|
1364
|
+
const photons = Math.min(this._device.limits.maxComputeWorkgroupsPerDimension * 64, Math.max(1, Math.round(Number.isFinite(count) ? count : (this._width * this._height) / 4)));
|
|
1365
|
+
this._causticLight.encode(encoder, this._rayTraceBindGroups.slice(0, this._bindless ? 2 : 3), this._activeTarget, {
|
|
1366
|
+
querySet: causticBatch ? querySet : undefined,
|
|
1367
|
+
width: this._width,
|
|
1368
|
+
height: this._height,
|
|
1369
|
+
clear,
|
|
1370
|
+
run: !!causticBatch,
|
|
1371
|
+
photons,
|
|
1372
|
+
batch: Math.floor(this._rtUniform.views.sampleCount[0]),
|
|
1373
|
+
...this._transport?.uniform,
|
|
1374
|
+
resourceTable: this._bindless?.table,
|
|
1375
|
+
orthographic: !this._causticPerspective,
|
|
1376
|
+
viewProjection,
|
|
1377
|
+
casterSphere: this._causticBounds,
|
|
1378
|
+
cameraScale: this._causticCameraScale
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
return (causticBatch ? 1 : 0) | (photonBatch ? 2 : 0);
|
|
1155
1382
|
}
|
|
1156
1383
|
async getDebugBVHData() {
|
|
1157
|
-
if (!this.
|
|
1158
|
-
throw new Error(
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
};
|
|
1384
|
+
if (!this._bvhCPU)
|
|
1385
|
+
throw new Error('software-bvh exposes no CPU-readable BVH debug buffer');
|
|
1386
|
+
// Debug capture pays for a combined copy only when explicitly requested.
|
|
1387
|
+
const buffer = new ArrayBuffer(this._bvhDataBytes);
|
|
1388
|
+
this._writeBVH(buffer, this._bvhCPU.tlas, this._bvhCPU.blases);
|
|
1389
|
+
return { buffer };
|
|
1163
1390
|
}
|
|
1164
1391
|
getDiagnostics() {
|
|
1165
1392
|
const atlas = this._materialData instanceof MaterialTable ? this._materialData.textureAtlasStats : undefined;
|
|
@@ -1174,7 +1401,7 @@ class WGPURayTracePass {
|
|
|
1174
1401
|
scale: atlas?.scale ?? 1, metadataBytes: 0, resourceTableCapacity: 0, retiredBytes: 0
|
|
1175
1402
|
})
|
|
1176
1403
|
},
|
|
1177
|
-
shaderCompilation: this.
|
|
1404
|
+
shaderCompilation: this._requestedVariant?.diagnostics,
|
|
1178
1405
|
lightSampling: {
|
|
1179
1406
|
emissiveTriangles: this._emissiveTriangles.length,
|
|
1180
1407
|
activeChoices: this._rtUniform.views.lightChoiceCount[0],
|
|
@@ -1194,22 +1421,13 @@ class WGPURayTracePass {
|
|
|
1194
1421
|
if (next === this._alphaBlendStrategy)
|
|
1195
1422
|
return false;
|
|
1196
1423
|
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
1424
|
return true;
|
|
1207
1425
|
}
|
|
1208
1426
|
hasDepthOfField() {
|
|
1209
1427
|
return this._apertureRadius > 0;
|
|
1210
1428
|
}
|
|
1211
1429
|
setBounce(bounce) {
|
|
1212
|
-
const next = normalizeBounces(bounce);
|
|
1430
|
+
const next = normalizeBounces(bounce, MAX_PATH_EVENTS_WGPU);
|
|
1213
1431
|
const changed = this._bounces !== next;
|
|
1214
1432
|
this._bounces = next;
|
|
1215
1433
|
this._rtUniform.views.bounces[0] = next;
|
|
@@ -1265,8 +1483,30 @@ class WGPURayTracePass {
|
|
|
1265
1483
|
this._ensureAdaptiveSamplingTexture(width, height);
|
|
1266
1484
|
this._device.queue.writeTexture({ texture: this._adaptiveSamplingTexture }, data, { bytesPerRow: width * Float32Array.BYTES_PER_ELEMENT }, [width, height]);
|
|
1267
1485
|
}
|
|
1486
|
+
_shareSpectralPrefix = true;
|
|
1268
1487
|
setQuality(options) {
|
|
1269
|
-
|
|
1488
|
+
this._shareSpectralPrefix = options.dispersionWavelengths !== 1;
|
|
1489
|
+
WGPURayTracePass.prototype._setCaustics.call(this, options.caustics);
|
|
1490
|
+
writeTraceQuality(this._rtUniform.views, options, MAX_PATH_EVENTS_WGPU);
|
|
1491
|
+
}
|
|
1492
|
+
_setCaustics(settings) {
|
|
1493
|
+
this._causticSettings = settings;
|
|
1494
|
+
const transport = researchTransport()?.configure(this, 'webgpu');
|
|
1495
|
+
if (!!transport?.enabled !== !!this._transport?.enabled)
|
|
1496
|
+
this._needsUpdateLightSamplingData = true;
|
|
1497
|
+
this._transport = transport;
|
|
1498
|
+
const hasCaster = !!(settings?.lightTracing || settings?.photonMapping) &&
|
|
1499
|
+
this._meshes.some((mesh) => mesh.materials.some(isCausticSpecular));
|
|
1500
|
+
const photonSupported = !(settings?.lightTracing || settings?.photonMapping) ||
|
|
1501
|
+
!hasCaster ||
|
|
1502
|
+
!hasStochasticCausticCaster(this._meshes);
|
|
1503
|
+
this._lightTracing = !!settings?.lightTracing && hasCaster && photonSupported;
|
|
1504
|
+
const photonMapping = !!settings?.photonMapping && hasCaster && photonSupported;
|
|
1505
|
+
this._photonMapping = photonMapping;
|
|
1506
|
+
this._causticKey =
|
|
1507
|
+
this._transport?.enabled || this._lightTracing || this._photonMapping
|
|
1508
|
+
? `${this._lightTracing}/${this._photonMapping}/${causticDiagnostics.fullEmissionRandom !== false}/${causticDiagnostics.photonMaxDepth ?? 20}/${causticDiagnostics.debugGroup}/${causticDiagnostics.photonDirectSubset !== false}/${this._transport?.key ?? ''}`
|
|
1509
|
+
: '';
|
|
1270
1510
|
}
|
|
1271
1511
|
_setEnvMap(envImage) {
|
|
1272
1512
|
const device = this._device;
|
|
@@ -1621,14 +1861,19 @@ class WGPURayTracePass {
|
|
|
1621
1861
|
this._needsUpdateBindGroups ||= resized;
|
|
1622
1862
|
}
|
|
1623
1863
|
_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
|
|
1864
|
+
this._constants.LIGHT_CAPABILITIES = lightShaderCapabilities(this._analyticLights.map((light) => light.type), this._emissiveTriangles.length);
|
|
1865
|
+
const lighting = prepareSceneLightSampling(this._analyticLights, this._emissiveTriangles, this._envImageSource && 'sky' in this._envImageSource
|
|
1866
|
+
? proceduralEnvironmentPower(this._envImageSource)
|
|
1867
|
+
: estimateEnvironmentPower(this._envImageSource), this._device.limits.maxTextureDimension2D, this._lightChoices, this._lightSamplingScratch, this._shadowCatcherLightChoices);
|
|
1868
|
+
researchTransport()?.prepare(this, 'webgpu', lighting);
|
|
1626
1869
|
this._analyticLightSelectionPmfs = lighting.analyticPmfs;
|
|
1627
1870
|
this._iesProfileTexelOffsets = lighting.profiles.offsets;
|
|
1628
1871
|
this._lightDomainDiagnostics = lighting.domains;
|
|
1629
1872
|
const { lightWidth: width, lightHeight: height, lightData: textureData } = lighting;
|
|
1630
1873
|
writeTraceLighting(this._rtUniform.views, lighting);
|
|
1631
|
-
const resized = !this._lightSamplingTexture ||
|
|
1874
|
+
const resized = !this._lightSamplingTexture ||
|
|
1875
|
+
width !== this._lightSamplingTextureSize[0] ||
|
|
1876
|
+
height !== this._lightSamplingTextureSize[1];
|
|
1632
1877
|
if (!this._lightSamplingTexture ||
|
|
1633
1878
|
width !== this._lightSamplingTextureSize[0] ||
|
|
1634
1879
|
height !== this._lightSamplingTextureSize[1]) {
|
|
@@ -1675,22 +1920,54 @@ class WGPURayTracePass {
|
|
|
1675
1920
|
const stacks = getTraversalStackSizes(tlas, blases);
|
|
1676
1921
|
this._constants.TLAS_STACK_SIZE = retainedShaderCapacity(stacks.tlas, Number(this._constants.TLAS_STACK_SIZE ?? 0));
|
|
1677
1922
|
this._constants.BLAS_STACK_SIZE = retainedShaderCapacity(stacks.blas, Number(this._constants.BLAS_STACK_SIZE ?? 0), 32);
|
|
1678
|
-
const
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1923
|
+
const size = tlas.buffer.byteLength + blases.reduce((sum, b) => sum + b.bvh.buffer.byteLength, 0);
|
|
1924
|
+
const limit = Math.min(this._device.limits.maxBufferSize, this._device.limits.maxStorageBufferBindingSize);
|
|
1925
|
+
if (size > limit)
|
|
1926
|
+
throw new RangeError(`BVH buffer requires ${size} bytes; device storage buffer limit is ${limit} bytes.`);
|
|
1927
|
+
let bvhBuffer = this._bvhBuffer;
|
|
1928
|
+
if (!bvhBuffer || bvhBuffer.size < Math.max(size, WGPU_SCENE_BUFFER_MIN_BYTES)) {
|
|
1929
|
+
bvhBuffer?.destroy();
|
|
1930
|
+
bvhBuffer = this._device.createBuffer({ label: 'BVH', size: Math.max(size, WGPU_SCENE_BUFFER_MIN_BYTES),
|
|
1931
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, mappedAtCreation: size < 0x80000000 });
|
|
1932
|
+
if (size < 0x80000000) {
|
|
1933
|
+
this._writeBVH(bvhBuffer.getMappedRange(), tlas, blases);
|
|
1934
|
+
bvhBuffer.unmap();
|
|
1935
|
+
}
|
|
1936
|
+
else
|
|
1937
|
+
this._uploadBVHBlocks(bvhBuffer, tlas, blases);
|
|
1938
|
+
}
|
|
1939
|
+
else
|
|
1940
|
+
this._uploadBVHBlocks(bvhBuffer, tlas, blases);
|
|
1941
|
+
this._bvhCPU = { tlas, blases };
|
|
1942
|
+
this._bvhDataBytes = size;
|
|
1943
|
+
const blasOffset = tlas.buffer.byteLength / 16;
|
|
1685
1944
|
const instancesBuffer = this._updateGPUBuffer('Instances', this._instancesBuffer, new Float32Array(tlas.instancesBuffer));
|
|
1686
|
-
if (bvhBuffer !== this._bvhBuffer || instancesBuffer !== this._instancesBuffer)
|
|
1945
|
+
if (bvhBuffer !== this._bvhBuffer || instancesBuffer !== this._instancesBuffer)
|
|
1687
1946
|
this._needsUpdateBindGroups = true;
|
|
1688
|
-
}
|
|
1689
1947
|
this._bvhBuffer = bvhBuffer;
|
|
1690
1948
|
this._instancesBuffer = instancesBuffer;
|
|
1691
|
-
this._debugBVHBuffer = bvhData.buffer.slice(0);
|
|
1692
1949
|
this._rtUniform.views.blasOffset.set([blasOffset]);
|
|
1693
1950
|
}
|
|
1951
|
+
_uploadBVHBlocks(buffer, tlas, blases) {
|
|
1952
|
+
this._device.queue.writeBuffer(buffer, 0, tlas.buffer);
|
|
1953
|
+
let target = tlas.buffer.byteLength;
|
|
1954
|
+
for (const blas of blases) {
|
|
1955
|
+
const chunkBytes = 64 * 1024 * 1024;
|
|
1956
|
+
for (let offset = 0; offset < blas.bvh.buffer.byteLength; offset += chunkBytes) {
|
|
1957
|
+
this._device.queue.writeBuffer(buffer, target + offset, blas.bvh.buffer, offset, Math.min(chunkBytes, blas.bvh.buffer.byteLength - offset));
|
|
1958
|
+
}
|
|
1959
|
+
target += blas.bvh.buffer.byteLength;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
_writeBVH(buffer, tlas, blases) {
|
|
1963
|
+
const bytes = new Uint8Array(buffer);
|
|
1964
|
+
bytes.set(new Uint8Array(tlas.buffer));
|
|
1965
|
+
let offset = tlas.buffer.byteLength;
|
|
1966
|
+
for (const blas of blases) {
|
|
1967
|
+
bytes.set(new Uint8Array(blas.bvh.buffer), offset);
|
|
1968
|
+
offset += blas.bvh.buffer.byteLength;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1694
1971
|
_updateMeshData() {
|
|
1695
1972
|
this._pendingGeometryUpdates.clear();
|
|
1696
1973
|
this._emissiveCache = new EmissiveTriangleCache();
|
|
@@ -1705,7 +1982,7 @@ class WGPURayTracePass {
|
|
|
1705
1982
|
}
|
|
1706
1983
|
this._geometriesNeedRefit.clear();
|
|
1707
1984
|
// TODO check bvh needs to change
|
|
1708
|
-
const res = updateMeshInstances(meshes);
|
|
1985
|
+
const res = updateMeshInstances(meshes, { mergeAttributes: false });
|
|
1709
1986
|
this._preparedMeshData = res;
|
|
1710
1987
|
this._membershipCache.capture(res);
|
|
1711
1988
|
this._pendingMembershipData = undefined;
|
|
@@ -1713,7 +1990,7 @@ class WGPURayTracePass {
|
|
|
1713
1990
|
const mergedGeometry = res.geometry;
|
|
1714
1991
|
const materials = res.materials;
|
|
1715
1992
|
const isEmpty = meshes.length === 0 || res.instances.length === 0;
|
|
1716
|
-
const tlas =
|
|
1993
|
+
const tlas = buildWebTLAS(res.instances, res.blases);
|
|
1717
1994
|
this._tlas = tlas;
|
|
1718
1995
|
this._uploadBVH(tlas, res.blases);
|
|
1719
1996
|
const materialData = (this._materialData = this._createMaterialData(materials, this._analyticLights, {
|
|
@@ -1723,13 +2000,23 @@ class WGPURayTracePass {
|
|
|
1723
2000
|
}));
|
|
1724
2001
|
this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, materials);
|
|
1725
2002
|
this._needsUpdateLightSamplingData = true;
|
|
1726
|
-
|
|
2003
|
+
// Displacement geometry and its CPU BVH bounds must use the same normal.
|
|
2004
|
+
// Preserve f32 normals in these scenes, avoiding quantization outside bounds.
|
|
2005
|
+
const attributeLayout = compactVertexLayout(res.geometries, materials.some(m => !!m.displacementMap));
|
|
1727
2006
|
const { stride, offsets } = attributeLayout;
|
|
1728
|
-
const
|
|
1729
|
-
|
|
2007
|
+
const vertexWords = res.geometries.reduce((sum, g) => sum + g.vertexCount, 0) * stride;
|
|
2008
|
+
const materialWords = attributeLayout.constants.HAS_TRIANGLE_MATERIAL ? this._bvhDataBytes / 16 : 0;
|
|
2009
|
+
const attrBufferData = new Uint32Array(vertexWords + materialWords);
|
|
2010
|
+
attributeLayout.constants.MATERIALINDEX_OFF = vertexWords;
|
|
2011
|
+
for (const geometry of res.geometries) {
|
|
2012
|
+
const region = res.geometryRegions.get(geometry);
|
|
2013
|
+
packCompactVertices(geometry, attrBufferData, stride, offsets, 0, geometry.vertexCount, region.vertexOffset, false, !!attributeLayout.constants.COLOR_FLOAT, !!attributeLayout.constants.NORMAL_FLOAT);
|
|
2014
|
+
}
|
|
1730
2015
|
this._attributeData = attrBufferData;
|
|
1731
2016
|
this._attributeStride = stride;
|
|
1732
2017
|
this._attributeOffsets = offsets;
|
|
2018
|
+
Object.assign(this._constants, attributeLayout.constants);
|
|
2019
|
+
this._packTriangleMaterials(res, tlas);
|
|
1733
2020
|
this._attrBuffers = this._updateGPUBuffer('Attributes', this._attrBuffers, attrBufferData);
|
|
1734
2021
|
this._materialsBuffer = this._updateGPUBuffer('Materials', this._materialsBuffer, materialData.getBufferArray(),
|
|
1735
2022
|
// WGSL requires a runtime array binding to contain at least one
|
|
@@ -1770,21 +2057,53 @@ class WGPURayTracePass {
|
|
|
1770
2057
|
}
|
|
1771
2058
|
this._hasShadowCatcher = materials.some((m) => m.shadowCatcher);
|
|
1772
2059
|
const hasDisplacement = materialData.getDisplacementMaps().length > 0;
|
|
1773
|
-
|
|
1774
|
-
this._hasDisplacement = hasDisplacement;
|
|
1775
|
-
// Needs to recreate pipeline layout
|
|
1776
|
-
this._rayTracePipelineLayout = undefined;
|
|
1777
|
-
}
|
|
2060
|
+
this._hasDisplacement = hasDisplacement;
|
|
1778
2061
|
Object.assign(this._constants, wgpuMaterialConstants(materials, materialData, mergedGeometry, this._bevelSamples, Number(this._constants.NUM_MAPS ?? 0)));
|
|
1779
2062
|
// Runtime storage metadata has no texture-count specialization.
|
|
1780
2063
|
if (this._bindless)
|
|
1781
2064
|
this._constants.NUM_MAPS = 20;
|
|
1782
2065
|
}
|
|
2066
|
+
_packTriangleMaterials(res, tlas) {
|
|
2067
|
+
if (!this._constants.HAS_TRIANGLE_MATERIAL)
|
|
2068
|
+
return;
|
|
2069
|
+
let offset = Number(this._constants.MATERIALINDEX_OFF) + tlas.buffer.byteLength / 16;
|
|
2070
|
+
res.geometries.forEach((g, i) => {
|
|
2071
|
+
packTriangleMaterials(g, res.geometryRegions.get(g).materialIndices, res.blases[i].bvh.buffer, this._attributeData, offset);
|
|
2072
|
+
offset += res.blases[i].bvh.buffer.byteLength / 16;
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
_refreshTriangleMaterials(res, tlas) {
|
|
2076
|
+
if (!this._constants.HAS_TRIANGLE_MATERIAL)
|
|
2077
|
+
return;
|
|
2078
|
+
const vertexWords = Number(this._constants.MATERIALINDEX_OFF);
|
|
2079
|
+
const required = vertexWords + this._bvhDataBytes / 16;
|
|
2080
|
+
if (this._attributeData.length !== required) {
|
|
2081
|
+
const next = new Uint32Array(required);
|
|
2082
|
+
next.set(this._attributeData.subarray(0, vertexWords));
|
|
2083
|
+
this._attributeData = next;
|
|
2084
|
+
}
|
|
2085
|
+
this._packTriangleMaterials(res, tlas);
|
|
2086
|
+
if (this._attrBuffers.size < this._attributeData.byteLength) {
|
|
2087
|
+
this._attrBuffers = this._updateGPUBuffer('Attributes', this._attrBuffers, this._attributeData);
|
|
2088
|
+
}
|
|
2089
|
+
else {
|
|
2090
|
+
const first = vertexWords * 4, end = this._attributeData.byteLength, chunk = 64 * 1024 * 1024;
|
|
2091
|
+
for (let offset = first; offset < end; offset += chunk) {
|
|
2092
|
+
this._device.queue.writeBuffer(this._attrBuffers, offset, this._attributeData.buffer, offset, Math.min(chunk, end - offset));
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
1783
2096
|
_updateGeometryData() {
|
|
1784
2097
|
const res = this._preparedMeshData, tlas = this._tlas;
|
|
2098
|
+
// Promote the scene color plane if an edit introduces HDR/negative RGB.
|
|
2099
|
+
if (!this._constants.COLOR_FLOAT && [...this._pendingGeometryUpdates].some(g => g.color?.array.some(value => !(value >= 0 && value <= 1)))) {
|
|
2100
|
+
this._updateMeshData();
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
1785
2103
|
const update = updatePreparedGeometries(res, this._pendingGeometryUpdates);
|
|
1786
|
-
for (const { first, count,
|
|
1787
|
-
|
|
2104
|
+
for (const { first, count, deformationOnly } of update.vertices) {
|
|
2105
|
+
const geometry = res.geometries.find(g => res.geometryRegions.get(g).vertexOffset === first);
|
|
2106
|
+
packCompactVertices(geometry, this._attributeData, this._attributeStride, this._attributeOffsets, 0, count, first, deformationOnly, !!this._constants.COLOR_FLOAT, !!this._constants.NORMAL_FLOAT);
|
|
1788
2107
|
const offset = first * this._attributeStride * 4, size = count * this._attributeStride * 4;
|
|
1789
2108
|
this._device.queue.writeBuffer(this._attrBuffers, offset, this._attributeData.buffer, offset, size);
|
|
1790
2109
|
}
|
|
@@ -1800,17 +2119,13 @@ class WGPURayTracePass {
|
|
|
1800
2119
|
this._uploadBVH(tlas, res.blases);
|
|
1801
2120
|
}
|
|
1802
2121
|
else {
|
|
1803
|
-
const bytes = new Uint8Array(this._debugBVHBuffer);
|
|
1804
|
-
const ranges = [];
|
|
1805
2122
|
for (const { blas, offset } of update.blases) {
|
|
1806
|
-
|
|
1807
|
-
bytes.set(new Uint8Array(blas.buffer), target);
|
|
1808
|
-
ranges.push({ offset: target, size: blas.buffer.byteLength });
|
|
1809
|
-
}
|
|
1810
|
-
for (const { offset, size } of mergeRefitRanges(ranges)) {
|
|
1811
|
-
this._device.queue.writeBuffer(this._bvhBuffer, offset, this._debugBVHBuffer, offset, size);
|
|
2123
|
+
this._device.queue.writeBuffer(this._bvhBuffer, tlas.buffer.byteLength + offset, blas.buffer);
|
|
1812
2124
|
}
|
|
1813
2125
|
}
|
|
2126
|
+
if (this._constants.HAS_TRIANGLE_MATERIAL && (update.relocated || update.vertices.some(v => v.topologyChanged))) {
|
|
2127
|
+
this._refreshTriangleMaterials(res, tlas);
|
|
2128
|
+
}
|
|
1814
2129
|
const bits = { bvh2: 1, bvh4: 2, 'bvh-gpu': 4, cwbvh: 8 };
|
|
1815
2130
|
this._blasLayoutMask = res.blases.reduce((mask, b) => mask | bits[b.bvh.layout ?? 'bvh2'], 0);
|
|
1816
2131
|
const stacks = getTraversalStackSizes(tlas, res.blases);
|
|
@@ -1870,9 +2185,10 @@ class WGPURayTracePass {
|
|
|
1870
2185
|
if (pending)
|
|
1871
2186
|
this._residentPreparedMeshData = res;
|
|
1872
2187
|
this._pendingMembershipData = undefined;
|
|
1873
|
-
const tlas =
|
|
2188
|
+
const tlas = buildWebTLAS(instances, res.blases);
|
|
1874
2189
|
this._tlas = tlas;
|
|
1875
2190
|
this._uploadBVH(tlas, res.blases);
|
|
2191
|
+
this._refreshTriangleMaterials(res, tlas);
|
|
1876
2192
|
this._emissiveTriangles = this._emissiveCache.collect(emissiveBindings, res.materials);
|
|
1877
2193
|
this._needsUpdateLightSamplingData = true;
|
|
1878
2194
|
this._rtUniform.views.isEmpty[0] = +(instances.length === 0);
|
|
@@ -1906,8 +2222,9 @@ class WGPURayTracePass {
|
|
|
1906
2222
|
const previousInstances = res.instances;
|
|
1907
2223
|
res.instances = updateMeshInstanceTransforms(res.instanceBindings, incremental ? res.instances : undefined, incremental ? dirtyIndices : undefined);
|
|
1908
2224
|
if (!sameInstanceMembership(previousInstances, res.instances)) {
|
|
1909
|
-
this._tlas =
|
|
2225
|
+
this._tlas = buildWebTLAS(res.instances, res.blases);
|
|
1910
2226
|
this._uploadBVH(this._tlas, res.blases);
|
|
2227
|
+
this._refreshTriangleMaterials(res, this._tlas);
|
|
1911
2228
|
this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, res.materials);
|
|
1912
2229
|
this._needsUpdateLightSamplingData = true;
|
|
1913
2230
|
this._rtUniform.views.isEmpty[0] = +(res.instances.length === 0);
|
|
@@ -1916,16 +2233,12 @@ class WGPURayTracePass {
|
|
|
1916
2233
|
return;
|
|
1917
2234
|
}
|
|
1918
2235
|
const ranges = refitTLAS(tlas, res.instances, res.blases, incremental ? dirtyIndices : undefined);
|
|
1919
|
-
const tlasData = new Float32Array(tlas.buffer);
|
|
1920
2236
|
for (const { offset, size } of ranges.tlasRanges) {
|
|
1921
2237
|
this._device.queue.writeBuffer(this._bvhBuffer, offset, tlas.buffer, offset, size);
|
|
1922
2238
|
}
|
|
1923
2239
|
for (const { offset, size } of ranges.instanceRanges) {
|
|
1924
2240
|
this._device.queue.writeBuffer(this._instancesBuffer, offset, tlas.instancesBuffer, offset, size);
|
|
1925
2241
|
}
|
|
1926
|
-
if (this._debugBVHBuffer) {
|
|
1927
|
-
new Float32Array(this._debugBVHBuffer).set(tlasData);
|
|
1928
|
-
}
|
|
1929
2242
|
if (!incremental ||
|
|
1930
2243
|
res.emissiveBindings.some(({ mesh }) => this._dirtyTransformMeshes.has(mesh))) {
|
|
1931
2244
|
this._emissiveTriangles = this._emissiveCache.collect(res.emissiveBindings, res.materials);
|
|
@@ -1945,8 +2258,6 @@ class WGPURayTracePass {
|
|
|
1945
2258
|
this._retiredBindless.add(retired);
|
|
1946
2259
|
void this._device.queue.onSubmittedWorkDone().catch(() => { }).then(() => { retired.dispose(); this._retiredBindless.delete(retired); });
|
|
1947
2260
|
this._bindless = undefined;
|
|
1948
|
-
this._computeShaderModule = undefined;
|
|
1949
|
-
this._rayTracePipelineLayout = undefined;
|
|
1950
2261
|
}
|
|
1951
2262
|
return new MaterialTable(...args);
|
|
1952
2263
|
}
|
|
@@ -2058,10 +2369,15 @@ class WGPURayTracePass {
|
|
|
2058
2369
|
}
|
|
2059
2370
|
// TODO reuse the code of RayTraceNode
|
|
2060
2371
|
setCamera(camera) {
|
|
2372
|
+
this._causticPerspective = camera.projection === 'perspective';
|
|
2373
|
+
const projection = camera.getProjectionMatrix(this._targetAspect);
|
|
2374
|
+
this._causticCameraScale = Math.abs(projection[0] * projection[5]) / 4;
|
|
2375
|
+
this._setCaustics(this._causticSettings);
|
|
2061
2376
|
const viewProjectionInvMat = this._viewProjInv;
|
|
2062
2377
|
this._apertureRadius = cameraApertureRadius(camera);
|
|
2063
2378
|
this._focusDistance = camera.focusDistance;
|
|
2064
|
-
this.
|
|
2379
|
+
mat4.invert(this._projectionInv, projection);
|
|
2380
|
+
mat4.mul(viewProjectionInvMat, camera.transform, this._projectionInv);
|
|
2065
2381
|
mat4.copy(this._cameraWorld, camera.transform);
|
|
2066
2382
|
}
|
|
2067
2383
|
cameraChanged(camera) {
|
|
@@ -2117,7 +2433,7 @@ class WGPURayTracePass {
|
|
|
2117
2433
|
const arr = this._rtUniform.views.jitter;
|
|
2118
2434
|
arr[0] = x;
|
|
2119
2435
|
arr[1] = y;
|
|
2120
|
-
this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance));
|
|
2436
|
+
this._device.queue.writeBuffer(this._uniforms.cameraUniform, 0, packCameraUniform(this._viewProjInv, this._cameraWorld, this._apertureRadius, this._focusDistance, this._projectionInv));
|
|
2121
2437
|
}
|
|
2122
2438
|
setSampleTiles(columns, rows, tileX, tileY) {
|
|
2123
2439
|
const arr = this._rtUniform.views.scaleOffset;
|
|
@@ -2147,12 +2463,14 @@ class WGPURayTracePass {
|
|
|
2147
2463
|
this._envDistribution?.dispose();
|
|
2148
2464
|
this._skyBaker?.dispose();
|
|
2149
2465
|
this._bindless?.dispose();
|
|
2150
|
-
this._retiredBindless.forEach(textures => textures.dispose());
|
|
2466
|
+
this._retiredBindless.forEach((textures) => textures.dispose());
|
|
2151
2467
|
this._retiredBindless.clear();
|
|
2468
|
+
this._causticLight?.dispose();
|
|
2469
|
+
this._photonMap?.dispose();
|
|
2152
2470
|
this._shaderCache.clear();
|
|
2153
2471
|
this._shaderCapabilities.clear();
|
|
2154
2472
|
const buffers = new Set([
|
|
2155
|
-
...Array.from(this._targetOutputs.values()).flatMap(target => target.buffers),
|
|
2473
|
+
...Array.from(this._targetOutputs.values()).flatMap((target) => target.buffers),
|
|
2156
2474
|
this._mapInfoUniform,
|
|
2157
2475
|
this._attrBuffers,
|
|
2158
2476
|
this._materialsBuffer,
|
|
@@ -2172,6 +2490,9 @@ class WGPURayTracePass {
|
|
|
2172
2490
|
this._adaptiveSamplingTexture
|
|
2173
2491
|
]);
|
|
2174
2492
|
textures.forEach((texture) => texture?.destroy());
|
|
2493
|
+
for (const job of this._queuedCompiles.splice(0))
|
|
2494
|
+
job.release();
|
|
2495
|
+
this._traceVariants.clear();
|
|
2175
2496
|
this._rayTracePipeline = undefined;
|
|
2176
2497
|
this._rayTraceBindGroups = undefined;
|
|
2177
2498
|
}
|