@driftengine/core 3.63.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +122 -0
- package/NOTICE +20 -0
- package/README.md +1 -1
- package/dist/core/loop.js +18 -2
- package/dist/core/storage.js +5 -0
- package/dist/geometry/meshBuilder.d.ts +1 -1
- package/dist/geometry/meshBuilder.js +6 -2
- package/dist/index.d.ts +69 -4
- package/dist/index.js +112 -2
- package/dist/input/input.js +6 -1
- package/dist/math/exact.d.ts +13 -3
- package/dist/math/exact.js +68 -3
- package/dist/math/frustum.d.ts +17 -2
- package/dist/math/frustum.js +24 -0
- package/dist/render/backend/api.d.ts +34 -1
- package/dist/render/backend/api.js +22 -1
- package/dist/render/backend/budget.d.ts +7 -6
- package/dist/render/backend/budget.js +7 -6
- package/dist/render/backend/createRenderer.d.ts +57 -0
- package/dist/render/backend/createRenderer.js +51 -2
- package/dist/render/backend/materialChanges.d.ts +41 -0
- package/dist/render/backend/materialChanges.js +38 -0
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/renderer.d.ts +80 -1
- package/dist/render/backend/webgl2/renderer.js +208 -10
- package/dist/render/backend/webgpu/buffers.d.ts +20 -1
- package/dist/render/backend/webgpu/buffers.js +116 -58
- package/dist/render/backend/webgpu/fieldCompose.d.ts +156 -0
- package/dist/render/backend/webgpu/fieldCompose.js +396 -0
- package/dist/render/backend/webgpu/giFieldPass.d.ts +66 -0
- package/dist/render/backend/webgpu/giFieldPass.js +137 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +728 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.js +2473 -0
- package/dist/render/backend/webgpu/linePass.d.ts +4 -14
- package/dist/render/backend/webgpu/linePass.js +4 -33
- package/dist/render/backend/webgpu/probeBake.d.ts +148 -0
- package/dist/render/backend/webgpu/probeBake.js +493 -0
- package/dist/render/backend/webgpu/renderer.d.ts +379 -24
- package/dist/render/backend/webgpu/renderer.js +1369 -76
- package/dist/render/backend/webgpu/scatterPass.js +15 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +19 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.js +58 -32
- package/dist/render/causticsRenderer.d.ts +3 -1
- package/dist/render/causticsRenderer.js +4 -1
- package/dist/render/depthConvention.d.ts +12 -0
- package/dist/render/depthConvention.js +14 -0
- package/dist/render/deviceFeatures.d.ts +32 -0
- package/dist/render/deviceFeatures.js +41 -0
- package/dist/render/fog.d.ts +79 -0
- package/dist/render/fog.js +138 -0
- package/dist/render/frame/alias.d.ts +30 -0
- package/dist/render/frame/alias.js +61 -0
- package/dist/render/frame/deps.d.ts +37 -0
- package/dist/render/frame/deps.js +122 -0
- package/dist/render/frame/flushGraph.d.ts +43 -0
- package/dist/render/frame/flushGraph.js +84 -0
- package/dist/render/frame/graphSchedule.d.ts +49 -0
- package/dist/render/frame/graphSchedule.js +120 -0
- package/dist/render/frame/index.d.ts +15 -0
- package/dist/render/frame/index.js +8 -0
- package/dist/render/frame/lifetime.d.ts +24 -0
- package/dist/render/frame/lifetime.js +36 -0
- package/dist/render/frame/passTimings.d.ts +42 -0
- package/dist/render/frame/passTimings.js +38 -0
- package/dist/render/frame/validate.d.ts +15 -0
- package/dist/render/frame/validate.js +22 -0
- package/dist/render/frame/virtual.d.ts +44 -0
- package/dist/render/frame/virtual.js +61 -0
- package/dist/render/gi/chain.d.ts +82 -0
- package/dist/render/gi/chain.js +135 -0
- package/dist/render/gi/denoise.d.ts +114 -0
- package/dist/render/gi/denoise.js +169 -0
- package/dist/render/gi/fieldScene.d.ts +125 -0
- package/dist/render/gi/fieldScene.js +219 -0
- package/dist/render/gi/globalField.d.ts +157 -0
- package/dist/render/gi/globalField.js +373 -0
- package/dist/render/gi/probeGridFit.d.ts +24 -0
- package/dist/render/gi/probeGridFit.js +71 -0
- package/dist/render/gi/probeTrace.d.ts +82 -0
- package/dist/render/gi/probeTrace.js +128 -0
- package/dist/render/gi/probeVolume.d.ts +136 -0
- package/dist/render/gi/probeVolume.js +191 -0
- package/dist/render/gi/reflection.d.ts +73 -0
- package/dist/render/gi/reflection.js +109 -0
- package/dist/render/gi/traceField.d.ts +91 -0
- package/dist/render/gi/traceField.js +129 -0
- package/dist/render/gi/traceScreen.d.ts +68 -0
- package/dist/render/gi/traceScreen.js +82 -0
- package/dist/render/gpuScene.d.ts +38 -0
- package/dist/render/gpuScene.js +41 -0
- package/dist/render/gpudriven/alphaTest.d.ts +28 -0
- package/dist/render/gpudriven/alphaTest.js +32 -0
- package/dist/render/gpudriven/clusterUpload.d.ts +59 -0
- package/dist/render/gpudriven/clusterUpload.js +75 -0
- package/dist/render/gpudriven/clusterWorld.d.ts +57 -0
- package/dist/render/gpudriven/clusterWorld.js +155 -0
- package/dist/render/gpudriven/compact.d.ts +51 -0
- package/dist/render/gpudriven/compact.js +68 -0
- package/dist/render/gpudriven/cullClusters.d.ts +129 -0
- package/dist/render/gpudriven/cullClusters.js +237 -0
- package/dist/render/gpudriven/decodeTables.d.ts +64 -0
- package/dist/render/gpudriven/decodeTables.js +219 -0
- package/dist/render/gpudriven/frustum.d.ts +25 -0
- package/dist/render/gpudriven/frustum.js +59 -0
- package/dist/render/gpudriven/geometryHarness.d.ts +15 -0
- package/dist/render/gpudriven/geometryHarness.js +31 -0
- package/dist/render/gpudriven/hzb.d.ts +60 -0
- package/dist/render/gpudriven/hzb.js +97 -0
- package/dist/render/gpudriven/ibl.d.ts +85 -0
- package/dist/render/gpudriven/ibl.js +129 -0
- package/dist/render/gpudriven/indirect.d.ts +67 -0
- package/dist/render/gpudriven/indirect.js +77 -0
- package/dist/render/gpudriven/instances.d.ts +43 -0
- package/dist/render/gpudriven/instances.js +172 -0
- package/dist/render/gpudriven/lit.d.ts +165 -0
- package/dist/render/gpudriven/lit.js +165 -0
- package/dist/render/gpudriven/lodCut.d.ts +28 -0
- package/dist/render/gpudriven/lodCut.js +38 -0
- package/dist/render/gpudriven/materialBin.d.ts +60 -0
- package/dist/render/gpudriven/materialBin.js +108 -0
- package/dist/render/gpudriven/materialTable.d.ts +88 -0
- package/dist/render/gpudriven/materialTable.js +115 -0
- package/dist/render/gpudriven/pipeline.d.ts +135 -0
- package/dist/render/gpudriven/pipeline.js +237 -0
- package/dist/render/gpudriven/rangeAlloc.d.ts +50 -0
- package/dist/render/gpudriven/rangeAlloc.js +112 -0
- package/dist/render/gpudriven/sceneUpload.d.ts +96 -0
- package/dist/render/gpudriven/sceneUpload.js +115 -0
- package/dist/render/gpudriven/shadeBins.d.ts +92 -0
- package/dist/render/gpudriven/shadeBins.js +182 -0
- package/dist/render/gpudriven/shadow.d.ts +136 -0
- package/dist/render/gpudriven/shadow.js +224 -0
- package/dist/render/gpudriven/shadowCamera.d.ts +87 -0
- package/dist/render/gpudriven/shadowCamera.js +345 -0
- package/dist/render/gpudriven/streamScene.d.ts +194 -0
- package/dist/render/gpudriven/streamScene.js +459 -0
- package/dist/render/gpudriven/surfaceFrame.d.ts +25 -0
- package/dist/render/gpudriven/surfaceFrame.js +51 -0
- package/dist/render/gpudriven/twoPhase.d.ts +122 -0
- package/dist/render/gpudriven/twoPhase.js +160 -0
- package/dist/render/gpudriven/visbuffer.d.ts +25 -0
- package/dist/render/gpudriven/visbuffer.js +37 -0
- package/dist/render/halfFloat.d.ts +10 -0
- package/dist/render/halfFloat.js +23 -4
- package/dist/render/inference/attentionTiled.d.ts +27 -0
- package/dist/render/inference/attentionTiled.js +117 -0
- package/dist/render/inference/denseKernels.d.ts +29 -0
- package/dist/render/inference/denseKernels.js +312 -0
- package/dist/render/inference/device.d.ts +21 -0
- package/dist/render/inference/device.js +32 -0
- package/dist/render/inference/deviceGraph.d.ts +34 -0
- package/dist/render/inference/deviceGraph.js +17 -0
- package/dist/render/inference/fuse.d.ts +15 -0
- package/dist/render/inference/fuse.js +37 -0
- package/dist/render/inference/kernelKit.d.ts +52 -0
- package/dist/render/inference/kernelKit.js +70 -0
- package/dist/render/inference/kernels.d.ts +3 -0
- package/dist/render/inference/kernels.js +11 -0
- package/dist/render/inference/plan.d.ts +13 -0
- package/dist/render/inference/plan.js +18 -0
- package/dist/render/inference/reuse.d.ts +39 -0
- package/dist/render/inference/reuse.js +65 -0
- package/dist/render/inference/runner.d.ts +31 -0
- package/dist/render/inference/runner.js +175 -0
- package/dist/render/inference/schedule.d.ts +39 -0
- package/dist/render/inference/schedule.js +59 -0
- package/dist/render/inference/shapeKernels.d.ts +10 -0
- package/dist/render/inference/shapeKernels.js +80 -0
- package/dist/render/inference/spatialKernels.d.ts +21 -0
- package/dist/render/inference/spatialKernels.js +179 -0
- package/dist/render/inference/splatRaster.wgsl.d.ts +75 -0
- package/dist/render/inference/splatRaster.wgsl.js +337 -0
- package/dist/render/inference/splatTiles.d.ts +79 -0
- package/dist/render/inference/splatTiles.js +161 -0
- package/dist/render/lineBatch.d.ts +2 -5
- package/dist/render/lineBatch.js +4 -24
- package/dist/render/mesh.d.ts +4 -0
- package/dist/render/mesh.js +45 -1
- package/dist/render/pass.d.ts +145 -8
- package/dist/render/prefilterEnvMap.d.ts +22 -0
- package/dist/render/prefilterEnvMap.js +31 -0
- package/dist/render/readback.d.ts +30 -0
- package/dist/render/readback.js +47 -0
- package/dist/render/recon/clamp.d.ts +36 -0
- package/dist/render/recon/clamp.js +92 -0
- package/dist/render/recon/disocclusion.d.ts +60 -0
- package/dist/render/recon/disocclusion.js +109 -0
- package/dist/render/recon/frameGen.d.ts +81 -0
- package/dist/render/recon/frameGen.js +150 -0
- package/dist/render/recon/frameSizes.d.ts +34 -0
- package/dist/render/recon/frameSizes.js +58 -0
- package/dist/render/recon/jitter.d.ts +60 -0
- package/dist/render/recon/jitter.js +118 -0
- package/dist/render/recon/motionVectors.d.ts +55 -0
- package/dist/render/recon/motionVectors.js +107 -0
- package/dist/render/recon/reproject.d.ts +41 -0
- package/dist/render/recon/reproject.js +131 -0
- package/dist/render/recon/resolve.d.ts +90 -0
- package/dist/render/recon/resolve.js +313 -0
- package/dist/render/recon/tier.d.ts +24 -0
- package/dist/render/recon/tier.js +8 -0
- package/dist/render/reflectionProbe.d.ts +26 -0
- package/dist/render/reflectionProbe.js +33 -0
- package/dist/render/renderQuality.d.ts +69 -0
- package/dist/render/renderQuality.js +22 -0
- package/dist/render/screenSpaceReflection.d.ts +23 -0
- package/dist/render/screenSpaceReflection.js +22 -3
- package/dist/render/sdfFont.js +2 -0
- package/dist/render/segmentQuads.d.ts +13 -0
- package/dist/render/segmentQuads.js +32 -0
- package/dist/render/shaders/flat/lobes.d.ts +27 -0
- package/dist/render/shaders/flat/lobes.js +33 -3
- package/dist/render/shaders/flat/preamble.d.ts +7 -0
- package/dist/render/shaders/flat/preamble.js +24 -1
- package/dist/render/shaders/flat/probeGrid.d.ts +1 -1
- package/dist/render/shaders/flat/probeGrid.js +71 -2
- package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -8
- package/dist/render/shaders/generated/flat.wgsl.js +64 -24
- package/dist/render/shaders/gi/composeField.wgsl.d.ts +56 -0
- package/dist/render/shaders/gi/composeField.wgsl.js +231 -0
- package/dist/render/shaders/gi/marchField.wgsl.d.ts +30 -0
- package/dist/render/shaders/gi/marchField.wgsl.js +141 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +113 -0
- package/dist/render/shaders/gi/probeBake.wgsl.js +635 -0
- package/dist/render/shaders/gi/probeTrace.wgsl.d.ts +72 -0
- package/dist/render/shaders/gi/probeTrace.wgsl.js +261 -0
- package/dist/render/shaders/gi/probeVolume.wgsl.d.ts +49 -0
- package/dist/render/shaders/gi/probeVolume.wgsl.js +242 -0
- package/dist/render/shaders/gi/sampleField.wgsl.d.ts +32 -0
- package/dist/render/shaders/gi/sampleField.wgsl.js +134 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +26 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +339 -0
- package/dist/render/shaders/gpudriven/compact.wgsl.d.ts +29 -0
- package/dist/render/shaders/gpudriven/compact.wgsl.js +58 -0
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +90 -0
- package/dist/render/shaders/gpudriven/cull.wgsl.js +423 -0
- package/dist/render/shaders/gpudriven/decode.wgsl.d.ts +72 -0
- package/dist/render/shaders/gpudriven/decode.wgsl.js +349 -0
- package/dist/render/shaders/gpudriven/environment.wgsl.d.ts +44 -0
- package/dist/render/shaders/gpudriven/environment.wgsl.js +137 -0
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +28 -0
- package/dist/render/shaders/gpudriven/fog.wgsl.js +107 -0
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +13 -0
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +62 -0
- package/dist/render/shaders/gpudriven/materialBin.wgsl.d.ts +51 -0
- package/dist/render/shaders/gpudriven/materialBin.wgsl.js +112 -0
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +25 -0
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +43 -0
- package/dist/render/shaders/gpudriven/meshTransform.wgsl.d.ts +18 -0
- package/dist/render/shaders/gpudriven/meshTransform.wgsl.js +37 -0
- package/dist/render/shaders/gpudriven/present.wgsl.d.ts +43 -0
- package/dist/render/shaders/gpudriven/present.wgsl.js +127 -0
- package/dist/render/shaders/gpudriven/shade.wgsl.d.ts +127 -0
- package/dist/render/shaders/gpudriven/shade.wgsl.js +874 -0
- package/dist/render/shaders/gpudriven/shadow.wgsl.d.ts +44 -0
- package/dist/render/shaders/gpudriven/shadow.wgsl.js +308 -0
- package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.d.ts +28 -0
- package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.js +245 -0
- package/dist/render/shaders/network.wgsl.d.ts +101 -0
- package/dist/render/shaders/network.wgsl.js +206 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +44 -0
- package/dist/render/shaders/recon/motion.wgsl.js +96 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +51 -0
- package/dist/render/shaders/recon/resolve.wgsl.js +733 -0
- package/dist/render/temporalAa.js +8 -33
- package/dist/render/textLayout.js +12 -6
- package/dist/render/textRenderer.d.ts +3 -1
- package/dist/render/textRenderer.js +4 -1
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +11 -0
- package/dist/render/windStreakRenderer.d.ts +2 -1
- package/dist/render/windStreakRenderer.js +3 -1
- package/dist/world/cell.d.ts +63 -0
- package/dist/world/cell.js +181 -0
- package/dist/world/cellStream.d.ts +103 -0
- package/dist/world/cellStream.js +198 -0
- package/dist/world/freeze.d.ts +81 -0
- package/dist/world/freeze.js +114 -0
- package/dist/world/rebase.d.ts +29 -0
- package/dist/world/rebase.js +41 -0
- package/package.json +8 -3
- package/scripts/frames.mjs +92 -0
- package/scripts/gpuCompute.mjs +323 -0
- package/scripts/png.d.mts +16 -0
- package/scripts/png.mjs +25 -0
- package/src/core/loop.ts +19 -2
- package/src/core/storage.ts +5 -0
- package/src/geometry/meshBuilder.ts +5 -1
- package/src/index.ts +246 -4
- package/src/input/input.ts +6 -1
- package/src/math/exact.ts +69 -3
- package/src/math/frustum.ts +52 -3
- package/src/render/backend/api.ts +42 -1
- package/src/render/backend/budget.ts +7 -6
- package/src/render/backend/createRenderer.ts +92 -3
- package/src/render/backend/materialChanges.ts +54 -0
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/renderer.ts +211 -9
- package/src/render/backend/webgpu/buffers.ts +182 -69
- package/src/render/backend/webgpu/fieldCompose.ts +482 -0
- package/src/render/backend/webgpu/giFieldPass.ts +200 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +2993 -0
- package/src/render/backend/webgpu/linePass.ts +4 -39
- package/src/render/backend/webgpu/probeBake.ts +588 -0
- package/src/render/backend/webgpu/renderer.ts +1481 -76
- package/src/render/backend/webgpu/scatterPass.ts +15 -1
- package/src/render/backend/webgpu/surfaceTexturePass.ts +63 -36
- package/src/render/causticsRenderer.ts +5 -2
- package/src/render/depthConvention.ts +15 -0
- package/src/render/deviceFeatures.ts +77 -0
- package/src/render/fog.ts +185 -0
- package/src/render/frame/alias.ts +97 -0
- package/src/render/frame/deps.ts +155 -0
- package/src/render/frame/flushGraph.ts +135 -0
- package/src/render/frame/graphSchedule.ts +170 -0
- package/src/render/frame/index.ts +52 -0
- package/src/render/frame/lifetime.ts +61 -0
- package/src/render/frame/passTimings.ts +69 -0
- package/src/render/frame/validate.ts +40 -0
- package/src/render/frame/virtual.ts +91 -0
- package/src/render/gi/chain.ts +225 -0
- package/src/render/gi/denoise.ts +276 -0
- package/src/render/gi/fieldScene.ts +264 -0
- package/src/render/gi/globalField.ts +543 -0
- package/src/render/gi/probeGridFit.ts +89 -0
- package/src/render/gi/probeTrace.ts +143 -0
- package/src/render/gi/probeVolume.ts +288 -0
- package/src/render/gi/reflection.ts +163 -0
- package/src/render/gi/traceField.ts +223 -0
- package/src/render/gi/traceScreen.ts +106 -0
- package/src/render/gpuScene.ts +80 -0
- package/src/render/gpudriven/alphaTest.ts +33 -0
- package/src/render/gpudriven/clusterUpload.ts +123 -0
- package/src/render/gpudriven/clusterWorld.ts +239 -0
- package/src/render/gpudriven/compact.ts +80 -0
- package/src/render/gpudriven/cullClusters.ts +333 -0
- package/src/render/gpudriven/decodeTables.ts +332 -0
- package/src/render/gpudriven/frustum.ts +70 -0
- package/src/render/gpudriven/geometryHarness.ts +51 -0
- package/src/render/gpudriven/hzb.ts +113 -0
- package/src/render/gpudriven/ibl.ts +147 -0
- package/src/render/gpudriven/indirect.ts +89 -0
- package/src/render/gpudriven/instances.ts +234 -0
- package/src/render/gpudriven/lit.ts +329 -0
- package/src/render/gpudriven/lodCut.ts +62 -0
- package/src/render/gpudriven/materialBin.ts +131 -0
- package/src/render/gpudriven/materialTable.ts +164 -0
- package/src/render/gpudriven/pipeline.ts +336 -0
- package/src/render/gpudriven/rangeAlloc.ts +122 -0
- package/src/render/gpudriven/sceneUpload.ts +173 -0
- package/src/render/gpudriven/shadeBins.ts +222 -0
- package/src/render/gpudriven/shadow.ts +285 -0
- package/src/render/gpudriven/shadowCamera.ts +406 -0
- package/src/render/gpudriven/streamScene.ts +575 -0
- package/src/render/gpudriven/surfaceFrame.ts +68 -0
- package/src/render/gpudriven/twoPhase.ts +214 -0
- package/src/render/gpudriven/visbuffer.ts +41 -0
- package/src/render/halfFloat.ts +22 -4
- package/src/render/inference/attentionTiled.ts +132 -0
- package/src/render/inference/denseKernels.ts +369 -0
- package/src/render/inference/device.ts +44 -0
- package/src/render/inference/deviceGraph.ts +39 -0
- package/src/render/inference/fuse.ts +47 -0
- package/src/render/inference/kernelKit.ts +120 -0
- package/src/render/inference/kernels.ts +18 -0
- package/src/render/inference/plan.ts +26 -0
- package/src/render/inference/reuse.ts +79 -0
- package/src/render/inference/runner.ts +204 -0
- package/src/render/inference/schedule.ts +80 -0
- package/src/render/inference/shapeKernels.ts +112 -0
- package/src/render/inference/spatialKernels.ts +220 -0
- package/src/render/inference/splatRaster.wgsl.ts +348 -0
- package/src/render/inference/splatTiles.ts +186 -0
- package/src/render/lineBatch.ts +4 -24
- package/src/render/mesh.ts +46 -1
- package/src/render/pass.ts +155 -10
- package/src/render/prefilterEnvMap.ts +33 -0
- package/src/render/readback.ts +73 -0
- package/src/render/recon/clamp.ts +113 -0
- package/src/render/recon/disocclusion.ts +143 -0
- package/src/render/recon/frameGen.ts +191 -0
- package/src/render/recon/frameSizes.ts +64 -0
- package/src/render/recon/jitter.ts +126 -0
- package/src/render/recon/motionVectors.ts +138 -0
- package/src/render/recon/reproject.ts +179 -0
- package/src/render/recon/resolve.ts +444 -0
- package/src/render/recon/tier.ts +31 -0
- package/src/render/reflectionProbe.ts +34 -0
- package/src/render/renderQuality.ts +87 -0
- package/src/render/screenSpaceReflection.ts +37 -3
- package/src/render/sdfFont.ts +2 -0
- package/src/render/segmentQuads.ts +39 -0
- package/src/render/shaders/flat/lobes.ts +35 -3
- package/src/render/shaders/flat/preamble.ts +25 -1
- package/src/render/shaders/flat/probeGrid.ts +77 -2
- package/src/render/shaders/generated/flat.wgsl.ts +64 -24
- package/src/render/shaders/gi/composeField.wgsl.ts +239 -0
- package/src/render/shaders/gi/marchField.wgsl.ts +146 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +649 -0
- package/src/render/shaders/gi/probeTrace.wgsl.ts +269 -0
- package/src/render/shaders/gi/probeVolume.wgsl.ts +246 -0
- package/src/render/shaders/gi/sampleField.wgsl.ts +140 -0
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +343 -0
- package/src/render/shaders/gpudriven/compact.wgsl.ts +59 -0
- package/src/render/shaders/gpudriven/cull.wgsl.ts +433 -0
- package/src/render/shaders/gpudriven/decode.wgsl.ts +373 -0
- package/src/render/shaders/gpudriven/environment.wgsl.ts +141 -0
- package/src/render/shaders/gpudriven/fog.wgsl.ts +110 -0
- package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +62 -0
- package/src/render/shaders/gpudriven/materialBin.wgsl.ts +116 -0
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +46 -0
- package/src/render/shaders/gpudriven/meshTransform.wgsl.ts +37 -0
- package/src/render/shaders/gpudriven/present.wgsl.ts +129 -0
- package/src/render/shaders/gpudriven/shade.wgsl.ts +884 -0
- package/src/render/shaders/gpudriven/shadow.wgsl.ts +322 -0
- package/src/render/shaders/gpudriven/visbufferRaster.wgsl.ts +251 -0
- package/src/render/shaders/network.wgsl.ts +238 -0
- package/src/render/shaders/recon/motion.wgsl.ts +101 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +741 -0
- package/src/render/temporalAa.ts +9 -33
- package/src/render/textLayout.ts +12 -6
- package/src/render/textRenderer.ts +5 -2
- package/src/render/translucentQueue.ts +11 -0
- package/src/render/windStreakRenderer.ts +4 -2
- package/src/world/cell.ts +231 -0
- package/src/world/cellStream.ts +278 -0
- package/src/world/freeze.ts +157 -0
- package/src/world/rebase.ts +56 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/** Where a cluster actually stands: its bounds, its cone and its error through its mesh transform. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* **The cull asks about the world and a cluster is baked in its mesh's own space.**
|
|
5
|
+
*
|
|
6
|
+
* `visbufferRaster.wgsl.ts` multiplies every vertex by `transforms[mesh]` and `shade.wgsl.ts`
|
|
7
|
+
* multiplies every normal by the same upper three-by-three. The culling did neither: it read
|
|
8
|
+
* `ClusterBuffers.bounds` and `ClusterBuffers.cones` as they came out of the bake, so a mesh placed
|
|
9
|
+
* anywhere but the origin was frustum-culled against the wrong region, cone-culled with the wrong
|
|
10
|
+
* orientation, LOD-cut on a misprojected error and occlusion-tested on the wrong rectangle — while
|
|
11
|
+
* the picture it drew was still drawn in the right place, because the raster had the transform.
|
|
12
|
+
*
|
|
13
|
+
* **Every rig in `DRAFT_SCENES` uses an identity transform**, which is the only reason it never
|
|
14
|
+
* showed. That is the shape §3 row 79 is about: the device check agreed, the reference agreed, and
|
|
15
|
+
* nothing in the repository placed a mesh anywhere.
|
|
16
|
+
*
|
|
17
|
+
* **It happens once, at upload, and not per frame.** This pipeline owns a fixed cluster set with
|
|
18
|
+
* static transforms — the same property `shadowCamera.ts` fits the light's box to — so the world
|
|
19
|
+
* bounds are a property of the scene. A pipeline that ever moves a mesh has to rewrite these two
|
|
20
|
+
* buffers when it does, and that is the cost of having them.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** What the transform needs of a packed cluster set. `ClusterBuffers` satisfies it. */
|
|
24
|
+
export interface ClusterWorldSource {
|
|
25
|
+
/** Four floats a cluster: centre and radius, in the mesh's own space. */
|
|
26
|
+
readonly bounds: Float32Array;
|
|
27
|
+
/** Four floats a cluster: cone axis and cutoff, in the mesh's own space. */
|
|
28
|
+
readonly cones: Float32Array;
|
|
29
|
+
/** Two floats a cluster: its own error and its parent's. */
|
|
30
|
+
readonly errors: Float32Array;
|
|
31
|
+
/** Which mesh each cluster belongs to, which is what it reaches a transform through. */
|
|
32
|
+
readonly meshOf: Uint32Array;
|
|
33
|
+
readonly count: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The two buffers the culling stages read, in the layout they are bound in. */
|
|
37
|
+
export interface ClusterWorld {
|
|
38
|
+
/** Eight floats a cluster: world centre, radius, cone axis, cutoff. `cullClusters` takes this. */
|
|
39
|
+
cull: Float32Array;
|
|
40
|
+
/** Six floats a cluster: world centre, radius, own error, parent error. `lodCut` reads these. */
|
|
41
|
+
lod: Float32Array;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Eight floats a cluster in `cull`, matching `CLUSTER_CULL_FLOATS` and the cull shader's stride. */
|
|
45
|
+
const CULL_FLOATS = 8;
|
|
46
|
+
|
|
47
|
+
/** Six floats a cluster in `lod`, matching the LOD shader's `i * 6u`. */
|
|
48
|
+
const LOD_FLOATS = 6;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* How far from a similarity a transform may be before the cone is refused.
|
|
52
|
+
*
|
|
53
|
+
* Relative, and loose on purpose: a rotation composed in single precision loses a few parts in ten
|
|
54
|
+
* million, and the comparison has to pass every matrix a `Transform` produces without passing a
|
|
55
|
+
* scale a modeller meant. A hundredth of a per cent is three orders of magnitude clear of the one
|
|
56
|
+
* and four clear of the other.
|
|
57
|
+
*/
|
|
58
|
+
const SIMILARITY_TOLERANCE = 1e-4;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Transform every cluster into the world, once.
|
|
62
|
+
*
|
|
63
|
+
* `transforms` is sixteen floats a mesh, column-major, indexed by `meshOf`.
|
|
64
|
+
*/
|
|
65
|
+
export function worldClusters(
|
|
66
|
+
clusters: ClusterWorldSource,
|
|
67
|
+
transforms: Float32Array,
|
|
68
|
+
): ClusterWorld {
|
|
69
|
+
const cull = new Float32Array(clusters.count * CULL_FLOATS);
|
|
70
|
+
const lod = new Float32Array(clusters.count * LOD_FLOATS);
|
|
71
|
+
worldClustersInto(clusters, transforms, cull, lod, 0);
|
|
72
|
+
return { cull, lod };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The same arithmetic, writing into arrays the caller owns at a cluster offset it chooses.
|
|
77
|
+
*
|
|
78
|
+
* **This exists because a streamed mesh computes its own bounds**, at the moment it is added, into
|
|
79
|
+
* the middle of a scene-sized buffer that already holds other meshes'. `worldClusters` is this
|
|
80
|
+
* with the allocation, so the two cannot drift — which this file asserts by composing two meshes
|
|
81
|
+
* both ways and comparing byte for byte.
|
|
82
|
+
*
|
|
83
|
+
* `base` is in clusters, not floats: the record at `base + c` is written for every cluster `c` of
|
|
84
|
+
* the source, and everything outside that range is left exactly as it was found.
|
|
85
|
+
*/
|
|
86
|
+
export function worldClustersInto(
|
|
87
|
+
clusters: ClusterWorldSource,
|
|
88
|
+
transforms: Float32Array,
|
|
89
|
+
cull: Float32Array,
|
|
90
|
+
lod: Float32Array,
|
|
91
|
+
base: number,
|
|
92
|
+
): void {
|
|
93
|
+
const count = clusters.count;
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* Per mesh rather than per cluster: a scene is thousands of clusters over a handful of meshes,
|
|
97
|
+
* and the two questions asked of a matrix — how much it scales, and whether it scales evenly —
|
|
98
|
+
* are nine multiplies each.
|
|
99
|
+
*/
|
|
100
|
+
const meshes = Math.floor(transforms.length / 16);
|
|
101
|
+
const scales = new Float64Array(meshes);
|
|
102
|
+
const similar = new Uint8Array(meshes);
|
|
103
|
+
for (let m = 0; m < meshes; m += 1) {
|
|
104
|
+
const at = m * 16;
|
|
105
|
+
const l0 = Math.hypot(
|
|
106
|
+
transforms[at] as number,
|
|
107
|
+
transforms[at + 1] as number,
|
|
108
|
+
transforms[at + 2] as number,
|
|
109
|
+
);
|
|
110
|
+
const l1 = Math.hypot(
|
|
111
|
+
transforms[at + 4] as number,
|
|
112
|
+
transforms[at + 5] as number,
|
|
113
|
+
transforms[at + 6] as number,
|
|
114
|
+
);
|
|
115
|
+
const l2 = Math.hypot(
|
|
116
|
+
transforms[at + 8] as number,
|
|
117
|
+
transforms[at + 9] as number,
|
|
118
|
+
transforms[at + 10] as number,
|
|
119
|
+
);
|
|
120
|
+
const longest = Math.max(l0, l1, l2);
|
|
121
|
+
scales[m] = longest;
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* **The longest basis vector is the scale, which is exact for `T · R · S` and conservative for
|
|
125
|
+
* nothing else.** A sphere of radius `r` maps to an ellipsoid whose longest semi-axis is the
|
|
126
|
+
* matrix's largest singular value, and for a rotation applied *after* a scale the columns are
|
|
127
|
+
* the scaled axes and their lengths are exactly the singular values. A sheared matrix — a scale
|
|
128
|
+
* applied after a rotation, which nothing in this engine composes — has a largest singular
|
|
129
|
+
* value above its longest column, and there the bound is too small and clusters are culled that
|
|
130
|
+
* should be drawn. `sceneShadowBounds` reads the same number and carries the same limit.
|
|
131
|
+
*
|
|
132
|
+
* A similarity is the case where the *cone* survives too: three columns of equal length, at
|
|
133
|
+
* right angles. Then the inverse transpose is the matrix over the scale squared, so a normal
|
|
134
|
+
* multiplied by the matrix and renormalised is the right direction and the cone's half-angle is
|
|
135
|
+
* unchanged. Anywhere else the two disagree by up to a right angle and the cone is refused.
|
|
136
|
+
*/
|
|
137
|
+
const even =
|
|
138
|
+
Math.abs(l0 - longest) <= SIMILARITY_TOLERANCE * longest &&
|
|
139
|
+
Math.abs(l1 - longest) <= SIMILARITY_TOLERANCE * longest &&
|
|
140
|
+
Math.abs(l2 - longest) <= SIMILARITY_TOLERANCE * longest;
|
|
141
|
+
const square = longest * longest;
|
|
142
|
+
const d01 =
|
|
143
|
+
(transforms[at] as number) * (transforms[at + 4] as number) +
|
|
144
|
+
(transforms[at + 1] as number) * (transforms[at + 5] as number) +
|
|
145
|
+
(transforms[at + 2] as number) * (transforms[at + 6] as number);
|
|
146
|
+
const d02 =
|
|
147
|
+
(transforms[at] as number) * (transforms[at + 8] as number) +
|
|
148
|
+
(transforms[at + 1] as number) * (transforms[at + 9] as number) +
|
|
149
|
+
(transforms[at + 2] as number) * (transforms[at + 10] as number);
|
|
150
|
+
const d12 =
|
|
151
|
+
(transforms[at + 4] as number) * (transforms[at + 8] as number) +
|
|
152
|
+
(transforms[at + 5] as number) * (transforms[at + 9] as number) +
|
|
153
|
+
(transforms[at + 6] as number) * (transforms[at + 10] as number);
|
|
154
|
+
const square90 =
|
|
155
|
+
Math.abs(d01) <= SIMILARITY_TOLERANCE * square &&
|
|
156
|
+
Math.abs(d02) <= SIMILARITY_TOLERANCE * square &&
|
|
157
|
+
Math.abs(d12) <= SIMILARITY_TOLERANCE * square;
|
|
158
|
+
similar[m] = longest > 0 && even && square90 ? 1 : 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (let c = 0; c < count; c += 1) {
|
|
162
|
+
const at = (clusters.meshOf[c] as number) * 16;
|
|
163
|
+
const scale = scales[clusters.meshOf[c] as number] as number;
|
|
164
|
+
|
|
165
|
+
const cx = clusters.bounds[c * 4] as number;
|
|
166
|
+
const cy = clusters.bounds[c * 4 + 1] as number;
|
|
167
|
+
const cz = clusters.bounds[c * 4 + 2] as number;
|
|
168
|
+
const wx =
|
|
169
|
+
(transforms[at] as number) * cx +
|
|
170
|
+
(transforms[at + 4] as number) * cy +
|
|
171
|
+
(transforms[at + 8] as number) * cz +
|
|
172
|
+
(transforms[at + 12] as number);
|
|
173
|
+
const wy =
|
|
174
|
+
(transforms[at + 1] as number) * cx +
|
|
175
|
+
(transforms[at + 5] as number) * cy +
|
|
176
|
+
(transforms[at + 9] as number) * cz +
|
|
177
|
+
(transforms[at + 13] as number);
|
|
178
|
+
const wz =
|
|
179
|
+
(transforms[at + 2] as number) * cx +
|
|
180
|
+
(transforms[at + 6] as number) * cy +
|
|
181
|
+
(transforms[at + 10] as number) * cz +
|
|
182
|
+
(transforms[at + 14] as number);
|
|
183
|
+
const radius = (clusters.bounds[c * 4 + 3] as number) * scale;
|
|
184
|
+
|
|
185
|
+
const ax = clusters.cones[c * 4] as number;
|
|
186
|
+
const ay = clusters.cones[c * 4 + 1] as number;
|
|
187
|
+
const az = clusters.cones[c * 4 + 2] as number;
|
|
188
|
+
const tx =
|
|
189
|
+
(transforms[at] as number) * ax +
|
|
190
|
+
(transforms[at + 4] as number) * ay +
|
|
191
|
+
(transforms[at + 8] as number) * az;
|
|
192
|
+
const ty =
|
|
193
|
+
(transforms[at + 1] as number) * ax +
|
|
194
|
+
(transforms[at + 5] as number) * ay +
|
|
195
|
+
(transforms[at + 9] as number) * az;
|
|
196
|
+
const tz =
|
|
197
|
+
(transforms[at + 2] as number) * ax +
|
|
198
|
+
(transforms[at + 6] as number) * ay +
|
|
199
|
+
(transforms[at + 10] as number) * az;
|
|
200
|
+
const length = Math.hypot(tx, ty, tz);
|
|
201
|
+
/*
|
|
202
|
+
* **A cutoff of one is a refusal rather than a wide cone.** `cullClusters` culls when
|
|
203
|
+
* `dot(axis, d) >= cutoff * |d| + radius`, and for a unit axis the left-hand side is at most
|
|
204
|
+
* `|d|` — so at one the test cannot fire however the cluster is aimed, which is the
|
|
205
|
+
* conservative answer and is already what a bake writes for a cluster whose normals span a
|
|
206
|
+
* hemisphere.
|
|
207
|
+
*/
|
|
208
|
+
const keepsCone = similar[clusters.meshOf[c] as number] === 1 && length > 0;
|
|
209
|
+
const out = (base + c) * CULL_FLOATS;
|
|
210
|
+
cull[out] = wx;
|
|
211
|
+
cull[out + 1] = wy;
|
|
212
|
+
cull[out + 2] = wz;
|
|
213
|
+
cull[out + 3] = radius;
|
|
214
|
+
cull[out + 4] = keepsCone ? tx / length : ax;
|
|
215
|
+
cull[out + 5] = keepsCone ? ty / length : ay;
|
|
216
|
+
cull[out + 6] = keepsCone ? tz / length : az;
|
|
217
|
+
cull[out + 7] = keepsCone ? (clusters.cones[c * 4 + 3] as number) : 1;
|
|
218
|
+
|
|
219
|
+
/*
|
|
220
|
+
* **The errors are lengths and a scale is a length's units.** `projectedError` divides a
|
|
221
|
+
* geometric error by the distance to the eye and multiplies by the screen height; the distance
|
|
222
|
+
* is in world metres now, so the error has to be. A mesh scaled up with its model-space error
|
|
223
|
+
* left alone draws its coarsest level everywhere.
|
|
224
|
+
*
|
|
225
|
+
* `Infinity` is how the coarsest level says it has no parent, and it is left alone: multiplied
|
|
226
|
+
* by a scale of zero it is `NaN`, every comparison against it is false, and the root of every
|
|
227
|
+
* mesh disappears.
|
|
228
|
+
*/
|
|
229
|
+
const ownError = clusters.errors[c * 2] as number;
|
|
230
|
+
const parentError = clusters.errors[c * 2 + 1] as number;
|
|
231
|
+
const into = (base + c) * LOD_FLOATS;
|
|
232
|
+
lod[into] = wx;
|
|
233
|
+
lod[into + 1] = wy;
|
|
234
|
+
lod[into + 2] = wz;
|
|
235
|
+
lod[into + 3] = radius;
|
|
236
|
+
lod[into + 4] = ownError * scale;
|
|
237
|
+
lod[into + 5] = Number.isFinite(parentError) ? parentError * scale : parentError;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning three flag buffers into the one list a draw can read, and the block that draws it.
|
|
3
|
+
*
|
|
4
|
+
* **The culls answer per cluster and a draw wants a list.** `lodCut` writes a flag, `cullClusters`
|
|
5
|
+
* writes a flag, and the history is a flag — all three addressed by cluster identifier, because a
|
|
6
|
+
* dispatch decides every cluster at once and cannot append in input order without a prefix sum.
|
|
7
|
+
* The compaction is the step that turns the three into `[4, 17, 18, 40]` and says how many there
|
|
8
|
+
* are, which is what `instanceCount` in an indirect block is.
|
|
9
|
+
*
|
|
10
|
+
* **Order is not part of the answer.** The device appends with an atomic and the arrival order is
|
|
11
|
+
* whatever the scheduler did; this reference appends in cluster order because a loop has one. So a
|
|
12
|
+
* check comparing the two sorts both — the same rule `cullClusters.ts` avoids needing by answering
|
|
13
|
+
* in flags, and the reason the compaction is the one stage that cannot.
|
|
14
|
+
*
|
|
15
|
+
* **Four words, not `INDIRECT_DRAW_WORDS`' five, and that is the plan being wrong rather than a
|
|
16
|
+
* shortcut.** The plan has this as `drawIndexedIndirect`. It cannot be: one block covers every
|
|
17
|
+
* surviving cluster, each cluster's indices begin somewhere different, and an indexed draw has one
|
|
18
|
+
* `firstIndex` for the whole block. So the raster shader fetches indices out of a storage buffer
|
|
19
|
+
* itself — `visbufferRaster.wgsl.ts` already does — and what is issued is the non-indexed
|
|
20
|
+
* `drawIndirect`, whose block is vertex count, instances, first vertex, first instance. The five
|
|
21
|
+
* word block would *happen* to work, because its extra words are zero where the four-word one
|
|
22
|
+
* wants zero; that is a coincidence and not a layout.
|
|
23
|
+
*
|
|
24
|
+
* **The history is per cluster here rather than the bit set `twoPhase.ts` holds.** A shader
|
|
25
|
+
* addressing a bit needs a shift and a mask around every read and write, and the atomic that
|
|
26
|
+
* records this frame's would be a read-modify-write on a word shared by thirty-two clusters. One
|
|
27
|
+
* word a cluster is the device's shape; the bit set is the processor's, and they answer the same
|
|
28
|
+
* question.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { CLUSTER_INDEX_CAP } from './indirect.ts';
|
|
32
|
+
|
|
33
|
+
/** Words in a non-indexed `drawIndirect` block: vertex count, instances, first vertex, first instance. */
|
|
34
|
+
export const DRAW_INDIRECT_WORDS = 4;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Which half of the frame a compaction is building.
|
|
38
|
+
*
|
|
39
|
+
* Phase one draws what the history holds; phase two draws what it does not. **Never "whether" —
|
|
40
|
+
* every live cluster is in exactly one of the two lists**, which is the invariant `twoPhase.ts`
|
|
41
|
+
* exists to state and the reason a history that is entirely stale costs a test rather than a hole.
|
|
42
|
+
*/
|
|
43
|
+
export const PHASE_ONE = 0;
|
|
44
|
+
export const PHASE_TWO = 1;
|
|
45
|
+
export type ClusterPhase = typeof PHASE_ONE | typeof PHASE_TWO;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Append every cluster this phase draws, and write the block that draws them.
|
|
49
|
+
*
|
|
50
|
+
* **`drawn` is next frame's history, and only phase two writes it** — for every cluster, seen last
|
|
51
|
+
* frame or not, from its own cull, which is the only one that tested against this frame's pyramid.
|
|
52
|
+
* That is `updateHistory` in `twoPhase.ts`: the history is replaced, not accumulated. Phase one's
|
|
53
|
+
* cull has no pyramid, so a cluster it keeps may be hidden; marking it there kept every cluster ever
|
|
54
|
+
* seen drawn for as long as it stayed in the frustum. The caller clears `drawn` once a frame.
|
|
55
|
+
*/
|
|
56
|
+
export function compactPhase(
|
|
57
|
+
phase: ClusterPhase,
|
|
58
|
+
selected: Uint32Array,
|
|
59
|
+
keep: Uint32Array,
|
|
60
|
+
history: Uint32Array,
|
|
61
|
+
count: number,
|
|
62
|
+
list: Uint32Array,
|
|
63
|
+
drawn: Uint32Array,
|
|
64
|
+
args: Uint32Array,
|
|
65
|
+
): number {
|
|
66
|
+
let at = 0;
|
|
67
|
+
for (let cluster = 0; cluster < count; cluster += 1) {
|
|
68
|
+
const seen = (history[cluster] ?? 0) !== 0;
|
|
69
|
+
const visible = (selected[cluster] ?? 0) !== 0 && (keep[cluster] ?? 0) !== 0;
|
|
70
|
+
if (phase === PHASE_TWO && visible) drawn[cluster] = 1;
|
|
71
|
+
if (seen !== (phase === PHASE_ONE) || !visible) continue;
|
|
72
|
+
list[at] = cluster;
|
|
73
|
+
at += 1;
|
|
74
|
+
}
|
|
75
|
+
args[0] = CLUSTER_INDEX_CAP;
|
|
76
|
+
args[1] = at;
|
|
77
|
+
args[2] = 0;
|
|
78
|
+
args[3] = 0;
|
|
79
|
+
return at;
|
|
80
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which clusters are worth rasterising: the three tests, in the order that makes them cheap.
|
|
3
|
+
*
|
|
4
|
+
* Frustum first, because it is six dot products and throws away most of a world. Then the normal
|
|
5
|
+
* cone, which is one more dot product and throws away roughly half of what is left on a closed
|
|
6
|
+
* surface. Then the hierarchical depth buffer, which costs a projection and four texel reads and
|
|
7
|
+
* is the only one that needs anything from this frame.
|
|
8
|
+
*
|
|
9
|
+
* **Every one of them is conservative in the same direction.** A cluster that might be visible
|
|
10
|
+
* survives; only a cluster that provably cannot be seen is dropped. Culling something visible is a
|
|
11
|
+
* hole in the picture that comes and goes with the camera and is diagnosed as almost anything
|
|
12
|
+
* else; keeping something invisible is a wasted draw. `frustum.ts` says the same in its own words
|
|
13
|
+
* and the two files have to agree about it, because a pipeline whose stages disagree about which
|
|
14
|
+
* way to round is one that drops geometry at exactly the boundaries.
|
|
15
|
+
*
|
|
16
|
+
* **The compaction is here and the flags are the device's.** A dispatch can decide every cluster
|
|
17
|
+
* in parallel and cannot append them in order without a prefix sum, so the shader writes one flag
|
|
18
|
+
* per cluster and this writes the ordered list from the flags. That split is also what
|
|
19
|
+
* `scripts/gpu-parity.mjs` compares: the flags are order-free, so they can be checked
|
|
20
|
+
* element-by-element against a reference that has not had to reproduce an atomic's arrival order.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { sphereOutsideFrustum } from './frustum.ts';
|
|
24
|
+
import { hzbOccluded } from './hzb.ts';
|
|
25
|
+
|
|
26
|
+
/** Eight floats a cluster: centre, radius, cone axis, cone cutoff. `clusterUpload.ts` packs them. */
|
|
27
|
+
export const CLUSTER_CULL_FLOATS = 8;
|
|
28
|
+
|
|
29
|
+
/** What a kept cluster's flag holds. One rather than a bit mask: there is one reason to keep. */
|
|
30
|
+
export const CLUSTER_KEEP = 1;
|
|
31
|
+
|
|
32
|
+
/** A depth pyramid as a dispatch binds it: one flat buffer, one offset a level. */
|
|
33
|
+
export interface ClusterHzb {
|
|
34
|
+
/** Every level's texels, coarsest last, as `hzbReduce` leaves them. */
|
|
35
|
+
readonly depths: Float32Array;
|
|
36
|
+
/** Where each level starts in `depths`. Its length is the level count. */
|
|
37
|
+
readonly offsets: Int32Array;
|
|
38
|
+
/** The base level's dimensions. Every level below is halved, never past one. */
|
|
39
|
+
readonly width: number;
|
|
40
|
+
readonly height: number;
|
|
41
|
+
/** The camera the pyramid was built with, which is this frame's in a two-phase pass. */
|
|
42
|
+
readonly viewProj: Float32Array;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A cluster's cone as it is carried, turned into the cutoff `coneBackfacing` reads.
|
|
47
|
+
*
|
|
48
|
+
* **A cluster carries a cosine and this test is written in a sine**, and until 2026-09-18 nothing
|
|
49
|
+
* said so. `buildClusters` writes "the minimum dot product of any face normal against the axis",
|
|
50
|
+
* the `MSHL` container stores that and says so, and `ClusterSource` passed it through to the test
|
|
51
|
+
* below untouched. Every cluster before then was a single quad or a near-flat patch, whose cosine
|
|
52
|
+
* of about one reads as a sine of one — a hemisphere, never culled — so the mismatch was silent and
|
|
53
|
+
* cone culling simply did not happen. The voxel sandbox grows its clusters by position, across
|
|
54
|
+
* every direction a block faces: a cosine of zero read as a sine of zero is a flat cluster, and a
|
|
55
|
+
* canopy seen from below lost every block whose cluster pointed up.
|
|
56
|
+
*
|
|
57
|
+
* `cos(a)` at or above zero is `sin(a) = sqrt(1 - cos(a)^2)`. Below zero the cone is wider than a
|
|
58
|
+
* hemisphere and no aim of the eye sees every face from behind, so the answer is one, which the
|
|
59
|
+
* test can never satisfy. Converted where a cluster is packed, once, so neither shader nor
|
|
60
|
+
* reference changes.
|
|
61
|
+
*/
|
|
62
|
+
export function cullCutoff(minimumDot: number): number {
|
|
63
|
+
if (minimumDot < 0) return 1;
|
|
64
|
+
return Math.sqrt(Math.max(0, 1 - minimumDot * minimumDot));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether every triangle in a cluster faces away from the eye.
|
|
69
|
+
*
|
|
70
|
+
* **`cutoff` is `sin(a)`**, where `a` is the half-angle of the cone containing every normal. Zero
|
|
71
|
+
* is a flat cluster; one is a cluster whose normals span a hemisphere, which can never be culled
|
|
72
|
+
* because the test below cannot be satisfied. Stating it as a sine rather than a cosine is what
|
|
73
|
+
* makes the expression a single comparison with no trigonometry in the shader.
|
|
74
|
+
*
|
|
75
|
+
* The derivation, because the constant is otherwise unfalsifiable. Let `d` be the vector from the
|
|
76
|
+
* eye to the cluster's centre. Directions from the eye to points on the cluster lie within
|
|
77
|
+
* `b = asin(radius / |d|)` of `d`. A triangle is backfacing when its normal has a positive dot
|
|
78
|
+
* with the direction the eye looks along it, so *every* triangle is backfacing when
|
|
79
|
+
* `angle(axis, d) + a + b < 90 degrees`, which is `dot(axis, normalise(d)) > sin(a + b)`.
|
|
80
|
+
* Expanding `sin(a + b)` to `sin a + radius / |d|` is exact at `a = 0` and errs on the side of
|
|
81
|
+
* keeping a cluster everywhere else, which is the direction this file rounds.
|
|
82
|
+
*
|
|
83
|
+
* Multiplied through by `|d|` so there is no divide and no normalise.
|
|
84
|
+
*/
|
|
85
|
+
export function coneBackfacing(
|
|
86
|
+
axisX: number,
|
|
87
|
+
axisY: number,
|
|
88
|
+
axisZ: number,
|
|
89
|
+
cutoff: number,
|
|
90
|
+
centreX: number,
|
|
91
|
+
centreY: number,
|
|
92
|
+
centreZ: number,
|
|
93
|
+
radius: number,
|
|
94
|
+
eyeX: number,
|
|
95
|
+
eyeY: number,
|
|
96
|
+
eyeZ: number,
|
|
97
|
+
): boolean {
|
|
98
|
+
const dx = centreX - eyeX;
|
|
99
|
+
const dy = centreY - eyeY;
|
|
100
|
+
const dz = centreZ - eyeZ;
|
|
101
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
102
|
+
return dx * axisX + dy * axisY + dz * axisZ >= cutoff * distance + radius;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The screen rectangle a sphere covers and the nearest clip depth it reaches.
|
|
107
|
+
*
|
|
108
|
+
* Writes `minX, minY, maxX, maxY, nearestDepth` into `out`, in the [0, 1] range a texture
|
|
109
|
+
* coordinate uses, and answers **false when the sphere has no rectangle at all** — when any part
|
|
110
|
+
* of it is at or behind the eye. That case is the reason this returns a boolean rather than
|
|
111
|
+
* clamping: a sphere straddling the near plane does not project to a rectangle, and every
|
|
112
|
+
* implementation that pretends it does culls it on the frames where the wrap happens to land
|
|
113
|
+
* outside the screen and keeps it on the frames where it does not. What that looks like is
|
|
114
|
+
* geometry flickering as the camera moves through it, which is the defect this function's shape
|
|
115
|
+
* exists to prevent.
|
|
116
|
+
*
|
|
117
|
+
* The eight corners of the sphere's bounding box rather than an analytic silhouette: the box is
|
|
118
|
+
* larger than the sphere, so the rectangle is conservative, and it is eight transforms instead of
|
|
119
|
+
* a quadratic.
|
|
120
|
+
*/
|
|
121
|
+
export function sphereScreenBounds(
|
|
122
|
+
viewProj: Float32Array,
|
|
123
|
+
centreX: number,
|
|
124
|
+
centreY: number,
|
|
125
|
+
centreZ: number,
|
|
126
|
+
radius: number,
|
|
127
|
+
out: Float32Array,
|
|
128
|
+
): boolean {
|
|
129
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
130
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
131
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
132
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
133
|
+
let nearest = Number.NEGATIVE_INFINITY;
|
|
134
|
+
|
|
135
|
+
for (let corner = 0; corner < 8; corner += 1) {
|
|
136
|
+
const x = centreX + (corner & 1 ? radius : -radius);
|
|
137
|
+
const y = centreY + (corner & 2 ? radius : -radius);
|
|
138
|
+
const z = centreZ + (corner & 4 ? radius : -radius);
|
|
139
|
+
const cw =
|
|
140
|
+
(viewProj[3] as number) * x +
|
|
141
|
+
(viewProj[7] as number) * y +
|
|
142
|
+
(viewProj[11] as number) * z +
|
|
143
|
+
(viewProj[15] as number);
|
|
144
|
+
/* At or behind the eye, so there is no rectangle. See the header. */
|
|
145
|
+
if (cw <= 1e-6) return false;
|
|
146
|
+
const cx =
|
|
147
|
+
(viewProj[0] as number) * x +
|
|
148
|
+
(viewProj[4] as number) * y +
|
|
149
|
+
(viewProj[8] as number) * z +
|
|
150
|
+
(viewProj[12] as number);
|
|
151
|
+
const cy =
|
|
152
|
+
(viewProj[1] as number) * x +
|
|
153
|
+
(viewProj[5] as number) * y +
|
|
154
|
+
(viewProj[9] as number) * z +
|
|
155
|
+
(viewProj[13] as number);
|
|
156
|
+
const cz =
|
|
157
|
+
(viewProj[2] as number) * x +
|
|
158
|
+
(viewProj[6] as number) * y +
|
|
159
|
+
(viewProj[10] as number) * z +
|
|
160
|
+
(viewProj[14] as number);
|
|
161
|
+
const inverse = 1 / cw;
|
|
162
|
+
const u = cx * inverse * 0.5 + 0.5;
|
|
163
|
+
const v = cy * inverse * 0.5 + 0.5;
|
|
164
|
+
minX = Math.min(minX, u);
|
|
165
|
+
maxX = Math.max(maxX, u);
|
|
166
|
+
minY = Math.min(minY, v);
|
|
167
|
+
maxY = Math.max(maxY, v);
|
|
168
|
+
/* Nearest is the *largest* clip depth here; `depthConvention.ts` puts the near plane at 1. */
|
|
169
|
+
nearest = Math.max(nearest, cz * inverse);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
out[0] = minX;
|
|
173
|
+
out[1] = minY;
|
|
174
|
+
out[2] = maxX;
|
|
175
|
+
out[3] = maxY;
|
|
176
|
+
out[4] = nearest;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The pyramid level whose texels are wide enough that four of them cover the rectangle.
|
|
182
|
+
*
|
|
183
|
+
* `ceil(log2(longest side in texels))` minus one, floored at zero: at that level the rectangle
|
|
184
|
+
* spans at most two texels each way, which is what a four-texel gather reads. One level coarser
|
|
185
|
+
* would read a texel covering geometry outside the rectangle and cull against an occluder that is
|
|
186
|
+
* not in front of this cluster at all.
|
|
187
|
+
*
|
|
188
|
+
* Clamped to the top of the pyramid, because a rectangle larger than the screen is a cluster the
|
|
189
|
+
* camera is inside and there is no level coarse enough for it.
|
|
190
|
+
*/
|
|
191
|
+
export function hzbLevelFor(
|
|
192
|
+
rectWidthTexels: number,
|
|
193
|
+
rectHeightTexels: number,
|
|
194
|
+
width: number,
|
|
195
|
+
height: number,
|
|
196
|
+
): number {
|
|
197
|
+
const longest = Math.max(rectWidthTexels, rectHeightTexels, 1);
|
|
198
|
+
const level = Math.max(0, Math.ceil(Math.log2(longest)) - 1);
|
|
199
|
+
const top = Math.floor(Math.log2(Math.max(1, Math.max(width, height))));
|
|
200
|
+
return Math.min(level, top);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Whether a rectangle's nearest depth is behind everything already drawn under it.
|
|
205
|
+
*
|
|
206
|
+
* **The weakest of the four texels decides.** Each holds the furthest depth in its own region, so
|
|
207
|
+
* a cluster is hidden only where it is behind *all* of them — and under reversed-Z the furthest of
|
|
208
|
+
* four is the smallest. Taking the nearest instead would cull a cluster visible through a gap,
|
|
209
|
+
* which is the failure mode that reads as holes appearing in geometry as the camera turns.
|
|
210
|
+
*/
|
|
211
|
+
export function clusterOccluded(
|
|
212
|
+
depths: Float32Array,
|
|
213
|
+
offsets: Int32Array,
|
|
214
|
+
width: number,
|
|
215
|
+
height: number,
|
|
216
|
+
minX: number,
|
|
217
|
+
minY: number,
|
|
218
|
+
maxX: number,
|
|
219
|
+
maxY: number,
|
|
220
|
+
nearestDepth: number,
|
|
221
|
+
): boolean {
|
|
222
|
+
const level = hzbLevelFor((maxX - minX) * width, (maxY - minY) * height, width, height);
|
|
223
|
+
if (level >= offsets.length) return false;
|
|
224
|
+
const levelWidth = Math.max(1, width >> level);
|
|
225
|
+
const levelHeight = Math.max(1, height >> level);
|
|
226
|
+
const base = offsets[level] as number;
|
|
227
|
+
|
|
228
|
+
const x0 = Math.max(0, Math.min(levelWidth - 1, Math.floor(minX * levelWidth)));
|
|
229
|
+
const x1 = Math.max(0, Math.min(levelWidth - 1, Math.floor(maxX * levelWidth)));
|
|
230
|
+
/*
|
|
231
|
+
* **The rows run the other way, and the swap is not cosmetic.** `sphereScreenBounds` reports the
|
|
232
|
+
* rectangle in clip-up coordinates — `v` of 1 is clip `y` of +1 — and the pyramid is seeded
|
|
233
|
+
* straight from the depth texture, whose row 0 is exactly there. So a `v` used as a row index
|
|
234
|
+
* reads the mirror of what the cluster covers, and the top edge of the rectangle becomes the
|
|
235
|
+
* bottom row.
|
|
236
|
+
*
|
|
237
|
+
* It shipped that way and drew holes: geometry near the top of the frame tested against whatever
|
|
238
|
+
* stood near the bottom, coming and going with the camera. `gpu-parity.mjs` agreed about it for
|
|
239
|
+
* as long, because the WGSL made the same mistake — a parity check compares two implementations
|
|
240
|
+
* and neither of them is the truth.
|
|
241
|
+
*/
|
|
242
|
+
const y0 = Math.max(0, Math.min(levelHeight - 1, Math.floor((1 - maxY) * levelHeight)));
|
|
243
|
+
const y1 = Math.max(0, Math.min(levelHeight - 1, Math.floor((1 - minY) * levelHeight)));
|
|
244
|
+
|
|
245
|
+
let furthest = Number.POSITIVE_INFINITY;
|
|
246
|
+
for (let y = y0; y <= y1; y += 1) {
|
|
247
|
+
for (let x = x0; x <= x1; x += 1) {
|
|
248
|
+
furthest = Math.min(furthest, depths[base + y * levelWidth + x] as number);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return hzbOccluded(nearestDepth, furthest);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Every test, over every cluster: one flag each, and the kept indices in input order.
|
|
256
|
+
*
|
|
257
|
+
* `selected`, where given, is the cut's flag a cluster: a cluster it did not select is not tested
|
|
258
|
+
* and not kept, which is how an instance cull reaches the clusters of a mesh it hid. Absent, every
|
|
259
|
+
* cluster is judged.
|
|
260
|
+
*
|
|
261
|
+
* `hzb` is null in the first phase of a two-phase pass, where there is no depth from this frame to
|
|
262
|
+
* test against yet — and null rather than an empty pyramid, because an empty one is a pyramid full
|
|
263
|
+
* of zeroes and under reversed-Z zero is the far plane, which occludes nothing and would look like
|
|
264
|
+
* the test running when it is not.
|
|
265
|
+
*
|
|
266
|
+
* Returns how many survived, which is also what an indirect draw's instance count wants.
|
|
267
|
+
*/
|
|
268
|
+
export function cullClusters(
|
|
269
|
+
clusters: Float32Array,
|
|
270
|
+
planes: Float32Array,
|
|
271
|
+
eyeX: number,
|
|
272
|
+
eyeY: number,
|
|
273
|
+
eyeZ: number,
|
|
274
|
+
hzb: ClusterHzb | null,
|
|
275
|
+
keep: Uint32Array,
|
|
276
|
+
list: Uint32Array,
|
|
277
|
+
selected: ArrayLike<number> | null = null,
|
|
278
|
+
/** False for the blended run, whose raster draws both sides. See `CULL_CONES`. */
|
|
279
|
+
cones = true,
|
|
280
|
+
): number {
|
|
281
|
+
const bounds = new Float32Array(5);
|
|
282
|
+
const count = Math.floor(clusters.length / CLUSTER_CULL_FLOATS);
|
|
283
|
+
let kept = 0;
|
|
284
|
+
|
|
285
|
+
for (let i = 0; i < count; i += 1) {
|
|
286
|
+
if (selected !== null && selected[i] === 0) {
|
|
287
|
+
keep[i] = 0;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const at = i * CLUSTER_CULL_FLOATS;
|
|
291
|
+
const cx = clusters[at] as number;
|
|
292
|
+
const cy = clusters[at + 1] as number;
|
|
293
|
+
const cz = clusters[at + 2] as number;
|
|
294
|
+
const radius = clusters[at + 3] as number;
|
|
295
|
+
|
|
296
|
+
let survives = !sphereOutsideFrustum(planes, cx, cy, cz, radius);
|
|
297
|
+
if (survives && cones) {
|
|
298
|
+
survives = !coneBackfacing(
|
|
299
|
+
clusters[at + 4] as number,
|
|
300
|
+
clusters[at + 5] as number,
|
|
301
|
+
clusters[at + 6] as number,
|
|
302
|
+
clusters[at + 7] as number,
|
|
303
|
+
cx,
|
|
304
|
+
cy,
|
|
305
|
+
cz,
|
|
306
|
+
radius,
|
|
307
|
+
eyeX,
|
|
308
|
+
eyeY,
|
|
309
|
+
eyeZ,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
if (survives && hzb !== null && sphereScreenBounds(hzb.viewProj, cx, cy, cz, radius, bounds)) {
|
|
313
|
+
survives = !clusterOccluded(
|
|
314
|
+
hzb.depths,
|
|
315
|
+
hzb.offsets,
|
|
316
|
+
hzb.width,
|
|
317
|
+
hzb.height,
|
|
318
|
+
bounds[0] as number,
|
|
319
|
+
bounds[1] as number,
|
|
320
|
+
bounds[2] as number,
|
|
321
|
+
bounds[3] as number,
|
|
322
|
+
bounds[4] as number,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
keep[i] = survives ? CLUSTER_KEEP : 0;
|
|
327
|
+
if (survives) {
|
|
328
|
+
list[kept] = i;
|
|
329
|
+
kept += 1;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return kept;
|
|
333
|
+
}
|