@driftengine/core 4.11.0 → 4.11.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 +36 -0
- package/README.md +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/render/ambientOcclusionPass.d.ts +3 -2
- package/dist/render/ambientOcclusionPass.js +4 -2
- package/dist/render/backend/api.d.ts +21 -1
- package/dist/render/backend/webgl2/renderer.d.ts +45 -4
- package/dist/render/backend/webgl2/renderer.js +112 -19
- package/dist/render/backend/webgpu/renderer.d.ts +52 -1
- package/dist/render/backend/webgpu/renderer.js +237 -89
- package/dist/render/instances.d.ts +13 -1
- package/dist/render/instances.js +10 -0
- package/dist/render/occlusionFade.d.ts +21 -0
- package/dist/render/occlusionFade.js +27 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shaders/ambientOcclusion.js +12 -1
- package/dist/render/shaders/flat/index.js +9 -2
- package/dist/render/shaders/flat/layered.d.ts +15 -7
- package/dist/render/shaders/flat/layered.js +114 -31
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +10 -3
- package/dist/render/shaders/flat/normalMap.d.ts +1 -1
- package/dist/render/shaders/flat/normalMap.js +11 -2
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -48
- package/dist/render/shaders/generated/flat.wgsl.js +71 -71
- package/dist/render/surfaceLayers.d.ts +83 -15
- package/dist/render/surfaceLayers.js +69 -13
- package/dist/render/surfaceTexture.d.ts +13 -4
- package/dist/render/transmission.d.ts +21 -0
- package/dist/render/transmission.js +21 -0
- package/dist/ui/splash.d.ts +42 -2
- package/dist/ui/splash.js +78 -13
- package/package.json +6 -6
- package/src/index.ts +2 -0
- package/src/render/ambientOcclusionPass.ts +4 -1
- package/src/render/backend/api.ts +31 -0
- package/src/render/backend/webgl2/renderer.ts +130 -20
- package/src/render/backend/webgpu/renderer.ts +371 -139
- package/src/render/instances.ts +18 -1
- package/src/render/occlusionFade.ts +29 -0
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shaders/ambientOcclusion.ts +12 -1
- package/src/render/shaders/flat/index.ts +9 -2
- package/src/render/shaders/flat/layered.ts +114 -31
- package/src/render/shaders/flat/main.ts +10 -3
- package/src/render/shaders/flat/normalMap.ts +11 -2
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
- package/src/render/shaders/generated/flat.wgsl.ts +71 -71
- package/src/render/surfaceLayers.ts +138 -19
- package/src/render/surfaceTexture.ts +13 -4
- package/src/render/transmission.ts +27 -0
- package/src/ui/splash.ts +83 -14
|
@@ -59,7 +59,7 @@ import type {
|
|
|
59
59
|
SkyColors,
|
|
60
60
|
TranslucentMeshOptions,
|
|
61
61
|
} from '../webgl2/renderer.ts';
|
|
62
|
-
import type { IncrementalMeshHandle, MeshOptions } from '../api.ts';
|
|
62
|
+
import type { IncrementalMeshHandle, MeshOptions, PrepareOptions } from '../api.ts';
|
|
63
63
|
import { resolveAtmosphere, type ResolvedAtmosphere } from '../../atmosphere.ts';
|
|
64
64
|
import { AMBIENT_SH_FLOATS, packAmbientSH } from '../../ambientHarmonics.ts';
|
|
65
65
|
import {
|
|
@@ -197,8 +197,16 @@ import {
|
|
|
197
197
|
type SurfaceOverlay,
|
|
198
198
|
} from '../../surfaceOverlay.ts';
|
|
199
199
|
import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.ts';
|
|
200
|
+
import { resolveTransmission } from '../../transmission.ts';
|
|
201
|
+
import { resolveOcclusionFade } from '../../occlusionFade.ts';
|
|
200
202
|
import { orthographicDepthSpan } from '../../lightMatrix.ts';
|
|
201
|
-
import {
|
|
203
|
+
import {
|
|
204
|
+
LAYER_FLOATS,
|
|
205
|
+
layerMaskMap,
|
|
206
|
+
layeredProjection,
|
|
207
|
+
layersFitBeside,
|
|
208
|
+
packSurfaceLayers,
|
|
209
|
+
} from '../../surfaceLayers.ts';
|
|
202
210
|
import type { SurfaceLayers } from '../../surfaceLayers.ts';
|
|
203
211
|
import {
|
|
204
212
|
frameReflectionsRefused,
|
|
@@ -756,6 +764,12 @@ const MAX_OVERLAYS = 64;
|
|
|
756
764
|
*/
|
|
757
765
|
const MAX_SKY_DRAWS = 64;
|
|
758
766
|
|
|
767
|
+
/** The draw a preparation asks pipelines for: every option at its default. See `prepareMesh`. */
|
|
768
|
+
const PREPARED_DRAW: TranslucentMeshOptions = {};
|
|
769
|
+
/** A preparation's morph states for a mesh with no targets, and for one with some. */
|
|
770
|
+
const NOT_MORPHED: readonly boolean[] = [false];
|
|
771
|
+
const MORPHED_OR_NOT: readonly boolean[] = [false, true];
|
|
772
|
+
|
|
759
773
|
/**
|
|
760
774
|
* Whether the environment probe's permutation fits this device's per-stage binding limits.
|
|
761
775
|
*
|
|
@@ -2347,6 +2361,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2347
2361
|
|
|
2348
2362
|
private readonly aoStaging = new ArrayBuffer(AO_FRAG_SIZE);
|
|
2349
2363
|
private readonly aoFloats = new Float32Array(this.aoStaging);
|
|
2364
|
+
/** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
|
|
2365
|
+
private readonly aoFade = new Float32Array([-1, 0]);
|
|
2350
2366
|
private aoBindGroup: GPUBindGroup | null = null;
|
|
2351
2367
|
private readonly aoBlurLayout: GPUBindGroupLayout;
|
|
2352
2368
|
/**
|
|
@@ -2999,7 +3015,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2999
3015
|
private readonly projection = new Float32Array(PROJECTION_FLOATS);
|
|
3000
3016
|
/** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
|
|
3001
3017
|
private readonly layerFloats = new Float32Array(LAYER_FLOATS);
|
|
3018
|
+
/**
|
|
3019
|
+
* `uGlassTint` as the material leaves it, its light from behind and that light's colour, which a
|
|
3020
|
+
* pane overwrites for its own draw and `releaseSeeThrough` puts back. See `transmission.ts`.
|
|
3021
|
+
*/
|
|
3022
|
+
private readonly transmission = new Float32Array([-1, -1, -1, 0]);
|
|
3002
3023
|
private warnedLayersModelMap = false;
|
|
3024
|
+
private warnedLayersTriplanar = false;
|
|
3003
3025
|
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
3004
3026
|
private readonly modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
3005
3027
|
/**
|
|
@@ -3475,9 +3497,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3475
3497
|
const orm = (material?.orm ?? null) as GpuSurfaceTexture | null;
|
|
3476
3498
|
const emissiveMap = (material?.emissive ?? null) as GpuSurfaceTexture | null;
|
|
3477
3499
|
const ownModelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
|
|
3478
|
-
/* A layered material's mask goes where a model's
|
|
3500
|
+
/* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
|
|
3479
3501
|
const layers = this.layersOf(material);
|
|
3480
|
-
const modelMap = ownModelMap ?? (
|
|
3502
|
+
const modelMap = ownModelMap ?? layerMaskMap(layers);
|
|
3481
3503
|
this.materialModel = material?.model ?? null;
|
|
3482
3504
|
const reflectivity = material?.reflectivity;
|
|
3483
3505
|
const environmentGain = material?.environmentGain;
|
|
@@ -3554,11 +3576,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3554
3576
|
/* The first physical highlight asked for is when the lit pipelines start carrying one. */
|
|
3555
3577
|
if (material?.physicalSpecular === true) this.enableLit('PHYSICAL_SPECULAR');
|
|
3556
3578
|
/* Maps placed by the world: the first material that asks is when lit pipelines carry it. */
|
|
3557
|
-
|
|
3579
|
+
const projection = layeredProjection(material?.projection, layers);
|
|
3580
|
+
if (projection !== (material?.projection ?? null)) this.warnLayersTriplanar();
|
|
3581
|
+
packSurfaceProjection(projection, this.projection);
|
|
3558
3582
|
f.set(this.projection, this.materialField('uWorldUv'));
|
|
3559
|
-
/* How much light from behind it lets through,
|
|
3560
|
-
|
|
3561
|
-
|
|
3583
|
+
/* How much light from behind it lets through, and its colour: transmission.ts. */
|
|
3584
|
+
resolveTransmission(material, this.transmission);
|
|
3585
|
+
f.set(this.transmission, this.materialField('uGlassTint'));
|
|
3562
3586
|
if ((this.projection[0] as number) > 0) this.enableLit('WORLD_UVS');
|
|
3563
3587
|
/* And its layers, the first time a material blends any: layered.ts. */
|
|
3564
3588
|
packSurfaceLayers(layers, this.layerFloats);
|
|
@@ -6325,17 +6349,28 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6325
6349
|
): SurfaceLayers<GpuSurfaceTexture> | null {
|
|
6326
6350
|
const layers = material?.layers ?? null;
|
|
6327
6351
|
if (layers === null || layers.repeats.length === 0) return null;
|
|
6328
|
-
if ((material?.modelMap
|
|
6352
|
+
if (layersFitBeside(layers, material?.modelMap)) return layers;
|
|
6329
6353
|
if (!this.warnedLayersModelMap) {
|
|
6330
6354
|
this.warnedLayersModelMap = true;
|
|
6331
6355
|
console.warn(
|
|
6332
|
-
"WebGPU: a material's layers are drawn as one: its mask is read where a model's map
|
|
6333
|
-
|
|
6356
|
+
"WebGPU: a material's layers are drawn as one: its mask is a map, read where a model's map " +
|
|
6357
|
+
"goes, and this material carries a `modelMap` too. A mask in the ORM array (`mask: 'orm'`) " +
|
|
6358
|
+
"or the vertex colour (`mask: 'vertex'`) leaves the slot free.",
|
|
6334
6359
|
);
|
|
6335
6360
|
}
|
|
6336
6361
|
return null;
|
|
6337
6362
|
}
|
|
6338
6363
|
|
|
6364
|
+
/** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
|
|
6365
|
+
private warnLayersTriplanar(): void {
|
|
6366
|
+
if (this.warnedLayersTriplanar) return;
|
|
6367
|
+
this.warnedLayersTriplanar = true;
|
|
6368
|
+
console.warn(
|
|
6369
|
+
"WebGPU: a layered material's triplanar projection is laid on the horizontal plane: every " +
|
|
6370
|
+
"layer's maps on three planes would be three times the reads. See `SurfaceMaterial.layers`.",
|
|
6371
|
+
);
|
|
6372
|
+
}
|
|
6373
|
+
|
|
6339
6374
|
/**
|
|
6340
6375
|
* What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
|
|
6341
6376
|
* where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
|
|
@@ -8937,7 +8972,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8937
8972
|
/* And no projection: maps follow the mesh's coordinates until a material asks otherwise. */
|
|
8938
8973
|
f.fill(0, at('uWorldUv'), at('uWorldUv') + 4);
|
|
8939
8974
|
/* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
|
|
8940
|
-
|
|
8975
|
+
resolveTransmission(null, this.transmission);
|
|
8976
|
+
f.set(this.transmission, at('uGlassTint'));
|
|
8941
8977
|
/* Nor layers: one layer until a material asks for more. */
|
|
8942
8978
|
f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
|
|
8943
8979
|
f[at('uCutout')] = 0;
|
|
@@ -9732,6 +9768,225 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9732
9768
|
);
|
|
9733
9769
|
}
|
|
9734
9770
|
|
|
9771
|
+
/**
|
|
9772
|
+
* Compile off the main thread every pipeline a draw of `mesh` in `material` takes. **The other
|
|
9773
|
+
* backend's `prepareMesh` carries the contract**; here a draw's key is `meshShape`'s, so a prepared
|
|
9774
|
+
* key is exactly the one the draw asks for, and the compiles are started as a skipping draw starts
|
|
9775
|
+
* them (`PipelineCache.lit`) whatever `pipelineCompile` says, into the frame's own caches.
|
|
9776
|
+
*/
|
|
9777
|
+
prepareMesh(
|
|
9778
|
+
mesh: GpuMesh,
|
|
9779
|
+
material: SurfaceMaterial<GpuSurfaceTexture> | null,
|
|
9780
|
+
options: PrepareOptions = {},
|
|
9781
|
+
): void {
|
|
9782
|
+
if (this.surface.lost) return;
|
|
9783
|
+
this.preparing(
|
|
9784
|
+
material,
|
|
9785
|
+
(into, blend) => {
|
|
9786
|
+
const skinned = mesh.isSkinned;
|
|
9787
|
+
const eight = skinned && mesh.isSkinnedEight;
|
|
9788
|
+
for (const morphed of mesh.morph === null ? NOT_MORPHED : MORPHED_OR_NOT) {
|
|
9789
|
+
this.meshShape(mesh, blend, PREPARED_DRAW, skinned, morphed, eight, false);
|
|
9790
|
+
this.meshPipeline(blend, false, skinned, morphed, eight, false, into);
|
|
9791
|
+
}
|
|
9792
|
+
},
|
|
9793
|
+
options,
|
|
9794
|
+
);
|
|
9795
|
+
}
|
|
9796
|
+
|
|
9797
|
+
/** `prepareMesh` for an instanced batch. See the other backend's `prepareInstanced`. */
|
|
9798
|
+
prepareInstanced(
|
|
9799
|
+
batch: GpuInstancedBatch,
|
|
9800
|
+
material: SurfaceMaterial<GpuSurfaceTexture> | null,
|
|
9801
|
+
options: PrepareOptions = {},
|
|
9802
|
+
): void {
|
|
9803
|
+
if (this.surface.lost) return;
|
|
9804
|
+
this.preparing(
|
|
9805
|
+
material,
|
|
9806
|
+
(into, blend) => {
|
|
9807
|
+
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
9808
|
+
this.instancedPipeline(
|
|
9809
|
+
batch.mesh,
|
|
9810
|
+
batch.animation !== null,
|
|
9811
|
+
blend,
|
|
9812
|
+
false,
|
|
9813
|
+
PREPARED_DRAW,
|
|
9814
|
+
true,
|
|
9815
|
+
0,
|
|
9816
|
+
coverage,
|
|
9817
|
+
into,
|
|
9818
|
+
);
|
|
9819
|
+
},
|
|
9820
|
+
options,
|
|
9821
|
+
);
|
|
9822
|
+
}
|
|
9823
|
+
|
|
9824
|
+
/**
|
|
9825
|
+
* `material` set, every cache a draw in it may build into passed to `start` with whether that draw
|
|
9826
|
+
* blends, compiles started rather than built (`PipelineCache.lit`), and then no material set and
|
|
9827
|
+
* each cache back to what `pipelineCompile` says.
|
|
9828
|
+
*/
|
|
9829
|
+
private preparing(
|
|
9830
|
+
material: SurfaceMaterial<GpuSurfaceTexture> | null,
|
|
9831
|
+
start: (into: PipelineCache, blend: boolean) => void,
|
|
9832
|
+
options: PrepareOptions,
|
|
9833
|
+
): void {
|
|
9834
|
+
this.setMaterial(material);
|
|
9835
|
+
this.skipCompiling(true);
|
|
9836
|
+
try {
|
|
9837
|
+
start(this.pipelines, false);
|
|
9838
|
+
if (options.translucent === true) {
|
|
9839
|
+
start(this.pipelines, true);
|
|
9840
|
+
/* A blended draw in a reconstructing frame lands in the late pass, whose cache is its own. */
|
|
9841
|
+
if (this.latePipelines !== this.pipelines) start(this.latePipelines, true);
|
|
9842
|
+
}
|
|
9843
|
+
} finally {
|
|
9844
|
+
this.skipCompiling(this.quality.pipelineCompile === 'skip');
|
|
9845
|
+
this.setMaterial(null);
|
|
9846
|
+
}
|
|
9847
|
+
}
|
|
9848
|
+
|
|
9849
|
+
/** What `meshShape` worked out for the mesh draw at hand. Reused, so a draw allocates no record. */
|
|
9850
|
+
private readonly drawShape = {
|
|
9851
|
+
base: '',
|
|
9852
|
+
key: '',
|
|
9853
|
+
depthWrite: true,
|
|
9854
|
+
layer: 0,
|
|
9855
|
+
adds: false,
|
|
9856
|
+
coverage: false,
|
|
9857
|
+
splits: false,
|
|
9858
|
+
modelKind: null as SurfaceModelKind | null,
|
|
9859
|
+
};
|
|
9860
|
+
|
|
9861
|
+
/**
|
|
9862
|
+
* The key a mesh draw's pipeline is cached under, and the pipeline state that key encodes, under
|
|
9863
|
+
* the current material and these options, into `drawShape`. **The one statement of it**:
|
|
9864
|
+
* `submitMesh` draws by it and `prepareMesh` compiles by it, so the two cannot disagree about a key.
|
|
9865
|
+
*/
|
|
9866
|
+
private meshShape(
|
|
9867
|
+
mesh: GpuMesh,
|
|
9868
|
+
blend: boolean,
|
|
9869
|
+
options: TranslucentMeshOptions,
|
|
9870
|
+
skinned: boolean,
|
|
9871
|
+
morphed: boolean,
|
|
9872
|
+
eight: boolean,
|
|
9873
|
+
clothed: boolean,
|
|
9874
|
+
): WebGPURenderer['drawShape'] {
|
|
9875
|
+
const modelKind = this.materialModel?.kind ?? null;
|
|
9876
|
+
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
9877
|
+
const keyed =
|
|
9878
|
+
`${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
|
|
9879
|
+
`${clothed ? '|cloth' : ''}`;
|
|
9880
|
+
/*
|
|
9881
|
+
* Depth writing and the overlay layer are pipeline state on this backend, so they belong in
|
|
9882
|
+
* the key: two draws of one mesh differing only in these would otherwise share a cached
|
|
9883
|
+
* pipeline and the second would silently take the first one's depth behaviour.
|
|
9884
|
+
*
|
|
9885
|
+
* **Both suffixes are empty for the defaults**, which is what keeps the keys `createMesh`
|
|
9886
|
+
* warms — `fullKey` and `${'`'}${'$'}{fullKey}|blend${'`'}` — the ones an ordinary draw still asks for. A
|
|
9887
|
+
* caller that opts in pays one compile on its first draw, the same bargain the overlay
|
|
9888
|
+
* pipelines above already make.
|
|
9889
|
+
*/
|
|
9890
|
+
const depthWrite = options.depthWrite ?? true;
|
|
9891
|
+
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
9892
|
+
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
9893
|
+
const adds = blend && options.additive === true;
|
|
9894
|
+
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
9895
|
+
/* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
|
|
9896
|
+
const splits = modelKind === 'skin' && !blend && this.skinSplits();
|
|
9897
|
+
const key =
|
|
9898
|
+
`${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
9899
|
+
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
9900
|
+
/* The two order-independent buffers are two formats and two blend states, so a draw into
|
|
9901
|
+
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
9902
|
+
`${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
|
|
9903
|
+
/* Culling is pipeline state here: a two-sided surface culls nothing. */
|
|
9904
|
+
`${this.materialDoubleSided ? '|2s' : ''}` +
|
|
9905
|
+
/* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
|
|
9906
|
+
`${coverage ? '|a2c' : ''}` +
|
|
9907
|
+
/* And the material's shading model, which is the fragment stage's own switch. */
|
|
9908
|
+
`${modelKind === null ? '' : `|m:${modelKind}`}` +
|
|
9909
|
+
/* And which half of a skin it draws, under the screen-space blur. */
|
|
9910
|
+
`${splits ? '|ss' : ''}`;
|
|
9911
|
+
|
|
9912
|
+
const shape = this.drawShape;
|
|
9913
|
+
shape.base = base;
|
|
9914
|
+
shape.key = key;
|
|
9915
|
+
shape.depthWrite = depthWrite;
|
|
9916
|
+
shape.layer = layer;
|
|
9917
|
+
shape.adds = adds;
|
|
9918
|
+
shape.coverage = coverage;
|
|
9919
|
+
shape.splits = splits;
|
|
9920
|
+
shape.modelKind = modelKind;
|
|
9921
|
+
return shape;
|
|
9922
|
+
}
|
|
9923
|
+
|
|
9924
|
+
/**
|
|
9925
|
+
* The pipeline `drawShape` names: the one built, one built here, or none where draws skip a
|
|
9926
|
+
* compile and this one has not landed (`PipelineCache.lit`) — and none for a split skin until all
|
|
9927
|
+
* of its halves have.
|
|
9928
|
+
*/
|
|
9929
|
+
private meshPipeline(
|
|
9930
|
+
blend: boolean,
|
|
9931
|
+
lands: boolean,
|
|
9932
|
+
skinned: boolean,
|
|
9933
|
+
morphed: boolean,
|
|
9934
|
+
eight: boolean,
|
|
9935
|
+
clothed: boolean,
|
|
9936
|
+
/** Which cache to build into: the one the draw's target reads, unless a preparation says. */
|
|
9937
|
+
into: PipelineCache | null = null,
|
|
9938
|
+
): GPURenderPipeline | null {
|
|
9939
|
+
const { base, key, depthWrite, layer, adds, coverage, splits, modelKind } = this.drawShape;
|
|
9940
|
+
/*
|
|
9941
|
+
* **Asynchronous by default, synchronous if it has to be, and never absent.**
|
|
9942
|
+
*
|
|
9943
|
+
* `createMesh` compiles off the main thread, so a draw can legitimately arrive before the
|
|
9944
|
+
* driver has finished — a consumer that awaits `ready()` never sees this, and one that
|
|
9945
|
+
* draws immediately gets the old behaviour rather than an exception. Correctness cannot be
|
|
9946
|
+
* allowed to depend on which of those happened, so the miss builds the pipeline here and
|
|
9947
|
+
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
9948
|
+
* thrown error in the middle of one.
|
|
9949
|
+
*/
|
|
9950
|
+
const pipelines = into ?? (blend ? this.blendedPipelines(lands) : this.targetPipelines());
|
|
9951
|
+
let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
|
|
9952
|
+
if (pipeline === null) {
|
|
9953
|
+
const present = this.meshPresent.get(base);
|
|
9954
|
+
if (present === undefined) {
|
|
9955
|
+
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
9956
|
+
}
|
|
9957
|
+
pipeline = flatPipeline(
|
|
9958
|
+
pipelines,
|
|
9959
|
+
this.surface.device,
|
|
9960
|
+
this.flatLayoutFor(skinned, morphed),
|
|
9961
|
+
this.variant,
|
|
9962
|
+
key,
|
|
9963
|
+
present,
|
|
9964
|
+
adds ? 'additive' : blend,
|
|
9965
|
+
skinned,
|
|
9966
|
+
morphed,
|
|
9967
|
+
depthWrite,
|
|
9968
|
+
layer,
|
|
9969
|
+
false,
|
|
9970
|
+
this.oitMode,
|
|
9971
|
+
this.materialDoubleSided,
|
|
9972
|
+
coverage,
|
|
9973
|
+
eight,
|
|
9974
|
+
clothed,
|
|
9975
|
+
modelKind,
|
|
9976
|
+
splits ? 'scene' : 'whole',
|
|
9977
|
+
);
|
|
9978
|
+
}
|
|
9979
|
+
/* A split skin draws its three halves or none of them. */
|
|
9980
|
+
if (
|
|
9981
|
+
pipeline !== null &&
|
|
9982
|
+
splits &&
|
|
9983
|
+
!this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)
|
|
9984
|
+
) {
|
|
9985
|
+
pipeline = null;
|
|
9986
|
+
}
|
|
9987
|
+
return pipeline;
|
|
9988
|
+
}
|
|
9989
|
+
|
|
9735
9990
|
/**
|
|
9736
9991
|
* @param blend Which of the two entry points above this came through, and **not** something to
|
|
9737
9992
|
* infer from `opacity`.
|
|
@@ -9857,7 +10112,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9857
10112
|
/* An eye's axis is the draw's, written into the open material. See `eyeAxisInWorld`. */
|
|
9858
10113
|
const modelKind = this.materialModel?.kind ?? null;
|
|
9859
10114
|
if (modelKind === 'eye') this.writeEyeAxis(model, this.skinPaletteSlot >= 0 && mesh.isSkinned);
|
|
9860
|
-
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
9861
10115
|
/*
|
|
9862
10116
|
* A rigged mesh takes the skinned vertex variant, an unrigged one the plain variant, and the
|
|
9863
10117
|
* key carries the flag so the two never share a cached pipeline. A rigged mesh whose caller
|
|
@@ -9872,41 +10126,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9872
10126
|
/* A cloth set and a skinned draw: the pipeline built with CLOTH_BOUND on, warmed when the
|
|
9873
10127
|
binding was made. See `createClothBinding`. */
|
|
9874
10128
|
const clothed = skinned && this.cloth !== null;
|
|
9875
|
-
const keyed =
|
|
9876
|
-
`${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
|
|
9877
|
-
`${clothed ? '|cloth' : ''}`;
|
|
9878
|
-
/*
|
|
9879
|
-
* Depth writing and the overlay layer are pipeline state on this backend, so they belong in
|
|
9880
|
-
* the key: two draws of one mesh differing only in these would otherwise share a cached
|
|
9881
|
-
* pipeline and the second would silently take the first one's depth behaviour.
|
|
9882
|
-
*
|
|
9883
|
-
* **Both suffixes are empty for the defaults**, which is what keeps the keys `createMesh`
|
|
9884
|
-
* warms — `fullKey` and `${'`'}${'$'}{fullKey}|blend${'`'}` — the ones an ordinary draw still asks for. A
|
|
9885
|
-
* caller that opts in pays one compile on its first draw, the same bargain the overlay
|
|
9886
|
-
* pipelines above already make.
|
|
9887
|
-
*/
|
|
9888
|
-
const depthWrite = options.depthWrite ?? true;
|
|
9889
|
-
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
9890
|
-
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
9891
|
-
const adds = blend && options.additive === true;
|
|
9892
|
-
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
9893
|
-
/* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
|
|
9894
|
-
const splits = modelKind === 'skin' && !blend && this.skinSplits();
|
|
9895
|
-
const key =
|
|
9896
|
-
`${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
9897
|
-
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
9898
|
-
/* The two order-independent buffers are two formats and two blend states, so a draw into
|
|
9899
|
-
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
9900
|
-
`${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
|
|
9901
|
-
/* Culling is pipeline state here: a two-sided surface culls nothing. */
|
|
9902
|
-
`${this.materialDoubleSided ? '|2s' : ''}` +
|
|
9903
|
-
/* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
|
|
9904
|
-
`${coverage ? '|a2c' : ''}` +
|
|
9905
|
-
/* And the material's shading model, which is the fragment stage's own switch. */
|
|
9906
|
-
`${modelKind === null ? '' : `|m:${modelKind}`}` +
|
|
9907
|
-
/* And which half of a skin it draws, under the screen-space blur. */
|
|
9908
|
-
`${splits ? '|ss' : ''}`;
|
|
9909
|
-
|
|
9910
10129
|
/*
|
|
9911
10130
|
* The morph uniforms, written only for a morphed draw and read from the *morphed* variant's
|
|
9912
10131
|
* field table. `FLAT_VERT_FIELDS` is the plain variant's and does not name them at all — every
|
|
@@ -9919,53 +10138,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9919
10138
|
this.perDraw.writeInt(slot, fields.uMorphTargetCount.offset, mesh.morph.targetCount);
|
|
9920
10139
|
this.perDraw.writeInt(slot, fields.uMorphTextureWidth.offset, mesh.morph.width);
|
|
9921
10140
|
}
|
|
9922
|
-
/*
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
|
|
9934
|
-
if (pipeline === null) {
|
|
9935
|
-
const present = this.meshPresent.get(base);
|
|
9936
|
-
if (present === undefined) {
|
|
9937
|
-
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
9938
|
-
}
|
|
9939
|
-
pipeline = flatPipeline(
|
|
9940
|
-
pipelines,
|
|
9941
|
-
this.surface.device,
|
|
9942
|
-
this.flatLayoutFor(skinned, morphed),
|
|
9943
|
-
this.variant,
|
|
9944
|
-
key,
|
|
9945
|
-
present,
|
|
9946
|
-
adds ? 'additive' : blend,
|
|
9947
|
-
skinned,
|
|
9948
|
-
morphed,
|
|
9949
|
-
depthWrite,
|
|
9950
|
-
layer,
|
|
9951
|
-
false,
|
|
9952
|
-
this.oitMode,
|
|
9953
|
-
this.materialDoubleSided,
|
|
9954
|
-
coverage,
|
|
9955
|
-
eight,
|
|
9956
|
-
clothed,
|
|
9957
|
-
modelKind,
|
|
9958
|
-
splits ? 'scene' : 'whole',
|
|
9959
|
-
);
|
|
9960
|
-
}
|
|
9961
|
-
/* A split skin draws its three halves or none of them. */
|
|
9962
|
-
if (
|
|
9963
|
-
pipeline !== null &&
|
|
9964
|
-
splits &&
|
|
9965
|
-
!this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)
|
|
9966
|
-
) {
|
|
9967
|
-
pipeline = null;
|
|
9968
|
-
}
|
|
10141
|
+
/* Its pipeline, by the key a prepared draw compiled too: see `meshShape`. */
|
|
10142
|
+
const { base, key, layer, depthWrite, splits } = this.meshShape(
|
|
10143
|
+
mesh,
|
|
10144
|
+
blend,
|
|
10145
|
+
options,
|
|
10146
|
+
skinned,
|
|
10147
|
+
morphed,
|
|
10148
|
+
eight,
|
|
10149
|
+
clothed,
|
|
10150
|
+
);
|
|
10151
|
+
let pipeline = this.meshPipeline(blend, lands, skinned, morphed, eight, clothed);
|
|
9969
10152
|
|
|
9970
10153
|
/*
|
|
9971
10154
|
* Guards the draw call only, not the restore below.
|
|
@@ -10356,6 +10539,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10356
10539
|
const seeAt = this.materialField('uSeeThrough');
|
|
10357
10540
|
this.perFrameFloats[seeAt] = 0;
|
|
10358
10541
|
this.perFrameFloats[seeAt + 2] = 0;
|
|
10542
|
+
/* The material's light through, over the pane's tint: transmission.ts. */
|
|
10543
|
+
this.perFrameFloats.set(this.transmission, this.materialField('uGlassTint'));
|
|
10359
10544
|
}
|
|
10360
10545
|
|
|
10361
10546
|
/**
|
|
@@ -10370,6 +10555,76 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10370
10555
|
* **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
|
|
10371
10556
|
* is built, and a per-instance opacity: `opacity` here dims the whole batch.
|
|
10372
10557
|
*/
|
|
10558
|
+
/**
|
|
10559
|
+
* An instanced draw's pipeline under the current material and these options: the one built, one
|
|
10560
|
+
* built here, or none while it compiles (`PipelineCache.lit`), its key, base and model left in
|
|
10561
|
+
* `drawShape`. **The one statement of an instanced key**: `submitInstanced` draws by it and
|
|
10562
|
+
* `prepareInstanced` compiles by it.
|
|
10563
|
+
*/
|
|
10564
|
+
private instancedPipeline(
|
|
10565
|
+
mesh: GpuMesh,
|
|
10566
|
+
animated: boolean,
|
|
10567
|
+
blend: boolean,
|
|
10568
|
+
lands: boolean,
|
|
10569
|
+
options: TranslucentMeshOptions,
|
|
10570
|
+
depthWrite: boolean,
|
|
10571
|
+
layer: number,
|
|
10572
|
+
coverage: boolean,
|
|
10573
|
+
/** Which cache to build into: the one the draw's target reads, unless a preparation says. */
|
|
10574
|
+
into: PipelineCache | null = null,
|
|
10575
|
+
): GPURenderPipeline | null {
|
|
10576
|
+
/* A batch shades by its material's model too; an eye's axis is a placement's, not a batch's. */
|
|
10577
|
+
const modelKind = this.materialModel?.kind ?? null;
|
|
10578
|
+
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
10579
|
+
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
10580
|
+
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
10581
|
+
plain one would hand a draw the wrong layout. */
|
|
10582
|
+
const adds = blend && options.additive === true;
|
|
10583
|
+
const key =
|
|
10584
|
+
`${base}|inst${!animated ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
10585
|
+
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
10586
|
+
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
10587
|
+
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
10588
|
+
|
|
10589
|
+
const shape = this.drawShape;
|
|
10590
|
+
shape.base = base;
|
|
10591
|
+
shape.key = key;
|
|
10592
|
+
shape.modelKind = modelKind;
|
|
10593
|
+
const pipelines = into ?? (blend ? this.blendedPipelines(lands) : this.targetPipelines());
|
|
10594
|
+
let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
|
|
10595
|
+
if (pipeline === null) {
|
|
10596
|
+
const present = this.meshPresent.get(base);
|
|
10597
|
+
if (present === undefined) {
|
|
10598
|
+
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
10599
|
+
}
|
|
10600
|
+
pipeline = flatPipeline(
|
|
10601
|
+
pipelines,
|
|
10602
|
+
this.surface.device,
|
|
10603
|
+
!animated ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout,
|
|
10604
|
+
this.variant,
|
|
10605
|
+
key,
|
|
10606
|
+
present,
|
|
10607
|
+
adds ? 'additive' : blend,
|
|
10608
|
+
false,
|
|
10609
|
+
false,
|
|
10610
|
+
depthWrite,
|
|
10611
|
+
layer,
|
|
10612
|
+
true,
|
|
10613
|
+
'none',
|
|
10614
|
+
this.materialDoubleSided,
|
|
10615
|
+
coverage,
|
|
10616
|
+
false,
|
|
10617
|
+
false,
|
|
10618
|
+
modelKind,
|
|
10619
|
+
'whole',
|
|
10620
|
+
false,
|
|
10621
|
+
animated,
|
|
10622
|
+
);
|
|
10623
|
+
}
|
|
10624
|
+
|
|
10625
|
+
return pipeline;
|
|
10626
|
+
}
|
|
10627
|
+
|
|
10373
10628
|
/**
|
|
10374
10629
|
* The next of a batch's cull slots this frame, with its cull queued against the current view; null
|
|
10375
10630
|
* when the batch has drawn from `MAX_CULL_SLOTS` views already or the frame's job ring is full,
|
|
@@ -10488,50 +10743,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10488
10743
|
const own =
|
|
10489
10744
|
ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog) || ditheredBlend;
|
|
10490
10745
|
if (own) this.materials.dirty();
|
|
10491
|
-
/*
|
|
10492
|
-
const
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
10505
|
-
let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
|
|
10506
|
-
if (pipeline === null) {
|
|
10507
|
-
const present = this.meshPresent.get(base);
|
|
10508
|
-
if (present === undefined) {
|
|
10509
|
-
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
10510
|
-
}
|
|
10511
|
-
pipeline = flatPipeline(
|
|
10512
|
-
pipelines,
|
|
10513
|
-
this.surface.device,
|
|
10514
|
-
animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout,
|
|
10515
|
-
this.variant,
|
|
10516
|
-
key,
|
|
10517
|
-
present,
|
|
10518
|
-
adds ? 'additive' : blend,
|
|
10519
|
-
false,
|
|
10520
|
-
false,
|
|
10521
|
-
depthWrite,
|
|
10522
|
-
layer,
|
|
10523
|
-
true,
|
|
10524
|
-
'none',
|
|
10525
|
-
this.materialDoubleSided,
|
|
10526
|
-
coverage,
|
|
10527
|
-
false,
|
|
10528
|
-
false,
|
|
10529
|
-
modelKind,
|
|
10530
|
-
'whole',
|
|
10531
|
-
false,
|
|
10532
|
-
animation !== null,
|
|
10533
|
-
);
|
|
10534
|
-
}
|
|
10746
|
+
/* Its pipeline, by the key a prepared batch compiled too: see `instancedPipeline`. */
|
|
10747
|
+
const pipeline = this.instancedPipeline(
|
|
10748
|
+
mesh,
|
|
10749
|
+
animation !== null,
|
|
10750
|
+
blend,
|
|
10751
|
+
lands,
|
|
10752
|
+
options,
|
|
10753
|
+
depthWrite,
|
|
10754
|
+
layer,
|
|
10755
|
+
coverage,
|
|
10756
|
+
);
|
|
10757
|
+
const { base, key, modelKind } = this.drawShape;
|
|
10535
10758
|
|
|
10536
10759
|
/* A batch whose pipeline is still compiling is left out, as a mesh is. */
|
|
10537
10760
|
const material = pipeline === null ? null : this.materialSlotForDraw();
|
|
@@ -14106,6 +14329,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14106
14329
|
const f = this.aoFloats;
|
|
14107
14330
|
const at = (name: string): number => this.postField(AO_FRAG_FIELDS, name);
|
|
14108
14331
|
f[at('uRadius')] = this.quality.ambientOcclusionRadius;
|
|
14332
|
+
f.set(this.aoFade, at('uFade'));
|
|
14109
14333
|
f.set(this.aoProjScale, at('uProjScale'));
|
|
14110
14334
|
f.set(this.aoInvProjection, at('uInvProjection'));
|
|
14111
14335
|
device.queue.writeBuffer(this.aoUniforms, 0, this.aoStaging);
|
|
@@ -17007,6 +17231,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
17007
17231
|
this.rebuildRushBindGroup();
|
|
17008
17232
|
}
|
|
17009
17233
|
|
|
17234
|
+
/**
|
|
17235
|
+
* Where ambient occlusion fades out with distance. **The other backend's
|
|
17236
|
+
* `setAmbientOcclusionFade` carries the reasoning**; this is the same setter over the same state.
|
|
17237
|
+
*/
|
|
17238
|
+
setAmbientOcclusionFade(distance: number, radius: number): void {
|
|
17239
|
+
resolveOcclusionFade(distance, radius, this.aoFade);
|
|
17240
|
+
}
|
|
17241
|
+
|
|
17010
17242
|
/**
|
|
17011
17243
|
* Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
|
|
17012
17244
|
* to take. **The other backend's `setDepthOfField` carries the reasoning**, whole: the
|