@driftengine/core 4.4.2 → 4.5.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 +46 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +79 -8
- package/dist/render/backend/webgl2/renderer.js +360 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -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/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -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 +2 -12
- package/dist/render/backend/webgpu/renderer.d.ts +171 -54
- package/dist/render/backend/webgpu/renderer.js +944 -390
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- 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 +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- 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/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- 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/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -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/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +7 -1
- package/dist/render/sceneTarget.js +58 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- 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 +4 -0
- 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 +115 -15
- 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 +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- 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 +4 -14
- 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/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 +44 -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/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/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +401 -108
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -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 +1108 -421
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- 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 +1 -1
- 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/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +58 -11
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- 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 +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/translucentQueue.ts +20 -1
|
@@ -16,7 +16,25 @@ export declare const OCTAHEDRAL_EDGE = 1024;
|
|
|
16
16
|
export interface PointShadowTarget {
|
|
17
17
|
beginFace(gl: WebGL2RenderingContext): void;
|
|
18
18
|
resolveFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
|
|
19
|
+
/** Whether the array has grown its glass layers: `growForGlass`. */
|
|
20
|
+
readonly hasGlass: boolean;
|
|
21
|
+
/** Whether `face` of `layer` holds glass, which a face that offers none must clear. */
|
|
22
|
+
heldGlass(layer: number, face: number): boolean;
|
|
23
|
+
/** The depth scratch again, cleared, for the face's glass. */
|
|
24
|
+
beginGlassFace(gl: WebGL2RenderingContext): void;
|
|
25
|
+
resolveGlassFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
|
|
26
|
+
/** The colour scratch, cleared to white, for what the face's glass lets through. */
|
|
27
|
+
beginTintFace(gl: WebGL2RenderingContext): void;
|
|
28
|
+
resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
|
|
19
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
|
|
32
|
+
*
|
|
33
|
+
* **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
|
|
34
|
+
* index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
|
|
35
|
+
* 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
|
|
36
|
+
*/
|
|
37
|
+
export declare function strideLayers(layers: Int32Array, stride: number): void;
|
|
20
38
|
/**
|
|
21
39
|
* Every point light's shadow as one array texture, and the scratch that fills it.
|
|
22
40
|
*
|
|
@@ -34,12 +52,16 @@ export interface PointShadowTarget {
|
|
|
34
52
|
* never the same object.
|
|
35
53
|
*/
|
|
36
54
|
export declare class PointShadowArray implements PointShadowTarget {
|
|
37
|
-
readonly texture: WebGLTexture;
|
|
38
55
|
/**
|
|
39
|
-
* How many
|
|
40
|
-
* for each rectangle a world said would cast. See the constructor's `extraLayers`.
|
|
56
|
+
* How many lights this array holds: the point pool, the two live transition maps, and two more
|
|
57
|
+
* for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
|
|
58
|
+
* lights, so it does not move when glass doubles the storage under it.
|
|
41
59
|
*/
|
|
42
60
|
readonly layers: number;
|
|
61
|
+
/** Replaced once, when glass first casts: see `growForGlass`. */
|
|
62
|
+
private current;
|
|
63
|
+
/** What the lights' glass lets through, made with the glass layers. */
|
|
64
|
+
private tint;
|
|
43
65
|
private readonly framebuffer;
|
|
44
66
|
private readonly scratchTexture;
|
|
45
67
|
private readonly scratchFramebuffer;
|
|
@@ -62,6 +84,35 @@ export declare class PointShadowArray implements PointShadowTarget {
|
|
|
62
84
|
* map.
|
|
63
85
|
*/
|
|
64
86
|
extraLayers?: number);
|
|
87
|
+
/** The array texture the lit pass samples. Replaced once, when glass first casts. */
|
|
88
|
+
get texture(): WebGLTexture;
|
|
89
|
+
get hasGlass(): boolean;
|
|
90
|
+
/** Physical layers a light: 1, or 2 once glass has interleaved the array. */
|
|
91
|
+
get stride(): number;
|
|
92
|
+
/** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
|
|
93
|
+
get tintTexture(): WebGLTexture | null;
|
|
94
|
+
/**
|
|
95
|
+
* Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
|
|
96
|
+
* the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
|
|
97
|
+
*
|
|
98
|
+
* **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
|
|
99
|
+
* its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
|
|
100
|
+
* baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
|
|
101
|
+
* again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
|
|
102
|
+
* glass layer born at the far plane with a white tint is exactly what its bake would draw. What
|
|
103
|
+
* it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
|
|
104
|
+
* chain, 1.4 MB at `'half'`), from then on.
|
|
105
|
+
*/
|
|
106
|
+
growForGlass(gl: WebGL2RenderingContext, tintEdge: number): boolean;
|
|
107
|
+
heldGlass(layer: number, face: number): boolean;
|
|
108
|
+
/** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
|
|
109
|
+
beginGlassFace(gl: WebGL2RenderingContext): void;
|
|
110
|
+
/** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
|
|
111
|
+
resolveGlassFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
|
|
112
|
+
beginTintFace(gl: WebGL2RenderingContext): void;
|
|
113
|
+
resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
|
|
114
|
+
/** The chain frost reads, for every light whose tint a round touched: once a round. */
|
|
115
|
+
finishTints(gl: WebGL2RenderingContext): void;
|
|
65
116
|
/**
|
|
66
117
|
* Bind the scratch as the render target for one face of one bake, and clear it.
|
|
67
118
|
*
|
|
@@ -91,5 +142,7 @@ export declare class PointShadowArray implements PointShadowTarget {
|
|
|
91
142
|
* from an empty vertex array.
|
|
92
143
|
*/
|
|
93
144
|
resolveFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
|
|
145
|
+
/** The scratch into physical layer `physical`'s region for `face`. */
|
|
146
|
+
private resolveInto;
|
|
94
147
|
dispose(gl: WebGL2RenderingContext): void;
|
|
95
148
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mat3 } from 'gl-matrix';
|
|
2
2
|
import { createEmptyTexture2D } from './emptyTexture.js';
|
|
3
3
|
import { LIVE_POINT_SHADOW_MAPS, POINT_SHADOW_POOL } from './lightBudget.js';
|
|
4
|
+
import { PointGlassTint } from './pointGlassTint.js';
|
|
4
5
|
import { pointShadowFaceRotation } from './pointShadowImage.js';
|
|
5
6
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
6
7
|
import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octahedralResolve.js';
|
|
@@ -15,6 +16,22 @@ import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octa
|
|
|
15
16
|
export const OCTAHEDRAL_EDGE = 1024;
|
|
16
17
|
/** Filled per resolved face, at module scope, because a bake runs inside the frame's budget. */
|
|
17
18
|
const faceRotation = mat3.create();
|
|
19
|
+
/**
|
|
20
|
+
* Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
|
|
21
|
+
*
|
|
22
|
+
* **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
|
|
23
|
+
* index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
|
|
24
|
+
* 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
|
|
25
|
+
*/
|
|
26
|
+
export function strideLayers(layers, stride) {
|
|
27
|
+
if (stride === 1)
|
|
28
|
+
return;
|
|
29
|
+
for (let i = 0; i < layers.length; i++) {
|
|
30
|
+
const layer = layers[i];
|
|
31
|
+
if (layer >= 0)
|
|
32
|
+
layers[i] = layer * stride;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
18
35
|
/**
|
|
19
36
|
* Every point light's shadow as one array texture, and the scratch that fills it.
|
|
20
37
|
*
|
|
@@ -32,12 +49,16 @@ const faceRotation = mat3.create();
|
|
|
32
49
|
* never the same object.
|
|
33
50
|
*/
|
|
34
51
|
export class PointShadowArray {
|
|
35
|
-
texture;
|
|
36
52
|
/**
|
|
37
|
-
* How many
|
|
38
|
-
* for each rectangle a world said would cast. See the constructor's `extraLayers`.
|
|
53
|
+
* How many lights this array holds: the point pool, the two live transition maps, and two more
|
|
54
|
+
* for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
|
|
55
|
+
* lights, so it does not move when glass doubles the storage under it.
|
|
39
56
|
*/
|
|
40
57
|
layers;
|
|
58
|
+
/** Replaced once, when glass first casts: see `growForGlass`. */
|
|
59
|
+
current;
|
|
60
|
+
/** What the lights' glass lets through, made with the glass layers. */
|
|
61
|
+
tint = null;
|
|
41
62
|
framebuffer;
|
|
42
63
|
scratchTexture;
|
|
43
64
|
scratchFramebuffer;
|
|
@@ -68,17 +89,8 @@ export class PointShadowArray {
|
|
|
68
89
|
*/
|
|
69
90
|
this.layers =
|
|
70
91
|
Math.min(lightCount, POINT_SHADOW_POOL) + LIVE_POINT_SHADOW_MAPS + Math.max(0, extraLayers);
|
|
71
|
-
const texture = gl.
|
|
72
|
-
|
|
73
|
-
throw new Error('PointShadowArray: createTexture failed');
|
|
74
|
-
this.texture = texture;
|
|
75
|
-
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
76
|
-
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.DEPTH_COMPONENT24, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, this.layers);
|
|
77
|
-
/* One storage level and no filtering, so textureLod(..., 0.0) is the only fetch there is. */
|
|
78
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
79
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
80
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
81
|
-
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
92
|
+
const texture = depthArray(gl, this.layers);
|
|
93
|
+
this.current = texture;
|
|
82
94
|
const scratchTexture = gl.createTexture();
|
|
83
95
|
if (scratchTexture === null)
|
|
84
96
|
throw new Error('PointShadowArray: createTexture failed');
|
|
@@ -141,6 +153,86 @@ export class PointShadowArray {
|
|
|
141
153
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
142
154
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
143
155
|
}
|
|
156
|
+
/** The array texture the lit pass samples. Replaced once, when glass first casts. */
|
|
157
|
+
get texture() {
|
|
158
|
+
return this.current;
|
|
159
|
+
}
|
|
160
|
+
get hasGlass() {
|
|
161
|
+
return this.tint !== null;
|
|
162
|
+
}
|
|
163
|
+
/** Physical layers a light: 1, or 2 once glass has interleaved the array. */
|
|
164
|
+
get stride() {
|
|
165
|
+
return this.tint === null ? 1 : 2;
|
|
166
|
+
}
|
|
167
|
+
/** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
|
|
168
|
+
get tintTexture() {
|
|
169
|
+
return this.tint?.texture ?? null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
|
|
173
|
+
* the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
|
|
174
|
+
*
|
|
175
|
+
* **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
|
|
176
|
+
* its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
|
|
177
|
+
* baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
|
|
178
|
+
* again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
|
|
179
|
+
* glass layer born at the far plane with a white tint is exactly what its bake would draw. What
|
|
180
|
+
* it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
|
|
181
|
+
* chain, 1.4 MB at `'half'`), from then on.
|
|
182
|
+
*/
|
|
183
|
+
growForGlass(gl, tintEdge) {
|
|
184
|
+
if (this.tint !== null)
|
|
185
|
+
return false;
|
|
186
|
+
const old = this.current;
|
|
187
|
+
const texture = depthArray(gl, this.layers * 2);
|
|
188
|
+
/* Off the unit it was made on, as the constructor leaves its own: see `beginFace`. */
|
|
189
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
190
|
+
const read = gl.createFramebuffer();
|
|
191
|
+
if (read === null)
|
|
192
|
+
throw new Error('PointShadowArray: createFramebuffer failed');
|
|
193
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
|
|
194
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
|
|
195
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
196
|
+
gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, old, 0, layer);
|
|
197
|
+
gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer);
|
|
198
|
+
gl.blitFramebuffer(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, 0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
|
|
199
|
+
}
|
|
200
|
+
/* Nothing is nearer a lamp than a pane that has not been drawn. */
|
|
201
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
202
|
+
gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer + 1);
|
|
203
|
+
gl.clearBufferfv(gl.DEPTH, 0, FAR_PLANE);
|
|
204
|
+
}
|
|
205
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
|
|
206
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
207
|
+
gl.deleteFramebuffer(read);
|
|
208
|
+
gl.deleteTexture(old);
|
|
209
|
+
this.current = texture;
|
|
210
|
+
this.tint = new PointGlassTint(gl, this.faceSize, tintEdge, this.layers);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
heldGlass(layer, face) {
|
|
214
|
+
return this.tint?.held(layer, face) ?? false;
|
|
215
|
+
}
|
|
216
|
+
/** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
|
|
217
|
+
beginGlassFace(gl) {
|
|
218
|
+
this.beginFace(gl);
|
|
219
|
+
}
|
|
220
|
+
/** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
|
|
221
|
+
resolveGlassFace(gl, layer, face, far, near) {
|
|
222
|
+
if (this.tint === null)
|
|
223
|
+
return;
|
|
224
|
+
this.resolveInto(gl, 2 * layer + 1, face, far, near);
|
|
225
|
+
}
|
|
226
|
+
beginTintFace(gl) {
|
|
227
|
+
this.tint?.beginFace(gl);
|
|
228
|
+
}
|
|
229
|
+
resolveTintFace(gl, layer, face, drawn) {
|
|
230
|
+
this.tint?.resolveFace(gl, layer, face, drawn);
|
|
231
|
+
}
|
|
232
|
+
/** The chain frost reads, for every light whose tint a round touched: once a round. */
|
|
233
|
+
finishTints(gl) {
|
|
234
|
+
this.tint?.fillMips(gl);
|
|
235
|
+
}
|
|
144
236
|
/**
|
|
145
237
|
* Bind the scratch as the render target for one face of one bake, and clear it.
|
|
146
238
|
*
|
|
@@ -176,8 +268,12 @@ export class PointShadowArray {
|
|
|
176
268
|
* from an empty vertex array.
|
|
177
269
|
*/
|
|
178
270
|
resolveFace(gl, layer, face, far, near) {
|
|
271
|
+
this.resolveInto(gl, layer * this.stride, face, far, near);
|
|
272
|
+
}
|
|
273
|
+
/** The scratch into physical layer `physical`'s region for `face`. */
|
|
274
|
+
resolveInto(gl, physical, face, far, near) {
|
|
179
275
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
180
|
-
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.
|
|
276
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.current, 0, physical);
|
|
181
277
|
gl.viewport(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE);
|
|
182
278
|
gl.useProgram(this.program);
|
|
183
279
|
gl.bindVertexArray(this.vao);
|
|
@@ -210,10 +306,25 @@ export class PointShadowArray {
|
|
|
210
306
|
dispose(gl) {
|
|
211
307
|
gl.deleteFramebuffer(this.framebuffer);
|
|
212
308
|
gl.deleteFramebuffer(this.scratchFramebuffer);
|
|
213
|
-
gl.deleteTexture(this.
|
|
309
|
+
gl.deleteTexture(this.current);
|
|
214
310
|
gl.deleteTexture(this.scratchTexture);
|
|
311
|
+
this.tint?.dispose(gl);
|
|
215
312
|
gl.deleteTexture(this.parked);
|
|
216
313
|
gl.deleteProgram(this.program);
|
|
217
314
|
gl.deleteVertexArray(this.vao);
|
|
218
315
|
}
|
|
219
316
|
}
|
|
317
|
+
const FAR_PLANE = new Float32Array([1]);
|
|
318
|
+
/** The depth array: one storage level and no filtering, so textureLod(..., 0.0) is the only fetch. */
|
|
319
|
+
function depthArray(gl, layers) {
|
|
320
|
+
const texture = gl.createTexture();
|
|
321
|
+
if (texture === null)
|
|
322
|
+
throw new Error('PointShadowArray: createTexture failed');
|
|
323
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
324
|
+
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.DEPTH_COMPONENT24, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, layers);
|
|
325
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
326
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
327
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
328
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
329
|
+
return texture;
|
|
330
|
+
}
|
|
@@ -21,6 +21,18 @@ import { FACE_COUNT, POINT_SHADOW_NEAR } from './pointShadowImage.ts';
|
|
|
21
21
|
* projection the shader reads. Nothing about the bake's cost, order or resumption moved.
|
|
22
22
|
*/
|
|
23
23
|
export { FACE_COUNT, POINT_SHADOW_NEAR };
|
|
24
|
+
/**
|
|
25
|
+
* What a bake asks of the renderer about glass, after each face's opaque depth.
|
|
26
|
+
*
|
|
27
|
+
* **Asked before it is drawn** because the answer decides whether the face binds a glass target at
|
|
28
|
+
* all: a face that offers no glass and holds none from a previous owner does no glass work.
|
|
29
|
+
*/
|
|
30
|
+
export interface PointGlassDraw {
|
|
31
|
+
/** Whether the face just drawn offered any glass. */
|
|
32
|
+
offered(): boolean;
|
|
33
|
+
/** Draw that glass: its depth, or what it lets through lit from the bake origin. */
|
|
34
|
+
draw(pass: 'depth' | 'tint', x: number, y: number, z: number): void;
|
|
35
|
+
}
|
|
24
36
|
export declare class PointShadowMap {
|
|
25
37
|
readonly size: number;
|
|
26
38
|
/**
|
|
@@ -85,7 +97,9 @@ export declare class PointShadowMap {
|
|
|
85
97
|
* bake is not sampled at all until it completes, so this only matters for a re-bake,
|
|
86
98
|
* where holding the old values keeps them matched to the old faces.
|
|
87
99
|
*/
|
|
88
|
-
bake(gl: WebGL2RenderingContext, x: number, y: number, z: number, range: number, drawCasters: (viewProj: mat4) => void, near?: number, sourceRadius?: number, maxFaces?: number
|
|
100
|
+
bake(gl: WebGL2RenderingContext, x: number, y: number, z: number, range: number, drawCasters: (viewProj: mat4) => void, near?: number, sourceRadius?: number, maxFaces?: number,
|
|
101
|
+
/** The face's glass, when glass shadows are on: `PointGlassDraw`. */
|
|
102
|
+
glass?: PointGlassDraw | null): number;
|
|
89
103
|
/**
|
|
90
104
|
* Nothing to free: every GPU object this map used to own belongs to `PointShadowArray` now.
|
|
91
105
|
*
|
|
@@ -93,5 +107,13 @@ export declare class PointShadowMap {
|
|
|
93
107
|
* needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
|
|
94
108
|
* renderer that built it.
|
|
95
109
|
*/
|
|
110
|
+
/**
|
|
111
|
+
* The face's glass, twice: where the nearest pane is, then what the panes let through.
|
|
112
|
+
*
|
|
113
|
+
* A face that offers no glass still clears a region a previous owner's glass is in — the depth to
|
|
114
|
+
* the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
|
|
115
|
+
* slot changes hands with its layer.
|
|
116
|
+
*/
|
|
117
|
+
private bakeGlassFace;
|
|
96
118
|
dispose(_gl: WebGL2RenderingContext): void;
|
|
97
119
|
}
|
|
@@ -113,7 +113,9 @@ export class PointShadowMap {
|
|
|
113
113
|
* bake is not sampled at all until it completes, so this only matters for a re-bake,
|
|
114
114
|
* where holding the old values keeps them matched to the old faces.
|
|
115
115
|
*/
|
|
116
|
-
bake(gl, x, y, z, range, drawCasters, near = POINT_SHADOW_NEAR, sourceRadius = DEFAULT_SOURCE_RADIUS, maxFaces = FACE_COUNT
|
|
116
|
+
bake(gl, x, y, z, range, drawCasters, near = POINT_SHADOW_NEAR, sourceRadius = DEFAULT_SOURCE_RADIUS, maxFaces = FACE_COUNT,
|
|
117
|
+
/** The face's glass, when glass shadows are on: `PointGlassDraw`. */
|
|
118
|
+
glass = null) {
|
|
117
119
|
/*
|
|
118
120
|
* What to render now, **and what to render it for**. A bake in flight owns the parameters
|
|
119
121
|
* it started with, so the arguments here are only a proposal for a bake that is not already
|
|
@@ -159,6 +161,8 @@ export class PointShadowMap {
|
|
|
159
161
|
* next face's pass does care about.
|
|
160
162
|
*/
|
|
161
163
|
array.resolveFace(gl, this.layer, face, plan.range, plan.near);
|
|
164
|
+
if (glass !== null)
|
|
165
|
+
this.bakeGlassFace(gl, array, glass, face, plan);
|
|
162
166
|
}
|
|
163
167
|
gl.enable(gl.CULL_FACE);
|
|
164
168
|
gl.cullFace(gl.BACK);
|
|
@@ -173,5 +177,27 @@ export class PointShadowMap {
|
|
|
173
177
|
* needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
|
|
174
178
|
* renderer that built it.
|
|
175
179
|
*/
|
|
180
|
+
/**
|
|
181
|
+
* The face's glass, twice: where the nearest pane is, then what the panes let through.
|
|
182
|
+
*
|
|
183
|
+
* A face that offers no glass still clears a region a previous owner's glass is in — the depth to
|
|
184
|
+
* the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
|
|
185
|
+
* slot changes hands with its layer.
|
|
186
|
+
*/
|
|
187
|
+
bakeGlassFace(gl, array, glass, face, plan) {
|
|
188
|
+
if (!array.hasGlass)
|
|
189
|
+
return;
|
|
190
|
+
const offered = glass.offered();
|
|
191
|
+
if (!offered && !array.heldGlass(this.layer, face))
|
|
192
|
+
return;
|
|
193
|
+
array.beginGlassFace(gl);
|
|
194
|
+
if (offered)
|
|
195
|
+
glass.draw('depth', plan.x, plan.y, plan.z);
|
|
196
|
+
array.resolveGlassFace(gl, this.layer, face, plan.range, plan.near);
|
|
197
|
+
array.beginTintFace(gl);
|
|
198
|
+
if (offered)
|
|
199
|
+
glass.draw('tint', plan.x, plan.y, plan.z);
|
|
200
|
+
array.resolveTintFace(gl, this.layer, face, offered);
|
|
201
|
+
}
|
|
176
202
|
dispose(_gl) { }
|
|
177
203
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which frame a change to a dynamic mesh or an instanced batch belongs to.
|
|
3
|
+
*
|
|
4
|
+
* **A consumer may change geometry in its simulation step, before `beginFrame`.** That change is
|
|
5
|
+
* what the *next* frame draws, so it is that frame's — counting it against the frame last begun
|
|
6
|
+
* would say the geometry changed a frame early and then did not change at all, which is a motion
|
|
7
|
+
* vector on the wrong frame and none on the right one.
|
|
8
|
+
*
|
|
9
|
+
* Only the **first** change in a frame keeps the state before it: a second rewrite in the same
|
|
10
|
+
* frame must not replace "where it was last frame" with "where it was a moment ago".
|
|
11
|
+
*/
|
|
12
|
+
export declare function frameOfChange(serial: number, inFrame: boolean): number;
|
|
13
|
+
export declare function keepsPrevious(lastChange: number, frame: number): boolean;
|
|
14
|
+
/** Slots `0 … n-1` pair with last frame's; a slot past last frame's count is new and has no past. */
|
|
15
|
+
export declare function pairedInstances(previousCount: number, count: number): number;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which frame a change to a dynamic mesh or an instanced batch belongs to.
|
|
3
|
+
*
|
|
4
|
+
* **A consumer may change geometry in its simulation step, before `beginFrame`.** That change is
|
|
5
|
+
* what the *next* frame draws, so it is that frame's — counting it against the frame last begun
|
|
6
|
+
* would say the geometry changed a frame early and then did not change at all, which is a motion
|
|
7
|
+
* vector on the wrong frame and none on the right one.
|
|
8
|
+
*
|
|
9
|
+
* Only the **first** change in a frame keeps the state before it: a second rewrite in the same
|
|
10
|
+
* frame must not replace "where it was last frame" with "where it was a moment ago".
|
|
11
|
+
*/
|
|
12
|
+
export function frameOfChange(serial, inFrame) {
|
|
13
|
+
return inFrame ? serial : serial + 1;
|
|
14
|
+
}
|
|
15
|
+
export function keepsPrevious(lastChange, frame) {
|
|
16
|
+
return lastChange !== frame;
|
|
17
|
+
}
|
|
18
|
+
/** Slots `0 … n-1` pair with last frame's; a slot past last frame's count is new and has no past. */
|
|
19
|
+
export function pairedInstances(previousCount, count) {
|
|
20
|
+
return Math.min(previousCount, count);
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two decisions about one draw in a reconstructing frame, and why they are the same set.
|
|
3
|
+
*
|
|
4
|
+
* A blended draw goes **late** — after the reconstruction, at output resolution, unjittered — and a
|
|
5
|
+
* mover's motion is **tracked**, only for draws that land in the picture being reconstructed. A
|
|
6
|
+
* planar mirror and a probe bake are other pictures drawn through the same verbs, and the overlay
|
|
7
|
+
* after `endFrame` is past the picture altogether; routing any of them late, or counting a mirrored
|
|
8
|
+
* draw as a mover's second draw of the frame, would be wrong in the way that reads as a ghost.
|
|
9
|
+
*
|
|
10
|
+
* Plain booleans rather than a state object, so the per-draw call allocates nothing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function routesLate(reconstructing: boolean, mirror: boolean, probe: boolean, presented: boolean): boolean;
|
|
13
|
+
/** The same set as `routesLate`, named for the other reader. */
|
|
14
|
+
export declare function tracksMotion(reconstructing: boolean, mirror: boolean, probe: boolean, presented: boolean): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two decisions about one draw in a reconstructing frame, and why they are the same set.
|
|
3
|
+
*
|
|
4
|
+
* A blended draw goes **late** — after the reconstruction, at output resolution, unjittered — and a
|
|
5
|
+
* mover's motion is **tracked**, only for draws that land in the picture being reconstructed. A
|
|
6
|
+
* planar mirror and a probe bake are other pictures drawn through the same verbs, and the overlay
|
|
7
|
+
* after `endFrame` is past the picture altogether; routing any of them late, or counting a mirrored
|
|
8
|
+
* draw as a mover's second draw of the frame, would be wrong in the way that reads as a ghost.
|
|
9
|
+
*
|
|
10
|
+
* Plain booleans rather than a state object, so the per-draw call allocates nothing.
|
|
11
|
+
*/
|
|
12
|
+
export function routesLate(reconstructing, mirror, probe, presented) {
|
|
13
|
+
return reconstructing && !mirror && !probe && !presented;
|
|
14
|
+
}
|
|
15
|
+
/** The same set as `routesLate`, named for the other reader. */
|
|
16
|
+
export function tracksMotion(reconstructing, mirror, probe, presented) {
|
|
17
|
+
return routesLate(reconstructing, mirror, probe, presented);
|
|
18
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which object a draw is, so a reconstruction can find where that object was last frame.
|
|
3
|
+
*
|
|
4
|
+
* **The engine cannot infer this and does not try.** `drawMesh` is immediate: a mesh drawn three
|
|
5
|
+
* times is three objects, and nothing in the call says which of last frame's three each one is. A
|
|
6
|
+
* consumer that makes one `Mover` per moving object and passes it with every draw of that object
|
|
7
|
+
* says exactly that, and the renderer keeps the rest — last frame's model and, for a skinned
|
|
8
|
+
* draw, last frame's palette.
|
|
9
|
+
*
|
|
10
|
+
* **Plain data, advanced by the renderer.** No renderer is needed to make one, so a consumer holds
|
|
11
|
+
* it beside the object it belongs to. Only a reconstructing WebGPU renderer reads or writes it; it
|
|
12
|
+
* costs nothing anywhere else. The fields past `kind` are the renderer's and a caller leaves them
|
|
13
|
+
* alone.
|
|
14
|
+
*
|
|
15
|
+
* **What would make this wrong** is a consumer whose objects are rebuilt every frame with no state
|
|
16
|
+
* of their own to hold a mover in; the identity then has to live in whatever *does* persist.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MOVER_KIND: 'drift.mover';
|
|
19
|
+
export interface Mover {
|
|
20
|
+
readonly kind: typeof MOVER_KIND;
|
|
21
|
+
/** The renderer that last drew it, from `claimRendererId`; -1 before its first draw. */
|
|
22
|
+
renderer: number;
|
|
23
|
+
/** That renderer's frame serial at the last draw. */
|
|
24
|
+
frame: number;
|
|
25
|
+
readonly model: Float32Array;
|
|
26
|
+
readonly previousModel: Float32Array;
|
|
27
|
+
/**
|
|
28
|
+
* This frame's palette, exactly `paletteLength` floats long once a skinned draw has set it.
|
|
29
|
+
* Swapped with the previous one each frame and reallocated only when the rig changes.
|
|
30
|
+
*/
|
|
31
|
+
palette: Float32Array;
|
|
32
|
+
previousPalette: Float32Array;
|
|
33
|
+
paletteLength: number;
|
|
34
|
+
previousPaletteLength: number;
|
|
35
|
+
}
|
|
36
|
+
/** Nothing to reproject from: first draw, a missed frame, another renderer, or a changed rig. */
|
|
37
|
+
export declare const MOVER_NONE = 0;
|
|
38
|
+
/** Drawn on the frame before by this renderer: `previousModel` and `previousPalette` are real. */
|
|
39
|
+
export declare const MOVER_MOTION = 1;
|
|
40
|
+
/** Drawn already this frame. The second draw is a different object and the caller's error. */
|
|
41
|
+
export declare const MOVER_TWICE = 2;
|
|
42
|
+
export type MoverVerdict = typeof MOVER_NONE | typeof MOVER_MOTION | typeof MOVER_TWICE;
|
|
43
|
+
/** A number no other renderer in this page holds, so a mover can tell renderers apart. */
|
|
44
|
+
export declare function claimRendererId(): number;
|
|
45
|
+
export declare function createMover(): Mover;
|
|
46
|
+
export declare function isMover(value: unknown): value is Mover;
|
|
47
|
+
/**
|
|
48
|
+
* Advance a mover to this draw, and say whether last frame's state is usable.
|
|
49
|
+
*
|
|
50
|
+
* **A second draw in one frame changes nothing**, so the first draw's motion stands; the caller
|
|
51
|
+
* warns. Otherwise what the mover held becomes its previous and this draw becomes its current —
|
|
52
|
+
* the palettes by swapping two arrays, so a steady rig allocates nothing after its first frame.
|
|
53
|
+
*
|
|
54
|
+
* **A changed joint count is no motion**: skinning a vertex with a palette of a different rig is
|
|
55
|
+
* not an old pose of this one.
|
|
56
|
+
*/
|
|
57
|
+
export declare function advanceMover(mover: Mover, renderer: number, frame: number, model: ArrayLike<number>, palette: Float32Array | null): MoverVerdict;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which object a draw is, so a reconstruction can find where that object was last frame.
|
|
3
|
+
*
|
|
4
|
+
* **The engine cannot infer this and does not try.** `drawMesh` is immediate: a mesh drawn three
|
|
5
|
+
* times is three objects, and nothing in the call says which of last frame's three each one is. A
|
|
6
|
+
* consumer that makes one `Mover` per moving object and passes it with every draw of that object
|
|
7
|
+
* says exactly that, and the renderer keeps the rest — last frame's model and, for a skinned
|
|
8
|
+
* draw, last frame's palette.
|
|
9
|
+
*
|
|
10
|
+
* **Plain data, advanced by the renderer.** No renderer is needed to make one, so a consumer holds
|
|
11
|
+
* it beside the object it belongs to. Only a reconstructing WebGPU renderer reads or writes it; it
|
|
12
|
+
* costs nothing anywhere else. The fields past `kind` are the renderer's and a caller leaves them
|
|
13
|
+
* alone.
|
|
14
|
+
*
|
|
15
|
+
* **What would make this wrong** is a consumer whose objects are rebuilt every frame with no state
|
|
16
|
+
* of their own to hold a mover in; the identity then has to live in whatever *does* persist.
|
|
17
|
+
*/
|
|
18
|
+
export const MOVER_KIND = 'drift.mover';
|
|
19
|
+
/** Nothing to reproject from: first draw, a missed frame, another renderer, or a changed rig. */
|
|
20
|
+
export const MOVER_NONE = 0;
|
|
21
|
+
/** Drawn on the frame before by this renderer: `previousModel` and `previousPalette` are real. */
|
|
22
|
+
export const MOVER_MOTION = 1;
|
|
23
|
+
/** Drawn already this frame. The second draw is a different object and the caller's error. */
|
|
24
|
+
export const MOVER_TWICE = 2;
|
|
25
|
+
let nextRendererId = 1;
|
|
26
|
+
/** A number no other renderer in this page holds, so a mover can tell renderers apart. */
|
|
27
|
+
export function claimRendererId() {
|
|
28
|
+
const id = nextRendererId;
|
|
29
|
+
nextRendererId += 1;
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
export function createMover() {
|
|
33
|
+
return {
|
|
34
|
+
kind: MOVER_KIND,
|
|
35
|
+
renderer: -1,
|
|
36
|
+
frame: -2,
|
|
37
|
+
model: new Float32Array(16),
|
|
38
|
+
previousModel: new Float32Array(16),
|
|
39
|
+
palette: new Float32Array(0),
|
|
40
|
+
previousPalette: new Float32Array(0),
|
|
41
|
+
paletteLength: 0,
|
|
42
|
+
previousPaletteLength: 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function isMover(value) {
|
|
46
|
+
return (typeof value === 'object' && value !== null && value.kind === MOVER_KIND);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Advance a mover to this draw, and say whether last frame's state is usable.
|
|
50
|
+
*
|
|
51
|
+
* **A second draw in one frame changes nothing**, so the first draw's motion stands; the caller
|
|
52
|
+
* warns. Otherwise what the mover held becomes its previous and this draw becomes its current —
|
|
53
|
+
* the palettes by swapping two arrays, so a steady rig allocates nothing after its first frame.
|
|
54
|
+
*
|
|
55
|
+
* **A changed joint count is no motion**: skinning a vertex with a palette of a different rig is
|
|
56
|
+
* not an old pose of this one.
|
|
57
|
+
*/
|
|
58
|
+
export function advanceMover(mover, renderer, frame, model, palette) {
|
|
59
|
+
if (mover.renderer === renderer && mover.frame === frame)
|
|
60
|
+
return MOVER_TWICE;
|
|
61
|
+
const drawnLast = mover.renderer === renderer && mover.frame === frame - 1;
|
|
62
|
+
mover.previousModel.set(mover.model);
|
|
63
|
+
const spare = mover.previousPalette;
|
|
64
|
+
mover.previousPalette = mover.palette;
|
|
65
|
+
mover.palette = spare;
|
|
66
|
+
mover.previousPaletteLength = mover.paletteLength;
|
|
67
|
+
mover.model.set(model);
|
|
68
|
+
if (palette === null) {
|
|
69
|
+
mover.paletteLength = 0;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
/* Exactly the rig's length, because the array is uploaded as it stands; reallocated only when
|
|
73
|
+
the rig changes, which a steady character never does. */
|
|
74
|
+
if (mover.palette.length !== palette.length)
|
|
75
|
+
mover.palette = new Float32Array(palette.length);
|
|
76
|
+
mover.palette.set(palette);
|
|
77
|
+
mover.paletteLength = palette.length;
|
|
78
|
+
}
|
|
79
|
+
mover.renderer = renderer;
|
|
80
|
+
mover.frame = frame;
|
|
81
|
+
if (!drawnLast)
|
|
82
|
+
return MOVER_NONE;
|
|
83
|
+
if (mover.paletteLength !== mover.previousPaletteLength)
|
|
84
|
+
return MOVER_NONE;
|
|
85
|
+
return MOVER_MOTION;
|
|
86
|
+
}
|
|
@@ -29,6 +29,8 @@ export type OutputTransform = 'none' | 'srgb' | 'aces';
|
|
|
29
29
|
export type ShadowFilterTaps = 4 | 8 | 12;
|
|
30
30
|
export type DirectionalShadowDepthLayers = 1 | 2;
|
|
31
31
|
export type WaterReflectionFilterTaps = 1 | 5 | 9;
|
|
32
|
+
/** How light through glass is coloured: see `RenderQuality.glassShadows`. */
|
|
33
|
+
export type GlassShadows = 'full' | 'half' | 'off';
|
|
32
34
|
export interface RenderQuality {
|
|
33
35
|
/**
|
|
34
36
|
* The transfer curve applied at the end of every shaded pass.
|
|
@@ -141,6 +143,25 @@ export interface RenderQuality {
|
|
|
141
143
|
readonly lightVolumeSamples: number;
|
|
142
144
|
/** Independently composited static sun depths; two preserves common overlaps. */
|
|
143
145
|
readonly directionalShadowDepthLayers: DirectionalShadowDepthLayers;
|
|
146
|
+
/**
|
|
147
|
+
* Whether light through glass takes the glass's colour, and at what size.
|
|
148
|
+
*
|
|
149
|
+
* **`'full'`, the default**: every shadow a glass caster reaches gains a layer holding the depth of
|
|
150
|
+
* the nearest pane and a tint layer holding what the panes let through, at the size of the depth
|
|
151
|
+
* map beside it — 8 bytes a texel, so 32 MB for a 2048 sun map and 8 MB for a 1024 lamp layer.
|
|
152
|
+
* **`'half'`** halves the tint layers only (the glass depth must match its array): 20 MB and 5 MB,
|
|
153
|
+
* and a coloured patch softer than an opaque shadow's edge. **`'off'`** is glass before this
|
|
154
|
+
* existed: a pane casts nothing at all.
|
|
155
|
+
*
|
|
156
|
+
* Nothing is allocated until a glass caster first reaches a shadow, so a scene with no glass pays
|
|
157
|
+
* no memory and no pass whatever this says. **The lit shader is the exception**: `'full'` and
|
|
158
|
+
* `'half'` compile the glass lookup into it, and a lookup that finds no glass and returns at its
|
|
159
|
+
* first line still costs the lit pass its registers — measured on the courtyard at night, 0.27 ms
|
|
160
|
+
* at 720p on WebGPU, 0.42 ms on WebGL2 and 1.2 ms at 4K. `'off'` compiles none of it, so a world
|
|
161
|
+
* that never casts through glass is that much faster for saying so. What would make `'full'` wrong
|
|
162
|
+
* is a phone budget with a lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
|
|
163
|
+
*/
|
|
164
|
+
readonly glassShadows: GlassShadows;
|
|
144
165
|
/** Horizontal world-space reach over which a directional shadow dissolves. */
|
|
145
166
|
readonly directionalShadowMaxDistance: number;
|
|
146
167
|
/** Maximum horizontal projection per vertical metre before low-angle shadows vanish. */
|
|
@@ -548,6 +569,20 @@ export interface RenderQuality {
|
|
|
548
569
|
* the render is a quarter of the output and no reconstruction holds an edge through that. A
|
|
549
570
|
* negative number is off, not a clamp to the bottom: it cannot mean "a little".
|
|
550
571
|
*
|
|
572
|
+
* **What it needs from you.** A `createMover()` per moving object, passed with that object's
|
|
573
|
+
* draws where `drawMesh` takes last frame's matrix — the renderer keeps last frame's model and,
|
|
574
|
+
* for a skinned draw, last frame's pose. Instanced batches and meshes rewritten with
|
|
575
|
+
* `updateMesh` need nothing: the batch and the mesh are their own identity. And `cameraCut()` at
|
|
576
|
+
* every cut, because nothing detects one: without it a cut blends the previous shot into the
|
|
577
|
+
* next. Blended draws — translucent meshes, particles, plumes, film, bolts, lines, world text —
|
|
578
|
+
* are drawn after the upscale at the output size and need nothing either; light volumes stay in
|
|
579
|
+
* the reconstructed picture, being a march that keeps its history.
|
|
580
|
+
*
|
|
581
|
+
* **What it cannot give back** is the resolution it draws at: an edge that never moves is softer
|
|
582
|
+
* than a native frame's at any ratio, because two thirds of the pixels at 1.5 are a guess made
|
|
583
|
+
* sharp. Measured on `demo/dev/ghost.html` against a native frame at four samples, still edges
|
|
584
|
+
* err by about 8 levels where a native frame at one sample errs by 3.
|
|
585
|
+
*
|
|
551
586
|
* `?recon=` is how it gets A/B'd in one reload.
|
|
552
587
|
*/
|
|
553
588
|
readonly reconstruction: number;
|