@driftengine/core 4.4.2 → 4.6.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 +97 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +28 -3
- package/dist/index.js +20 -1
- package/dist/render/backend/api.d.ts +34 -14
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +201 -16
- package/dist/render/backend/webgl2/renderer.js +596 -224
- package/dist/render/backend/webgpu/buffers.d.ts +29 -1
- package/dist/render/backend/webgpu/buffers.js +61 -4
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +7 -2
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
- package/dist/render/backend/webgpu/flatPass.js +57 -9
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +206 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +23 -2
- package/dist/render/backend/webgpu/instanced.js +41 -4
- package/dist/render/backend/webgpu/latePass.d.ts +30 -0
- package/dist/render/backend/webgpu/latePass.js +140 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +3 -13
- package/dist/render/backend/webgpu/renderer.d.ts +271 -64
- package/dist/render/backend/webgpu/renderer.js +1395 -580
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +22 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +36 -35
- package/dist/render/lightBudget.js +38 -37
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +18 -24
- package/dist/render/mesh.js +51 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +16 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +60 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +94 -15
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +9 -3
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +190 -38
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +96 -26
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
- package/dist/render/shaders/generated/depth.wgsl.js +230 -7
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
- package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +21 -17
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +69 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +61 -1
- package/src/render/backend/api.ts +36 -11
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +700 -250
- package/src/render/backend/webgpu/buffers.ts +92 -5
- package/src/render/backend/webgpu/depthPass.ts +7 -2
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +62 -11
- package/src/render/backend/webgpu/glassTintPass.ts +245 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +42 -4
- package/src/render/backend/webgpu/latePass.ts +161 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/renderer.ts +1671 -670
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +91 -51
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +41 -38
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +59 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +16 -0
- package/src/render/sceneTarget.ts +62 -11
- package/src/render/shaders/depth.ts +118 -15
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +22 -3
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +190 -38
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +96 -26
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +237 -7
- package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +72 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/translucentQueue.ts +20 -1
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
|
@@ -255,7 +255,8 @@ export function createScatterDepthBindGroupLayout(device, fragBinding, fragSize,
|
|
|
255
255
|
{
|
|
256
256
|
binding: previous.texture,
|
|
257
257
|
visibility: VISIBILITY_FRAGMENT,
|
|
258
|
-
|
|
258
|
+
/* The depth program's peel read, an array since the sun's maps became one. */
|
|
259
|
+
texture: { sampleType: 'unfilterable-float', viewDimension: '2d-array' },
|
|
259
260
|
},
|
|
260
261
|
{
|
|
261
262
|
binding: previous.sampler,
|
|
@@ -125,7 +125,12 @@ export function createSdfTextBindGroupLayout(device) {
|
|
|
125
125
|
visibility: VISIBILITY_FRAGMENT,
|
|
126
126
|
buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: SDF_TEXT_FRAG_SIZE },
|
|
127
127
|
},
|
|
128
|
-
|
|
128
|
+
/* The atlas is a surface texture, and every surface texture is a `2d-array` view. */
|
|
129
|
+
{
|
|
130
|
+
binding: ATLAS.texture,
|
|
131
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
132
|
+
texture: { viewDimension: '2d-array' },
|
|
133
|
+
},
|
|
129
134
|
{ binding: ATLAS.sampler, visibility: VISIBILITY_FRAGMENT, sampler: {} },
|
|
130
135
|
],
|
|
131
136
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The instance cull, in WGSL, hand-authored under the compute exception `AGENTS.md` records
|
|
3
|
+
* (2026-08-24): a compute shader has no WebGL2 twin to generate from, and `npm run wgsl` neither
|
|
4
|
+
* writes nor polices this file.
|
|
5
|
+
*
|
|
6
|
+
* **It keeps exactly what `cullInstances` keeps**, and `gpu-parity.mjs` runs both on a device: an
|
|
7
|
+
* instance's world sphere is the mesh's sphere with its centre through the model matrix and its
|
|
8
|
+
* radius scaled by the largest column, and it is kept unless wholly beyond one of the six planes.
|
|
9
|
+
* The one difference is order — survivors are compacted by `atomicAdd`, so they arrive in whatever
|
|
10
|
+
* order the invocations reached it. That is invisible to an opaque draw and is why a blended draw
|
|
11
|
+
* is never culled here.
|
|
12
|
+
*
|
|
13
|
+
* `args` is a `drawIndexedIndirect` record, `(indexCount, instanceCount, firstIndex, baseVertex,
|
|
14
|
+
* firstInstance)`; the renderer writes it with a zero instance count before each dispatch and this
|
|
15
|
+
* counts the survivors into it.
|
|
16
|
+
*/
|
|
17
|
+
export declare const INSTANCE_CULL_WORKGROUP = 64;
|
|
18
|
+
export declare const INSTANCE_CULL_WGSL = "\nstruct Params {\n planes: array<vec4<f32>, 6>,\n sphere: vec4<f32>,\n count: u32,\n pad0: u32,\n pad1: u32,\n pad2: u32,\n}\n\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read> source: array<f32>;\n@group(0) @binding(2) var<storage, read_write> kept: array<f32>;\n@group(0) @binding(3) var<storage, read_write> args: array<atomic<u32>, 5>;\n\nconst STRIDE: u32 = 20u;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i >= params.count) { return; }\n let at = i * STRIDE;\n let c0 = vec3<f32>(source[at], source[at + 1u], source[at + 2u]);\n let c1 = vec3<f32>(source[at + 4u], source[at + 5u], source[at + 6u]);\n let c2 = vec3<f32>(source[at + 8u], source[at + 9u], source[at + 10u]);\n let t = vec3<f32>(source[at + 12u], source[at + 13u], source[at + 14u]);\n let local = params.sphere;\n let centre = c0 * local.x + c1 * local.y + c2 * local.z + t;\n let scale = sqrt(max(dot(c0, c0), max(dot(c1, c1), dot(c2, c2))));\n let radius = local.w * scale;\n for (var p = 0u; p < 6u; p = p + 1u) {\n let plane = params.planes[p];\n if (dot(plane.xyz, centre) + plane.w < -radius) { return; }\n }\n let slot = atomicAdd(&args[1], 1u);\n let to = slot * STRIDE;\n for (var k = 0u; k < STRIDE; k = k + 1u) {\n kept[to + k] = source[at + k];\n }\n}\n";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { INSTANCE_FLOATS } from '../../../instances.js';
|
|
2
|
+
/**
|
|
3
|
+
* The instance cull, in WGSL, hand-authored under the compute exception `AGENTS.md` records
|
|
4
|
+
* (2026-08-24): a compute shader has no WebGL2 twin to generate from, and `npm run wgsl` neither
|
|
5
|
+
* writes nor polices this file.
|
|
6
|
+
*
|
|
7
|
+
* **It keeps exactly what `cullInstances` keeps**, and `gpu-parity.mjs` runs both on a device: an
|
|
8
|
+
* instance's world sphere is the mesh's sphere with its centre through the model matrix and its
|
|
9
|
+
* radius scaled by the largest column, and it is kept unless wholly beyond one of the six planes.
|
|
10
|
+
* The one difference is order — survivors are compacted by `atomicAdd`, so they arrive in whatever
|
|
11
|
+
* order the invocations reached it. That is invisible to an opaque draw and is why a blended draw
|
|
12
|
+
* is never culled here.
|
|
13
|
+
*
|
|
14
|
+
* `args` is a `drawIndexedIndirect` record, `(indexCount, instanceCount, firstIndex, baseVertex,
|
|
15
|
+
* firstInstance)`; the renderer writes it with a zero instance count before each dispatch and this
|
|
16
|
+
* counts the survivors into it.
|
|
17
|
+
*/
|
|
18
|
+
export const INSTANCE_CULL_WORKGROUP = 64;
|
|
19
|
+
export const INSTANCE_CULL_WGSL = /* wgsl */ `
|
|
20
|
+
struct Params {
|
|
21
|
+
planes: array<vec4<f32>, 6>,
|
|
22
|
+
sphere: vec4<f32>,
|
|
23
|
+
count: u32,
|
|
24
|
+
pad0: u32,
|
|
25
|
+
pad1: u32,
|
|
26
|
+
pad2: u32,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
30
|
+
@group(0) @binding(1) var<storage, read> source: array<f32>;
|
|
31
|
+
@group(0) @binding(2) var<storage, read_write> kept: array<f32>;
|
|
32
|
+
@group(0) @binding(3) var<storage, read_write> args: array<atomic<u32>, 5>;
|
|
33
|
+
|
|
34
|
+
const STRIDE: u32 = ${INSTANCE_FLOATS}u;
|
|
35
|
+
|
|
36
|
+
@compute @workgroup_size(${INSTANCE_CULL_WORKGROUP})
|
|
37
|
+
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
38
|
+
let i = id.x;
|
|
39
|
+
if (i >= params.count) { return; }
|
|
40
|
+
let at = i * STRIDE;
|
|
41
|
+
let c0 = vec3<f32>(source[at], source[at + 1u], source[at + 2u]);
|
|
42
|
+
let c1 = vec3<f32>(source[at + 4u], source[at + 5u], source[at + 6u]);
|
|
43
|
+
let c2 = vec3<f32>(source[at + 8u], source[at + 9u], source[at + 10u]);
|
|
44
|
+
let t = vec3<f32>(source[at + 12u], source[at + 13u], source[at + 14u]);
|
|
45
|
+
let local = params.sphere;
|
|
46
|
+
let centre = c0 * local.x + c1 * local.y + c2 * local.z + t;
|
|
47
|
+
let scale = sqrt(max(dot(c0, c0), max(dot(c1, c1), dot(c2, c2))));
|
|
48
|
+
let radius = local.w * scale;
|
|
49
|
+
for (var p = 0u; p < 6u; p = p + 1u) {
|
|
50
|
+
let plane = params.planes[p];
|
|
51
|
+
if (dot(plane.xyz, centre) + plane.w < -radius) { return; }
|
|
52
|
+
}
|
|
53
|
+
let slot = atomicAdd(&args[1], 1u);
|
|
54
|
+
let to = slot * STRIDE;
|
|
55
|
+
for (var k = 0u; k < STRIDE; k = k + 1u) {
|
|
56
|
+
kept[to + k] = source[at + k];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* **What would make it wrong** is a genuine crowd — a stadium, a swarm of rigged birds — and the
|
|
14
14
|
* fix is then the one `skinPalette.ts` names: one texture holding every palette with a per-draw
|
|
15
15
|
* row offset, which spends a uniform and a shader change to stop spending textures.
|
|
16
|
+
*
|
|
17
|
+
* **A reconstructing frame spends one more per skinned mover**: last frame's pose is uploaded into
|
|
18
|
+
* a slot of its own for the motion pass. So the same budget is about twelve such characters with a
|
|
19
|
+
* reflection and three shadow layers each.
|
|
16
20
|
*/
|
|
17
21
|
export declare const MAX_SKIN_PALETTES = 128;
|
|
18
22
|
/**
|
|
@@ -14,6 +14,10 @@ import { SkinPaletteTexture } from './skinPaletteTexture.js';
|
|
|
14
14
|
* **What would make it wrong** is a genuine crowd — a stadium, a swarm of rigged birds — and the
|
|
15
15
|
* fix is then the one `skinPalette.ts` names: one texture holding every palette with a per-draw
|
|
16
16
|
* row offset, which spends a uniform and a shader change to stop spending textures.
|
|
17
|
+
*
|
|
18
|
+
* **A reconstructing frame spends one more per skinned mover**: last frame's pose is uploaded into
|
|
19
|
+
* a slot of its own for the motion pass. So the same budget is about twelve such characters with a
|
|
20
|
+
* reflection and three shadow layers each.
|
|
17
21
|
*/
|
|
18
22
|
export const MAX_SKIN_PALETTES = 128;
|
|
19
23
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mip chain of the frame's refraction copy, recorded into the frame's own encoder.
|
|
3
|
+
*
|
|
4
|
+
* **Frosted glass reads a blurrier level of what is behind it** (`glass.ts`), so on a frame that draws
|
|
5
|
+
* glass the copy needs every level filled from the one above. `generateMipChain` does that for an
|
|
6
|
+
* upload, on an encoder of its own that it submits at once — which here would run before the frame's
|
|
7
|
+
* encoder had even made the copy, and would build views and bind groups on every call. So this
|
|
8
|
+
* makes them once per copy texture and records one pass per level wherever the copy was taken.
|
|
9
|
+
*
|
|
10
|
+
* What it costs is a render pass per level on every frame that takes the copy — about a dozen for a
|
|
11
|
+
* 1080p copy, each a quarter of the one before, together about a third of the copy itself — and
|
|
12
|
+
* nothing on a frame that shows nothing through anything. **A frame that refracts and draws no glass
|
|
13
|
+
* pays it too**, and that is the trade: the copy is taken at the first draw that asks and shared by
|
|
14
|
+
* every later one, so a glass pane drawn after a refracting one would otherwise read levels nothing
|
|
15
|
+
* filled. WebGL2 fills its chain on the first glass draw instead, because a `generateMipmap` there
|
|
16
|
+
* ends no pass. What would change it is knowing at the copy whether glass follows in the frame.
|
|
17
|
+
*/
|
|
18
|
+
import type { MipPipelines } from './surfaceTexturePass.ts';
|
|
19
|
+
export declare class SnapshotMips {
|
|
20
|
+
private readonly label;
|
|
21
|
+
private readonly pipeline;
|
|
22
|
+
private readonly targets;
|
|
23
|
+
private readonly groups;
|
|
24
|
+
constructor(device: GPUDevice, pipelines: MipPipelines, texture: GPUTexture, format: GPUTextureFormat,
|
|
25
|
+
/** One layer of an array texture, where the chain is a layer's rather than the texture's. */
|
|
26
|
+
layer?: number | null, label?: string);
|
|
27
|
+
/** Fill every level from level 0, in `encoder`, after whatever wrote level 0 there. */
|
|
28
|
+
record(encoder: GPUCommandEncoder): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mipBlitPipeline } from './surfaceTexturePass.js';
|
|
2
|
+
export class SnapshotMips {
|
|
3
|
+
label;
|
|
4
|
+
pipeline;
|
|
5
|
+
targets = [];
|
|
6
|
+
groups = [];
|
|
7
|
+
constructor(device, pipelines, texture, format,
|
|
8
|
+
/** One layer of an array texture, where the chain is a layer's rather than the texture's. */
|
|
9
|
+
layer = null, label = 'refract.mips') {
|
|
10
|
+
this.label = label;
|
|
11
|
+
this.pipeline = mipBlitPipeline(device, pipelines, format);
|
|
12
|
+
const sampler = device.createSampler({
|
|
13
|
+
label: 'refract.mips.sampler',
|
|
14
|
+
magFilter: 'linear',
|
|
15
|
+
minFilter: 'linear',
|
|
16
|
+
});
|
|
17
|
+
const layout = this.pipeline.getBindGroupLayout(0);
|
|
18
|
+
const at = layer === null ? {} : { dimension: '2d', baseArrayLayer: layer, arrayLayerCount: 1 };
|
|
19
|
+
for (let level = 1; level < texture.mipLevelCount; level++) {
|
|
20
|
+
const source = texture.createView({ ...at, baseMipLevel: level - 1, mipLevelCount: 1 });
|
|
21
|
+
this.targets.push(texture.createView({ ...at, baseMipLevel: level, mipLevelCount: 1 }));
|
|
22
|
+
this.groups.push(device.createBindGroup({
|
|
23
|
+
label: `refract.mips.${String(level)}`,
|
|
24
|
+
layout,
|
|
25
|
+
entries: [
|
|
26
|
+
{ binding: 0, resource: source },
|
|
27
|
+
{ binding: 1, resource: sampler },
|
|
28
|
+
],
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Fill every level from level 0, in `encoder`, after whatever wrote level 0 there. */
|
|
33
|
+
record(encoder) {
|
|
34
|
+
for (let i = 0; i < this.targets.length; i++) {
|
|
35
|
+
const pass = encoder.beginRenderPass({
|
|
36
|
+
label: this.label,
|
|
37
|
+
colorAttachments: [
|
|
38
|
+
{
|
|
39
|
+
view: this.targets[i],
|
|
40
|
+
loadOp: 'clear',
|
|
41
|
+
storeOp: 'store',
|
|
42
|
+
clearValue: [0, 0, 0, 0],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
pass.setPipeline(this.pipeline);
|
|
47
|
+
pass.setBindGroup(0, this.groups[i]);
|
|
48
|
+
pass.draw(3);
|
|
49
|
+
pass.end();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -29,8 +29,19 @@ export interface MipPipelines {
|
|
|
29
29
|
* drew it at a seventh of its authored size. Writing the blit twice would be two statements of one
|
|
30
30
|
* thing, and the second copy is the one that would quietly disagree about the format.
|
|
31
31
|
*/
|
|
32
|
-
export declare function generateMipChain(device: GPUDevice, pipelines: MipPipelines, texture: GPUTexture, format: GPUTextureFormat, levels: number
|
|
33
|
-
/**
|
|
32
|
+
export declare function generateMipChain(device: GPUDevice, pipelines: MipPipelines, texture: GPUTexture, format: GPUTextureFormat, levels: number,
|
|
33
|
+
/** How many array layers to fill, each its own chain. One for a plain texture. */
|
|
34
|
+
layers?: number): void;
|
|
35
|
+
/**
|
|
36
|
+
* Cached per format, because the two differ: an `-srgb` target encodes on write, and blitting a
|
|
37
|
+
* linear chain through a pipeline declared for the other one is a gamma error per level.
|
|
38
|
+
*/
|
|
39
|
+
export declare function mipBlitPipeline(device: GPUDevice, pipelines: MipPipelines, format: GPUTextureFormat): GPURenderPipeline;
|
|
40
|
+
/**
|
|
41
|
+
* An image uploaded once and bound per material — or several of one size, as an array's layers.
|
|
42
|
+
* Every surface texture is a `2d-array` view, a plain image an array of one; `surfaceTexture.ts`
|
|
43
|
+
* says why.
|
|
44
|
+
*/
|
|
34
45
|
export declare class GpuSurfaceTexture {
|
|
35
46
|
private readonly device;
|
|
36
47
|
private readonly pipelines;
|
|
@@ -42,7 +53,15 @@ export declare class GpuSurfaceTexture {
|
|
|
42
53
|
private levels;
|
|
43
54
|
private width;
|
|
44
55
|
private height;
|
|
45
|
-
|
|
56
|
+
/** How many images the array holds. */
|
|
57
|
+
readonly layers: number;
|
|
58
|
+
/** The per-layer effects table, or null for a texture given none. See `surfaceEffects.ts`. */
|
|
59
|
+
private effectsTable;
|
|
60
|
+
/** Its view, which the flat group binds beside this image when it is a material's albedo. */
|
|
61
|
+
effectsView: GPUTextureView | null;
|
|
62
|
+
constructor(device: GPUDevice, pipelines: PipelineCache, source: TexImageSource | readonly TexImageSource[], options?: SurfaceTextureOptions);
|
|
63
|
+
/** A view of every layer, as the shaders' `texture_2d_array` bindings read it. */
|
|
64
|
+
private arrayView;
|
|
46
65
|
/** The view every binding of this image holds. A new one when `update` changes the size. */
|
|
47
66
|
get view(): GPUTextureView;
|
|
48
67
|
/**
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from '../../surfaceEffects.js';
|
|
2
|
+
import { toHalfFloats } from '../../halfFloat.js';
|
|
3
|
+
import { isSourceList, layerSize, refuseArrayUpdate, sourceSize } from '../../textureSource.js';
|
|
1
4
|
import { shaderModule } from './shaderModules.js';
|
|
2
5
|
/**
|
|
3
6
|
* A caller's image on the device, and the mip chain WebGPU will not build for it.
|
|
@@ -65,7 +68,9 @@ export function mipLevelCount(width, height) {
|
|
|
65
68
|
* drew it at a seventh of its authored size. Writing the blit twice would be two statements of one
|
|
66
69
|
* thing, and the second copy is the one that would quietly disagree about the format.
|
|
67
70
|
*/
|
|
68
|
-
export function generateMipChain(device, pipelines, texture, format, levels
|
|
71
|
+
export function generateMipChain(device, pipelines, texture, format, levels,
|
|
72
|
+
/** How many array layers to fill, each its own chain. One for a plain texture. */
|
|
73
|
+
layers = 1) {
|
|
69
74
|
if (levels < 2)
|
|
70
75
|
return;
|
|
71
76
|
const pipeline = mipBlitPipeline(device, pipelines, format);
|
|
@@ -75,33 +80,37 @@ export function generateMipChain(device, pipelines, texture, format, levels) {
|
|
|
75
80
|
minFilter: 'linear',
|
|
76
81
|
});
|
|
77
82
|
const encoder = device.createCommandEncoder({ label: 'mip.chain' });
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
/* One level of one layer at a time: a render target is a single 2D subresource, and the source
|
|
84
|
+
view is one level of the same layer, so no pass reads what it writes. */
|
|
85
|
+
for (let layer = 0; layer < layers; layer++)
|
|
86
|
+
for (let level = 1; level < levels; level++) {
|
|
87
|
+
const one = { dimension: '2d', baseArrayLayer: layer, arrayLayerCount: 1 };
|
|
88
|
+
const source = texture.createView({ ...one, baseMipLevel: level - 1, mipLevelCount: 1 });
|
|
89
|
+
const target = texture.createView({ ...one, baseMipLevel: level, mipLevelCount: 1 });
|
|
90
|
+
const pass = encoder.beginRenderPass({
|
|
91
|
+
label: `mip.level${level}`,
|
|
92
|
+
colorAttachments: [
|
|
93
|
+
{ view: target, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
pass.setPipeline(pipeline);
|
|
97
|
+
pass.setBindGroup(0, device.createBindGroup({
|
|
98
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
99
|
+
entries: [
|
|
100
|
+
{ binding: 0, resource: source },
|
|
101
|
+
{ binding: 1, resource: sampler },
|
|
102
|
+
],
|
|
103
|
+
}));
|
|
104
|
+
pass.draw(3);
|
|
105
|
+
pass.end();
|
|
106
|
+
}
|
|
98
107
|
device.queue.submit([encoder.finish()]);
|
|
99
108
|
}
|
|
100
109
|
/**
|
|
101
110
|
* Cached per format, because the two differ: an `-srgb` target encodes on write, and blitting a
|
|
102
111
|
* linear chain through a pipeline declared for the other one is a gamma error per level.
|
|
103
112
|
*/
|
|
104
|
-
function mipBlitPipeline(device, pipelines, format) {
|
|
113
|
+
export function mipBlitPipeline(device, pipelines, format) {
|
|
105
114
|
return pipelines.get(`surface.mip:${format}`, () => ({
|
|
106
115
|
label: `surface.mip:${format}`,
|
|
107
116
|
layout: 'auto',
|
|
@@ -117,13 +126,11 @@ function mipBlitPipeline(device, pipelines, format) {
|
|
|
117
126
|
primitive: { topology: 'triangle-list' },
|
|
118
127
|
}));
|
|
119
128
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
/** An image uploaded once and bound per material. */
|
|
129
|
+
/**
|
|
130
|
+
* An image uploaded once and bound per material — or several of one size, as an array's layers.
|
|
131
|
+
* Every surface texture is a `2d-array` view, a plain image an array of one; `surfaceTexture.ts`
|
|
132
|
+
* says why.
|
|
133
|
+
*/
|
|
127
134
|
export class GpuSurfaceTexture {
|
|
128
135
|
device;
|
|
129
136
|
pipelines;
|
|
@@ -135,10 +142,18 @@ export class GpuSurfaceTexture {
|
|
|
135
142
|
levels;
|
|
136
143
|
width;
|
|
137
144
|
height;
|
|
145
|
+
/** How many images the array holds. */
|
|
146
|
+
layers;
|
|
147
|
+
/** The per-layer effects table, or null for a texture given none. See `surfaceEffects.ts`. */
|
|
148
|
+
effectsTable = null;
|
|
149
|
+
/** Its view, which the flat group binds beside this image when it is a material's albedo. */
|
|
150
|
+
effectsView = null;
|
|
138
151
|
constructor(device, pipelines, source, options = {}) {
|
|
139
152
|
this.device = device;
|
|
140
153
|
this.pipelines = pipelines;
|
|
141
|
-
const
|
|
154
|
+
const sources = isSourceList(source) ? source : [source];
|
|
155
|
+
const { width, height } = layerSize(sources);
|
|
156
|
+
this.layers = sources.length;
|
|
142
157
|
this.mipmapped = options.mipmap ?? true;
|
|
143
158
|
/* `SRGB8_ALPHA8`'s equivalent. Decoded in the sampler, before filtering, which is the only
|
|
144
159
|
place it is correct — `surfaceTexture.ts` makes the argument in full. */
|
|
@@ -147,7 +162,7 @@ export class GpuSurfaceTexture {
|
|
|
147
162
|
this.height = height;
|
|
148
163
|
this.levels = this.mipmapped ? mipLevelCount(width, height) : 1;
|
|
149
164
|
this.texture = this.allocate();
|
|
150
|
-
this.current = this.texture
|
|
165
|
+
this.current = this.arrayView(this.texture);
|
|
151
166
|
const wrap = (options.wrap ?? 'repeat') === 'repeat' ? 'repeat' : 'clamp-to-edge';
|
|
152
167
|
/*
|
|
153
168
|
* Nearest where the caller said their pixels are the subject. `mipmapFilter` stays as it was:
|
|
@@ -178,7 +193,25 @@ export class GpuSurfaceTexture {
|
|
|
178
193
|
? Math.max(1, Math.floor(options.anisotropy ?? DEFAULT_ANISOTROPY))
|
|
179
194
|
: 1,
|
|
180
195
|
});
|
|
181
|
-
this.upload(
|
|
196
|
+
this.upload(sources);
|
|
197
|
+
/* Half floats, as `renderer.ts` uploads it: filterable, so the ordinary float layout takes it,
|
|
198
|
+
and read by `textureLoad` alone, so the filter never runs. */
|
|
199
|
+
const effects = options.effects ?? [];
|
|
200
|
+
if (effects.length > 0) {
|
|
201
|
+
const table = device.createTexture({
|
|
202
|
+
label: 'surface.effects',
|
|
203
|
+
size: [SURFACE_EFFECT_TEXELS, effects.length],
|
|
204
|
+
format: 'rgba16float',
|
|
205
|
+
usage: 0x4 | 0x2, // TEXTURE_BINDING | COPY_DST
|
|
206
|
+
});
|
|
207
|
+
device.queue.writeTexture({ texture: table }, toHalfFloats(packSurfaceEffects(effects)), { bytesPerRow: SURFACE_EFFECT_TEXELS * 8, rowsPerImage: effects.length }, { width: SURFACE_EFFECT_TEXELS, height: effects.length, depthOrArrayLayers: 1 });
|
|
208
|
+
this.effectsTable = table;
|
|
209
|
+
this.effectsView = table.createView();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** A view of every layer, as the shaders' `texture_2d_array` bindings read it. */
|
|
213
|
+
arrayView(texture) {
|
|
214
|
+
return texture.createView({ dimension: '2d-array', arrayLayerCount: this.layers });
|
|
182
215
|
}
|
|
183
216
|
/** The view every binding of this image holds. A new one when `update` changes the size. */
|
|
184
217
|
get view() {
|
|
@@ -202,23 +235,24 @@ export class GpuSurfaceTexture {
|
|
|
202
235
|
const replaced = this.texture;
|
|
203
236
|
if (replaced === null)
|
|
204
237
|
return null;
|
|
238
|
+
refuseArrayUpdate(this.layers);
|
|
205
239
|
const { width, height } = sourceSize(source);
|
|
206
240
|
if (width === this.width && height === this.height) {
|
|
207
|
-
this.upload(source);
|
|
241
|
+
this.upload([source]);
|
|
208
242
|
return null;
|
|
209
243
|
}
|
|
210
244
|
this.width = width;
|
|
211
245
|
this.height = height;
|
|
212
246
|
this.levels = this.mipmapped ? mipLevelCount(width, height) : 1;
|
|
213
247
|
this.texture = this.allocate();
|
|
214
|
-
this.current = this.texture
|
|
215
|
-
this.upload(source);
|
|
248
|
+
this.current = this.arrayView(this.texture);
|
|
249
|
+
this.upload([source]);
|
|
216
250
|
return replaced;
|
|
217
251
|
}
|
|
218
252
|
allocate() {
|
|
219
253
|
return this.device.createTexture({
|
|
220
254
|
label: 'surface.texture',
|
|
221
|
-
size: [this.width, this.height],
|
|
255
|
+
size: [this.width, this.height, this.layers],
|
|
222
256
|
format: this.format,
|
|
223
257
|
mipLevelCount: this.levels,
|
|
224
258
|
/*
|
|
@@ -243,8 +277,11 @@ export class GpuSurfaceTexture {
|
|
|
243
277
|
return;
|
|
244
278
|
this.texture.destroy();
|
|
245
279
|
this.texture = null;
|
|
280
|
+
this.effectsTable?.destroy();
|
|
281
|
+
this.effectsTable = null;
|
|
282
|
+
this.effectsView = null;
|
|
246
283
|
}
|
|
247
|
-
upload(
|
|
284
|
+
upload(sources) {
|
|
248
285
|
const texture = this.texture;
|
|
249
286
|
if (texture === null)
|
|
250
287
|
return;
|
|
@@ -259,7 +296,9 @@ export class GpuSurfaceTexture {
|
|
|
259
296
|
* The orientation check that passed this backend was run on a canvas alone, where the two
|
|
260
297
|
* agreed. Measure both source types, or a texture pipeline is half tested.
|
|
261
298
|
*/
|
|
262
|
-
|
|
299
|
+
for (let layer = 0; layer < sources.length; layer++) {
|
|
300
|
+
this.device.queue.copyExternalImageToTexture({ source: sources[layer], flipY: false }, { texture, origin: [0, 0, layer] }, [this.width, this.height]);
|
|
301
|
+
}
|
|
263
302
|
if (this.levels > 1)
|
|
264
303
|
this.generateMips(texture);
|
|
265
304
|
}
|
|
@@ -271,6 +310,6 @@ export class GpuSurfaceTexture {
|
|
|
271
310
|
* an attachment and a resource at once — rejected, and rejected silently enough to matter.
|
|
272
311
|
*/
|
|
273
312
|
generateMips(texture) {
|
|
274
|
-
generateMipChain(this.device, this.pipelines, texture, this.format, this.levels);
|
|
313
|
+
generateMipChain(this.device, this.pipelines, texture, this.format, this.levels, this.layers);
|
|
275
314
|
}
|
|
276
315
|
}
|
|
@@ -32,3 +32,10 @@ export interface FieldLight extends LightReach {
|
|
|
32
32
|
* `distance` is the scene's distance field, or null for no occlusion.
|
|
33
33
|
*/
|
|
34
34
|
export declare function bakeBrick(layout: LightFieldLayout, brick: number, lights: readonly FieldLight[], falloff: PointLightFalloff, distance: DistanceAt | null, light: Float32Array, direction: Float32Array): void;
|
|
35
|
+
/**
|
|
36
|
+
* One sample: the light arriving at (px, py, pz) from `lights[candidates[first..last)]`, written at
|
|
37
|
+
* `texel` in `light` (colour, then 1 for a sample and 0 for one inside solid geometry) and in
|
|
38
|
+
* `direction` (the brightness-weighted way it arrives from). Shared by every bake, so a brick and a
|
|
39
|
+
* dense volume cannot come to disagree about what a sample is.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sampleFieldLight(px: number, py: number, pz: number, lights: readonly FieldLight[], candidates: ArrayLike<number>, first: number, last: number, falloff: PointLightFalloff, distance: DistanceAt | null, light: Float32Array, direction: Float32Array, texel: number): void;
|
|
@@ -33,63 +33,68 @@ export function bakeBrick(layout, brick, lights, falloff, distance, light, direc
|
|
|
33
33
|
for (let k = 0; k < BRICK_SAMPLES; k++) {
|
|
34
34
|
for (let j = 0; j < BRICK_SAMPLES; j++) {
|
|
35
35
|
for (let i = 0; i < BRICK_SAMPLES; i++) {
|
|
36
|
-
|
|
37
|
-
const px = bx + i * spacing;
|
|
38
|
-
const py = by + j * spacing;
|
|
39
|
-
const pz = bz + k * spacing;
|
|
40
|
-
light.fill(0, texel, texel + 4);
|
|
41
|
-
direction.fill(0, texel, texel + 4);
|
|
42
|
-
if (distance !== null && distance(px, py, pz) < 0)
|
|
43
|
-
continue;
|
|
44
|
-
let r = 0;
|
|
45
|
-
let g = 0;
|
|
46
|
-
let b = 0;
|
|
47
|
-
let towardX = 0;
|
|
48
|
-
let towardY = 0;
|
|
49
|
-
let towardZ = 0;
|
|
50
|
-
let weight = 0;
|
|
51
|
-
for (let listed = first; listed < last; listed++) {
|
|
52
|
-
const source = lights[layout.lights[listed]];
|
|
53
|
-
if (source === undefined)
|
|
54
|
-
continue;
|
|
55
|
-
const dx = source.x - px;
|
|
56
|
-
const dy = source.y - py;
|
|
57
|
-
const dz = source.z - pz;
|
|
58
|
-
const dist = Math.hypot(dx, dy, dz);
|
|
59
|
-
if (dist >= source.radius)
|
|
60
|
-
continue;
|
|
61
|
-
const shape = pointLightShape(dist, source.radius, falloff);
|
|
62
|
-
if (shape <= 0)
|
|
63
|
-
continue;
|
|
64
|
-
const seen = distance === null
|
|
65
|
-
? 1
|
|
66
|
-
: softVisibility(distance, px, py, pz, source.x, source.y, source.z, source.sourceRadius);
|
|
67
|
-
if (seen <= 0)
|
|
68
|
-
continue;
|
|
69
|
-
const lr = source.r * shape * seen;
|
|
70
|
-
const lg = source.g * shape * seen;
|
|
71
|
-
const lb = source.b * shape * seen;
|
|
72
|
-
r += lr;
|
|
73
|
-
g += lg;
|
|
74
|
-
b += lb;
|
|
75
|
-
const bright = 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
|
|
76
|
-
if (dist > 1e-6 && bright > 0) {
|
|
77
|
-
towardX += (bright * dx) / dist;
|
|
78
|
-
towardY += (bright * dy) / dist;
|
|
79
|
-
towardZ += (bright * dz) / dist;
|
|
80
|
-
weight += bright;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
light[texel] = r;
|
|
84
|
-
light[texel + 1] = g;
|
|
85
|
-
light[texel + 2] = b;
|
|
86
|
-
light[texel + 3] = 1;
|
|
87
|
-
if (weight > 0) {
|
|
88
|
-
direction[texel] = towardX / weight;
|
|
89
|
-
direction[texel + 1] = towardY / weight;
|
|
90
|
-
direction[texel + 2] = towardZ / weight;
|
|
91
|
-
}
|
|
36
|
+
sampleFieldLight(bx + i * spacing, by + j * spacing, bz + k * spacing, lights, layout.lights, first, last, falloff, distance, light, direction, (i + BRICK_SAMPLES * (j + BRICK_SAMPLES * k)) * 4);
|
|
92
37
|
}
|
|
93
38
|
}
|
|
94
39
|
}
|
|
95
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* One sample: the light arriving at (px, py, pz) from `lights[candidates[first..last)]`, written at
|
|
43
|
+
* `texel` in `light` (colour, then 1 for a sample and 0 for one inside solid geometry) and in
|
|
44
|
+
* `direction` (the brightness-weighted way it arrives from). Shared by every bake, so a brick and a
|
|
45
|
+
* dense volume cannot come to disagree about what a sample is.
|
|
46
|
+
*/
|
|
47
|
+
export function sampleFieldLight(px, py, pz, lights, candidates, first, last, falloff, distance, light, direction, texel) {
|
|
48
|
+
light.fill(0, texel, texel + 4);
|
|
49
|
+
direction.fill(0, texel, texel + 4);
|
|
50
|
+
if (distance !== null && distance(px, py, pz) < 0)
|
|
51
|
+
return;
|
|
52
|
+
let r = 0;
|
|
53
|
+
let g = 0;
|
|
54
|
+
let b = 0;
|
|
55
|
+
let towardX = 0;
|
|
56
|
+
let towardY = 0;
|
|
57
|
+
let towardZ = 0;
|
|
58
|
+
let weight = 0;
|
|
59
|
+
for (let listed = first; listed < last; listed++) {
|
|
60
|
+
const source = lights[candidates[listed]];
|
|
61
|
+
if (source === undefined)
|
|
62
|
+
continue;
|
|
63
|
+
const dx = source.x - px;
|
|
64
|
+
const dy = source.y - py;
|
|
65
|
+
const dz = source.z - pz;
|
|
66
|
+
const dist = Math.hypot(dx, dy, dz);
|
|
67
|
+
if (dist >= source.radius)
|
|
68
|
+
continue;
|
|
69
|
+
const shape = pointLightShape(dist, source.radius, falloff);
|
|
70
|
+
if (shape <= 0)
|
|
71
|
+
continue;
|
|
72
|
+
const seen = distance === null
|
|
73
|
+
? 1
|
|
74
|
+
: softVisibility(distance, px, py, pz, source.x, source.y, source.z, source.sourceRadius);
|
|
75
|
+
if (seen <= 0)
|
|
76
|
+
continue;
|
|
77
|
+
const lr = source.r * shape * seen;
|
|
78
|
+
const lg = source.g * shape * seen;
|
|
79
|
+
const lb = source.b * shape * seen;
|
|
80
|
+
r += lr;
|
|
81
|
+
g += lg;
|
|
82
|
+
b += lb;
|
|
83
|
+
const bright = 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
|
|
84
|
+
if (dist > 1e-6 && bright > 0) {
|
|
85
|
+
towardX += (bright * dx) / dist;
|
|
86
|
+
towardY += (bright * dy) / dist;
|
|
87
|
+
towardZ += (bright * dz) / dist;
|
|
88
|
+
weight += bright;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
light[texel] = r;
|
|
92
|
+
light[texel + 1] = g;
|
|
93
|
+
light[texel + 2] = b;
|
|
94
|
+
light[texel + 3] = 1;
|
|
95
|
+
if (weight > 0) {
|
|
96
|
+
direction[texel] = towardX / weight;
|
|
97
|
+
direction[texel + 1] = towardY / weight;
|
|
98
|
+
direction[texel + 2] = towardZ / weight;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DriftLight for a whole world: every fixed light summed into one dense grid, baked offline.
|
|
3
|
+
*
|
|
4
|
+
* **Dense, where the courtyard field is sparse, because a city is lit nearly everywhere.** A brick
|
|
5
|
+
* earns its index and its shared faces where light is rare; street lamps reaching thirty metres
|
|
6
|
+
* forty metres apart leave almost no cell dark, so the sparseness buys nothing and the shared
|
|
7
|
+
* faces cost 2.4× the samples. At 8 m a sample, a two-kilometre city 128 m tall is 256 × 16 × 256
|
|
8
|
+
* samples, two half-float texels each: 16 MB on the GPU, and the renderer's lookup is one filtered
|
|
9
|
+
* fetch for the light and one for its direction.
|
|
10
|
+
*
|
|
11
|
+
* **The same sample as every other bake.** `sampleFieldLight` is shared with `bakeBrick`, so a dense
|
|
12
|
+
* volume and a brick agree bit for bit wherever both have a sample. The order a sample's lights are
|
|
13
|
+
* summed in does not have to match for that: the sum runs in double precision and is stored in
|
|
14
|
+
* single, which the last bits of a few terms in another order do not reach.
|
|
15
|
+
*
|
|
16
|
+
* **Lights are bucketed first**, four samples a bucket on every axis, so a sample sums only the
|
|
17
|
+
* lights whose reach touches its bucket rather than all of them: a city of ten thousand lamps is a
|
|
18
|
+
* few dozen candidates a sample. Offline and allocation-free per sample; it may take seconds.
|
|
19
|
+
*
|
|
20
|
+
* What it gives up is resolution: at 8 m a lamp's pool is a few samples across, which is why this
|
|
21
|
+
* stands in only past the exact lights, where a pool is a few dozen pixels.
|
|
22
|
+
*/
|
|
23
|
+
import type { FieldLight } from './bake.ts';
|
|
24
|
+
import type { PointLightFalloff } from './falloff.ts';
|
|
25
|
+
import type { DistanceAt } from './visibility.ts';
|
|
26
|
+
/** The most samples along x or z: WebGL2 promises 256 on every axis of a 3D texture. */
|
|
27
|
+
export declare const DENSE_MAX_AXIS = 256;
|
|
28
|
+
/** The most along y, which holds the light and then the direction: two layers in 256. */
|
|
29
|
+
export declare const DENSE_MAX_HEIGHT = 128;
|
|
30
|
+
export interface DenseLightVolume {
|
|
31
|
+
/** The world position of sample (0, 0, 0). */
|
|
32
|
+
readonly origin: readonly [number, number, number];
|
|
33
|
+
/** Metres between two samples. */
|
|
34
|
+
readonly spacing: number;
|
|
35
|
+
/** Samples on each axis. */
|
|
36
|
+
readonly dims: readonly [number, number, number];
|
|
37
|
+
/** Four floats a sample, x fastest then y then z: the colour, then 1 for a sample and 0 inside. */
|
|
38
|
+
readonly light: Float32Array;
|
|
39
|
+
/** Four floats a sample: the direction light arrives from, weighted by brightness, then 0. */
|
|
40
|
+
readonly direction: Float32Array;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sum `lights` over the box `bounds` (min xyz, max xyz) at `spacing` metres. `distance` occludes, or
|
|
44
|
+
* null for none. The first sample stands on the box's min corner.
|
|
45
|
+
*/
|
|
46
|
+
export declare function bakeDenseField(lights: readonly FieldLight[], falloff: PointLightFalloff, distance: DistanceAt | null, bounds: ArrayLike<number>, spacing: number): DenseLightVolume;
|