@driftengine/core 4.5.0 → 4.6.1
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 +62 -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 +249 -128
- package/dist/render/backend/webgpu/buffers.d.ts +17 -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/motionPass.js +2 -1
- 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 +478 -214
- 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 +23 -24
- package/dist/render/mesh.js +56 -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 +311 -150
- package/src/render/backend/webgpu/buffers.ts +49 -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/motionPass.ts +2 -1
- package/src/render/backend/webgpu/renderer.ts +556 -239
- 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 +64 -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
|
@@ -103,6 +103,28 @@ export const VERTEX_LAYOUT: readonly VertexAttribute[] = [
|
|
|
103
103
|
];
|
|
104
104
|
|
|
105
105
|
/** Geometry on the device, with the buffers bound in layout order. */
|
|
106
|
+
/**
|
|
107
|
+
* The texture coordinates of a mesh that names texture-array layers: (u, v, layer), three floats.
|
|
108
|
+
*
|
|
109
|
+
* **Three only where layers exist.** Every location is spent, so the layer rides in this attribute,
|
|
110
|
+
* and widening it for every textured mesh would add four bytes to each of its vertices for nothing.
|
|
111
|
+
* A mesh without layers keeps two, and the shader's `vec3` input reads z as 0 — WebGPU fills a
|
|
112
|
+
* component the format does not carry with 0 (and w with 1) — which is layer 0. The pipeline key
|
|
113
|
+
* carries `:layers`, so the two vertex layouts never share a pipeline.
|
|
114
|
+
*/
|
|
115
|
+
export const LAYERED_UVS: VertexAttribute = {
|
|
116
|
+
name: 'uvs',
|
|
117
|
+
shaderLocation: 5,
|
|
118
|
+
format: 'float32x3',
|
|
119
|
+
components: 3,
|
|
120
|
+
optional: true,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** The attribute as this mesh lays it out: the UV field widens where the mesh names layers. */
|
|
124
|
+
function laidOut(attribute: VertexAttribute, layered: boolean): VertexAttribute {
|
|
125
|
+
return layered && attribute.name === 'uvs' ? LAYERED_UVS : attribute;
|
|
126
|
+
}
|
|
127
|
+
|
|
106
128
|
export interface GpuMesh {
|
|
107
129
|
/**
|
|
108
130
|
* The buffers a draw binds, in the order `vertexBufferLayouts` describes them.
|
|
@@ -132,6 +154,13 @@ export interface GpuMesh {
|
|
|
132
154
|
*/
|
|
133
155
|
readonly skinOffsets: { readonly joints: number; readonly weights: number } | null;
|
|
134
156
|
readonly indexBuffer: GPUBuffer;
|
|
157
|
+
/**
|
|
158
|
+
* How many indices the mesh draws. **Zero is a mesh with nothing to draw, and no pass draws it**:
|
|
159
|
+
* a level can hold none of some kind of geometry, and a draw of zero indices does nothing but
|
|
160
|
+
* earn a warning from the device in every pass that issues it. Every place a mesh enters a pass
|
|
161
|
+
* checks this beside `complete` or its vertex buffers, and WebGL2 skips the same meshes, so the
|
|
162
|
+
* two backends count the same draws.
|
|
163
|
+
*/
|
|
135
164
|
readonly indexCount: number;
|
|
136
165
|
/**
|
|
137
166
|
* How big this mesh is, in its own space.
|
|
@@ -281,9 +310,10 @@ export function createGpuMeshIncremental(
|
|
|
281
310
|
validateMeshData(data);
|
|
282
311
|
const bounds = boundsOfPositions(data.positions, createBounds());
|
|
283
312
|
const vertexCount = data.positions.length / 3;
|
|
313
|
+
const layered = data.layers !== undefined;
|
|
284
314
|
const supplied = VERTEX_LAYOUT.filter(
|
|
285
315
|
(attribute) => !attribute.optional || data[attribute.name as keyof MeshData] !== undefined,
|
|
286
|
-
);
|
|
316
|
+
).map((attribute) => laidOut(attribute, layered));
|
|
287
317
|
|
|
288
318
|
/* Interleaved: one stride holding every attribute the mesh actually supplied. */
|
|
289
319
|
const stride = supplied.reduce((sum, attribute) => sum + attribute.components * 4, 0);
|
|
@@ -459,7 +489,21 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
|
|
|
459
489
|
let fieldOffset = 0;
|
|
460
490
|
for (const attribute of supplied) {
|
|
461
491
|
const source = data[attribute.name as keyof MeshData] as Float32Array;
|
|
462
|
-
|
|
492
|
+
if (attribute === LAYERED_UVS) {
|
|
493
|
+
/* (u, v) from the coordinates and the layer after them, one field of three. */
|
|
494
|
+
interleaveField(interleaved, source, 2, step, fieldOffset, from, to);
|
|
495
|
+
interleaveField(
|
|
496
|
+
interleaved,
|
|
497
|
+
data.layers as Float32Array,
|
|
498
|
+
1,
|
|
499
|
+
step,
|
|
500
|
+
fieldOffset + 2,
|
|
501
|
+
from,
|
|
502
|
+
to,
|
|
503
|
+
);
|
|
504
|
+
} else {
|
|
505
|
+
interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
|
|
506
|
+
}
|
|
463
507
|
fieldOffset += attribute.components;
|
|
464
508
|
}
|
|
465
509
|
device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
|
|
@@ -678,7 +722,9 @@ export function vertexBufferLayouts(
|
|
|
678
722
|
const constants: GPUVertexAttribute[] = [];
|
|
679
723
|
let offset = 0;
|
|
680
724
|
|
|
681
|
-
|
|
725
|
+
const layered = present['layers'] === true;
|
|
726
|
+
VERTEX_LAYOUT.forEach((declared, index) => {
|
|
727
|
+
const attribute = laidOut(declared, layered);
|
|
682
728
|
/*
|
|
683
729
|
* **The instanced pipeline reclaims the two skinning locations, and it is allowed to because
|
|
684
730
|
* it cannot skin.** Locations 11 and 12 are the joint indices and weights; the INSTANCED
|
|
@@ -137,7 +137,11 @@ export function createDepthBindGroupLayout(
|
|
|
137
137
|
{
|
|
138
138
|
binding: CUTOUT_MAP.texture,
|
|
139
139
|
visibility: VISIBILITY_FRAGMENT,
|
|
140
|
-
texture
|
|
140
|
+
/* A surface texture, and every surface texture is a `2d-array` view. */
|
|
141
|
+
texture: {
|
|
142
|
+
sampleType: 'float' as GPUTextureSampleType,
|
|
143
|
+
viewDimension: '2d-array' as GPUTextureViewDimension,
|
|
144
|
+
},
|
|
141
145
|
},
|
|
142
146
|
{
|
|
143
147
|
binding: CUTOUT_MAP.sampler,
|
|
@@ -28,6 +28,11 @@ export interface DrawCommand {
|
|
|
28
28
|
indexed: boolean;
|
|
29
29
|
count: number;
|
|
30
30
|
instances: number;
|
|
31
|
+
/**
|
|
32
|
+
* A `drawIndexedIndirect` record to draw from instead of `count` and `instances`: an instance
|
|
33
|
+
* cull's survivors, counted on the device. Null for every other draw.
|
|
34
|
+
*/
|
|
35
|
+
indirect: GPUBuffer | null;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
export interface CommandPool {
|
|
@@ -50,6 +55,7 @@ function emptyCommand(): DrawCommand {
|
|
|
50
55
|
indexed: false,
|
|
51
56
|
count: 0,
|
|
52
57
|
instances: 1,
|
|
58
|
+
indirect: null,
|
|
53
59
|
};
|
|
54
60
|
}
|
|
55
61
|
|
|
@@ -8,6 +8,14 @@ import { vertexBufferLayouts } from './buffers.ts';
|
|
|
8
8
|
import type { PipelineCache } from './pipelineCache.ts';
|
|
9
9
|
import { shaderModule } from './shaderModules.ts';
|
|
10
10
|
|
|
11
|
+
/** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
|
|
12
|
+
const SURFACE_MAPS: ReadonlySet<string> = new Set([
|
|
13
|
+
'uAlbedo',
|
|
14
|
+
'uNormalMap',
|
|
15
|
+
'uOrmMap',
|
|
16
|
+
'uEmissiveMap',
|
|
17
|
+
]);
|
|
18
|
+
|
|
11
19
|
/**
|
|
12
20
|
* The flat pipeline: the shader that draws the world.
|
|
13
21
|
*
|
|
@@ -290,9 +298,20 @@ export function createFlatBindGroupLayout(
|
|
|
290
298
|
const volume = texture.type === 'sampler3D' || texture.type === 'usampler3D';
|
|
291
299
|
/* Glass tints are colour, mipmapped and filtered: a frosted pane is read at a coarser level. */
|
|
292
300
|
const tint = name.endsWith('GlassTints');
|
|
301
|
+
/*
|
|
302
|
+
* **The four material maps are arrays too, and they are colour.** Every surface texture is a
|
|
303
|
+
* `2d-array` view since texture arrays landed, so the rule below — an array is a shadow map —
|
|
304
|
+
* would declare albedo unfilterable and bind a non-filtering sampler to it, which reads as
|
|
305
|
+
* every textured surface going nearest-neighbour and losing its mip chain. Named, as
|
|
306
|
+
* `uEnvironment` is, because a pattern wide enough to catch them catches the shadow arrays.
|
|
307
|
+
*/
|
|
308
|
+
const surface = SURFACE_MAPS.has(name);
|
|
293
309
|
const isShadow =
|
|
294
310
|
integer ||
|
|
295
|
-
(!tint &&
|
|
311
|
+
(!tint &&
|
|
312
|
+
!surface &&
|
|
313
|
+
name !== 'uEnvironment' &&
|
|
314
|
+
(name.endsWith('ShadowMap') || cube || array));
|
|
296
315
|
entries.push({
|
|
297
316
|
binding: texture.texture,
|
|
298
317
|
visibility: VISIBILITY_FRAGMENT,
|
|
@@ -462,8 +481,11 @@ export function flatPipeline(
|
|
|
462
481
|
* `depthMask` alone and only turns blending on. A translucent part of a loaded model is a
|
|
463
482
|
* pane of glass in a solid object, not a particle, and it has to occlude what is behind it
|
|
464
483
|
* for the rest of the model to sort correctly.
|
|
484
|
+
*
|
|
485
|
+
* `'additive'` adds instead: colour times alpha onto the target, its alpha kept. The key must
|
|
486
|
+
* carry it (`|add`).
|
|
465
487
|
*/
|
|
466
|
-
translucent = false,
|
|
488
|
+
translucent: boolean | 'additive' = false,
|
|
467
489
|
/** Whether this pipeline reads a joint palette. See `FlatVertexOptions`. */
|
|
468
490
|
skinned = false,
|
|
469
491
|
/** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
|
|
@@ -576,7 +598,7 @@ function flatDescriptor(
|
|
|
576
598
|
variant: FlatVariant,
|
|
577
599
|
key: string,
|
|
578
600
|
present: Readonly<Record<string, boolean>>,
|
|
579
|
-
translucent: boolean,
|
|
601
|
+
translucent: boolean | 'additive',
|
|
580
602
|
/** Whether this pipeline reads a joint palette. See `FlatVertexOptions`. */
|
|
581
603
|
skinned: boolean,
|
|
582
604
|
/** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
|
|
@@ -626,19 +648,27 @@ function flatDescriptor(
|
|
|
626
648
|
constants: flatFragmentConstants(variant, cache),
|
|
627
649
|
targets: [
|
|
628
650
|
oitTarget(oit) ??
|
|
629
|
-
(translucent
|
|
651
|
+
(translucent === 'additive'
|
|
630
652
|
? {
|
|
631
653
|
format: cache.format,
|
|
632
654
|
blend: {
|
|
633
|
-
color: {
|
|
634
|
-
|
|
635
|
-
dstFactor: 'one-minus-src-alpha',
|
|
636
|
-
operation: 'add',
|
|
637
|
-
},
|
|
638
|
-
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
655
|
+
color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
|
|
656
|
+
alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
|
|
639
657
|
},
|
|
640
658
|
}
|
|
641
|
-
:
|
|
659
|
+
: translucent
|
|
660
|
+
? {
|
|
661
|
+
format: cache.format,
|
|
662
|
+
blend: {
|
|
663
|
+
color: {
|
|
664
|
+
srcFactor: 'src-alpha',
|
|
665
|
+
dstFactor: 'one-minus-src-alpha',
|
|
666
|
+
operation: 'add',
|
|
667
|
+
},
|
|
668
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
669
|
+
},
|
|
670
|
+
}
|
|
671
|
+
: { format: cache.format }),
|
|
642
672
|
],
|
|
643
673
|
},
|
|
644
674
|
primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
|
|
@@ -90,7 +90,8 @@ export class GpuGlassTint {
|
|
|
90
90
|
{
|
|
91
91
|
binding: CUTOUT_MAP.texture,
|
|
92
92
|
visibility: VISIBILITY_FRAGMENT,
|
|
93
|
-
texture
|
|
93
|
+
/* A surface texture, and every surface texture is a `2d-array` view. */
|
|
94
|
+
texture: { sampleType: 'float', viewDimension: '2d-array' },
|
|
94
95
|
},
|
|
95
96
|
{
|
|
96
97
|
binding: CUTOUT_MAP.sampler,
|
|
@@ -0,0 +1,165 @@
|
|
|
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.ts';
|
|
17
|
+
import { INSTANCE_CULL_WGSL, INSTANCE_CULL_WORKGROUP } from './shaders/instanceCull.wgsl.ts';
|
|
18
|
+
import { shaderModule } from './shaderModules.ts';
|
|
19
|
+
|
|
20
|
+
/** Views a batch can be culled for in one frame. See the header. */
|
|
21
|
+
export const MAX_CULL_SLOTS = 4;
|
|
22
|
+
|
|
23
|
+
/** Jobs one frame may queue before the rest draw uncut. A ring size, not an opinion. */
|
|
24
|
+
const MAX_JOBS = 4096;
|
|
25
|
+
|
|
26
|
+
/** Bytes of `Params`: six planes, the mesh's sphere, and the instance count padded to 16. */
|
|
27
|
+
const PARAMS_BYTES = 6 * 16 + 16 + 16;
|
|
28
|
+
|
|
29
|
+
const USAGE = {
|
|
30
|
+
VERTEX: 0x0020,
|
|
31
|
+
UNIFORM: 0x0040,
|
|
32
|
+
STORAGE: 0x0080,
|
|
33
|
+
INDIRECT: 0x0100,
|
|
34
|
+
COPY_DST: 0x0008,
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
/** One set of survivors: the compacted instances, the indirect record, and what the cull reads. */
|
|
38
|
+
export interface CullSlot {
|
|
39
|
+
readonly instances: GPUBuffer;
|
|
40
|
+
readonly args: GPUBuffer;
|
|
41
|
+
readonly params: GPUBuffer;
|
|
42
|
+
readonly group: GPUBindGroup;
|
|
43
|
+
/** How many instances the last queued job tested, which sizes its dispatch. */
|
|
44
|
+
count: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class InstanceCullPass {
|
|
48
|
+
private readonly pipeline: GPUComputePipeline;
|
|
49
|
+
private readonly layout: GPUBindGroupLayout;
|
|
50
|
+
private readonly jobs: (CullSlot | null)[] = new Array<CullSlot | null>(MAX_JOBS).fill(null);
|
|
51
|
+
private queued = 0;
|
|
52
|
+
private readonly paramsStaging = new ArrayBuffer(PARAMS_BYTES);
|
|
53
|
+
private readonly paramFloats = new Float32Array(this.paramsStaging);
|
|
54
|
+
private readonly paramWords = new Uint32Array(this.paramsStaging);
|
|
55
|
+
private readonly argsStaging = new Uint32Array(5);
|
|
56
|
+
|
|
57
|
+
constructor(private readonly device: GPUDevice) {
|
|
58
|
+
this.layout = device.createBindGroupLayout({
|
|
59
|
+
label: 'instances.cull',
|
|
60
|
+
entries: [
|
|
61
|
+
{ binding: 0, visibility: 0x4, buffer: { type: 'uniform' } },
|
|
62
|
+
{ binding: 1, visibility: 0x4, buffer: { type: 'read-only-storage' } },
|
|
63
|
+
{ binding: 2, visibility: 0x4, buffer: { type: 'storage' } },
|
|
64
|
+
{ binding: 3, visibility: 0x4, buffer: { type: 'storage' } },
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
this.pipeline = device.createComputePipeline({
|
|
68
|
+
label: 'instances.cull',
|
|
69
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [this.layout] }),
|
|
70
|
+
compute: {
|
|
71
|
+
module: shaderModule(device, { label: 'instances.cull', code: INSTANCE_CULL_WGSL }),
|
|
72
|
+
entryPoint: 'main',
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A slot reading `source`, a batch's full instance buffer of `capacity` instances. */
|
|
78
|
+
createSlot(source: GPUBuffer, capacity: number, label: string): CullSlot {
|
|
79
|
+
const size = Math.max(1, capacity) * INSTANCE_STRIDE;
|
|
80
|
+
const instances = this.device.createBuffer({
|
|
81
|
+
label: `${label}.kept`,
|
|
82
|
+
size,
|
|
83
|
+
usage: USAGE.VERTEX | USAGE.STORAGE,
|
|
84
|
+
});
|
|
85
|
+
const args = this.device.createBuffer({
|
|
86
|
+
label: `${label}.args`,
|
|
87
|
+
size: 20,
|
|
88
|
+
usage: USAGE.INDIRECT | USAGE.STORAGE | USAGE.COPY_DST,
|
|
89
|
+
});
|
|
90
|
+
const params = this.device.createBuffer({
|
|
91
|
+
label: `${label}.params`,
|
|
92
|
+
size: PARAMS_BYTES,
|
|
93
|
+
usage: USAGE.UNIFORM | USAGE.COPY_DST,
|
|
94
|
+
});
|
|
95
|
+
const group = this.device.createBindGroup({
|
|
96
|
+
label: `${label}.cull`,
|
|
97
|
+
layout: this.layout,
|
|
98
|
+
entries: [
|
|
99
|
+
{ binding: 0, resource: { buffer: params } },
|
|
100
|
+
{ binding: 1, resource: { buffer: source, size } },
|
|
101
|
+
{ binding: 2, resource: { buffer: instances } },
|
|
102
|
+
{ binding: 3, resource: { buffer: args } },
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
return { instances, args, params, group, count: 0 };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Queue a cull of `count` instances against six `planes`, the mesh's local sphere being
|
|
110
|
+
* (centre, radius). Returns false when the frame's job ring is full, and the caller draws uncut.
|
|
111
|
+
*/
|
|
112
|
+
queue(
|
|
113
|
+
slot: CullSlot,
|
|
114
|
+
planes: Float32Array,
|
|
115
|
+
centre: ArrayLike<number>,
|
|
116
|
+
radius: number,
|
|
117
|
+
count: number,
|
|
118
|
+
indexCount: number,
|
|
119
|
+
): boolean {
|
|
120
|
+
if (this.queued >= MAX_JOBS) return false;
|
|
121
|
+
const floats = this.paramFloats;
|
|
122
|
+
for (let i = 0; i < 24; i++) floats[i] = planes[i] as number;
|
|
123
|
+
floats[24] = centre[0] ?? 0;
|
|
124
|
+
floats[25] = centre[1] ?? 0;
|
|
125
|
+
floats[26] = centre[2] ?? 0;
|
|
126
|
+
floats[27] = radius;
|
|
127
|
+
this.paramWords[28] = count;
|
|
128
|
+
this.device.queue.writeBuffer(slot.params, 0, this.paramsStaging);
|
|
129
|
+
const args = this.argsStaging;
|
|
130
|
+
args[0] = indexCount;
|
|
131
|
+
args[1] = 0;
|
|
132
|
+
args[2] = 0;
|
|
133
|
+
args[3] = 0;
|
|
134
|
+
args[4] = 0;
|
|
135
|
+
this.device.queue.writeBuffer(slot.args, 0, args);
|
|
136
|
+
slot.count = count;
|
|
137
|
+
this.jobs[this.queued++] = slot;
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Dispatch everything queued, in one compute pass and one submit. Nothing queued is nothing done. */
|
|
142
|
+
flush(): void {
|
|
143
|
+
if (this.queued === 0) return;
|
|
144
|
+
const encoder = this.device.createCommandEncoder({ label: 'instances.cull' });
|
|
145
|
+
const pass = encoder.beginComputePass({ label: 'instances.cull' });
|
|
146
|
+
pass.setPipeline(this.pipeline);
|
|
147
|
+
for (let i = 0; i < this.queued; i++) {
|
|
148
|
+
const slot = this.jobs[i];
|
|
149
|
+
if (slot === null || slot === undefined) continue;
|
|
150
|
+
pass.setBindGroup(0, slot.group);
|
|
151
|
+
pass.dispatchWorkgroups(Math.ceil(slot.count / INSTANCE_CULL_WORKGROUP));
|
|
152
|
+
this.jobs[i] = null;
|
|
153
|
+
}
|
|
154
|
+
pass.end();
|
|
155
|
+
this.device.queue.submit([encoder.finish()]);
|
|
156
|
+
this.queued = 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Every buffer a slot owns, destroyed with its batch. */
|
|
161
|
+
export function destroySlot(slot: CullSlot): void {
|
|
162
|
+
slot.instances.destroy();
|
|
163
|
+
slot.args.destroy();
|
|
164
|
+
slot.params.destroy();
|
|
165
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { instancesBox } from '../../instanceCull.ts';
|
|
1
2
|
import { INSTANCE_FLOATS, INSTANCE_STRIDE, packInstances } from '../../instances.ts';
|
|
3
|
+
import { destroySlot } from './instanceCullPass.ts';
|
|
4
|
+
import type { CullSlot } from './instanceCullPass.ts';
|
|
2
5
|
import type { MeshInstances } from '../../instances.ts';
|
|
3
6
|
import type { GpuMesh } from './buffers.ts';
|
|
4
7
|
|
|
5
8
|
/** VERTEX | COPY_DST, spelled the way every other pass here spells it. */
|
|
6
9
|
const USAGE_VERTEX = 0x0020 | 0x0008;
|
|
10
|
+
/** A culling batch's source is read by the cull as storage too. */
|
|
11
|
+
const USAGE_STORAGE = 0x0080;
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* One mesh's per-instance buffer on the device.
|
|
@@ -21,9 +26,20 @@ export class GpuInstancedBatch {
|
|
|
21
26
|
readonly capacity: number;
|
|
22
27
|
private readonly staging: Float32Array;
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
/** Whether the camera draw culls this batch. See `InstancedOptions.cull`. */
|
|
30
|
+
readonly cull: boolean;
|
|
31
|
+
/** The world box around every live instance, fitted at upload. See `instancesBox`. */
|
|
32
|
+
readonly box = new Float32Array([1, 1, 1, -1, -1, -1]);
|
|
33
|
+
/** One set of survivors per view drawn this frame. See `InstanceCullPass`. */
|
|
34
|
+
readonly slots: CullSlot[] = [];
|
|
35
|
+
/** The frame the slots were last taken in, and how many were taken. */
|
|
36
|
+
slotFrame = -1;
|
|
37
|
+
slotsTaken = 0;
|
|
38
|
+
|
|
39
|
+
constructor(device: GPUDevice, mesh: GpuMesh, capacity: number, label: string, cull = false) {
|
|
25
40
|
this.mesh = mesh;
|
|
26
41
|
this.capacity = capacity;
|
|
42
|
+
this.cull = cull;
|
|
27
43
|
this.staging = new Float32Array(capacity * INSTANCE_FLOATS);
|
|
28
44
|
this.buffer = device.createBuffer({
|
|
29
45
|
/*
|
|
@@ -31,8 +47,8 @@ export class GpuInstancedBatch {
|
|
|
31
47
|
* naming the resource it was about, and an unlabelled buffer names nothing.
|
|
32
48
|
*/
|
|
33
49
|
label,
|
|
34
|
-
size: capacity * INSTANCE_STRIDE,
|
|
35
|
-
usage: USAGE_VERTEX,
|
|
50
|
+
size: Math.max(1, capacity) * INSTANCE_STRIDE,
|
|
51
|
+
usage: USAGE_VERTEX | (cull ? USAGE_STORAGE : 0),
|
|
36
52
|
});
|
|
37
53
|
}
|
|
38
54
|
|
|
@@ -55,6 +71,7 @@ export class GpuInstancedBatch {
|
|
|
55
71
|
* `writeBuffer` copies at the call, so packing afterwards cannot reach that copy.
|
|
56
72
|
*/
|
|
57
73
|
upload(queue: GPUQueue, data: MeshInstances, previous: GPUBuffer | null = null): void {
|
|
74
|
+
if (this.cull) instancesBox(data, this.mesh.bounds, this.box);
|
|
58
75
|
const count = Math.min(data.count, this.capacity);
|
|
59
76
|
if (previous !== null) {
|
|
60
77
|
if (this.liveCount > 0) {
|
|
@@ -71,5 +88,6 @@ export class GpuInstancedBatch {
|
|
|
71
88
|
dispose(): void {
|
|
72
89
|
this.previousBuffer?.destroy();
|
|
73
90
|
this.buffer.destroy();
|
|
91
|
+
for (const slot of this.slots) destroySlot(slot);
|
|
74
92
|
}
|
|
75
93
|
}
|
|
@@ -25,7 +25,8 @@ export class LatePass {
|
|
|
25
25
|
private readonly layout: GPUBindGroupLayout;
|
|
26
26
|
private readonly pipeline: GPURenderPipeline;
|
|
27
27
|
private readonly sizes: GPUBuffer;
|
|
28
|
-
|
|
28
|
+
/** The render size and the output size, then the frame's jitter in render texels. */
|
|
29
|
+
private readonly sizeStaging = new Float32Array(8);
|
|
29
30
|
private group: GPUBindGroup | null = null;
|
|
30
31
|
private groupSource: GPUTextureView | null = null;
|
|
31
32
|
|
|
@@ -49,7 +50,7 @@ export class LatePass {
|
|
|
49
50
|
});
|
|
50
51
|
this.sizes = device.createBuffer({
|
|
51
52
|
label: 'recon.lateDepth.sizes',
|
|
52
|
-
size:
|
|
53
|
+
size: 32,
|
|
53
54
|
usage: 0x40 | 0x8, // UNIFORM | COPY_DST
|
|
54
55
|
});
|
|
55
56
|
}
|
|
@@ -69,7 +70,10 @@ export class LatePass {
|
|
|
69
70
|
return this.depthTextureView as GPUTextureView;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Fill the output-size depth from `source`, the render's single-sample depth, drawn jittered by
|
|
75
|
+
* (`jitterX`, `jitterY`) render texels — which the fill takes back out (`recon/lateDepth.ts`).
|
|
76
|
+
*/
|
|
73
77
|
upscale(
|
|
74
78
|
encoder: GPUCommandEncoder,
|
|
75
79
|
source: GPUTextureView,
|
|
@@ -77,12 +81,16 @@ export class LatePass {
|
|
|
77
81
|
renderHeight: number,
|
|
78
82
|
outputWidth: number,
|
|
79
83
|
outputHeight: number,
|
|
84
|
+
jitterX: number,
|
|
85
|
+
jitterY: number,
|
|
80
86
|
): void {
|
|
81
87
|
const target = this.depthView(outputWidth, outputHeight);
|
|
82
88
|
this.sizeStaging[0] = renderWidth;
|
|
83
89
|
this.sizeStaging[1] = renderHeight;
|
|
84
90
|
this.sizeStaging[2] = outputWidth;
|
|
85
91
|
this.sizeStaging[3] = outputHeight;
|
|
92
|
+
this.sizeStaging[4] = jitterX;
|
|
93
|
+
this.sizeStaging[5] = jitterY;
|
|
86
94
|
this.device.queue.writeBuffer(this.sizes, 0, this.sizeStaging);
|
|
87
95
|
if (this.group === null || this.groupSource !== source) {
|
|
88
96
|
this.group = this.device.createBindGroup({
|
|
@@ -120,6 +128,7 @@ export class LatePass {
|
|
|
120
128
|
command.indexBuffer = null;
|
|
121
129
|
command.indexed = false;
|
|
122
130
|
command.instances = 1;
|
|
131
|
+
command.indirect = null;
|
|
123
132
|
return command;
|
|
124
133
|
}
|
|
125
134
|
|
|
@@ -509,7 +509,8 @@ export class MotionPass {
|
|
|
509
509
|
} else {
|
|
510
510
|
pipeline = this.pipelineFor(mesh.vertexStride);
|
|
511
511
|
}
|
|
512
|
-
|
|
512
|
+
/* A mesh with nothing in it draws nothing: see `GpuMesh.indexCount`. */
|
|
513
|
+
if (pipeline === null || mesh.indexCount === 0) continue;
|
|
513
514
|
if (pipeline !== bound) {
|
|
514
515
|
pass.setPipeline(pipeline);
|
|
515
516
|
bound = pipeline;
|