@driftengine/core 4.5.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +51 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.js +13 -1
- package/dist/render/backend/api.d.ts +17 -3
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +122 -8
- package/dist/render/backend/webgl2/renderer.js +238 -121
- package/dist/render/backend/webgpu/buffers.d.ts +10 -0
- package/dist/render/backend/webgpu/buffers.js +34 -3
- package/dist/render/backend/webgpu/depthPass.js +5 -1
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
- package/dist/render/backend/webgpu/flatPass.js +38 -9
- package/dist/render/backend/webgpu/glassTintPass.js +2 -1
- package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +11 -1
- package/dist/render/backend/webgpu/instanced.js +21 -3
- package/dist/render/backend/webgpu/latePass.d.ts +6 -2
- package/dist/render/backend/webgpu/latePass.js +11 -4
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -10
- package/dist/render/backend/webgpu/renderer.js +462 -201
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- package/dist/render/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +22 -33
- package/dist/render/lightBudget.js +24 -35
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +18 -24
- package/dist/render/mesh.js +51 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- package/dist/render/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +2 -0
- package/dist/render/shaders/depth.js +12 -9
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- package/dist/render/shaders/flat/index.js +5 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +75 -23
- package/dist/render/shaders/flat/preamble.js +75 -20
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
- package/dist/render/shaders/generated/depth.wgsl.js +8 -8
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
- package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
- package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- package/dist/render/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +51 -1
- package/src/render/backend/api.ts +21 -2
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +300 -143
- package/src/render/backend/webgpu/buffers.ts +42 -3
- package/src/render/backend/webgpu/depthPass.ts +5 -1
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +41 -11
- package/src/render/backend/webgpu/glassTintPass.ts +2 -1
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +21 -3
- package/src/render/backend/webgpu/latePass.ts +12 -3
- package/src/render/backend/webgpu/renderer.ts +540 -226
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- package/src/render/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +25 -36
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +59 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +4 -0
- package/src/render/shaders/depth.ts +12 -9
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/index.ts +5 -3
- package/src/render/shaders/flat/main.ts +75 -23
- package/src/render/shaders/flat/preamble.ts +75 -20
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +8 -8
- package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
- package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
|
@@ -26,6 +26,16 @@ export interface VertexAttribute {
|
|
|
26
26
|
*/
|
|
27
27
|
export declare const VERTEX_LAYOUT: readonly VertexAttribute[];
|
|
28
28
|
/** Geometry on the device, with the buffers bound in layout order. */
|
|
29
|
+
/**
|
|
30
|
+
* The texture coordinates of a mesh that names texture-array layers: (u, v, layer), three floats.
|
|
31
|
+
*
|
|
32
|
+
* **Three only where layers exist.** Every location is spent, so the layer rides in this attribute,
|
|
33
|
+
* and widening it for every textured mesh would add four bytes to each of its vertices for nothing.
|
|
34
|
+
* A mesh without layers keeps two, and the shader's `vec3` input reads z as 0 — WebGPU fills a
|
|
35
|
+
* component the format does not carry with 0 (and w with 1) — which is layer 0. The pipeline key
|
|
36
|
+
* carries `:layers`, so the two vertex layouts never share a pipeline.
|
|
37
|
+
*/
|
|
38
|
+
export declare const LAYERED_UVS: VertexAttribute;
|
|
29
39
|
export interface GpuMesh {
|
|
30
40
|
/**
|
|
31
41
|
* The buffers a draw binds, in the order `vertexBufferLayouts` describes them.
|
|
@@ -84,6 +84,27 @@ export const VERTEX_LAYOUT = [
|
|
|
84
84
|
*/
|
|
85
85
|
{ name: 'channel', shaderLocation: 13, format: 'float32x4', components: 4, optional: true },
|
|
86
86
|
];
|
|
87
|
+
/** Geometry on the device, with the buffers bound in layout order. */
|
|
88
|
+
/**
|
|
89
|
+
* The texture coordinates of a mesh that names texture-array layers: (u, v, layer), three floats.
|
|
90
|
+
*
|
|
91
|
+
* **Three only where layers exist.** Every location is spent, so the layer rides in this attribute,
|
|
92
|
+
* and widening it for every textured mesh would add four bytes to each of its vertices for nothing.
|
|
93
|
+
* A mesh without layers keeps two, and the shader's `vec3` input reads z as 0 — WebGPU fills a
|
|
94
|
+
* component the format does not carry with 0 (and w with 1) — which is layer 0. The pipeline key
|
|
95
|
+
* carries `:layers`, so the two vertex layouts never share a pipeline.
|
|
96
|
+
*/
|
|
97
|
+
export const LAYERED_UVS = {
|
|
98
|
+
name: 'uvs',
|
|
99
|
+
shaderLocation: 5,
|
|
100
|
+
format: 'float32x3',
|
|
101
|
+
components: 3,
|
|
102
|
+
optional: true,
|
|
103
|
+
};
|
|
104
|
+
/** The attribute as this mesh lays it out: the UV field widens where the mesh names layers. */
|
|
105
|
+
function laidOut(attribute, layered) {
|
|
106
|
+
return layered && attribute.name === 'uvs' ? LAYERED_UVS : attribute;
|
|
107
|
+
}
|
|
87
108
|
/**
|
|
88
109
|
* Upload a mesh, all of it, now.
|
|
89
110
|
*
|
|
@@ -130,7 +151,8 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
130
151
|
validateMeshData(data);
|
|
131
152
|
const bounds = boundsOfPositions(data.positions, createBounds());
|
|
132
153
|
const vertexCount = data.positions.length / 3;
|
|
133
|
-
const
|
|
154
|
+
const layered = data.layers !== undefined;
|
|
155
|
+
const supplied = VERTEX_LAYOUT.filter((attribute) => !attribute.optional || data[attribute.name] !== undefined).map((attribute) => laidOut(attribute, layered));
|
|
134
156
|
/* Interleaved: one stride holding every attribute the mesh actually supplied. */
|
|
135
157
|
const stride = supplied.reduce((sum, attribute) => sum + attribute.components * 4, 0);
|
|
136
158
|
const step = stride / 4;
|
|
@@ -271,7 +293,14 @@ function* uploadSteps(job) {
|
|
|
271
293
|
let fieldOffset = 0;
|
|
272
294
|
for (const attribute of supplied) {
|
|
273
295
|
const source = data[attribute.name];
|
|
274
|
-
|
|
296
|
+
if (attribute === LAYERED_UVS) {
|
|
297
|
+
/* (u, v) from the coordinates and the layer after them, one field of three. */
|
|
298
|
+
interleaveField(interleaved, source, 2, step, fieldOffset, from, to);
|
|
299
|
+
interleaveField(interleaved, data.layers, 1, step, fieldOffset + 2, from, to);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
|
|
303
|
+
}
|
|
275
304
|
fieldOffset += attribute.components;
|
|
276
305
|
}
|
|
277
306
|
device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
|
|
@@ -449,7 +478,9 @@ instanced = false) {
|
|
|
449
478
|
const interleaved = [];
|
|
450
479
|
const constants = [];
|
|
451
480
|
let offset = 0;
|
|
452
|
-
|
|
481
|
+
const layered = present['layers'] === true;
|
|
482
|
+
VERTEX_LAYOUT.forEach((declared, index) => {
|
|
483
|
+
const attribute = laidOut(declared, layered);
|
|
453
484
|
/*
|
|
454
485
|
* **The instanced pipeline reclaims the two skinning locations, and it is allowed to because
|
|
455
486
|
* it cannot skin.** Locations 11 and 12 are the joint indices and weights; the INSTANCED
|
|
@@ -119,7 +119,11 @@ cutout = false) {
|
|
|
119
119
|
{
|
|
120
120
|
binding: CUTOUT_MAP.texture,
|
|
121
121
|
visibility: VISIBILITY_FRAGMENT,
|
|
122
|
-
texture
|
|
122
|
+
/* A surface texture, and every surface texture is a `2d-array` view. */
|
|
123
|
+
texture: {
|
|
124
|
+
sampleType: 'float',
|
|
125
|
+
viewDimension: '2d-array',
|
|
126
|
+
},
|
|
123
127
|
},
|
|
124
128
|
{
|
|
125
129
|
binding: CUTOUT_MAP.sampler,
|
|
@@ -24,6 +24,11 @@ export interface DrawCommand {
|
|
|
24
24
|
indexed: boolean;
|
|
25
25
|
count: number;
|
|
26
26
|
instances: number;
|
|
27
|
+
/**
|
|
28
|
+
* A `drawIndexedIndirect` record to draw from instead of `count` and `instances`: an instance
|
|
29
|
+
* cull's survivors, counted on the device. Null for every other draw.
|
|
30
|
+
*/
|
|
31
|
+
indirect: GPUBuffer | null;
|
|
27
32
|
}
|
|
28
33
|
export interface CommandPool {
|
|
29
34
|
commands: DrawCommand[];
|
|
@@ -178,8 +178,11 @@ export declare function flatPipeline(cache: PipelineCache, device: GPUDevice, la
|
|
|
178
178
|
* `depthMask` alone and only turns blending on. A translucent part of a loaded model is a
|
|
179
179
|
* pane of glass in a solid object, not a particle, and it has to occlude what is behind it
|
|
180
180
|
* for the rest of the model to sort correctly.
|
|
181
|
+
*
|
|
182
|
+
* `'additive'` adds instead: colour times alpha onto the target, its alpha kept. The key must
|
|
183
|
+
* carry it (`|add`).
|
|
181
184
|
*/
|
|
182
|
-
translucent?: boolean,
|
|
185
|
+
translucent?: boolean | 'additive',
|
|
183
186
|
/** Whether this pipeline reads a joint palette. See `FlatVertexOptions`. */
|
|
184
187
|
skinned?: boolean,
|
|
185
188
|
/** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
|
|
@@ -2,6 +2,13 @@ import { DEPTH_COMPARE, DEPTH_FORMAT, depthOffsetForLayer } from '../../depthCon
|
|
|
2
2
|
import { FLAT_BINDINGS, FLAT_FRAG_WGSL, FLAT_VERT_WGSL, } from '../../shaders/generated/flat.wgsl.js';
|
|
3
3
|
import { vertexBufferLayouts } from './buffers.js';
|
|
4
4
|
import { shaderModule } from './shaderModules.js';
|
|
5
|
+
/** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
|
|
6
|
+
const SURFACE_MAPS = new Set([
|
|
7
|
+
'uAlbedo',
|
|
8
|
+
'uNormalMap',
|
|
9
|
+
'uOrmMap',
|
|
10
|
+
'uEmissiveMap',
|
|
11
|
+
]);
|
|
5
12
|
/**
|
|
6
13
|
* The key for a set of flags.
|
|
7
14
|
*
|
|
@@ -198,8 +205,19 @@ export function createFlatBindGroupLayout(device, variant, skinned = false, morp
|
|
|
198
205
|
const volume = texture.type === 'sampler3D' || texture.type === 'usampler3D';
|
|
199
206
|
/* Glass tints are colour, mipmapped and filtered: a frosted pane is read at a coarser level. */
|
|
200
207
|
const tint = name.endsWith('GlassTints');
|
|
208
|
+
/*
|
|
209
|
+
* **The four material maps are arrays too, and they are colour.** Every surface texture is a
|
|
210
|
+
* `2d-array` view since texture arrays landed, so the rule below — an array is a shadow map —
|
|
211
|
+
* would declare albedo unfilterable and bind a non-filtering sampler to it, which reads as
|
|
212
|
+
* every textured surface going nearest-neighbour and losing its mip chain. Named, as
|
|
213
|
+
* `uEnvironment` is, because a pattern wide enough to catch them catches the shadow arrays.
|
|
214
|
+
*/
|
|
215
|
+
const surface = SURFACE_MAPS.has(name);
|
|
201
216
|
const isShadow = integer ||
|
|
202
|
-
(!tint &&
|
|
217
|
+
(!tint &&
|
|
218
|
+
!surface &&
|
|
219
|
+
name !== 'uEnvironment' &&
|
|
220
|
+
(name.endsWith('ShadowMap') || cube || array));
|
|
203
221
|
entries.push({
|
|
204
222
|
binding: texture.texture,
|
|
205
223
|
visibility: VISIBILITY_FRAGMENT,
|
|
@@ -349,6 +367,9 @@ export function flatPipeline(cache, device, layout, variant, key, present,
|
|
|
349
367
|
* `depthMask` alone and only turns blending on. A translucent part of a loaded model is a
|
|
350
368
|
* pane of glass in a solid object, not a particle, and it has to occlude what is behind it
|
|
351
369
|
* for the rest of the model to sort correctly.
|
|
370
|
+
*
|
|
371
|
+
* `'additive'` adds instead: colour times alpha onto the target, its alpha kept. The key must
|
|
372
|
+
* carry it (`|add`).
|
|
352
373
|
*/
|
|
353
374
|
translucent = false,
|
|
354
375
|
/** Whether this pipeline reads a joint palette. See `FlatVertexOptions`. */
|
|
@@ -470,19 +491,27 @@ doubleSided = false) {
|
|
|
470
491
|
constants: flatFragmentConstants(variant, cache),
|
|
471
492
|
targets: [
|
|
472
493
|
oitTarget(oit) ??
|
|
473
|
-
(translucent
|
|
494
|
+
(translucent === 'additive'
|
|
474
495
|
? {
|
|
475
496
|
format: cache.format,
|
|
476
497
|
blend: {
|
|
477
|
-
color: {
|
|
478
|
-
|
|
479
|
-
dstFactor: 'one-minus-src-alpha',
|
|
480
|
-
operation: 'add',
|
|
481
|
-
},
|
|
482
|
-
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
498
|
+
color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
|
|
499
|
+
alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
|
|
483
500
|
},
|
|
484
501
|
}
|
|
485
|
-
:
|
|
502
|
+
: translucent
|
|
503
|
+
? {
|
|
504
|
+
format: cache.format,
|
|
505
|
+
blend: {
|
|
506
|
+
color: {
|
|
507
|
+
srcFactor: 'src-alpha',
|
|
508
|
+
dstFactor: 'one-minus-src-alpha',
|
|
509
|
+
operation: 'add',
|
|
510
|
+
},
|
|
511
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
512
|
+
},
|
|
513
|
+
}
|
|
514
|
+
: { format: cache.format }),
|
|
486
515
|
],
|
|
487
516
|
},
|
|
488
517
|
primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
|
|
@@ -66,7 +66,8 @@ export class GpuGlassTint {
|
|
|
66
66
|
{
|
|
67
67
|
binding: CUTOUT_MAP.texture,
|
|
68
68
|
visibility: VISIBILITY_FRAGMENT,
|
|
69
|
-
texture
|
|
69
|
+
/* A surface texture, and every surface texture is a `2d-array` view. */
|
|
70
|
+
texture: { sampleType: 'float', viewDimension: '2d-array' },
|
|
70
71
|
},
|
|
71
72
|
{
|
|
72
73
|
binding: CUTOUT_MAP.sampler,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Views a batch can be culled for in one frame. See the header. */
|
|
2
|
+
export declare const MAX_CULL_SLOTS = 4;
|
|
3
|
+
/** One set of survivors: the compacted instances, the indirect record, and what the cull reads. */
|
|
4
|
+
export interface CullSlot {
|
|
5
|
+
readonly instances: GPUBuffer;
|
|
6
|
+
readonly args: GPUBuffer;
|
|
7
|
+
readonly params: GPUBuffer;
|
|
8
|
+
readonly group: GPUBindGroup;
|
|
9
|
+
/** How many instances the last queued job tested, which sizes its dispatch. */
|
|
10
|
+
count: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class InstanceCullPass {
|
|
13
|
+
private readonly device;
|
|
14
|
+
private readonly pipeline;
|
|
15
|
+
private readonly layout;
|
|
16
|
+
private readonly jobs;
|
|
17
|
+
private queued;
|
|
18
|
+
private readonly paramsStaging;
|
|
19
|
+
private readonly paramFloats;
|
|
20
|
+
private readonly paramWords;
|
|
21
|
+
private readonly argsStaging;
|
|
22
|
+
constructor(device: GPUDevice);
|
|
23
|
+
/** A slot reading `source`, a batch's full instance buffer of `capacity` instances. */
|
|
24
|
+
createSlot(source: GPUBuffer, capacity: number, label: string): CullSlot;
|
|
25
|
+
/**
|
|
26
|
+
* Queue a cull of `count` instances against six `planes`, the mesh's local sphere being
|
|
27
|
+
* (centre, radius). Returns false when the frame's job ring is full, and the caller draws uncut.
|
|
28
|
+
*/
|
|
29
|
+
queue(slot: CullSlot, planes: Float32Array, centre: ArrayLike<number>, radius: number, count: number, indexCount: number): boolean;
|
|
30
|
+
/** Dispatch everything queued, in one compute pass and one submit. Nothing queued is nothing done. */
|
|
31
|
+
flush(): void;
|
|
32
|
+
}
|
|
33
|
+
/** Every buffer a slot owns, destroyed with its batch. */
|
|
34
|
+
export declare function destroySlot(slot: CullSlot): void;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instance culls on the device: a batch's instances tested against a view and compacted into an
|
|
3
|
+
* indirect draw, many batches in one compute submit.
|
|
4
|
+
*
|
|
5
|
+
* **Queued during the frame, dispatched in one submit from `flushRings`.** Every submit that can carry
|
|
6
|
+
* a culled draw — the frame, a mirror, a probe face — calls `flushRings` first, so the dispatch that
|
|
7
|
+
* fills a slot is always on the queue ahead of the pass that draws from it. The parameters and the
|
|
8
|
+
* zeroed instance count are written with `queue.writeBuffer` when the job is queued, which the queue
|
|
9
|
+
* orders ahead of both submits.
|
|
10
|
+
*
|
|
11
|
+
* **A slot is one (batch, view) pair in one frame.** A batch drawn from the camera, a mirror and a
|
|
12
|
+
* probe face needs three sets of survivors, so a batch holds up to `MAX_CULL_SLOTS`, taken in order
|
|
13
|
+
* each frame; past that the renderer draws every instance, which is right and merely slower. Each
|
|
14
|
+
* slot owns its buffers and a bind group built once, so queueing allocates nothing.
|
|
15
|
+
*/
|
|
16
|
+
import { INSTANCE_STRIDE } from '../../instances.js';
|
|
17
|
+
import { INSTANCE_CULL_WGSL, INSTANCE_CULL_WORKGROUP } from './shaders/instanceCull.wgsl.js';
|
|
18
|
+
import { shaderModule } from './shaderModules.js';
|
|
19
|
+
/** Views a batch can be culled for in one frame. See the header. */
|
|
20
|
+
export const MAX_CULL_SLOTS = 4;
|
|
21
|
+
/** Jobs one frame may queue before the rest draw uncut. A ring size, not an opinion. */
|
|
22
|
+
const MAX_JOBS = 4096;
|
|
23
|
+
/** Bytes of `Params`: six planes, the mesh's sphere, and the instance count padded to 16. */
|
|
24
|
+
const PARAMS_BYTES = 6 * 16 + 16 + 16;
|
|
25
|
+
const USAGE = {
|
|
26
|
+
VERTEX: 0x0020,
|
|
27
|
+
UNIFORM: 0x0040,
|
|
28
|
+
STORAGE: 0x0080,
|
|
29
|
+
INDIRECT: 0x0100,
|
|
30
|
+
COPY_DST: 0x0008,
|
|
31
|
+
};
|
|
32
|
+
export class InstanceCullPass {
|
|
33
|
+
device;
|
|
34
|
+
pipeline;
|
|
35
|
+
layout;
|
|
36
|
+
jobs = new Array(MAX_JOBS).fill(null);
|
|
37
|
+
queued = 0;
|
|
38
|
+
paramsStaging = new ArrayBuffer(PARAMS_BYTES);
|
|
39
|
+
paramFloats = new Float32Array(this.paramsStaging);
|
|
40
|
+
paramWords = new Uint32Array(this.paramsStaging);
|
|
41
|
+
argsStaging = new Uint32Array(5);
|
|
42
|
+
constructor(device) {
|
|
43
|
+
this.device = device;
|
|
44
|
+
this.layout = device.createBindGroupLayout({
|
|
45
|
+
label: 'instances.cull',
|
|
46
|
+
entries: [
|
|
47
|
+
{ binding: 0, visibility: 0x4, buffer: { type: 'uniform' } },
|
|
48
|
+
{ binding: 1, visibility: 0x4, buffer: { type: 'read-only-storage' } },
|
|
49
|
+
{ binding: 2, visibility: 0x4, buffer: { type: 'storage' } },
|
|
50
|
+
{ binding: 3, visibility: 0x4, buffer: { type: 'storage' } },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
this.pipeline = device.createComputePipeline({
|
|
54
|
+
label: 'instances.cull',
|
|
55
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [this.layout] }),
|
|
56
|
+
compute: {
|
|
57
|
+
module: shaderModule(device, { label: 'instances.cull', code: INSTANCE_CULL_WGSL }),
|
|
58
|
+
entryPoint: 'main',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** A slot reading `source`, a batch's full instance buffer of `capacity` instances. */
|
|
63
|
+
createSlot(source, capacity, label) {
|
|
64
|
+
const size = Math.max(1, capacity) * INSTANCE_STRIDE;
|
|
65
|
+
const instances = this.device.createBuffer({
|
|
66
|
+
label: `${label}.kept`,
|
|
67
|
+
size,
|
|
68
|
+
usage: USAGE.VERTEX | USAGE.STORAGE,
|
|
69
|
+
});
|
|
70
|
+
const args = this.device.createBuffer({
|
|
71
|
+
label: `${label}.args`,
|
|
72
|
+
size: 20,
|
|
73
|
+
usage: USAGE.INDIRECT | USAGE.STORAGE | USAGE.COPY_DST,
|
|
74
|
+
});
|
|
75
|
+
const params = this.device.createBuffer({
|
|
76
|
+
label: `${label}.params`,
|
|
77
|
+
size: PARAMS_BYTES,
|
|
78
|
+
usage: USAGE.UNIFORM | USAGE.COPY_DST,
|
|
79
|
+
});
|
|
80
|
+
const group = this.device.createBindGroup({
|
|
81
|
+
label: `${label}.cull`,
|
|
82
|
+
layout: this.layout,
|
|
83
|
+
entries: [
|
|
84
|
+
{ binding: 0, resource: { buffer: params } },
|
|
85
|
+
{ binding: 1, resource: { buffer: source, size } },
|
|
86
|
+
{ binding: 2, resource: { buffer: instances } },
|
|
87
|
+
{ binding: 3, resource: { buffer: args } },
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
return { instances, args, params, group, count: 0 };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Queue a cull of `count` instances against six `planes`, the mesh's local sphere being
|
|
94
|
+
* (centre, radius). Returns false when the frame's job ring is full, and the caller draws uncut.
|
|
95
|
+
*/
|
|
96
|
+
queue(slot, planes, centre, radius, count, indexCount) {
|
|
97
|
+
if (this.queued >= MAX_JOBS)
|
|
98
|
+
return false;
|
|
99
|
+
const floats = this.paramFloats;
|
|
100
|
+
for (let i = 0; i < 24; i++)
|
|
101
|
+
floats[i] = planes[i];
|
|
102
|
+
floats[24] = centre[0] ?? 0;
|
|
103
|
+
floats[25] = centre[1] ?? 0;
|
|
104
|
+
floats[26] = centre[2] ?? 0;
|
|
105
|
+
floats[27] = radius;
|
|
106
|
+
this.paramWords[28] = count;
|
|
107
|
+
this.device.queue.writeBuffer(slot.params, 0, this.paramsStaging);
|
|
108
|
+
const args = this.argsStaging;
|
|
109
|
+
args[0] = indexCount;
|
|
110
|
+
args[1] = 0;
|
|
111
|
+
args[2] = 0;
|
|
112
|
+
args[3] = 0;
|
|
113
|
+
args[4] = 0;
|
|
114
|
+
this.device.queue.writeBuffer(slot.args, 0, args);
|
|
115
|
+
slot.count = count;
|
|
116
|
+
this.jobs[this.queued++] = slot;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
/** Dispatch everything queued, in one compute pass and one submit. Nothing queued is nothing done. */
|
|
120
|
+
flush() {
|
|
121
|
+
if (this.queued === 0)
|
|
122
|
+
return;
|
|
123
|
+
const encoder = this.device.createCommandEncoder({ label: 'instances.cull' });
|
|
124
|
+
const pass = encoder.beginComputePass({ label: 'instances.cull' });
|
|
125
|
+
pass.setPipeline(this.pipeline);
|
|
126
|
+
for (let i = 0; i < this.queued; i++) {
|
|
127
|
+
const slot = this.jobs[i];
|
|
128
|
+
if (slot === null || slot === undefined)
|
|
129
|
+
continue;
|
|
130
|
+
pass.setBindGroup(0, slot.group);
|
|
131
|
+
pass.dispatchWorkgroups(Math.ceil(slot.count / INSTANCE_CULL_WORKGROUP));
|
|
132
|
+
this.jobs[i] = null;
|
|
133
|
+
}
|
|
134
|
+
pass.end();
|
|
135
|
+
this.device.queue.submit([encoder.finish()]);
|
|
136
|
+
this.queued = 0;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Every buffer a slot owns, destroyed with its batch. */
|
|
140
|
+
export function destroySlot(slot) {
|
|
141
|
+
slot.instances.destroy();
|
|
142
|
+
slot.args.destroy();
|
|
143
|
+
slot.params.destroy();
|
|
144
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CullSlot } from './instanceCullPass.ts';
|
|
1
2
|
import type { MeshInstances } from '../../instances.ts';
|
|
2
3
|
import type { GpuMesh } from './buffers.ts';
|
|
3
4
|
/**
|
|
@@ -15,7 +16,16 @@ export declare class GpuInstancedBatch {
|
|
|
15
16
|
readonly mesh: GpuMesh;
|
|
16
17
|
readonly capacity: number;
|
|
17
18
|
private readonly staging;
|
|
18
|
-
|
|
19
|
+
/** Whether the camera draw culls this batch. See `InstancedOptions.cull`. */
|
|
20
|
+
readonly cull: boolean;
|
|
21
|
+
/** The world box around every live instance, fitted at upload. See `instancesBox`. */
|
|
22
|
+
readonly box: Float32Array<ArrayBuffer>;
|
|
23
|
+
/** One set of survivors per view drawn this frame. See `InstanceCullPass`. */
|
|
24
|
+
readonly slots: CullSlot[];
|
|
25
|
+
/** The frame the slots were last taken in, and how many were taken. */
|
|
26
|
+
slotFrame: number;
|
|
27
|
+
slotsTaken: number;
|
|
28
|
+
constructor(device: GPUDevice, mesh: GpuMesh, capacity: number, label: string, cull?: boolean);
|
|
19
29
|
/** Last frame's packed slots, for a reconstruction; made on first use by the renderer. */
|
|
20
30
|
previousBuffer: GPUBuffer | null;
|
|
21
31
|
/** How many slots the upload before the last one held, and how many the last one holds. */
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { instancesBox } from '../../instanceCull.js';
|
|
1
2
|
import { INSTANCE_FLOATS, INSTANCE_STRIDE, packInstances } from '../../instances.js';
|
|
3
|
+
import { destroySlot } from './instanceCullPass.js';
|
|
2
4
|
/** VERTEX | COPY_DST, spelled the way every other pass here spells it. */
|
|
3
5
|
const USAGE_VERTEX = 0x0020 | 0x0008;
|
|
6
|
+
/** A culling batch's source is read by the cull as storage too. */
|
|
7
|
+
const USAGE_STORAGE = 0x0080;
|
|
4
8
|
/**
|
|
5
9
|
* One mesh's per-instance buffer on the device.
|
|
6
10
|
*
|
|
@@ -16,9 +20,19 @@ export class GpuInstancedBatch {
|
|
|
16
20
|
mesh;
|
|
17
21
|
capacity;
|
|
18
22
|
staging;
|
|
19
|
-
|
|
23
|
+
/** Whether the camera draw culls this batch. See `InstancedOptions.cull`. */
|
|
24
|
+
cull;
|
|
25
|
+
/** The world box around every live instance, fitted at upload. See `instancesBox`. */
|
|
26
|
+
box = new Float32Array([1, 1, 1, -1, -1, -1]);
|
|
27
|
+
/** One set of survivors per view drawn this frame. See `InstanceCullPass`. */
|
|
28
|
+
slots = [];
|
|
29
|
+
/** The frame the slots were last taken in, and how many were taken. */
|
|
30
|
+
slotFrame = -1;
|
|
31
|
+
slotsTaken = 0;
|
|
32
|
+
constructor(device, mesh, capacity, label, cull = false) {
|
|
20
33
|
this.mesh = mesh;
|
|
21
34
|
this.capacity = capacity;
|
|
35
|
+
this.cull = cull;
|
|
22
36
|
this.staging = new Float32Array(capacity * INSTANCE_FLOATS);
|
|
23
37
|
this.buffer = device.createBuffer({
|
|
24
38
|
/*
|
|
@@ -26,8 +40,8 @@ export class GpuInstancedBatch {
|
|
|
26
40
|
* naming the resource it was about, and an unlabelled buffer names nothing.
|
|
27
41
|
*/
|
|
28
42
|
label,
|
|
29
|
-
size: capacity * INSTANCE_STRIDE,
|
|
30
|
-
usage: USAGE_VERTEX,
|
|
43
|
+
size: Math.max(1, capacity) * INSTANCE_STRIDE,
|
|
44
|
+
usage: USAGE_VERTEX | (cull ? USAGE_STORAGE : 0),
|
|
31
45
|
});
|
|
32
46
|
}
|
|
33
47
|
/** Last frame's packed slots, for a reconstruction; made on first use by the renderer. */
|
|
@@ -48,6 +62,8 @@ export class GpuInstancedBatch {
|
|
|
48
62
|
* `writeBuffer` copies at the call, so packing afterwards cannot reach that copy.
|
|
49
63
|
*/
|
|
50
64
|
upload(queue, data, previous = null) {
|
|
65
|
+
if (this.cull)
|
|
66
|
+
instancesBox(data, this.mesh.bounds, this.box);
|
|
51
67
|
const count = Math.min(data.count, this.capacity);
|
|
52
68
|
if (previous !== null) {
|
|
53
69
|
if (this.liveCount > 0) {
|
|
@@ -64,5 +80,7 @@ export class GpuInstancedBatch {
|
|
|
64
80
|
dispose() {
|
|
65
81
|
this.previousBuffer?.destroy();
|
|
66
82
|
this.buffer.destroy();
|
|
83
|
+
for (const slot of this.slots)
|
|
84
|
+
destroySlot(slot);
|
|
67
85
|
}
|
|
68
86
|
}
|
|
@@ -7,14 +7,18 @@ export declare class LatePass {
|
|
|
7
7
|
private readonly layout;
|
|
8
8
|
private readonly pipeline;
|
|
9
9
|
private readonly sizes;
|
|
10
|
+
/** The render size and the output size, then the frame's jitter in render texels. */
|
|
10
11
|
private readonly sizeStaging;
|
|
11
12
|
private group;
|
|
12
13
|
private groupSource;
|
|
13
14
|
constructor(device: GPUDevice);
|
|
14
15
|
/** The output-size depth, (re)made when the output changes size. */
|
|
15
16
|
depthView(width: number, height: number): GPUTextureView;
|
|
16
|
-
/**
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Fill the output-size depth from `source`, the render's single-sample depth, drawn jittered by
|
|
19
|
+
* (`jitterX`, `jitterY`) render texels — which the fill takes back out (`recon/lateDepth.ts`).
|
|
20
|
+
*/
|
|
21
|
+
upscale(encoder: GPUCommandEncoder, source: GPUTextureView, renderWidth: number, renderHeight: number, outputWidth: number, outputHeight: number, jitterX: number, jitterY: number): void;
|
|
18
22
|
/** A command slot for one late draw. Every field a caller uses it fills; see `takeCommand`. */
|
|
19
23
|
take(): DrawCommand;
|
|
20
24
|
/** Late draws recorded this frame. */
|
|
@@ -23,7 +23,8 @@ export class LatePass {
|
|
|
23
23
|
layout;
|
|
24
24
|
pipeline;
|
|
25
25
|
sizes;
|
|
26
|
-
|
|
26
|
+
/** The render size and the output size, then the frame's jitter in render texels. */
|
|
27
|
+
sizeStaging = new Float32Array(8);
|
|
27
28
|
group = null;
|
|
28
29
|
groupSource = null;
|
|
29
30
|
constructor(device) {
|
|
@@ -46,7 +47,7 @@ export class LatePass {
|
|
|
46
47
|
});
|
|
47
48
|
this.sizes = device.createBuffer({
|
|
48
49
|
label: 'recon.lateDepth.sizes',
|
|
49
|
-
size:
|
|
50
|
+
size: 32,
|
|
50
51
|
usage: 0x40 | 0x8, // UNIFORM | COPY_DST
|
|
51
52
|
});
|
|
52
53
|
}
|
|
@@ -64,13 +65,18 @@ export class LatePass {
|
|
|
64
65
|
}
|
|
65
66
|
return this.depthTextureView;
|
|
66
67
|
}
|
|
67
|
-
/**
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Fill the output-size depth from `source`, the render's single-sample depth, drawn jittered by
|
|
70
|
+
* (`jitterX`, `jitterY`) render texels — which the fill takes back out (`recon/lateDepth.ts`).
|
|
71
|
+
*/
|
|
72
|
+
upscale(encoder, source, renderWidth, renderHeight, outputWidth, outputHeight, jitterX, jitterY) {
|
|
69
73
|
const target = this.depthView(outputWidth, outputHeight);
|
|
70
74
|
this.sizeStaging[0] = renderWidth;
|
|
71
75
|
this.sizeStaging[1] = renderHeight;
|
|
72
76
|
this.sizeStaging[2] = outputWidth;
|
|
73
77
|
this.sizeStaging[3] = outputHeight;
|
|
78
|
+
this.sizeStaging[4] = jitterX;
|
|
79
|
+
this.sizeStaging[5] = jitterY;
|
|
74
80
|
this.device.queue.writeBuffer(this.sizes, 0, this.sizeStaging);
|
|
75
81
|
if (this.group === null || this.groupSource !== source) {
|
|
76
82
|
this.group = this.device.createBindGroup({
|
|
@@ -107,6 +113,7 @@ export class LatePass {
|
|
|
107
113
|
command.indexBuffer = null;
|
|
108
114
|
command.indexed = false;
|
|
109
115
|
command.instances = 1;
|
|
116
|
+
command.indirect = null;
|
|
110
117
|
return command;
|
|
111
118
|
}
|
|
112
119
|
/** Late draws recorded this frame. */
|
|
@@ -26,7 +26,7 @@ import type { PipelineCache } from './pipelineCache.ts';
|
|
|
26
26
|
* float target the generated shaders can sample as an ordinary texture.
|
|
27
27
|
*/
|
|
28
28
|
export declare const RUSH_FRAG_FIELDS: UniformFields;
|
|
29
|
-
export declare const RUSH_FRAG_SIZE:
|
|
29
|
+
export declare const RUSH_FRAG_SIZE: 208;
|
|
30
30
|
declare const RUSH_UNIFORMS: 1;
|
|
31
31
|
declare const RUSH_TEXTURES: {
|
|
32
32
|
readonly uScene: {
|