@driftengine/core 4.8.7 → 4.10.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 +123 -11
- package/README.md +43 -38
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -3
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +19 -4
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +180 -4
- package/dist/render/backend/webgl2/renderer.js +550 -40
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +19 -4
- package/dist/render/backend/webgpu/buffers.js +18 -11
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
- package/dist/render/backend/webgpu/drawCommand.js +10 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
- package/dist/render/backend/webgpu/flatPass.js +191 -43
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
- package/dist/render/backend/webgpu/pipelineCache.js +7 -0
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +283 -37
- package/dist/render/backend/webgpu/renderer.js +1363 -202
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +241 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- package/dist/render/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +5 -0
- package/dist/render/mesh.js +6 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +39 -8
- package/dist/render/renderQuality.js +4 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +24 -1
- package/dist/render/screenSpaceReflection.d.ts +7 -1
- package/dist/render/screenSpaceReflection.js +5 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +2 -0
- package/dist/render/shaders/flat/index.d.ts +16 -0
- package/dist/render/shaders/flat/index.js +33 -5
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +43 -5
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +3 -3
- package/dist/render/shaders/flat/preamble.js +38 -24
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
- package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
- package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/water.wgsl.js +1 -1
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +26 -0
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +106 -1
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +57 -7
- package/dist/render/surfaceTexture.js +125 -17
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +24 -2
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +22 -3
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +672 -37
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +42 -16
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/drawCommand.ts +22 -4
- package/src/render/backend/webgpu/flatPass.ts +250 -40
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/pipelineCache.ts +7 -0
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +1655 -230
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +363 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/displayRange.ts +61 -0
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/glCompressed.ts +83 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +6 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +44 -8
- package/src/render/sceneTarget.ts +30 -2
- package/src/render/screenSpaceReflection.ts +7 -1
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/glassTint.ts +5 -1
- package/src/render/shaders/flat/index.ts +49 -5
- package/src/render/shaders/flat/main.ts +43 -5
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +3 -3
- package/src/render/shaders/flat/preamble.ts +38 -24
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
- package/src/render/shaders/generated/water.wgsl.ts +1 -1
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +26 -0
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/ssrPass.ts +138 -1
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +161 -27
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
|
@@ -5,6 +5,7 @@ import { FLAT_BINDINGS, FLAT_FRAG_WGSL, FLAT_VERT_WGSL, } from '../../shaders/ge
|
|
|
5
5
|
import { vertexBufferLayouts } from './buffers.js';
|
|
6
6
|
import { shaderModule } from './shaderModules.js';
|
|
7
7
|
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
|
|
8
|
+
import { REFLECTION_SURFACE_FORMAT } from './reflectionSurfacePass.js';
|
|
8
9
|
/** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
|
|
9
10
|
const SURFACE_MAPS = new Set([
|
|
10
11
|
'uAlbedo',
|
|
@@ -51,9 +52,13 @@ const FRAGMENT_BINDINGS = FLAT_BINDINGS.flatFrag;
|
|
|
51
52
|
*/
|
|
52
53
|
const VERTEX_BINDINGS = FLAT_BINDINGS.flatVert;
|
|
53
54
|
/** The key for a set of vertex flags: the same rule `variantKey` uses, flags on, sorted, joined. */
|
|
54
|
-
export function flatVertexKey(skinned, morphed, instanced = false
|
|
55
|
+
export function flatVertexKey(skinned, morphed, instanced = false,
|
|
56
|
+
/** Whether an instanced variant plays a bone animation. See `shaders/boneAnimation.ts`. */
|
|
57
|
+
animated = false) {
|
|
55
58
|
const on = [];
|
|
56
59
|
/* Alphabetical, because the generator's own `variantKey` sorts and the two keys must agree. */
|
|
60
|
+
if (animated)
|
|
61
|
+
on.push('animated');
|
|
57
62
|
if (instanced)
|
|
58
63
|
on.push('instanced');
|
|
59
64
|
if (morphed)
|
|
@@ -63,8 +68,8 @@ export function flatVertexKey(skinned, morphed, instanced = false) {
|
|
|
63
68
|
return on.length === 0 ? 'none' : on.join('+');
|
|
64
69
|
}
|
|
65
70
|
/** Everything the generator recorded about one variant of the vertex stage. */
|
|
66
|
-
export function flatVertexBindings(skinned, morphed = false, instanced = false) {
|
|
67
|
-
const key = flatVertexKey(skinned, morphed, instanced);
|
|
71
|
+
export function flatVertexBindings(skinned, morphed = false, instanced = false, animated = false) {
|
|
72
|
+
const key = flatVertexKey(skinned, morphed, instanced, animated);
|
|
68
73
|
const found = VERTEX_BINDINGS[key];
|
|
69
74
|
if (found === undefined)
|
|
70
75
|
throw new Error(`flatPass: no vertex bindings for variant "${key}"`);
|
|
@@ -135,6 +140,8 @@ for (const [key, bindings] of Object.entries(VERTEX_BINDINGS)) {
|
|
|
135
140
|
}
|
|
136
141
|
}
|
|
137
142
|
export const FLAT_VERT_FIELDS = FLAT_BINDINGS.flatVert.none.fields;
|
|
143
|
+
/** The animated instanced variant's block: the instanced fields, and the clip's `uBoneClip`. */
|
|
144
|
+
export const FLAT_ANIMATED_VERT_FIELDS = FLAT_BINDINGS.flatVert['animated+instanced'].fields;
|
|
138
145
|
/**
|
|
139
146
|
* The **largest** variant's block, because one ring serves every draw.
|
|
140
147
|
*
|
|
@@ -144,6 +151,48 @@ export const FLAT_VERT_FIELDS = FLAT_BINDINGS.flatVert.none.fields;
|
|
|
144
151
|
*/
|
|
145
152
|
export const FLAT_VERT_SIZE = Math.max(...Object.values(VERTEX_BINDINGS).map((bindings) => bindings.uniformSize));
|
|
146
153
|
const FLAT_VERT_BINDING = PLAIN_VERTEX.uniforms;
|
|
154
|
+
/*
|
|
155
|
+
* **The view block may be shorter in a variant, never different.** One slot serves every lit draw
|
|
156
|
+
* of a pass, plain and instanced alike, so every field a variant's view block declares must sit
|
|
157
|
+
* where the plain variant has it. The instanced variant declares no wind, and its block is the plain
|
|
158
|
+
* one's first two matrices.
|
|
159
|
+
*/
|
|
160
|
+
const PLAIN_VIEW_FIELDS = PLAIN_VERTEX.viewFields ?? {};
|
|
161
|
+
for (const [key, bindings] of Object.entries(VERTEX_BINDINGS)) {
|
|
162
|
+
if (bindings.viewUniforms !== PLAIN_VERTEX.viewUniforms) {
|
|
163
|
+
throw new Error(`flatPass: vertex variant "${key}" binds its view block elsewhere`);
|
|
164
|
+
}
|
|
165
|
+
for (const [field, here] of Object.entries(bindings.viewFields ?? {})) {
|
|
166
|
+
if (PLAIN_VIEW_FIELDS[field]?.offset !== here.offset) {
|
|
167
|
+
throw new Error(`flatPass: vertex variant "${key}" puts view field ${field} at ${here.offset} where the ` +
|
|
168
|
+
`plain variant has it at ${PLAIN_VIEW_FIELDS[field]?.offset}.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** The view block's fields, binding and size: the largest variant's, as `FLAT_VERT_SIZE` is. */
|
|
173
|
+
export const FLAT_VIEW_FIELDS = PLAIN_VIEW_FIELDS;
|
|
174
|
+
export const FLAT_VIEW_SIZE = Math.max(...Object.values(VERTEX_BINDINGS).map((bindings) => bindings.viewSize ?? 0));
|
|
175
|
+
const FLAT_VIEW_BINDING = PLAIN_VERTEX.viewUniforms ?? -1;
|
|
176
|
+
if (FLAT_VIEW_BINDING < 0)
|
|
177
|
+
throw new Error('flatPass: the lit vertex stage declares no view block');
|
|
178
|
+
/**
|
|
179
|
+
* The most sampled textures and distinct samplers any lit variant carrying the environment probe
|
|
180
|
+
* binds in its fragment stage, counted from the generated bindings: what a device must allow per
|
|
181
|
+
* stage before the probe's permutation can be built. A sampler several textures share counts once,
|
|
182
|
+
* which is what `// wgsl:share` buys.
|
|
183
|
+
*/
|
|
184
|
+
export function probeStageCeilings() {
|
|
185
|
+
let textures = 0;
|
|
186
|
+
let samplers = 0;
|
|
187
|
+
for (const [variant, bindings] of Object.entries(FLAT_BINDINGS.flatFrag)) {
|
|
188
|
+
if (!variant.includes('environmentProbe'))
|
|
189
|
+
continue;
|
|
190
|
+
const listed = Object.values(bindings.textures ?? {});
|
|
191
|
+
textures = Math.max(textures, listed.length);
|
|
192
|
+
samplers = Math.max(samplers, new Set(listed.map((entry) => entry.sampler)).size);
|
|
193
|
+
}
|
|
194
|
+
return { textures, samplers };
|
|
195
|
+
}
|
|
147
196
|
/** Everything the generator recorded about one permutation of the fragment stage. */
|
|
148
197
|
export function flatFragmentBindings(variant) {
|
|
149
198
|
const found = FRAGMENT_BINDINGS[variant];
|
|
@@ -151,6 +200,26 @@ export function flatFragmentBindings(variant) {
|
|
|
151
200
|
throw new Error(`flatPass: no bindings for variant "${variant}"`);
|
|
152
201
|
return found;
|
|
153
202
|
}
|
|
203
|
+
export function flatFragmentScratch(variant) {
|
|
204
|
+
const bindings = flatFragmentBindings(variant);
|
|
205
|
+
const { materialUniforms, materialSize, materialFields } = bindings;
|
|
206
|
+
if (materialUniforms === undefined ||
|
|
207
|
+
materialSize === undefined ||
|
|
208
|
+
materialFields === undefined) {
|
|
209
|
+
throw new Error(`flatPass: variant "${variant}" has no material block`);
|
|
210
|
+
}
|
|
211
|
+
const fields = { ...bindings.fields };
|
|
212
|
+
for (const [name, field] of Object.entries(materialFields)) {
|
|
213
|
+
fields[name] = { ...field, offset: bindings.uniformSize + field.offset };
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
fields,
|
|
217
|
+
passSize: bindings.uniformSize,
|
|
218
|
+
passBinding: bindings.uniforms,
|
|
219
|
+
materialSize,
|
|
220
|
+
materialBinding: materialUniforms,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
154
223
|
/**
|
|
155
224
|
* The lit fragment stage's constants, set from the cache's switches, keyed by each one's **id**.
|
|
156
225
|
*
|
|
@@ -160,7 +229,7 @@ export function flatFragmentBindings(variant) {
|
|
|
160
229
|
* "GLASS_SHADOWS" not found*, and the frame drew nothing. Every override the variant declares is
|
|
161
230
|
* set, so a switch added to the shader and not to the cache is refused here rather than defaulted.
|
|
162
231
|
*/
|
|
163
|
-
function flatFragmentConstants(variant, cache, model, skinPass = 'whole') {
|
|
232
|
+
function flatFragmentConstants(variant, cache, model, skinPass = 'whole', reflectionSurface = false) {
|
|
164
233
|
const overrides = flatFragmentBindings(variant).overrides;
|
|
165
234
|
if (overrides?.['GLASS_SHADOWS'] === undefined) {
|
|
166
235
|
throw new Error(`flatPass: variant "${variant}" has no glass switch`);
|
|
@@ -178,6 +247,11 @@ function flatFragmentConstants(variant, cache, model, skinPass = 'whole') {
|
|
|
178
247
|
constants[String(id)] = skinPass !== 'whole' && name === SKIN_HALF_SWITCH[skinPass] ? 1 : 0;
|
|
179
248
|
continue;
|
|
180
249
|
}
|
|
250
|
+
/* The reflection pass's surface half, set from the key too. See `reflectionSurface.ts`. */
|
|
251
|
+
if (name === 'REFLECTION_SURFACE') {
|
|
252
|
+
constants[String(id)] = reflectionSurface ? 1 : 0;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
181
255
|
if (!(name in cache.litSwitches)) {
|
|
182
256
|
throw new Error(`flatPass: variant "${variant}" declares a switch "${name}" no cache sets`);
|
|
183
257
|
}
|
|
@@ -185,6 +259,13 @@ function flatFragmentConstants(variant, cache, model, skinPass = 'whole') {
|
|
|
185
259
|
}
|
|
186
260
|
return constants;
|
|
187
261
|
}
|
|
262
|
+
/** The material block's binding, which every lit variant declares. */
|
|
263
|
+
function materialBinding(fragment) {
|
|
264
|
+
if (fragment.materialUniforms === undefined) {
|
|
265
|
+
throw new Error('flatPass: the lit fragment stage declares no material block');
|
|
266
|
+
}
|
|
267
|
+
return fragment.materialUniforms;
|
|
268
|
+
}
|
|
188
269
|
/** The depth format both backends compare with. */
|
|
189
270
|
/* Re-exported so the twenty passes that already import it from here do not each learn a new
|
|
190
271
|
module; `depthConvention.ts` is where the decision lives and why. */
|
|
@@ -194,11 +275,15 @@ const VISIBILITY_FRAGMENT = 0x2;
|
|
|
194
275
|
/**
|
|
195
276
|
* The layout this variant binds against.
|
|
196
277
|
*
|
|
197
|
-
*
|
|
198
|
-
* from its own slot of one buffer
|
|
199
|
-
*
|
|
278
|
+
* All four blocks are **dynamic**: the vertex block because every draw supplies its own model
|
|
279
|
+
* matrix and tint from its own slot of one buffer; the fragment stage's pass block — the lights, the
|
|
280
|
+
* fog, the camera — and the vertex stage's view block because a frame binds more than one pass; and
|
|
281
|
+
* the material block because a material changes between draws. Their dynamic offsets go in binding
|
|
282
|
+
* order: the draw's, the pass's, the material's, the view's.
|
|
200
283
|
*/
|
|
201
|
-
export function createFlatBindGroupLayout(device, variant, skinned = false, morphed = false
|
|
284
|
+
export function createFlatBindGroupLayout(device, variant, skinned = false, morphed = false,
|
|
285
|
+
/** The instanced variant that plays a bone animation: its three vertex textures. */
|
|
286
|
+
animated = false) {
|
|
202
287
|
const fragment = flatFragmentBindings(variant);
|
|
203
288
|
const entries = [
|
|
204
289
|
{
|
|
@@ -206,15 +291,31 @@ export function createFlatBindGroupLayout(device, variant, skinned = false, morp
|
|
|
206
291
|
visibility: VISIBILITY_VERTEX,
|
|
207
292
|
buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: FLAT_VERT_SIZE },
|
|
208
293
|
},
|
|
294
|
+
{
|
|
295
|
+
binding: FLAT_VIEW_BINDING,
|
|
296
|
+
visibility: VISIBILITY_VERTEX,
|
|
297
|
+
/* Dynamic, a slot a pass, for the reason the fragment stage's pass block is. */
|
|
298
|
+
buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: FLAT_VIEW_SIZE },
|
|
299
|
+
},
|
|
209
300
|
{
|
|
210
301
|
binding: fragment.uniforms,
|
|
211
302
|
visibility: VISIBILITY_FRAGMENT,
|
|
212
303
|
/*
|
|
213
|
-
* Dynamic, because
|
|
214
|
-
*
|
|
304
|
+
* Dynamic, because a pass's lights and camera change between passes of one frame — a
|
|
305
|
+
* mirror's, a capture's — and one buffer cannot hold two answers. See `WebGPURenderer.passRing`.
|
|
215
306
|
*/
|
|
216
307
|
buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: fragment.uniformSize },
|
|
217
308
|
},
|
|
309
|
+
{
|
|
310
|
+
binding: materialBinding(fragment),
|
|
311
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
312
|
+
/* Dynamic, because a material changes between draws. See `MaterialStore`. */
|
|
313
|
+
buffer: {
|
|
314
|
+
type: 'uniform',
|
|
315
|
+
hasDynamicOffset: true,
|
|
316
|
+
minBindingSize: fragment.materialSize ?? 0,
|
|
317
|
+
},
|
|
318
|
+
},
|
|
218
319
|
];
|
|
219
320
|
/** What each sampler binding was declared as, so a shared one is declared once and agrees. */
|
|
220
321
|
const samplerKind = new Map();
|
|
@@ -355,11 +456,33 @@ export function createFlatBindGroupLayout(device, variant, skinned = false, morp
|
|
|
355
456
|
});
|
|
356
457
|
}
|
|
357
458
|
}
|
|
459
|
+
/* A bone animation's places, turns and the batch's clocks, read by index as the palette is. */
|
|
460
|
+
if (animated) {
|
|
461
|
+
for (const binding of animationBindings()) {
|
|
462
|
+
entries.push({
|
|
463
|
+
binding,
|
|
464
|
+
visibility: VISIBILITY_VERTEX,
|
|
465
|
+
texture: { sampleType: 'unfilterable-float' },
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
358
469
|
return device.createBindGroupLayout({
|
|
359
|
-
label: `flat.layout:${variant}|${flatVertexKey(skinned, morphed)}`,
|
|
470
|
+
label: `flat.layout:${variant}|${flatVertexKey(skinned, morphed, animated, animated)}`,
|
|
360
471
|
entries,
|
|
361
472
|
});
|
|
362
473
|
}
|
|
474
|
+
/** The three textures a bone animation binds, in the order `AnimationViews` holds them. */
|
|
475
|
+
function animationBindings() {
|
|
476
|
+
const textures = flatVertexBindings(false, false, true, true).textures;
|
|
477
|
+
const at = (name) => {
|
|
478
|
+
const found = textures[name];
|
|
479
|
+
if (found === undefined) {
|
|
480
|
+
throw new Error(`flatPass: the animated vertex shader declares no ${name}`);
|
|
481
|
+
}
|
|
482
|
+
return found.texture;
|
|
483
|
+
};
|
|
484
|
+
return [at('uBonePlaces'), at('uBoneTurns'), at('uInstanceClocks')];
|
|
485
|
+
}
|
|
363
486
|
/**
|
|
364
487
|
* Bind the buffers and one view per declared texture.
|
|
365
488
|
*
|
|
@@ -369,20 +492,27 @@ export function createFlatBindGroupLayout(device, variant, skinned = false, morp
|
|
|
369
492
|
* and WebGPU still requires something there, which is why `renderer.ts` keeps an
|
|
370
493
|
* `emptyTexture2D` for the same reason on the other side.
|
|
371
494
|
*/
|
|
372
|
-
export function createFlatBindGroup(device, layout, variant, perDraw,
|
|
495
|
+
export function createFlatBindGroup(device, layout, variant, perDraw, passes, materials, views, resolve,
|
|
373
496
|
/** The palette view for a skinned bind group, or null for an unskinned one. */
|
|
374
497
|
palette = null,
|
|
375
498
|
/** The delta view for a morphed bind group, or null for an unmorphed one. */
|
|
376
499
|
deltas = null,
|
|
377
500
|
/** A skinned bind group's cloth views — the draw's, or the stand-in where it sets none. */
|
|
378
|
-
cloth = null
|
|
501
|
+
cloth = null,
|
|
502
|
+
/** An animated instanced group's views. See `AnimationViews`. */
|
|
503
|
+
animation = null) {
|
|
379
504
|
const fragment = flatFragmentBindings(variant);
|
|
380
505
|
const entries = [
|
|
381
506
|
{
|
|
382
507
|
binding: FLAT_VERT_BINDING,
|
|
383
508
|
resource: { buffer: perDraw, size: FLAT_VERT_SIZE },
|
|
384
509
|
},
|
|
385
|
-
{ binding:
|
|
510
|
+
{ binding: FLAT_VIEW_BINDING, resource: { buffer: views, size: FLAT_VIEW_SIZE } },
|
|
511
|
+
{ binding: fragment.uniforms, resource: { buffer: passes, size: fragment.uniformSize } },
|
|
512
|
+
{
|
|
513
|
+
binding: materialBinding(fragment),
|
|
514
|
+
resource: { buffer: materials, size: fragment.materialSize ?? 0 },
|
|
515
|
+
},
|
|
386
516
|
];
|
|
387
517
|
/* The same dedupe the layout does, and for the same reason: the shadow bindings share one
|
|
388
518
|
sampler, and a bind group that filled it fifteen times would be rejected. Every member
|
|
@@ -417,8 +547,14 @@ cloth = null) {
|
|
|
417
547
|
entries.push({ binding: particlesAt, resource: cloth.particles });
|
|
418
548
|
entries.push({ binding: restAt, resource: cloth.rest });
|
|
419
549
|
}
|
|
550
|
+
if (animation !== null) {
|
|
551
|
+
const [places, turns, clocks] = animationBindings();
|
|
552
|
+
entries.push({ binding: places, resource: animation.places });
|
|
553
|
+
entries.push({ binding: turns, resource: animation.turns });
|
|
554
|
+
entries.push({ binding: clocks, resource: animation.clocks });
|
|
555
|
+
}
|
|
420
556
|
return device.createBindGroup({
|
|
421
|
-
label: `flat.bindGroup:${variant}|${flatVertexKey(palette !== null, deltas !== null)}`,
|
|
557
|
+
label: `flat.bindGroup:${variant}|${flatVertexKey(palette !== null, deltas !== null, animation !== null, animation !== null)}`,
|
|
422
558
|
layout,
|
|
423
559
|
entries,
|
|
424
560
|
});
|
|
@@ -481,8 +617,15 @@ cloth = false,
|
|
|
481
617
|
/** Which shading model its fragment stage shades by, null the standard. The key must carry it. */
|
|
482
618
|
model = null,
|
|
483
619
|
/** Which half of a skin it draws. The key must carry it: `|ss`, `|sd` or `|sa`. See `SkinHalf`. */
|
|
484
|
-
skinPass = 'whole'
|
|
485
|
-
|
|
620
|
+
skinPass = 'whole',
|
|
621
|
+
/**
|
|
622
|
+
* The reflection pass's surface half: the draw again, against its own depth, into the two
|
|
623
|
+
* targets `reflectionSurfacePass.ts` holds. The key must carry it: `|rs`.
|
|
624
|
+
*/
|
|
625
|
+
reflectionSurface = false,
|
|
626
|
+
/** An instanced pipeline that plays a bone animation. The key must carry it: `|anim`. */
|
|
627
|
+
animated = false) {
|
|
628
|
+
return cache.get(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed, depthWrite, depthLayer, instanced, oit, doubleSided, alphaToCoverage, skinEight, cloth, model, skinPass, reflectionSurface, animated), true);
|
|
486
629
|
}
|
|
487
630
|
/**
|
|
488
631
|
* The same pipeline, compiled off the main thread.
|
|
@@ -562,10 +705,13 @@ oit = 'none',
|
|
|
562
705
|
/** Culls nothing: a surface seen from both faces. */
|
|
563
706
|
doubleSided = false, alphaToCoverage = false, skinEight = false,
|
|
564
707
|
/** Whether its vertex stage places vertices by a cloth binding. See `clothBinding.ts`. */
|
|
565
|
-
cloth = false, model = null, skinPass = 'whole'
|
|
708
|
+
cloth = false, model = null, skinPass = 'whole', reflectionSurface = false,
|
|
709
|
+
/** An instanced pipeline that plays a bone animation. See `shaders/boneAnimation.ts`. */
|
|
710
|
+
animated = false) {
|
|
566
711
|
const offset = depthOffsetForLayer(depthLayer);
|
|
567
|
-
/* The diffuse and colour halves are the same surface again, into the blur's targets
|
|
568
|
-
|
|
712
|
+
/* The diffuse and colour halves are the same surface again, into the blur's targets; and so is
|
|
713
|
+
the reflection pass's surface half, into its two. */
|
|
714
|
+
const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo' || reflectionSurface;
|
|
569
715
|
return {
|
|
570
716
|
label: key,
|
|
571
717
|
layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
@@ -576,12 +722,12 @@ cloth = false, model = null, skinPass = 'whole') {
|
|
|
576
722
|
* layout validates, draws, and is wrong, exactly as this file's header says.
|
|
577
723
|
*/
|
|
578
724
|
module: shaderModule(device, {
|
|
579
|
-
label: `flat.vert:${flatVertexKey(skinned, morphed, instanced)}`,
|
|
580
|
-
code: FLAT_VERT_WGSL[flatVertexKey(skinned, morphed, instanced)] ?? '',
|
|
725
|
+
label: `flat.vert:${flatVertexKey(skinned, morphed, instanced, animated)}`,
|
|
726
|
+
code: FLAT_VERT_WGSL[flatVertexKey(skinned, morphed, instanced, animated)] ?? '',
|
|
581
727
|
}),
|
|
582
728
|
entryPoint: 'main',
|
|
583
729
|
buffers: vertexBufferLayouts(present, instanced),
|
|
584
|
-
constants: vertexConstants(flatVertexBindings(skinned, morphed, instanced), skinEight, cloth, model === 'lightmap'),
|
|
730
|
+
constants: vertexConstants(flatVertexBindings(skinned, morphed, instanced, animated), skinEight, cloth, model === 'lightmap'),
|
|
585
731
|
},
|
|
586
732
|
fragment: {
|
|
587
733
|
module: shaderModule(device, {
|
|
@@ -590,31 +736,33 @@ cloth = false, model = null, skinPass = 'whole') {
|
|
|
590
736
|
}),
|
|
591
737
|
entryPoint: 'main',
|
|
592
738
|
/* Every variant declares it, so every lit pipeline may set it: see `PipelineCache`. */
|
|
593
|
-
constants: flatFragmentConstants(variant, cache, model, skinPass),
|
|
594
|
-
targets:
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
blend: {
|
|
600
|
-
color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
|
|
601
|
-
alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
|
|
602
|
-
},
|
|
603
|
-
}
|
|
604
|
-
: translucent
|
|
739
|
+
constants: flatFragmentConstants(variant, cache, model, skinPass, reflectionSurface),
|
|
740
|
+
targets: reflectionSurface
|
|
741
|
+
? [null, { format: REFLECTION_SURFACE_FORMAT }, { format: REFLECTION_SURFACE_FORMAT }]
|
|
742
|
+
: [
|
|
743
|
+
oitTarget(oit) ??
|
|
744
|
+
(translucent === 'additive'
|
|
605
745
|
? {
|
|
606
746
|
format: cache.format,
|
|
607
747
|
blend: {
|
|
608
|
-
color: {
|
|
609
|
-
|
|
610
|
-
dstFactor: 'one-minus-src-alpha',
|
|
611
|
-
operation: 'add',
|
|
612
|
-
},
|
|
613
|
-
alpha: SCENE_ALPHA_COVERS,
|
|
748
|
+
color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
|
|
749
|
+
alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
|
|
614
750
|
},
|
|
615
751
|
}
|
|
616
|
-
:
|
|
617
|
-
|
|
752
|
+
: translucent
|
|
753
|
+
? {
|
|
754
|
+
format: cache.format,
|
|
755
|
+
blend: {
|
|
756
|
+
color: {
|
|
757
|
+
srcFactor: 'src-alpha',
|
|
758
|
+
dstFactor: 'one-minus-src-alpha',
|
|
759
|
+
operation: 'add',
|
|
760
|
+
},
|
|
761
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
762
|
+
},
|
|
763
|
+
}
|
|
764
|
+
: { format: cache.format }),
|
|
765
|
+
],
|
|
618
766
|
},
|
|
619
767
|
primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
|
|
620
768
|
/* Coverage only where a sample count can carry it: WebGPU refuses it at one sample. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CullSlot } from './instanceCullPass.ts';
|
|
2
2
|
import type { MeshInstances } from '../../instances.ts';
|
|
3
3
|
import type { GpuMesh } from './buffers.ts';
|
|
4
|
+
import { GpuInstanceClocks, type GpuBoneAnimation } from './boneAnimations.ts';
|
|
4
5
|
/**
|
|
5
6
|
* One mesh's per-instance buffer on the device.
|
|
6
7
|
*
|
|
@@ -25,7 +26,19 @@ export declare class GpuInstancedBatch {
|
|
|
25
26
|
/** The frame the slots were last taken in, and how many were taken. */
|
|
26
27
|
slotFrame: number;
|
|
27
28
|
slotsTaken: number;
|
|
28
|
-
|
|
29
|
+
/** The bone animation every instance plays, and its clocks; null for a batch that plays none. */
|
|
30
|
+
readonly animation: GpuBoneAnimation | null;
|
|
31
|
+
readonly clocks: GpuInstanceClocks | null;
|
|
32
|
+
/**
|
|
33
|
+
* An animated batch's own groups — its lit twins by the material group they were built beside, its
|
|
34
|
+
* shadow groups by what they cut with — and the renderer's group epoch they were built in, after
|
|
35
|
+
* which every one is dropped. See `WebGPURenderer.animatedGroup`.
|
|
36
|
+
*/
|
|
37
|
+
readonly litGroups: Map<GPUBindGroup, GPUBindGroup>;
|
|
38
|
+
readonly depthGroups: Map<unknown, GPUBindGroup>;
|
|
39
|
+
readonly depthPeelGroups: Map<unknown, GPUBindGroup>;
|
|
40
|
+
groupEpoch: number;
|
|
41
|
+
constructor(device: GPUDevice, mesh: GpuMesh, capacity: number, label: string, cull?: boolean, animation?: GpuBoneAnimation | null);
|
|
29
42
|
/** Last frame's packed slots, for a reconstruction; made on first use by the renderer. */
|
|
30
43
|
previousBuffer: GPUBuffer | null;
|
|
31
44
|
/** How many slots the upload before the last one held, and how many the last one holds. */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { instancesBox } from '../../instanceCull.js';
|
|
2
2
|
import { INSTANCE_FLOATS, INSTANCE_STRIDE, packInstances } from '../../instances.js';
|
|
3
3
|
import { destroySlot } from './instanceCullPass.js';
|
|
4
|
+
import { packInstanceClocks } from '../../boneAnimation.js';
|
|
5
|
+
import { GpuInstanceClocks } from './boneAnimations.js';
|
|
4
6
|
/** VERTEX | COPY_DST, spelled the way every other pass here spells it. */
|
|
5
7
|
const USAGE_VERTEX = 0x0020 | 0x0008;
|
|
6
8
|
/** A culling batch's source is read by the cull as storage too. */
|
|
@@ -29,10 +31,26 @@ export class GpuInstancedBatch {
|
|
|
29
31
|
/** The frame the slots were last taken in, and how many were taken. */
|
|
30
32
|
slotFrame = -1;
|
|
31
33
|
slotsTaken = 0;
|
|
32
|
-
|
|
34
|
+
/** The bone animation every instance plays, and its clocks; null for a batch that plays none. */
|
|
35
|
+
animation;
|
|
36
|
+
clocks;
|
|
37
|
+
/**
|
|
38
|
+
* An animated batch's own groups — its lit twins by the material group they were built beside, its
|
|
39
|
+
* shadow groups by what they cut with — and the renderer's group epoch they were built in, after
|
|
40
|
+
* which every one is dropped. See `WebGPURenderer.animatedGroup`.
|
|
41
|
+
*/
|
|
42
|
+
litGroups = new Map();
|
|
43
|
+
depthGroups = new Map();
|
|
44
|
+
depthPeelGroups = new Map();
|
|
45
|
+
groupEpoch = -1;
|
|
46
|
+
constructor(device, mesh, capacity, label, cull = false, animation = null) {
|
|
33
47
|
this.mesh = mesh;
|
|
34
48
|
this.capacity = capacity;
|
|
49
|
+
/* An animated batch is culled whole and never by instance: see `submitInstanced`. */
|
|
35
50
|
this.cull = cull;
|
|
51
|
+
this.animation = animation;
|
|
52
|
+
this.clocks =
|
|
53
|
+
animation === null ? null : new GpuInstanceClocks(device, capacity, `${label}.clocks`);
|
|
36
54
|
this.staging = new Float32Array(capacity * INSTANCE_FLOATS);
|
|
37
55
|
this.buffer = device.createBuffer({
|
|
38
56
|
/*
|
|
@@ -76,10 +94,15 @@ export class GpuInstancedBatch {
|
|
|
76
94
|
return;
|
|
77
95
|
packInstances(data, this.staging);
|
|
78
96
|
queue.writeBuffer(this.buffer, 0, this.staging, 0, count * INSTANCE_FLOATS);
|
|
97
|
+
if (this.clocks !== null) {
|
|
98
|
+
packInstanceClocks(data, this.clocks.staging);
|
|
99
|
+
this.clocks.upload(queue, count);
|
|
100
|
+
}
|
|
79
101
|
}
|
|
80
102
|
dispose() {
|
|
81
103
|
this.previousBuffer?.destroy();
|
|
82
104
|
this.buffer.destroy();
|
|
105
|
+
this.clocks?.dispose();
|
|
83
106
|
for (const slot of this.slots)
|
|
84
107
|
destroySlot(slot);
|
|
85
108
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { BudgetCounter } from '../budget.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The lit stage's material blocks, each kept in its slot for as long as later draws ask for the
|
|
4
|
+
* same numbers, so a material that has not changed since the last frame costs no upload at all.
|
|
5
|
+
*
|
|
6
|
+
* **Keyed by content, because nothing else names a material.** A material here is whatever the
|
|
7
|
+
* setters left in the block when a draw took it — a consumer never hands over an object that could
|
|
8
|
+
* carry an identity — so the block's own words are the key: hashed to find a bucket, compared word
|
|
9
|
+
* for word to accept a match, and bound by the slot's offset. What it gives up is a hash and a
|
|
10
|
+
* compare of a few hundred bytes per material change, against copying and uploading the whole
|
|
11
|
+
* block every frame, which a stage of four hundred materials measured at megabytes a frame.
|
|
12
|
+
*
|
|
13
|
+
* **A slot the frame has asked for is never given away within it**, for the reason `UniformRing`
|
|
14
|
+
* exists: a queue write does not interleave with recorded draws, so rewriting a slot a recorded draw
|
|
15
|
+
* reads would hand that draw the new numbers. A frame asking for more distinct blocks than there are
|
|
16
|
+
* slots is refused, counted and skipped, as the ring was. Across frames rewriting is safe, because a
|
|
17
|
+
* write issued after a submit is ordered after the commands in it.
|
|
18
|
+
*
|
|
19
|
+
* **What a new block displaces is a slot nobody asked for this frame or the last**, found by a hand
|
|
20
|
+
* that sweeps the slots in turn; only when every slot was asked for recently does it take one the
|
|
21
|
+
* last frame used. What would make that wrong is a scene whose distinct materials a frame approach
|
|
22
|
+
* the store's size: it then rewrites some of its steady materials every frame, which costs their
|
|
23
|
+
* upload and nothing else.
|
|
24
|
+
*/
|
|
25
|
+
export declare class MaterialStore {
|
|
26
|
+
private readonly device;
|
|
27
|
+
private readonly usage;
|
|
28
|
+
private readonly label;
|
|
29
|
+
private readonly budget;
|
|
30
|
+
/** Replaced by `growTo`, so every bind group over it is rebuilt when that says so. */
|
|
31
|
+
buffer: GPUBuffer;
|
|
32
|
+
/** Bytes per slot, rounded up to the alignment a dynamic offset requires. */
|
|
33
|
+
readonly slotSize: number;
|
|
34
|
+
private readonly words;
|
|
35
|
+
private readonly stride;
|
|
36
|
+
private capacity;
|
|
37
|
+
private staging;
|
|
38
|
+
private ints;
|
|
39
|
+
/** Per slot: its block's hash, the next slot in its bucket or -1, and whether it holds one. */
|
|
40
|
+
private hashes;
|
|
41
|
+
private next;
|
|
42
|
+
private held;
|
|
43
|
+
/** Per slot: the frame that last asked for it, and whether it was written since the last flush. */
|
|
44
|
+
private askedIn;
|
|
45
|
+
private pending;
|
|
46
|
+
/** Per slot: how many holders pinned it. A pinned slot is never displaced. */
|
|
47
|
+
private pins;
|
|
48
|
+
/**
|
|
49
|
+
* How many times the store has forgotten everything it held, which growth does: a holder of an
|
|
50
|
+
* offset compares this, because an offset from before is a slot of a buffer that is gone.
|
|
51
|
+
*/
|
|
52
|
+
epoch: number;
|
|
53
|
+
/** Per bucket: its first slot, or -1. A power of two at least twice the slots. */
|
|
54
|
+
private heads;
|
|
55
|
+
private mask;
|
|
56
|
+
private lowPending;
|
|
57
|
+
private highPending;
|
|
58
|
+
private hand;
|
|
59
|
+
/** Starts past zero, so a slot never asked for reads as long unasked. */
|
|
60
|
+
private frame;
|
|
61
|
+
constructor(device: GPUDevice, bytesPerBlock: number, slots: number, usage: number, label?: string, budget?: BudgetCounter | null);
|
|
62
|
+
/** How many distinct blocks a frame may hold. */
|
|
63
|
+
get slots(): number;
|
|
64
|
+
/** Start a frame: every slot the last one asked for may be displaced from here on, last. */
|
|
65
|
+
beginFrame(): void;
|
|
66
|
+
/**
|
|
67
|
+
* The byte offset of a slot holding exactly `block`, written there if no slot did, or null when
|
|
68
|
+
* every slot is one this frame has asked for. `block` is the whole block, word for word.
|
|
69
|
+
*/
|
|
70
|
+
place(block: Int32Array): number | null;
|
|
71
|
+
/** Upload every slot written since the last flush, one write per run of neighbouring slots. */
|
|
72
|
+
flush(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Make room for at least `slots`, forgetting every block held, and say whether the buffer was
|
|
75
|
+
* replaced. Called where `UniformRing.growTo` is, at a frame's start, for the reasons it gives,
|
|
76
|
+
* and refused past the same ceiling.
|
|
77
|
+
*/
|
|
78
|
+
growTo(slots: number): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Keep the block at `offset` where it is until `unpin`: a holder binding it by offset frame after
|
|
81
|
+
* frame — a list recorded once — whether or not anything asks for it. Counted, so two holders of
|
|
82
|
+
* one block each let go of their own pin.
|
|
83
|
+
*/
|
|
84
|
+
pin(offset: number): void;
|
|
85
|
+
unpin(offset: number): void;
|
|
86
|
+
dispose(): void;
|
|
87
|
+
private holds;
|
|
88
|
+
/**
|
|
89
|
+
* The slot a new block goes in: the first from the hand nobody asked for this frame or the last,
|
|
90
|
+
* else the first this frame has not asked for, else none. One sweep at most.
|
|
91
|
+
*/
|
|
92
|
+
private displace;
|
|
93
|
+
private unlink;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* MurmurHash3's 32-bit mix over the block's words. Not FNV, whose multiply carries a difference only
|
|
97
|
+
* upward: the bucket is the hash's low bits, and a material's words are mostly floats, whose low
|
|
98
|
+
* mantissa bits are zero for every value a person types — 0.5, 1, 2 — so blocks differing only in
|
|
99
|
+
* those would all have shared one bucket. The compare that follows a match is what decides.
|
|
100
|
+
*/
|
|
101
|
+
export declare function hashWords(block: Int32Array, words: number): number;
|