@driftengine/core 4.3.0 → 4.4.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 +282 -0
- package/README.md +1 -1
- package/dist/cinematic/cameraPath.d.ts +53 -0
- package/dist/cinematic/cameraPath.js +118 -0
- package/dist/environment/daylight.d.ts +77 -0
- package/dist/environment/daylight.js +77 -0
- package/dist/geometry/meshConcat.d.ts +9 -0
- package/dist/geometry/meshConcat.js +101 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +14 -1
- package/dist/render/ambientOcclusionPass.js +5 -1
- package/dist/render/backend/createRenderer.d.ts +13 -1
- package/dist/render/backend/createRenderer.js +6 -5
- package/dist/render/backend/webgl2/renderer.d.ts +147 -1
- package/dist/render/backend/webgl2/renderer.js +402 -23
- package/dist/render/backend/webgpu/buffers.js +46 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +81 -3
- package/dist/render/backend/webgpu/depthPass.js +75 -14
- package/dist/render/backend/webgpu/exposurePass.d.ts +39 -0
- package/dist/render/backend/webgpu/exposurePass.js +190 -0
- package/dist/render/backend/webgpu/fieldCompose.d.ts +11 -0
- package/dist/render/backend/webgpu/fieldCompose.js +13 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +3 -1
- package/dist/render/backend/webgpu/flatPass.js +12 -5
- package/dist/render/backend/webgpu/particlePass.d.ts +8 -0
- package/dist/render/backend/webgpu/particlePass.js +1 -0
- package/dist/render/backend/webgpu/postPass.d.ts +13 -3
- package/dist/render/backend/webgpu/probeBake.d.ts +59 -3
- package/dist/render/backend/webgpu/probeBake.js +97 -7
- package/dist/render/backend/webgpu/probeTargets.d.ts +65 -0
- package/dist/render/backend/webgpu/probeTargets.js +180 -0
- package/dist/render/backend/webgpu/renderer.d.ts +174 -11
- package/dist/render/backend/webgpu/renderer.js +682 -252
- package/dist/render/backend/webgpu/scatterPass.js +7 -7
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +9 -0
- package/dist/render/backend/webgpu/skyPass.js +5 -1
- package/dist/render/backend/webgpu/uniformRing.d.ts +31 -1
- package/dist/render/backend/webgpu/uniformRing.js +53 -3
- package/dist/render/bloomChain.d.ts +6 -0
- package/dist/render/bloomChain.js +8 -0
- package/dist/render/clusteredLights.d.ts +19 -11
- package/dist/render/clusteredLights.js +96 -45
- package/dist/render/cutoutCaster.d.ts +23 -0
- package/dist/render/cutoutCaster.js +20 -0
- package/dist/render/driftLight/bake.d.ts +34 -0
- package/dist/render/driftLight/bake.js +95 -0
- package/dist/render/driftLight/falloff.d.ts +16 -0
- package/dist/render/driftLight/falloff.js +23 -0
- package/dist/render/driftLight/layout.d.ts +59 -0
- package/dist/render/driftLight/layout.js +138 -0
- package/dist/render/driftLight/lightField.d.ts +108 -0
- package/dist/render/driftLight/lightField.js +179 -0
- package/dist/render/driftLight/uniforms.d.ts +28 -0
- package/dist/render/driftLight/uniforms.js +41 -0
- package/dist/render/driftLight/visibility.d.ts +18 -0
- package/dist/render/driftLight/visibility.js +47 -0
- package/dist/render/emptyTexture.d.ts +6 -0
- package/dist/render/emptyTexture.js +19 -0
- package/dist/render/exposurePass.d.ts +48 -0
- package/dist/render/exposurePass.js +172 -0
- package/dist/render/flameLight.d.ts +71 -0
- package/dist/render/flameLight.js +63 -0
- package/dist/render/gi/bounceLights.d.ts +34 -0
- package/dist/render/gi/bounceLights.js +66 -0
- package/dist/render/gi/fieldReach.d.ts +12 -0
- package/dist/render/gi/fieldReach.js +62 -0
- package/dist/render/gi/globalField.d.ts +8 -0
- package/dist/render/gi/globalField.js +31 -0
- package/dist/render/gi/probeTrace.d.ts +3 -1
- package/dist/render/gi/probeTrace.js +3 -1
- package/dist/render/gpuCapability.js +19 -4
- package/dist/render/lightBudget.d.ts +11 -0
- package/dist/render/lightBudget.js +11 -0
- package/dist/render/pointLightSelection.d.ts +16 -0
- package/dist/render/pointLightSelection.js +178 -83
- package/dist/render/pointShadowBudget.d.ts +12 -2
- package/dist/render/pointShadowBudget.js +16 -10
- package/dist/render/probeGrid.d.ts +11 -1
- package/dist/render/probeGrid.js +5 -2
- package/dist/render/probeSweeps.d.ts +45 -0
- package/dist/render/probeSweeps.js +77 -0
- package/dist/render/reflectionProbe.d.ts +43 -1
- package/dist/render/reflectionProbe.js +17 -3
- package/dist/render/renderQuality.d.ts +3 -1
- package/dist/render/rendererHarness.d.ts +9 -0
- package/dist/render/rendererHarness.js +18 -3
- package/dist/render/sceneTarget.d.ts +20 -0
- package/dist/render/sceneTarget.js +62 -1
- package/dist/render/shaders/ambientOcclusion.d.ts +63 -6
- package/dist/render/shaders/ambientOcclusion.js +188 -71
- package/dist/render/shaders/cutoutCoverage.d.ts +31 -0
- package/dist/render/shaders/cutoutCoverage.js +40 -0
- package/dist/render/shaders/depth.d.ts +25 -0
- package/dist/render/shaders/depth.js +69 -3
- package/dist/render/shaders/exposure.d.ts +52 -0
- package/dist/render/shaders/exposure.js +124 -0
- package/dist/render/shaders/filmLook.d.ts +47 -0
- package/dist/render/shaders/filmLook.js +98 -0
- package/dist/render/shaders/flat/directionalShadow.js +35 -4
- package/dist/render/shaders/flat/driftLight.d.ts +29 -0
- package/dist/render/shaders/flat/driftLight.js +75 -0
- package/dist/render/shaders/flat/index.js +5 -0
- package/dist/render/shaders/flat/lobes.js +13 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +68 -9
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/probeGrid.js +46 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +8 -3
- package/dist/render/shaders/generated/depth.wgsl.d.ts +100 -0
- package/dist/render/shaders/generated/depth.wgsl.js +100 -0
- package/dist/render/shaders/generated/exposure.wgsl.d.ts +42 -0
- package/dist/render/shaders/generated/exposure.wgsl.js +48 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1800 -1344
- package/dist/render/shaders/generated/flat.wgsl.js +1816 -1360
- package/dist/render/shaders/generated/localExposure.wgsl.d.ts +18 -0
- package/dist/render/shaders/generated/localExposure.wgsl.js +24 -0
- package/dist/render/shaders/generated/rush.wgsl.d.ts +40 -10
- package/dist/render/shaders/generated/rush.wgsl.js +40 -10
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +16 -0
- package/dist/render/shaders/gi/driftLight.wgsl.js +49 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +29 -4
- package/dist/render/shaders/gi/probeBake.wgsl.js +190 -20
- package/dist/render/shaders/globalMedium.js +4 -0
- package/dist/render/shaders/localExposure.d.ts +35 -0
- package/dist/render/shaders/localExposure.js +138 -0
- package/dist/render/shaders/prefilter.d.ts +1 -1
- package/dist/render/shaders/prefilter.js +6 -0
- package/dist/render/shaders/rush.js +33 -5
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +15 -3
- package/dist/render/shaders/temporalResolve.js +39 -1
- package/dist/render/shadowCasters.d.ts +5 -3
- package/dist/render/shadowMap.js +13 -0
- package/dist/render/surfaceTexture.d.ts +5 -0
- package/package.json +6 -6
- package/scripts/gpuCompute.mjs +24 -3
- package/scripts/png.mjs +23 -12
- package/src/cinematic/cameraPath.ts +178 -0
- package/src/environment/daylight.ts +152 -0
- package/src/geometry/meshConcat.ts +100 -0
- package/src/index.ts +32 -0
- package/src/render/ambientOcclusionPass.ts +5 -1
- package/src/render/backend/createRenderer.ts +22 -6
- package/src/render/backend/webgl2/renderer.ts +439 -20
- package/src/render/backend/webgpu/buffers.ts +51 -8
- package/src/render/backend/webgpu/depthPass.ts +78 -10
- package/src/render/backend/webgpu/exposurePass.ts +234 -0
- package/src/render/backend/webgpu/fieldCompose.ts +15 -0
- package/src/render/backend/webgpu/flatPass.ts +10 -2
- package/src/render/backend/webgpu/particlePass.ts +9 -0
- package/src/render/backend/webgpu/probeBake.ts +143 -9
- package/src/render/backend/webgpu/probeTargets.ts +211 -0
- package/src/render/backend/webgpu/renderer.ts +808 -273
- package/src/render/backend/webgpu/scatterPass.ts +7 -7
- package/src/render/backend/webgpu/skinPaletteRing.ts +10 -0
- package/src/render/backend/webgpu/skyPass.ts +5 -1
- package/src/render/backend/webgpu/uniformRing.ts +49 -3
- package/src/render/bloomChain.ts +9 -0
- package/src/render/clusteredLights.ts +108 -63
- package/src/render/cutoutCaster.ts +44 -0
- package/src/render/driftLight/bake.ts +125 -0
- package/src/render/driftLight/falloff.ts +26 -0
- package/src/render/driftLight/layout.ts +177 -0
- package/src/render/driftLight/lightField.ts +227 -0
- package/src/render/driftLight/uniforms.ts +61 -0
- package/src/render/driftLight/visibility.ts +58 -0
- package/src/render/emptyTexture.ts +41 -0
- package/src/render/exposurePass.ts +186 -0
- package/src/render/flameLight.ts +135 -0
- package/src/render/gi/bounceLights.ts +68 -0
- package/src/render/gi/fieldReach.ts +74 -0
- package/src/render/gi/globalField.ts +39 -0
- package/src/render/gi/probeTrace.ts +3 -1
- package/src/render/gpuCapability.ts +18 -5
- package/src/render/lightBudget.ts +12 -0
- package/src/render/pointLightSelection.ts +191 -79
- package/src/render/pointShadowBudget.ts +16 -8
- package/src/render/probeGrid.ts +13 -2
- package/src/render/probeSweeps.ts +80 -0
- package/src/render/reflectionProbe.ts +54 -2
- package/src/render/renderQuality.ts +3 -1
- package/src/render/rendererHarness.ts +25 -3
- package/src/render/sceneTarget.ts +75 -1
- package/src/render/shaders/ambientOcclusion.ts +205 -71
- package/src/render/shaders/cutoutCoverage.ts +43 -0
- package/src/render/shaders/depth.ts +80 -3
- package/src/render/shaders/exposure.ts +128 -0
- package/src/render/shaders/filmLook.ts +111 -0
- package/src/render/shaders/flat/directionalShadow.ts +35 -4
- package/src/render/shaders/flat/driftLight.ts +75 -0
- package/src/render/shaders/flat/index.ts +5 -0
- package/src/render/shaders/flat/lobes.ts +13 -0
- package/src/render/shaders/flat/main.ts +68 -9
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/probeGrid.ts +46 -3
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +8 -3
- package/src/render/shaders/generated/depth.wgsl.ts +103 -0
- package/src/render/shaders/generated/exposure.wgsl.ts +51 -0
- package/src/render/shaders/generated/flat.wgsl.ts +1816 -1360
- package/src/render/shaders/generated/localExposure.wgsl.ts +26 -0
- package/src/render/shaders/generated/rush.wgsl.ts +40 -10
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +49 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +191 -20
- package/src/render/shaders/globalMedium.ts +4 -0
- package/src/render/shaders/localExposure.ts +154 -0
- package/src/render/shaders/prefilter.ts +6 -0
- package/src/render/shaders/rush.ts +33 -5
- package/src/render/shaders/tangentFrame.ts +15 -3
- package/src/render/shaders/temporalResolve.ts +39 -1
- package/src/render/shadowCasters.ts +5 -3
- package/src/render/shadowMap.ts +14 -0
- package/src/render/surfaceTexture.ts +5 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { boundsOfPositions, createBounds } from '../../../math/bounds.ts';
|
|
2
2
|
import type { Bounds } from '../../../math/bounds.ts';
|
|
3
3
|
import { MorphTexture } from './morphTexture.ts';
|
|
4
|
+
import { validateMeshData } from '../../mesh.ts';
|
|
4
5
|
import type { MeshData } from '../../mesh.ts';
|
|
5
6
|
import { ABSENT_ATTRIBUTE } from '../../vertexDefaults.ts';
|
|
6
7
|
import { UPLOAD_BYTES_PER_STEP } from '../../uploadStep.ts';
|
|
@@ -255,6 +256,11 @@ export function createGpuMeshIncremental(
|
|
|
255
256
|
data: MeshData,
|
|
256
257
|
dynamic = false,
|
|
257
258
|
): IncrementalGpuMesh {
|
|
259
|
+
/*
|
|
260
|
+
* The other backend's refusal, and the premise of the interleave below: every attribute holds
|
|
261
|
+
* exactly a vertex's worth for every vertex, so nothing reads past the end of its array.
|
|
262
|
+
*/
|
|
263
|
+
validateMeshData(data);
|
|
258
264
|
const bounds = boundsOfPositions(data.positions, createBounds());
|
|
259
265
|
const vertexCount = data.positions.length / 3;
|
|
260
266
|
const supplied = VERTEX_LAYOUT.filter(
|
|
@@ -434,14 +440,8 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
|
|
|
434
440
|
let fieldOffset = 0;
|
|
435
441
|
for (const attribute of supplied) {
|
|
436
442
|
const source = data[attribute.name as keyof MeshData] as Float32Array;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
for (let component = 0; component < width; component++) {
|
|
440
|
-
interleaved[vertex * step + fieldOffset + component] =
|
|
441
|
-
source[vertex * width + component] ?? 0;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
fieldOffset += width;
|
|
443
|
+
interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
|
|
444
|
+
fieldOffset += attribute.components;
|
|
445
445
|
}
|
|
446
446
|
device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
|
|
447
447
|
taken += 1;
|
|
@@ -462,6 +462,49 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
|
|
|
462
462
|
job.progress.uploaded = true;
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
+
/**
|
|
466
|
+
* One attribute of vertices `from` to `to` into its field of the interleaved rows.
|
|
467
|
+
*
|
|
468
|
+
* **Unrolled by width, because the general loop was most of a heavy load's upload.** A loop over
|
|
469
|
+
* components inside a loop over vertices, with the index recomputed from both, cost 1.5 s of main
|
|
470
|
+
* thread on a courtyard of five packs; a loop per width walking two cursors does the same writes in
|
|
471
|
+
* about a third of the time, measured on four million vertices in Node. Every width the layout
|
|
472
|
+
* carries is one, two, three or four, so the last arm is the one-float case and nothing else.
|
|
473
|
+
*/
|
|
474
|
+
function interleaveField(
|
|
475
|
+
rows: Float32Array,
|
|
476
|
+
source: Float32Array,
|
|
477
|
+
width: number,
|
|
478
|
+
step: number,
|
|
479
|
+
fieldOffset: number,
|
|
480
|
+
from: number,
|
|
481
|
+
to: number,
|
|
482
|
+
): void {
|
|
483
|
+
let write = from * step + fieldOffset;
|
|
484
|
+
const end = to * width;
|
|
485
|
+
if (width === 3) {
|
|
486
|
+
for (let read = from * 3; read < end; read += 3, write += step) {
|
|
487
|
+
rows[write] = source[read] as number;
|
|
488
|
+
rows[write + 1] = source[read + 1] as number;
|
|
489
|
+
rows[write + 2] = source[read + 2] as number;
|
|
490
|
+
}
|
|
491
|
+
} else if (width === 4) {
|
|
492
|
+
for (let read = from * 4; read < end; read += 4, write += step) {
|
|
493
|
+
rows[write] = source[read] as number;
|
|
494
|
+
rows[write + 1] = source[read + 1] as number;
|
|
495
|
+
rows[write + 2] = source[read + 2] as number;
|
|
496
|
+
rows[write + 3] = source[read + 3] as number;
|
|
497
|
+
}
|
|
498
|
+
} else if (width === 2) {
|
|
499
|
+
for (let read = from * 2; read < end; read += 2, write += step) {
|
|
500
|
+
rows[write] = source[read] as number;
|
|
501
|
+
rows[write + 1] = source[read + 1] as number;
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
for (let read = from; read < end; read++, write += step) rows[write] = source[read] as number;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
465
508
|
/** The handle, made where it can see only what it hands out. */
|
|
466
509
|
function gpuMeshOf(parts: {
|
|
467
510
|
readonly vertices: GPUBuffer;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEPTH_BINDINGS,
|
|
3
|
+
DEPTH_CUTOUT_FRAG_WGSL,
|
|
4
|
+
DEPTH_CUTOUT_VERT_WGSL,
|
|
3
5
|
DEPTH_FRAG_WGSL,
|
|
6
|
+
DEPTH_INSTANCED_CUTOUT_VERT_WGSL,
|
|
4
7
|
DEPTH_INSTANCED_VERT_WGSL,
|
|
5
8
|
DEPTH_SKINNED_VERT_WGSL,
|
|
6
9
|
DEPTH_VERT_WGSL,
|
|
@@ -20,6 +23,15 @@ import { shaderModule } from './shaderModules.ts';
|
|
|
20
23
|
export const DEPTH_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_VERT.fields;
|
|
21
24
|
export const DEPTH_VERT_SIZE = DEPTH_BINDINGS.DEPTH_VERT.uniformSize;
|
|
22
25
|
export const DEPTH_FRAG_SIZE = DEPTH_BINDINGS.DEPTH_FRAG.uniformSize;
|
|
26
|
+
/*
|
|
27
|
+
* The cutout variants' vertex blocks. The rigid one is the plain block with `uUvScale` and
|
|
28
|
+
* `uAlphaCutout` after every other field, so a slot written for it is written with the plain
|
|
29
|
+
* offsets plus these two; the instanced one has no model and no wind, so its two sit earlier.
|
|
30
|
+
*/
|
|
31
|
+
export const DEPTH_CUTOUT_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.fields;
|
|
32
|
+
export const DEPTH_CUTOUT_VERT_SIZE = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.uniformSize;
|
|
33
|
+
export const DEPTH_INSTANCED_CUTOUT_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_INSTANCED_CUTOUT_VERT.fields;
|
|
34
|
+
const CUTOUT_MAP = DEPTH_BINDINGS.DEPTH_CUTOUT_FRAG.textures.uCutoutMap;
|
|
23
35
|
/** Re-exported so the scatter depth pass can pair with the same fragment stage. */
|
|
24
36
|
export { DEPTH_FRAG_WGSL };
|
|
25
37
|
export const DEPTH_FRAG_BINDING = DEPTH_BINDINGS.DEPTH_FRAG.uniforms;
|
|
@@ -55,9 +67,15 @@ export function createDepthBindGroupLayout(
|
|
|
55
67
|
* frame that skins nothing pays nothing at all.
|
|
56
68
|
*/
|
|
57
69
|
skinned = false,
|
|
70
|
+
/**
|
|
71
|
+
* Whether this layout carries a cutout map, for a caster whose shape is in its texture's alpha.
|
|
72
|
+
* Separate for the palette's reason: the plain layout stays exactly what it was, and a frame with
|
|
73
|
+
* no cutout caster pays nothing.
|
|
74
|
+
*/
|
|
75
|
+
cutout = false,
|
|
58
76
|
): GPUBindGroupLayout {
|
|
59
77
|
return device.createBindGroupLayout({
|
|
60
|
-
label: skinned ? 'depth.layout.skinned' : 'depth.layout',
|
|
78
|
+
label: skinned ? 'depth.layout.skinned' : cutout ? 'depth.layout.cutout' : 'depth.layout',
|
|
61
79
|
entries: [
|
|
62
80
|
/* The texture and no sampler: the shader reads the palette with `textureLoad`, an integer
|
|
63
81
|
coordinate with nothing to filter, which is what `flatPass.ts` binds for the same
|
|
@@ -74,7 +92,11 @@ export function createDepthBindGroupLayout(
|
|
|
74
92
|
{
|
|
75
93
|
binding: VERT_BINDING,
|
|
76
94
|
visibility: VISIBILITY_VERTEX,
|
|
77
|
-
buffer: {
|
|
95
|
+
buffer: {
|
|
96
|
+
type: 'uniform',
|
|
97
|
+
hasDynamicOffset: true,
|
|
98
|
+
minBindingSize: cutout ? DEPTH_CUTOUT_VERT_SIZE : DEPTH_VERT_SIZE,
|
|
99
|
+
},
|
|
78
100
|
},
|
|
79
101
|
{
|
|
80
102
|
binding: FRAG_BINDING,
|
|
@@ -107,6 +129,22 @@ export function createDepthBindGroupLayout(
|
|
|
107
129
|
visibility: VISIBILITY_FRAGMENT,
|
|
108
130
|
sampler: { type: 'non-filtering' },
|
|
109
131
|
},
|
|
132
|
+
/* A colour map sampled with its own filter and mips, so a leaf's edge in the map is the edge
|
|
133
|
+
a colour pass at that distance would draw. */
|
|
134
|
+
...(cutout
|
|
135
|
+
? [
|
|
136
|
+
{
|
|
137
|
+
binding: CUTOUT_MAP.texture,
|
|
138
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
139
|
+
texture: { sampleType: 'float' as GPUTextureSampleType },
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
binding: CUTOUT_MAP.sampler,
|
|
143
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
144
|
+
sampler: { type: 'filtering' as GPUSamplerBindingType },
|
|
145
|
+
},
|
|
146
|
+
]
|
|
147
|
+
: []),
|
|
110
148
|
],
|
|
111
149
|
});
|
|
112
150
|
}
|
|
@@ -120,16 +158,35 @@ export function createDepthBindGroup(
|
|
|
120
158
|
sampler: GPUSampler,
|
|
121
159
|
/** The joint palette, for a layout built with `skinned`. Omit for the rigid layout. */
|
|
122
160
|
palette: GPUTextureView | null = null,
|
|
161
|
+
/** The cutout map and its sampler, for a layout built with `cutout`. */
|
|
162
|
+
cutout: { readonly view: GPUTextureView; readonly sampler: GPUSampler } | null = null,
|
|
123
163
|
): GPUBindGroup {
|
|
124
164
|
return device.createBindGroup({
|
|
125
|
-
label:
|
|
165
|
+
label:
|
|
166
|
+
palette !== null
|
|
167
|
+
? 'depth.bindGroup.skinned'
|
|
168
|
+
: cutout !== null
|
|
169
|
+
? 'depth.bindGroup.cutout'
|
|
170
|
+
: 'depth.bindGroup',
|
|
126
171
|
layout,
|
|
127
172
|
entries: [
|
|
128
|
-
{
|
|
173
|
+
{
|
|
174
|
+
binding: VERT_BINDING,
|
|
175
|
+
resource: {
|
|
176
|
+
buffer: perDraw,
|
|
177
|
+
size: cutout !== null ? DEPTH_CUTOUT_VERT_SIZE : DEPTH_VERT_SIZE,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
129
180
|
{ binding: FRAG_BINDING, resource: { buffer: perPass, size: DEPTH_FRAG_SIZE } },
|
|
130
181
|
{ binding: PREVIOUS.texture, resource: previous },
|
|
131
182
|
{ binding: PREVIOUS.sampler, resource: sampler },
|
|
132
183
|
...(palette !== null ? [{ binding: DEPTH_PALETTE_BINDING.texture, resource: palette }] : []),
|
|
184
|
+
...(cutout !== null
|
|
185
|
+
? [
|
|
186
|
+
{ binding: CUTOUT_MAP.texture, resource: cutout.view },
|
|
187
|
+
{ binding: CUTOUT_MAP.sampler, resource: cutout.sampler },
|
|
188
|
+
]
|
|
189
|
+
: []),
|
|
133
190
|
],
|
|
134
191
|
});
|
|
135
192
|
}
|
|
@@ -169,6 +226,8 @@ export function depthPipeline(
|
|
|
169
226
|
* reclaims the two attribute locations the joint indices and weights occupy.
|
|
170
227
|
*/
|
|
171
228
|
instanced = false,
|
|
229
|
+
/** Whether this pipeline casts a cutout: the variant that reads a map and discards by it. */
|
|
230
|
+
cutout = false,
|
|
172
231
|
): GPURenderPipeline {
|
|
173
232
|
return cache.get(key, () => ({
|
|
174
233
|
label: key,
|
|
@@ -176,17 +235,26 @@ export function depthPipeline(
|
|
|
176
235
|
vertex: {
|
|
177
236
|
module: shaderModule(
|
|
178
237
|
device,
|
|
179
|
-
|
|
180
|
-
?
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
238
|
+
cutout
|
|
239
|
+
? instanced
|
|
240
|
+
? { label: 'depth.instanced.cutout.vert', code: DEPTH_INSTANCED_CUTOUT_VERT_WGSL }
|
|
241
|
+
: { label: 'depth.cutout.vert', code: DEPTH_CUTOUT_VERT_WGSL }
|
|
242
|
+
: instanced
|
|
243
|
+
? { label: 'depth.instanced.vert', code: DEPTH_INSTANCED_VERT_WGSL }
|
|
244
|
+
: skinned
|
|
245
|
+
? { label: 'depth.skinned.vert', code: DEPTH_SKINNED_VERT_WGSL }
|
|
246
|
+
: { label: 'depth.vert', code: DEPTH_VERT_WGSL },
|
|
184
247
|
),
|
|
185
248
|
entryPoint: 'main',
|
|
186
249
|
buffers: vertexBufferLayouts(present, instanced),
|
|
187
250
|
},
|
|
188
251
|
fragment: {
|
|
189
|
-
module: shaderModule(
|
|
252
|
+
module: shaderModule(
|
|
253
|
+
device,
|
|
254
|
+
cutout
|
|
255
|
+
? { label: 'depth.cutout.frag', code: DEPTH_CUTOUT_FRAG_WGSL }
|
|
256
|
+
: { label: 'depth.frag', code: DEPTH_FRAG_WGSL },
|
|
257
|
+
),
|
|
190
258
|
entryPoint: 'main',
|
|
191
259
|
targets: [],
|
|
192
260
|
},
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXPOSURE_ADAPT_FRAG_WGSL,
|
|
3
|
+
EXPOSURE_BINDINGS,
|
|
4
|
+
EXPOSURE_METER_FRAG_WGSL,
|
|
5
|
+
} from '../../shaders/generated/exposure.wgsl.ts';
|
|
6
|
+
import {
|
|
7
|
+
EXPOSURE_LOCAL_FRAG_WGSL,
|
|
8
|
+
LOCALEXPOSURE_BINDINGS,
|
|
9
|
+
} from '../../shaders/generated/localExposure.wgsl.ts';
|
|
10
|
+
import { METER_GRID, adaptBlend } from '../../shaders/exposure.ts';
|
|
11
|
+
import { LOCAL_GRID_WIDTH } from '../../shaders/localExposure.ts';
|
|
12
|
+
import { createPostStageLayout, postPipeline } from './postPass.ts';
|
|
13
|
+
import type { PipelineCache } from './pipelineCache.ts';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Eye adaptation on WebGPU: meter the finished scene into the frame's own encoder, and follow it
|
|
17
|
+
* with a held brightness the composite reads. See `shaders/exposure.ts` for the design.
|
|
18
|
+
*
|
|
19
|
+
* **One held texel the composite always binds, and a scratch the adaptation writes.** Reading and
|
|
20
|
+
* writing one texture in a pass is refused, and a pair that swapped roles would mean a composite
|
|
21
|
+
* bind group per role; so the adaptation writes the scratch from the held value and a copy puts it
|
|
22
|
+
* back, on the same encoder, before the composite reads it. Recorded into the frame's encoder
|
|
23
|
+
* rather than submitted apart, because a separate submission would run before the frame that
|
|
24
|
+
* draws the scene it is meant to measure.
|
|
25
|
+
*
|
|
26
|
+
* **And local exposure's grid, when that is asked for**, in the same encoder after the meter:
|
|
27
|
+
* `rg16float`, a sum and a count per tile per band, sampled filtered by the composite. See
|
|
28
|
+
* `localExposure.ts`.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const METER = EXPOSURE_BINDINGS.EXPOSURE_METER_FRAG.textures;
|
|
32
|
+
const ADAPT = EXPOSURE_BINDINGS.EXPOSURE_ADAPT_FRAG;
|
|
33
|
+
const LOCAL = LOCALEXPOSURE_BINDINGS.EXPOSURE_LOCAL_FRAG.textures;
|
|
34
|
+
const USAGE_RENDER = 0x10;
|
|
35
|
+
const USAGE_TEXTURE = 0x04;
|
|
36
|
+
const USAGE_COPY_SRC = 0x01;
|
|
37
|
+
const USAGE_COPY_DST = 0x02;
|
|
38
|
+
const USAGE_UNIFORM_DST = 0x40 | 0x08;
|
|
39
|
+
const VISIBILITY_FRAGMENT = 0x2;
|
|
40
|
+
|
|
41
|
+
export class ExposurePass {
|
|
42
|
+
private readonly meter: GPUTexture;
|
|
43
|
+
private readonly meterView: GPUTextureView;
|
|
44
|
+
private readonly held: GPUTexture;
|
|
45
|
+
/** The texel the composite binds, for its bind group. */
|
|
46
|
+
readonly heldView: GPUTextureView;
|
|
47
|
+
private readonly scratch: GPUTexture;
|
|
48
|
+
private readonly scratchView: GPUTextureView;
|
|
49
|
+
private readonly localGrid: GPUTexture;
|
|
50
|
+
/** Local exposure's grid, for the composite's bind group. */
|
|
51
|
+
readonly localView: GPUTextureView;
|
|
52
|
+
private readonly localLayout: GPUBindGroupLayout;
|
|
53
|
+
private localGroup: GPUBindGroup | null = null;
|
|
54
|
+
private localSource: GPUTextureView | null = null;
|
|
55
|
+
private readonly meterLayout: GPUBindGroupLayout;
|
|
56
|
+
private readonly adaptLayout: GPUBindGroupLayout;
|
|
57
|
+
private readonly adaptGroup: GPUBindGroup;
|
|
58
|
+
private readonly uniforms: GPUBuffer;
|
|
59
|
+
private readonly blend = new Float32Array(4);
|
|
60
|
+
private meterGroup: GPUBindGroup | null = null;
|
|
61
|
+
private meterSource: GPUTextureView | null = null;
|
|
62
|
+
/** Whether anything is held yet: the first frame, and the first after a cut, snaps. */
|
|
63
|
+
private holding = false;
|
|
64
|
+
|
|
65
|
+
constructor(
|
|
66
|
+
private readonly device: GPUDevice,
|
|
67
|
+
private readonly pipelines: PipelineCache,
|
|
68
|
+
private readonly sampler: GPUSampler,
|
|
69
|
+
) {
|
|
70
|
+
const target = (label: string, size: number, usage: number): GPUTexture =>
|
|
71
|
+
device.createTexture({ label, size: [size, size], format: 'r16float', usage });
|
|
72
|
+
this.meter = target('exposure.meter', METER_GRID, USAGE_RENDER | USAGE_TEXTURE);
|
|
73
|
+
this.held = target('exposure.held', 1, USAGE_RENDER | USAGE_TEXTURE | USAGE_COPY_DST);
|
|
74
|
+
this.scratch = target('exposure.scratch', 1, USAGE_RENDER | USAGE_COPY_SRC);
|
|
75
|
+
this.localGrid = device.createTexture({
|
|
76
|
+
label: 'exposure.localGrid',
|
|
77
|
+
size: [LOCAL_GRID_WIDTH, METER_GRID],
|
|
78
|
+
format: 'rg16float',
|
|
79
|
+
usage: USAGE_RENDER | USAGE_TEXTURE,
|
|
80
|
+
});
|
|
81
|
+
this.localView = this.localGrid.createView();
|
|
82
|
+
this.meterView = this.meter.createView();
|
|
83
|
+
this.heldView = this.held.createView();
|
|
84
|
+
this.scratchView = this.scratch.createView();
|
|
85
|
+
|
|
86
|
+
/* The meter has no uniforms, so its layout is its one texture and sampler and nothing else. */
|
|
87
|
+
this.meterLayout = device.createBindGroupLayout({
|
|
88
|
+
label: 'exposure.meterLayout',
|
|
89
|
+
entries: [
|
|
90
|
+
{ binding: METER.uScene.texture, visibility: VISIBILITY_FRAGMENT, texture: {} },
|
|
91
|
+
{ binding: METER.uScene.sampler, visibility: VISIBILITY_FRAGMENT, sampler: {} },
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
this.localLayout = device.createBindGroupLayout({
|
|
95
|
+
label: 'exposure.localLayout',
|
|
96
|
+
entries: [
|
|
97
|
+
{ binding: LOCAL.uScene.texture, visibility: VISIBILITY_FRAGMENT, texture: {} },
|
|
98
|
+
{ binding: LOCAL.uScene.sampler, visibility: VISIBILITY_FRAGMENT, sampler: {} },
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
this.adaptLayout = createPostStageLayout(device, ADAPT.uniforms, ADAPT.uniformSize, [
|
|
102
|
+
{ binding: ADAPT.textures.uMeter },
|
|
103
|
+
{ binding: ADAPT.textures.uHeld },
|
|
104
|
+
]);
|
|
105
|
+
this.uniforms = device.createBuffer({
|
|
106
|
+
label: 'exposure.uniforms',
|
|
107
|
+
size: ADAPT.uniformSize,
|
|
108
|
+
usage: USAGE_UNIFORM_DST,
|
|
109
|
+
});
|
|
110
|
+
this.adaptGroup = device.createBindGroup({
|
|
111
|
+
label: 'exposure.adaptGroup',
|
|
112
|
+
layout: this.adaptLayout,
|
|
113
|
+
entries: [
|
|
114
|
+
{ binding: ADAPT.uniforms, resource: { buffer: this.uniforms, size: ADAPT.uniformSize } },
|
|
115
|
+
{ binding: ADAPT.textures.uMeter.texture, resource: this.meterView },
|
|
116
|
+
{ binding: ADAPT.textures.uMeter.sampler, resource: sampler },
|
|
117
|
+
{ binding: ADAPT.textures.uHeld.texture, resource: this.heldView },
|
|
118
|
+
{ binding: ADAPT.textures.uHeld.sampler, resource: sampler },
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Forget what is held, so the next frame meters afresh rather than easing from another shot. */
|
|
124
|
+
cut(): void {
|
|
125
|
+
this.holding = false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Meter `scene` and move the held brightness toward it by `dtSec` of adaptation, and with `local`
|
|
130
|
+
* draw local exposure's grid too.
|
|
131
|
+
*/
|
|
132
|
+
run(encoder: GPUCommandEncoder, scene: GPUTextureView, dtSec: number, local = false): void {
|
|
133
|
+
const { device } = this;
|
|
134
|
+
if (this.meterSource !== scene || this.meterGroup === null) {
|
|
135
|
+
this.meterGroup = device.createBindGroup({
|
|
136
|
+
label: 'exposure.meterGroup',
|
|
137
|
+
layout: this.meterLayout,
|
|
138
|
+
entries: [
|
|
139
|
+
{ binding: METER.uScene.texture, resource: scene },
|
|
140
|
+
{ binding: METER.uScene.sampler, resource: this.sampler },
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
this.meterSource = scene;
|
|
144
|
+
}
|
|
145
|
+
this.blend[0] = adaptBlend(dtSec, !this.holding);
|
|
146
|
+
device.queue.writeBuffer(this.uniforms, 0, this.blend);
|
|
147
|
+
|
|
148
|
+
const meter = encoder.beginRenderPass({
|
|
149
|
+
label: 'exposure.meter',
|
|
150
|
+
colorAttachments: [
|
|
151
|
+
{ view: this.meterView, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
152
|
+
],
|
|
153
|
+
});
|
|
154
|
+
meter.setPipeline(
|
|
155
|
+
postPipeline(
|
|
156
|
+
this.pipelines,
|
|
157
|
+
device,
|
|
158
|
+
this.meterLayout,
|
|
159
|
+
'exposure.meter',
|
|
160
|
+
EXPOSURE_METER_FRAG_WGSL,
|
|
161
|
+
'r16float',
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
meter.setBindGroup(0, this.meterGroup);
|
|
165
|
+
meter.draw(3);
|
|
166
|
+
meter.end();
|
|
167
|
+
if (local) this.drawLocal(encoder, scene);
|
|
168
|
+
|
|
169
|
+
const adapt = encoder.beginRenderPass({
|
|
170
|
+
label: 'exposure.adapt',
|
|
171
|
+
colorAttachments: [
|
|
172
|
+
{ view: this.scratchView, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
adapt.setPipeline(
|
|
176
|
+
postPipeline(
|
|
177
|
+
this.pipelines,
|
|
178
|
+
device,
|
|
179
|
+
this.adaptLayout,
|
|
180
|
+
'exposure.adapt',
|
|
181
|
+
EXPOSURE_ADAPT_FRAG_WGSL,
|
|
182
|
+
'r16float',
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
adapt.setBindGroup(0, this.adaptGroup, [0]);
|
|
186
|
+
adapt.draw(3);
|
|
187
|
+
adapt.end();
|
|
188
|
+
encoder.copyTextureToTexture({ texture: this.scratch }, { texture: this.held }, [1, 1, 1]);
|
|
189
|
+
this.holding = true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Local exposure's grid, from the same scene the meter read. */
|
|
193
|
+
private drawLocal(encoder: GPUCommandEncoder, scene: GPUTextureView): void {
|
|
194
|
+
const { device } = this;
|
|
195
|
+
if (this.localSource !== scene || this.localGroup === null) {
|
|
196
|
+
this.localGroup = device.createBindGroup({
|
|
197
|
+
label: 'exposure.localGroup',
|
|
198
|
+
layout: this.localLayout,
|
|
199
|
+
entries: [
|
|
200
|
+
{ binding: LOCAL.uScene.texture, resource: scene },
|
|
201
|
+
{ binding: LOCAL.uScene.sampler, resource: this.sampler },
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
this.localSource = scene;
|
|
205
|
+
}
|
|
206
|
+
const pass = encoder.beginRenderPass({
|
|
207
|
+
label: 'exposure.local',
|
|
208
|
+
colorAttachments: [
|
|
209
|
+
{ view: this.localView, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
pass.setPipeline(
|
|
213
|
+
postPipeline(
|
|
214
|
+
this.pipelines,
|
|
215
|
+
device,
|
|
216
|
+
this.localLayout,
|
|
217
|
+
'exposure.local',
|
|
218
|
+
EXPOSURE_LOCAL_FRAG_WGSL,
|
|
219
|
+
'rg16float',
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
pass.setBindGroup(0, this.localGroup);
|
|
223
|
+
pass.draw(3);
|
|
224
|
+
pass.end();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
dispose(): void {
|
|
228
|
+
this.localGrid.destroy();
|
|
229
|
+
this.meter.destroy();
|
|
230
|
+
this.held.destroy();
|
|
231
|
+
this.scratch.destroy();
|
|
232
|
+
this.uniforms.destroy();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -71,6 +71,16 @@ export const DEFAULT_FIELD_COMPOSE: FieldComposeOptions = {
|
|
|
71
71
|
radius: 4,
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The most cascades a field is given to reach a probe grid, which is `cascadesForGrid`'s ceiling.
|
|
76
|
+
*
|
|
77
|
+
* Six from the 4 m default reach 128 m, and each is the same 49 cubed samples, about half a
|
|
78
|
+
* megabyte, so the bound is on memory rather than on distance. What would make it wrong is a scene
|
|
79
|
+
* whose probe grid is longer than about a hundred metres, whose far probes are then left untraced
|
|
80
|
+
* rather than traced against a field that is not there.
|
|
81
|
+
*/
|
|
82
|
+
export const MAX_FIELD_CASCADES = 6;
|
|
83
|
+
|
|
74
84
|
export interface FieldComposeOptions {
|
|
75
85
|
/** Samples a side, every cascade. */
|
|
76
86
|
readonly resolution: number;
|
|
@@ -147,6 +157,11 @@ export class FieldComposer {
|
|
|
147
157
|
private timed = false;
|
|
148
158
|
private composeMsValue = 0;
|
|
149
159
|
|
|
160
|
+
/** How many cascades this composer was built with. */
|
|
161
|
+
get cascadeCount(): number {
|
|
162
|
+
return this.options.cascades;
|
|
163
|
+
}
|
|
164
|
+
|
|
150
165
|
constructor(device: GPUDevice, options: FieldComposeOptions) {
|
|
151
166
|
if (!(Math.trunc(options.resolution) >= 2)) {
|
|
152
167
|
throw new Error(
|
|
@@ -269,7 +269,9 @@ export function createFlatBindGroupLayout(
|
|
|
269
269
|
* other kind against a `uint` texture, and the shader never samples it — every read is a
|
|
270
270
|
* `texelFetch`, which is why the declaration shares a sampler rather than bringing its own.
|
|
271
271
|
*/
|
|
272
|
-
const integer = texture.type === 'usampler2D';
|
|
272
|
+
const integer = texture.type === 'usampler2D' || texture.type === 'usampler3D';
|
|
273
|
+
/* DriftLight's brick index and atlas, the lit pass's only volumes. See `driftLight.ts`. */
|
|
274
|
+
const volume = texture.type === 'sampler3D' || texture.type === 'usampler3D';
|
|
273
275
|
const isShadow =
|
|
274
276
|
integer || (name !== 'uEnvironment' && (name.endsWith('ShadowMap') || cube || array));
|
|
275
277
|
entries.push({
|
|
@@ -279,6 +281,7 @@ export function createFlatBindGroupLayout(
|
|
|
279
281
|
sampleType: integer ? 'uint' : isShadow ? 'unfilterable-float' : 'float',
|
|
280
282
|
...(cube ? { viewDimension: 'cube' as const } : {}),
|
|
281
283
|
...(array ? { viewDimension: '2d-array' as const } : {}),
|
|
284
|
+
...(volume ? { viewDimension: '3d' as const } : {}),
|
|
282
285
|
},
|
|
283
286
|
});
|
|
284
287
|
/*
|
|
@@ -460,6 +463,8 @@ export function flatPipeline(
|
|
|
460
463
|
instanced = false,
|
|
461
464
|
/** Which order-independent buffer this pipeline writes, if any. The key must carry it. */
|
|
462
465
|
oit: OitTarget = 'none',
|
|
466
|
+
/** Culls nothing, for a surface seen from both faces. The key must carry it: `|2s`. */
|
|
467
|
+
doubleSided = false,
|
|
463
468
|
): GPURenderPipeline {
|
|
464
469
|
return cache.get(key, () =>
|
|
465
470
|
flatDescriptor(
|
|
@@ -476,6 +481,7 @@ export function flatPipeline(
|
|
|
476
481
|
depthLayer,
|
|
477
482
|
instanced,
|
|
478
483
|
oit,
|
|
484
|
+
doubleSided,
|
|
479
485
|
),
|
|
480
486
|
);
|
|
481
487
|
}
|
|
@@ -571,6 +577,8 @@ function flatDescriptor(
|
|
|
571
577
|
* `flatFrag`'s permutation axis, which `scripts/wgsl.ts` measures at about 247 KB gzipped.
|
|
572
578
|
*/
|
|
573
579
|
oit: OitTarget = 'none',
|
|
580
|
+
/** Culls nothing: a surface seen from both faces. */
|
|
581
|
+
doubleSided = false,
|
|
574
582
|
): GPURenderPipelineDescriptor {
|
|
575
583
|
const offset = depthOffsetForLayer(depthLayer);
|
|
576
584
|
return {
|
|
@@ -612,7 +620,7 @@ function flatDescriptor(
|
|
|
612
620
|
: { format: cache.format }),
|
|
613
621
|
],
|
|
614
622
|
},
|
|
615
|
-
primitive: { topology: 'triangle-list', cullMode: 'back' },
|
|
623
|
+
primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
|
|
616
624
|
multisample: { count: cache.sampleCount },
|
|
617
625
|
/*
|
|
618
626
|
* `less`, matching the WebGL2 path's `LESS` rather than `LEQUAL`, and for the reason
|
|
@@ -118,6 +118,14 @@ export interface GpuParticles {
|
|
|
118
118
|
readonly fragInts: Int32Array;
|
|
119
119
|
readonly fields: UniformFields;
|
|
120
120
|
readonly bindGroup: GPUBindGroup;
|
|
121
|
+
/**
|
|
122
|
+
* The encoder this batch was last drawn into, by the renderer's count, or -1.
|
|
123
|
+
*
|
|
124
|
+
* **A second draw into the same encoder is refused**, because this batch's buffers are written
|
|
125
|
+
* at each draw and every write lands before the encoder runs: both draws would show the second.
|
|
126
|
+
* WebGL2 has no such limit, and a limit one backend has is said, not left to a wrong picture.
|
|
127
|
+
*/
|
|
128
|
+
drawnInEncoder: number;
|
|
121
129
|
dispose(): void;
|
|
122
130
|
}
|
|
123
131
|
|
|
@@ -215,6 +223,7 @@ export function createGpuParticles(
|
|
|
215
223
|
const fragStaging = new ArrayBuffer(fragSize);
|
|
216
224
|
|
|
217
225
|
return {
|
|
226
|
+
drawnInEncoder: -1,
|
|
218
227
|
corners: cornerBuffer,
|
|
219
228
|
instances,
|
|
220
229
|
indexBuffer,
|