@driftengine/core 4.4.2 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +46 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +79 -8
- package/dist/render/backend/webgl2/renderer.js +360 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +2 -12
- package/dist/render/backend/webgpu/renderer.d.ts +171 -54
- package/dist/render/backend/webgpu/renderer.js +944 -390
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +7 -1
- package/dist/render/sceneTarget.js +58 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +401 -108
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/renderer.ts +1108 -421
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +58 -11
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/translucentQueue.ts +20 -1
|
@@ -125,6 +125,12 @@ export interface GpuMesh {
|
|
|
125
125
|
* its own does.
|
|
126
126
|
*/
|
|
127
127
|
readonly vertexStride: number;
|
|
128
|
+
/**
|
|
129
|
+
* Byte offsets of the joint indices and weights in the interleaved vertex, or null for a mesh
|
|
130
|
+
* that supplied neither. For a pass that skins through a shader of its own — the motion pass —
|
|
131
|
+
* which has to find them without the `present` map the generated pipelines are keyed by.
|
|
132
|
+
*/
|
|
133
|
+
readonly skinOffsets: { readonly joints: number; readonly weights: number } | null;
|
|
128
134
|
readonly indexBuffer: GPUBuffer;
|
|
129
135
|
readonly indexCount: number;
|
|
130
136
|
/**
|
|
@@ -192,7 +198,19 @@ export interface GpuMesh {
|
|
|
192
198
|
* than every mesh paying it.
|
|
193
199
|
*/
|
|
194
200
|
readonly update:
|
|
195
|
-
((
|
|
201
|
+
| ((
|
|
202
|
+
device: GPUDevice,
|
|
203
|
+
positions: Float32Array,
|
|
204
|
+
normals?: Float32Array,
|
|
205
|
+
previous?: GPUBuffer | null,
|
|
206
|
+
) => void)
|
|
207
|
+
| null;
|
|
208
|
+
/**
|
|
209
|
+
* A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's interleaved
|
|
210
|
+
* rows, made the first time a reconstructing renderer rewrites the mesh, and the frame of the
|
|
211
|
+
* last rewrite. Null on a mesh created without `{ dynamic: true }`, which never moves a vertex.
|
|
212
|
+
*/
|
|
213
|
+
readonly motion: { previous: GPUBuffer | null; changed: number } | null;
|
|
196
214
|
dispose(): void;
|
|
197
215
|
}
|
|
198
216
|
|
|
@@ -377,6 +395,7 @@ export function createGpuMeshIncremental(
|
|
|
377
395
|
progress,
|
|
378
396
|
hasTangents: data.tangents !== undefined,
|
|
379
397
|
isSkinned: data.joints !== undefined,
|
|
398
|
+
skinOffsets: skinOffsetsOf(supplied),
|
|
380
399
|
hasChannel: data.channel !== undefined,
|
|
381
400
|
morph:
|
|
382
401
|
data.morphTargets === undefined || data.morphTargetCount === undefined
|
|
@@ -505,6 +524,22 @@ function interleaveField(
|
|
|
505
524
|
}
|
|
506
525
|
}
|
|
507
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Where the joints and weights sit in a vertex of `supplied`, the attributes a mesh interleaves in
|
|
529
|
+
* layout order; null unless it supplied both.
|
|
530
|
+
*/
|
|
531
|
+
function skinOffsetsOf(supplied: readonly VertexAttribute[]): GpuMesh['skinOffsets'] {
|
|
532
|
+
let floats = 0;
|
|
533
|
+
let joints = -1;
|
|
534
|
+
let weights = -1;
|
|
535
|
+
for (const attribute of supplied) {
|
|
536
|
+
if (attribute.name === 'joints') joints = floats * 4;
|
|
537
|
+
if (attribute.name === 'weights') weights = floats * 4;
|
|
538
|
+
floats += attribute.components;
|
|
539
|
+
}
|
|
540
|
+
return joints >= 0 && weights >= 0 ? { joints, weights } : null;
|
|
541
|
+
}
|
|
542
|
+
|
|
508
543
|
/** The handle, made where it can see only what it hands out. */
|
|
509
544
|
function gpuMeshOf(parts: {
|
|
510
545
|
readonly vertices: GPUBuffer;
|
|
@@ -516,14 +551,17 @@ function gpuMeshOf(parts: {
|
|
|
516
551
|
readonly progress: { readonly uploaded: boolean };
|
|
517
552
|
readonly hasTangents: boolean;
|
|
518
553
|
readonly isSkinned: boolean;
|
|
554
|
+
readonly skinOffsets: GpuMesh['skinOffsets'];
|
|
519
555
|
readonly hasChannel: boolean;
|
|
520
556
|
readonly morph: MorphTexture | null;
|
|
521
557
|
readonly update: GpuMesh['update'];
|
|
522
558
|
}): GpuMesh {
|
|
559
|
+
const motion = parts.update === null ? null : { previous: null as GPUBuffer | null, changed: -1 };
|
|
523
560
|
const { vertices, constants, indexBuffer, progress } = parts;
|
|
524
561
|
return {
|
|
525
562
|
vertexBuffers: [vertices, constants],
|
|
526
563
|
vertexStride: parts.stride,
|
|
564
|
+
skinOffsets: parts.skinOffsets,
|
|
527
565
|
indexBuffer,
|
|
528
566
|
indexCount: parts.indexCount,
|
|
529
567
|
bounds: parts.bounds,
|
|
@@ -535,7 +573,9 @@ function gpuMeshOf(parts: {
|
|
|
535
573
|
hasChannel: parts.hasChannel,
|
|
536
574
|
morph: parts.morph,
|
|
537
575
|
update: parts.update,
|
|
576
|
+
motion,
|
|
538
577
|
dispose(): void {
|
|
578
|
+
motion?.previous?.destroy();
|
|
539
579
|
vertices.destroy();
|
|
540
580
|
constants.destroy();
|
|
541
581
|
indexBuffer.destroy();
|
|
@@ -557,7 +597,12 @@ function dynamicUpdate(
|
|
|
557
597
|
positionField: number,
|
|
558
598
|
normalField: number,
|
|
559
599
|
): NonNullable<GpuMesh['update']> {
|
|
560
|
-
return (
|
|
600
|
+
return (
|
|
601
|
+
target: GPUDevice,
|
|
602
|
+
positions: Float32Array,
|
|
603
|
+
normals?: Float32Array,
|
|
604
|
+
previous: GPUBuffer | null = null,
|
|
605
|
+
): void => {
|
|
561
606
|
if (positions.length !== vertexCount * 3) {
|
|
562
607
|
throw new Error(
|
|
563
608
|
`this mesh has ${vertexCount} vertices and the update has ${positions.length / 3}. ` +
|
|
@@ -565,6 +610,9 @@ function dynamicUpdate(
|
|
|
565
610
|
'every other attribute are sized against it.',
|
|
566
611
|
);
|
|
567
612
|
}
|
|
613
|
+
/* The rows as they stand are last frame's, handed over before a position of them changes.
|
|
614
|
+
`writeBuffer` copies at the call, so patching the array afterwards cannot reach this copy. */
|
|
615
|
+
if (previous !== null) target.queue.writeBuffer(previous, 0, interleaved);
|
|
568
616
|
/* Everything that decides whether this is on screen starts from the bounds, so a cloth
|
|
569
617
|
that blew sideways out of its original box would be culled while still visible. */
|
|
570
618
|
boundsOfPositions(positions, bounds);
|
|
@@ -122,7 +122,8 @@ export function createDepthBindGroupLayout(
|
|
|
122
122
|
{
|
|
123
123
|
binding: PREVIOUS.texture,
|
|
124
124
|
visibility: VISIBILITY_FRAGMENT,
|
|
125
|
-
|
|
125
|
+
/* An array since the sun's maps became one (`shadowMap.ts`): the peel reads its static layer. */
|
|
126
|
+
texture: { sampleType: 'unfilterable-float', viewDimension: '2d-array' },
|
|
126
127
|
},
|
|
127
128
|
{
|
|
128
129
|
binding: PREVIOUS.sampler,
|
|
@@ -82,6 +82,8 @@ export interface StageBindings {
|
|
|
82
82
|
>
|
|
83
83
|
>;
|
|
84
84
|
readonly textures: Readonly<Record<string, TextureBinding>>;
|
|
85
|
+
/** Each pipeline-overridable constant's id, by the GLSL name it was declared under. */
|
|
86
|
+
readonly overrides?: Readonly<Record<string, number>>;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
/*
|
|
@@ -188,6 +190,20 @@ export function flatFragmentBindings(variant: FlatVariant): StageBindings {
|
|
|
188
190
|
return found;
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
/**
|
|
194
|
+
* The lit fragment stage's constants, set from the cache: the glass switch, keyed by its **id**.
|
|
195
|
+
*
|
|
196
|
+
* Not by its name, because an override declared with `@id` answers to the id alone — WebGPU's
|
|
197
|
+
* "pipeline-overridable constant identifier string" — and naga writes one on every specialisation
|
|
198
|
+
* constant. Keyed by name, Chrome refused every lit pipeline: *Pipeline overridable constant
|
|
199
|
+
* "GLASS_SHADOWS" not found*, and the frame drew nothing.
|
|
200
|
+
*/
|
|
201
|
+
function flatFragmentConstants(variant: FlatVariant, cache: PipelineCache): Record<string, number> {
|
|
202
|
+
const id = flatFragmentBindings(variant).overrides?.['GLASS_SHADOWS'];
|
|
203
|
+
if (id === undefined) throw new Error(`flatPass: variant "${variant}" has no glass switch`);
|
|
204
|
+
return { [String(id)]: cache.glassShadows ? 1 : 0 };
|
|
205
|
+
}
|
|
206
|
+
|
|
191
207
|
/** The depth format both backends compare with. */
|
|
192
208
|
/* Re-exported so the twenty passes that already import it from here do not each learn a new
|
|
193
209
|
module; `depthConvention.ts` is where the decision lives and why. */
|
|
@@ -272,8 +288,11 @@ export function createFlatBindGroupLayout(
|
|
|
272
288
|
const integer = texture.type === 'usampler2D' || texture.type === 'usampler3D';
|
|
273
289
|
/* DriftLight's brick index and atlas, the lit pass's only volumes. See `driftLight.ts`. */
|
|
274
290
|
const volume = texture.type === 'sampler3D' || texture.type === 'usampler3D';
|
|
291
|
+
/* Glass tints are colour, mipmapped and filtered: a frosted pane is read at a coarser level. */
|
|
292
|
+
const tint = name.endsWith('GlassTints');
|
|
275
293
|
const isShadow =
|
|
276
|
-
integer ||
|
|
294
|
+
integer ||
|
|
295
|
+
(!tint && name !== 'uEnvironment' && (name.endsWith('ShadowMap') || cube || array));
|
|
277
296
|
entries.push({
|
|
278
297
|
binding: texture.texture,
|
|
279
298
|
visibility: VISIBILITY_FRAGMENT,
|
|
@@ -603,6 +622,8 @@ function flatDescriptor(
|
|
|
603
622
|
code: FLAT_FRAG_WGSL[variant] ?? '',
|
|
604
623
|
}),
|
|
605
624
|
entryPoint: 'main',
|
|
625
|
+
/* Every variant declares it, so every lit pipeline may set it: see `PipelineCache`. */
|
|
626
|
+
constants: flatFragmentConstants(variant, cache),
|
|
606
627
|
targets: [
|
|
607
628
|
oitTarget(oit) ??
|
|
608
629
|
(translucent
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a pane lets through, drawn into a shadow's tint layer on this device: the layouts, the
|
|
3
|
+
* per-draw block, the bind groups and the pipelines for the five tint programs (`depth.ts`).
|
|
4
|
+
*
|
|
5
|
+
* **The vertex half is the depth pass's**, block for block — the tint's vertex stages are the depth
|
|
6
|
+
* ones with the world position handed on — so a draw's vertex slot comes from the shadow pass's own
|
|
7
|
+
* ring and only the pane's 32 bytes are this file's. **The fragment half multiplies**: an
|
|
8
|
+
* `rgba8unorm` target cleared to white, `dst × src` for colour and `dst-alpha × src` for clarity, and
|
|
9
|
+
* no depth, so every pane on a ray counts in any order. Built the first time glass casts, so a scene
|
|
10
|
+
* with no glass builds none of it.
|
|
11
|
+
*/
|
|
12
|
+
import { DEPTH_BINDINGS } from '../../shaders/generated/depth.wgsl.ts';
|
|
13
|
+
import {
|
|
14
|
+
GLASS_TINT_CUTOUT_FRAG_WGSL,
|
|
15
|
+
GLASS_TINT_CUTOUT_VERT_WGSL,
|
|
16
|
+
GLASS_TINT_FRAG_WGSL,
|
|
17
|
+
GLASS_TINT_INSTANCED_CUTOUT_VERT_WGSL,
|
|
18
|
+
GLASS_TINT_INSTANCED_VERT_WGSL,
|
|
19
|
+
GLASS_TINT_SKINNED_VERT_WGSL,
|
|
20
|
+
GLASS_TINT_VERT_WGSL,
|
|
21
|
+
} from '../../shaders/generated/depth.wgsl.ts';
|
|
22
|
+
import type { ResolvedGlass } from '../../glass.ts';
|
|
23
|
+
import { vertexBufferLayouts } from './buffers.ts';
|
|
24
|
+
import type { PipelineCache } from './pipelineCache.ts';
|
|
25
|
+
import { shaderModule } from './shaderModules.ts';
|
|
26
|
+
import { UniformRing } from './uniformRing.ts';
|
|
27
|
+
|
|
28
|
+
const VISIBILITY_VERTEX = 0x1;
|
|
29
|
+
const VISIBILITY_FRAGMENT = 0x2;
|
|
30
|
+
const USAGE_UNIFORM_DST = 0x40 | 0x8; // UNIFORM | COPY_DST
|
|
31
|
+
|
|
32
|
+
const VERT = DEPTH_BINDINGS.GLASS_TINT_VERT;
|
|
33
|
+
const CUTOUT_VERT = DEPTH_BINDINGS.GLASS_TINT_CUTOUT_VERT;
|
|
34
|
+
const FRAG = DEPTH_BINDINGS.GLASS_TINT_FRAG;
|
|
35
|
+
const CUTOUT_MAP = DEPTH_BINDINGS.GLASS_TINT_CUTOUT_FRAG.textures.uCutoutMap;
|
|
36
|
+
const PALETTE = DEPTH_BINDINGS.GLASS_TINT_SKINNED_VERT.textures.uJointPalette;
|
|
37
|
+
const PANE = FRAG.fields.uGlassPane.offset;
|
|
38
|
+
const LIGHT = FRAG.fields.uGlassLight.offset;
|
|
39
|
+
|
|
40
|
+
/** Panes a frame can tint: a pane is a draw per shadow it reaches, and panes are few. */
|
|
41
|
+
const MAX_TINT_DRAWS = 512;
|
|
42
|
+
|
|
43
|
+
export type GlassTintKind = 'rigid' | 'instanced' | 'skinned' | 'cutout' | 'instancedCutout';
|
|
44
|
+
|
|
45
|
+
/** The multiplying state: colour times what passes, clarity times clarity, order-free. */
|
|
46
|
+
export const GLASS_TINT_BLEND: GPUBlendState = {
|
|
47
|
+
color: { operation: 'add', srcFactor: 'dst', dstFactor: 'zero' },
|
|
48
|
+
alpha: { operation: 'add', srcFactor: 'dst-alpha', dstFactor: 'zero' },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export class GpuGlassTint {
|
|
52
|
+
readonly frag: UniformRing;
|
|
53
|
+
private readonly layouts: Record<'plain' | 'cutout' | 'skinned', GPUBindGroupLayout>;
|
|
54
|
+
private plainGroup: GPUBindGroup | null = null;
|
|
55
|
+
private groupVertBuffer: GPUBuffer | null = null;
|
|
56
|
+
private groupFragBuffer: GPUBuffer | null = null;
|
|
57
|
+
private readonly cutoutGroups = new Map<GPUTextureView, GPUBindGroup>();
|
|
58
|
+
private readonly skinnedGroups = new Map<GPUTextureView, GPUBindGroup>();
|
|
59
|
+
private readonly device: GPUDevice;
|
|
60
|
+
|
|
61
|
+
constructor(device: GPUDevice) {
|
|
62
|
+
this.device = device;
|
|
63
|
+
this.frag = new UniformRing(
|
|
64
|
+
device,
|
|
65
|
+
FRAG.uniformSize,
|
|
66
|
+
MAX_TINT_DRAWS,
|
|
67
|
+
USAGE_UNIFORM_DST,
|
|
68
|
+
'glassTint.ring',
|
|
69
|
+
);
|
|
70
|
+
const vert = (size: number): GPUBindGroupLayoutEntry => ({
|
|
71
|
+
binding: VERT.uniforms,
|
|
72
|
+
visibility: VISIBILITY_VERTEX,
|
|
73
|
+
buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: size },
|
|
74
|
+
});
|
|
75
|
+
const frag: GPUBindGroupLayoutEntry = {
|
|
76
|
+
binding: FRAG.uniforms,
|
|
77
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
78
|
+
buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: FRAG.uniformSize },
|
|
79
|
+
};
|
|
80
|
+
this.layouts = {
|
|
81
|
+
plain: device.createBindGroupLayout({
|
|
82
|
+
label: 'glassTint.layout',
|
|
83
|
+
entries: [vert(VERT.uniformSize), frag],
|
|
84
|
+
}),
|
|
85
|
+
cutout: device.createBindGroupLayout({
|
|
86
|
+
label: 'glassTint.layout.cutout',
|
|
87
|
+
entries: [
|
|
88
|
+
vert(CUTOUT_VERT.uniformSize),
|
|
89
|
+
frag,
|
|
90
|
+
{
|
|
91
|
+
binding: CUTOUT_MAP.texture,
|
|
92
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
93
|
+
texture: { sampleType: 'float' },
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
binding: CUTOUT_MAP.sampler,
|
|
97
|
+
visibility: VISIBILITY_FRAGMENT,
|
|
98
|
+
sampler: { type: 'filtering' },
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
}),
|
|
102
|
+
skinned: device.createBindGroupLayout({
|
|
103
|
+
label: 'glassTint.layout.skinned',
|
|
104
|
+
entries: [
|
|
105
|
+
{
|
|
106
|
+
binding: PALETTE.texture,
|
|
107
|
+
visibility: VISIBILITY_VERTEX,
|
|
108
|
+
texture: { sampleType: 'unfilterable-float' },
|
|
109
|
+
},
|
|
110
|
+
vert(VERT.uniformSize),
|
|
111
|
+
frag,
|
|
112
|
+
],
|
|
113
|
+
}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** One pane's block: what it lets through, how clear it is, and the light it is drawn for. */
|
|
118
|
+
writePane(slot: number, glass: ResolvedGlass, light: Float32Array): void {
|
|
119
|
+
const pane = paneScratch;
|
|
120
|
+
pane[0] = glass.transmission * glass.tint[0];
|
|
121
|
+
pane[1] = glass.transmission * glass.tint[1];
|
|
122
|
+
pane[2] = glass.transmission * glass.tint[2];
|
|
123
|
+
pane[3] = 1 - glass.frost;
|
|
124
|
+
this.frag.writeFloats(slot, PANE, pane);
|
|
125
|
+
this.frag.writeFloats(slot, LIGHT, light);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The plain group, rebuilt only when a ring has grown a new buffer under it. */
|
|
129
|
+
group(vertBuffer: GPUBuffer): GPUBindGroup {
|
|
130
|
+
if (
|
|
131
|
+
this.plainGroup === null ||
|
|
132
|
+
this.groupVertBuffer !== vertBuffer ||
|
|
133
|
+
this.groupFragBuffer !== this.frag.buffer
|
|
134
|
+
) {
|
|
135
|
+
this.cutoutGroups.clear();
|
|
136
|
+
this.skinnedGroups.clear();
|
|
137
|
+
this.groupVertBuffer = vertBuffer;
|
|
138
|
+
this.groupFragBuffer = this.frag.buffer;
|
|
139
|
+
this.plainGroup = this.device.createBindGroup({
|
|
140
|
+
label: 'glassTint.group',
|
|
141
|
+
layout: this.layouts.plain,
|
|
142
|
+
entries: [
|
|
143
|
+
{ binding: VERT.uniforms, resource: { buffer: vertBuffer, size: VERT.uniformSize } },
|
|
144
|
+
{
|
|
145
|
+
binding: FRAG.uniforms,
|
|
146
|
+
resource: { buffer: this.frag.buffer, size: FRAG.uniformSize },
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return this.plainGroup;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** A cutout pane's group, one per map, built the first time that map casts colour. */
|
|
155
|
+
cutoutGroup(vertBuffer: GPUBuffer, view: GPUTextureView, sampler: GPUSampler): GPUBindGroup {
|
|
156
|
+
this.group(vertBuffer);
|
|
157
|
+
const held = this.cutoutGroups.get(view);
|
|
158
|
+
if (held !== undefined) return held;
|
|
159
|
+
const made = this.device.createBindGroup({
|
|
160
|
+
label: 'glassTint.group.cutout',
|
|
161
|
+
layout: this.layouts.cutout,
|
|
162
|
+
entries: [
|
|
163
|
+
{
|
|
164
|
+
binding: CUTOUT_VERT.uniforms,
|
|
165
|
+
resource: { buffer: vertBuffer, size: CUTOUT_VERT.uniformSize },
|
|
166
|
+
},
|
|
167
|
+
{ binding: FRAG.uniforms, resource: { buffer: this.frag.buffer, size: FRAG.uniformSize } },
|
|
168
|
+
{ binding: CUTOUT_MAP.texture, resource: view },
|
|
169
|
+
{ binding: CUTOUT_MAP.sampler, resource: sampler },
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
this.cutoutGroups.set(view, made);
|
|
173
|
+
return made;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** A skinned pane's group, one per palette view. */
|
|
177
|
+
skinnedGroup(vertBuffer: GPUBuffer, palette: GPUTextureView): GPUBindGroup {
|
|
178
|
+
this.group(vertBuffer);
|
|
179
|
+
const held = this.skinnedGroups.get(palette);
|
|
180
|
+
if (held !== undefined) return held;
|
|
181
|
+
const made = this.device.createBindGroup({
|
|
182
|
+
label: 'glassTint.group.skinned',
|
|
183
|
+
layout: this.layouts.skinned,
|
|
184
|
+
entries: [
|
|
185
|
+
{ binding: PALETTE.texture, resource: palette },
|
|
186
|
+
{ binding: VERT.uniforms, resource: { buffer: vertBuffer, size: VERT.uniformSize } },
|
|
187
|
+
{ binding: FRAG.uniforms, resource: { buffer: this.frag.buffer, size: FRAG.uniformSize } },
|
|
188
|
+
],
|
|
189
|
+
});
|
|
190
|
+
this.skinnedGroups.set(palette, made);
|
|
191
|
+
return made;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The pipeline for one kind of caster, cull mode and vertex layout, cached by key. */
|
|
195
|
+
pipeline(
|
|
196
|
+
cache: PipelineCache,
|
|
197
|
+
kind: GlassTintKind,
|
|
198
|
+
key: string,
|
|
199
|
+
present: Readonly<Record<string, boolean>>,
|
|
200
|
+
cullMode: GPUCullMode,
|
|
201
|
+
): GPURenderPipeline {
|
|
202
|
+
const cutout = kind === 'cutout' || kind === 'instancedCutout';
|
|
203
|
+
const instanced = kind === 'instanced' || kind === 'instancedCutout';
|
|
204
|
+
const layout =
|
|
205
|
+
kind === 'skinned' ? this.layouts.skinned : cutout ? this.layouts.cutout : this.layouts.plain;
|
|
206
|
+
const vertex =
|
|
207
|
+
kind === 'rigid'
|
|
208
|
+
? GLASS_TINT_VERT_WGSL
|
|
209
|
+
: kind === 'instanced'
|
|
210
|
+
? GLASS_TINT_INSTANCED_VERT_WGSL
|
|
211
|
+
: kind === 'skinned'
|
|
212
|
+
? GLASS_TINT_SKINNED_VERT_WGSL
|
|
213
|
+
: kind === 'cutout'
|
|
214
|
+
? GLASS_TINT_CUTOUT_VERT_WGSL
|
|
215
|
+
: GLASS_TINT_INSTANCED_CUTOUT_VERT_WGSL;
|
|
216
|
+
const label = `glass-tint|${kind}|${cullMode}|${key}`;
|
|
217
|
+
return cache.get(label, () => ({
|
|
218
|
+
label,
|
|
219
|
+
layout: this.device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
220
|
+
vertex: {
|
|
221
|
+
module: shaderModule(this.device, { label: `glassTint.${kind}.vert`, code: vertex }),
|
|
222
|
+
entryPoint: 'main',
|
|
223
|
+
buffers: vertexBufferLayouts(present, instanced),
|
|
224
|
+
},
|
|
225
|
+
fragment: {
|
|
226
|
+
module: shaderModule(this.device, {
|
|
227
|
+
label: cutout ? 'glassTint.cutout.frag' : 'glassTint.frag',
|
|
228
|
+
code: cutout ? GLASS_TINT_CUTOUT_FRAG_WGSL : GLASS_TINT_FRAG_WGSL,
|
|
229
|
+
}),
|
|
230
|
+
entryPoint: 'main',
|
|
231
|
+
targets: [{ format: 'rgba8unorm', blend: GLASS_TINT_BLEND }],
|
|
232
|
+
},
|
|
233
|
+
/* The depth pass's primitive state exactly — its winding included, for the reason
|
|
234
|
+
`depthPipeline` gives: this map is sampled, not presented. */
|
|
235
|
+
primitive: { topology: 'triangle-list', cullMode, frontFace: 'cw' },
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
dispose(): void {
|
|
240
|
+
this.frag.dispose();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const paneScratch = new Float32Array(4);
|