@driftengine/core 4.10.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 +107 -0
- package/README.md +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2 -2
- 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 +141 -3
- package/dist/render/backend/webgl2/renderer.js +454 -42
- package/dist/render/backend/webgpu/buffers.d.ts +5 -0
- package/dist/render/backend/webgpu/buffers.js +98 -48
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
- package/dist/render/backend/webgpu/flatPass.js +2 -1
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
- package/dist/render/backend/webgpu/pipelineCache.js +39 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -1
- package/dist/render/backend/webgpu/renderer.js +423 -95
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- package/dist/render/backend/webgpu/staticBundles.js +2 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -0
- package/dist/render/instances.d.ts +13 -1
- package/dist/render/instances.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/mesh.d.ts +9 -0
- package/dist/render/mesh.js +38 -14
- package/dist/render/occlusionFade.d.ts +21 -0
- package/dist/render/occlusionFade.js +27 -0
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/ambientOcclusion.js +12 -1
- package/dist/render/shaders/flat/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +24 -4
- package/dist/render/shaders/flat/layered.d.ts +27 -0
- package/dist/render/shaders/flat/layered.js +198 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +50 -16
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +23 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +41 -19
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- 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 +768 -368
- package/dist/render/shaders/generated/flat.wgsl.js +791 -391
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/surfaceLayers.d.ts +107 -0
- package/dist/render/surfaceLayers.js +82 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +39 -0
- package/dist/render/transmission.d.ts +21 -0
- package/dist/render/transmission.js +21 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -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 +6 -2
- package/src/render/ambientOcclusionPass.ts +4 -1
- package/src/render/backend/api.ts +31 -0
- package/src/render/backend/webgl2/renderer.ts +519 -56
- package/src/render/backend/webgpu/buffers.ts +153 -54
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/drawCommand.ts +3 -0
- package/src/render/backend/webgpu/flatPass.ts +26 -28
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +46 -7
- package/src/render/backend/webgpu/renderer.ts +622 -164
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/staticBundles.ts +2 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +18 -1
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/occlusionFade.ts +29 -0
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/ambientOcclusion.ts +12 -1
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +39 -4
- package/src/render/shaders/flat/layered.ts +198 -0
- package/src/render/shaders/flat/main.ts +50 -16
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +23 -3
- package/src/render/shaders/flat/overlay.ts +41 -19
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
- package/src/render/shaders/generated/flat.wgsl.ts +791 -391
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/surfaceLayers.ts +176 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +39 -0
- package/src/render/transmission.ts +27 -0
- package/src/render/vertexPacking.ts +109 -0
- package/src/ui/splash.ts +83 -14
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -90,7 +90,8 @@ import { clamp } from '../../../math/scalar.ts';
|
|
|
90
90
|
import { attachContextLoss } from '../../contextLoss.ts';
|
|
91
91
|
import { isWeakGpuFamily, webgl2RendererName } from '../../gpuCapability.ts';
|
|
92
92
|
import { GpuTimer } from '../../gpuTimer.ts';
|
|
93
|
-
import { compileProgram, uniformLocations } from '../../shader.ts';
|
|
93
|
+
import { compileProgram, finishProgram, startProgram, uniformLocations } from '../../shader.ts';
|
|
94
|
+
import type { StartedProgram } from '../../shader.ts';
|
|
94
95
|
import {
|
|
95
96
|
countUniformVectors,
|
|
96
97
|
FULL_LIGHT_BUDGET,
|
|
@@ -104,6 +105,19 @@ import {
|
|
|
104
105
|
packSurfaceOverlay,
|
|
105
106
|
type SurfaceOverlay,
|
|
106
107
|
} from '../../surfaceOverlay.ts';
|
|
108
|
+
import { PROJECTION_FLOATS, packSurfaceProjection, projects } from '../../surfaceProjection.ts';
|
|
109
|
+
import { resolveTransmission } from '../../transmission.ts';
|
|
110
|
+
import { resolveOcclusionFade } from '../../occlusionFade.ts';
|
|
111
|
+
import { orthographicDepthSpan } from '../../lightMatrix.ts';
|
|
112
|
+
import {
|
|
113
|
+
LAYER_FLOATS,
|
|
114
|
+
layerMaskMap,
|
|
115
|
+
layeredProjection,
|
|
116
|
+
layersFitBeside,
|
|
117
|
+
packSurfaceLayers,
|
|
118
|
+
} from '../../surfaceLayers.ts';
|
|
119
|
+
import type { SurfaceLayers } from '../../surfaceLayers.ts';
|
|
120
|
+
import type { SurfaceProjection } from '../../surfaceProjection.ts';
|
|
107
121
|
import { FILM_FRAG, FILM_VERT } from '../../shaders/film.ts';
|
|
108
122
|
import { SceneTarget } from '../../sceneTarget.ts';
|
|
109
123
|
import type { ColourGradeLut } from '../../colourGrade.ts';
|
|
@@ -276,7 +290,7 @@ import type { BufferSize } from '../../drawingBuffer.ts';
|
|
|
276
290
|
import { Mesh, createMeshIncremental } from '../../mesh.ts';
|
|
277
291
|
import type { IncrementalMesh } from '../../mesh.ts';
|
|
278
292
|
import type { MeshData } from '../../mesh.ts';
|
|
279
|
-
import type { MeshOptions } from '../api.ts';
|
|
293
|
+
import type { MeshOptions, PrepareOptions } from '../api.ts';
|
|
280
294
|
import { SurfaceTexture } from '../../surfaceTexture.ts';
|
|
281
295
|
import { sceneCaptureTexels } from '../../sceneCapture.ts';
|
|
282
296
|
import { GlSceneCaptureTarget } from './sceneCaptureTarget.ts';
|
|
@@ -1049,6 +1063,37 @@ const IDENTITY_CLIP = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0
|
|
|
1049
1063
|
/** Standing still, for a caller that asked for dust and not for wind. */
|
|
1050
1064
|
const NO_DRIFT: Vec3 = [0, 0, 0];
|
|
1051
1065
|
|
|
1066
|
+
/** Whether two matrices hold the same sixteen numbers. */
|
|
1067
|
+
function sameMatrix(a: ArrayLike<number>, b: ArrayLike<number>): boolean {
|
|
1068
|
+
for (let k = 0; k < 16; k++) if (a[k] !== b[k]) return false;
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/** How often `ready` asks the driver whether a program is done, and how many times before waiting. */
|
|
1073
|
+
const READY_POLL_MS = 4;
|
|
1074
|
+
const READY_MAX_POLLS = 500;
|
|
1075
|
+
|
|
1076
|
+
/** The halves a skin draws under the screen-space blur, each a program of its own. */
|
|
1077
|
+
const SPLIT_SKIN_HALVES = ['scene', 'diffuse', 'albedo'] as const;
|
|
1078
|
+
|
|
1079
|
+
/** A lit program with its uniform table, as every flat program is kept. */
|
|
1080
|
+
interface FlatProgram {
|
|
1081
|
+
program: WebGLProgram;
|
|
1082
|
+
uniforms: Record<string, WebGLUniformLocation>;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/** The vertex variant a draw takes, and its program where it is not the plain one. */
|
|
1086
|
+
interface DrawVariant {
|
|
1087
|
+
/** What a model's program is keyed by: `plain`, `skinned8`, `cloth4m` and the rest. */
|
|
1088
|
+
name: string;
|
|
1089
|
+
skinned: boolean;
|
|
1090
|
+
morphed: boolean;
|
|
1091
|
+
cloth: { binding: GlClothBinding; particles: GlClothParticles } | null;
|
|
1092
|
+
clothed: FlatProgram | undefined;
|
|
1093
|
+
program: WebGLProgram | null;
|
|
1094
|
+
uniforms: Record<string, WebGLUniformLocation>;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1052
1097
|
export class WebGL2Renderer implements RendererApi {
|
|
1053
1098
|
/**
|
|
1054
1099
|
* Private on purpose. The GL context never leaves this directory, so a
|
|
@@ -1347,6 +1392,23 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1347
1392
|
string,
|
|
1348
1393
|
{ program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> }
|
|
1349
1394
|
>();
|
|
1395
|
+
/**
|
|
1396
|
+
* Model programs still compiling, where draws skip a compile (`pipelineCompile: 'skip'`) and the
|
|
1397
|
+
* context offers `KHR_parallel_shader_compile`; `parallelCompile` is its completion query then,
|
|
1398
|
+
* and null where draws wait for a program here instead. See `modelProgram`.
|
|
1399
|
+
*/
|
|
1400
|
+
private readonly modelPending = new Map<string, StartedProgram>();
|
|
1401
|
+
/** What `drawVariantOf` answers, rewritten each draw rather than made. */
|
|
1402
|
+
private readonly drawVariant: DrawVariant = {
|
|
1403
|
+
name: 'plain',
|
|
1404
|
+
skinned: false,
|
|
1405
|
+
morphed: false,
|
|
1406
|
+
cloth: null,
|
|
1407
|
+
clothed: undefined,
|
|
1408
|
+
program: null,
|
|
1409
|
+
uniforms: {},
|
|
1410
|
+
};
|
|
1411
|
+
private readonly parallelCompile: { readonly COMPLETION_STATUS_KHR: number } | null;
|
|
1350
1412
|
/** The last palette chosen, which an eye's axis is turned by. See `setSkinPalette`. */
|
|
1351
1413
|
private paletteData: Float32Array | null = null;
|
|
1352
1414
|
|
|
@@ -1410,7 +1472,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1410
1472
|
private oitReplaying = false;
|
|
1411
1473
|
/** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
|
|
1412
1474
|
private readonly surfaceScene = new Float32Array(4);
|
|
1413
|
-
/** The
|
|
1475
|
+
/** The three lanes of `uWriteMode`, kept here because a `vec3` is written whole. */
|
|
1414
1476
|
private oitWeight = 0;
|
|
1415
1477
|
/**
|
|
1416
1478
|
* How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
|
|
@@ -1418,6 +1480,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1418
1480
|
*/
|
|
1419
1481
|
private surfaceFog = FOG_RECEDE;
|
|
1420
1482
|
private ditherFade = 0;
|
|
1483
|
+
private ditherOpacity = 0;
|
|
1421
1484
|
/** Said once, not per frame, where the context cannot give the float target the sum needs. */
|
|
1422
1485
|
private oitRefused = false;
|
|
1423
1486
|
/** Said once for the whole renderer. See `refuseOitMultisampled`. */
|
|
@@ -1544,6 +1607,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1544
1607
|
private readonly aoProjScale = new Float32Array(2);
|
|
1545
1608
|
/** Where this frame's jitter put the occlusion, in the composite's uv. See `uAoOffset`. */
|
|
1546
1609
|
private readonly aoOffset = new Float32Array(2);
|
|
1610
|
+
/** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
|
|
1611
|
+
private readonly aoFade = new Float32Array([-1, 0]);
|
|
1547
1612
|
/**
|
|
1548
1613
|
* Whether this frame has already been resolved to the canvas.
|
|
1549
1614
|
*
|
|
@@ -1697,6 +1762,32 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1697
1762
|
private overlayTexture: WebGLTexture | null = null;
|
|
1698
1763
|
/** Whether this device's lit stage has room for the overlay's vectors: asked once. */
|
|
1699
1764
|
private overlayRoom: boolean | null = null;
|
|
1765
|
+
/**
|
|
1766
|
+
* The matrix the sun's dynamic shadow pass was last drawn with, uncorrected, and whether one has
|
|
1767
|
+
* been: what the moving layer is read through. See `movingLightFor`.
|
|
1768
|
+
*/
|
|
1769
|
+
private readonly movingLightViewProj = new Float32Array(16);
|
|
1770
|
+
private movingLightKnown = false;
|
|
1771
|
+
/** Whether the lit stage has room for the moving layer's matrix, and whether its refusal was said. */
|
|
1772
|
+
private movingRoom: boolean | null = null;
|
|
1773
|
+
private warnedMovingRoom = false;
|
|
1774
|
+
/** The same answer for a material's projection, and whether its refusal was said. */
|
|
1775
|
+
private projectionRoom: boolean | null = null;
|
|
1776
|
+
private warnedProjectionRoom = false;
|
|
1777
|
+
/** `uWorldUv`, the current material's projection packed. See `surfaceProjection.ts`. */
|
|
1778
|
+
private readonly projection = new Float32Array(PROJECTION_FLOATS);
|
|
1779
|
+
/**
|
|
1780
|
+
* `uGlassTint` as the current material leaves it: its light from behind and that light's colour,
|
|
1781
|
+
* which a pane overwrites for its own draw and `unbindSeeThrough` puts back. See `transmission.ts`.
|
|
1782
|
+
*/
|
|
1783
|
+
private readonly transmission = new Float32Array([-1, -1, -1, 0]);
|
|
1784
|
+
/** `uLayers`, the current material's layers packed. See `surfaceLayers.ts`. */
|
|
1785
|
+
private readonly layerFloats = new Float32Array(LAYER_FLOATS);
|
|
1786
|
+
/** Whether the lit stage has room for layers' two vectors, and which refusals were said. */
|
|
1787
|
+
private layersRoom: boolean | null = null;
|
|
1788
|
+
private warnedLayersRoom = false;
|
|
1789
|
+
private warnedLayersModelMap = false;
|
|
1790
|
+
private warnedLayersTriplanar = false;
|
|
1700
1791
|
private warnedOverlayRoom = false;
|
|
1701
1792
|
private warnedOverlayBlocks = false;
|
|
1702
1793
|
/** Whether a probe is being baked, so the passes that would recurse into one can tell. */
|
|
@@ -3024,6 +3115,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3024
3115
|
* Measured on an RX 9070 XT through ANGLE/Vulkan: offered. `scripts/depth-survey.mjs` is what
|
|
3025
3116
|
* asks, and it also reports what WebGPU can do.
|
|
3026
3117
|
*/
|
|
3118
|
+
/*
|
|
3119
|
+
* Where draws skip a compile, the query that says a program is done without waiting for it;
|
|
3120
|
+
* where the context has none, a program is waited for as it always was. See `modelProgram`.
|
|
3121
|
+
*/
|
|
3122
|
+
this.parallelCompile =
|
|
3123
|
+
this.quality.pipelineCompile === 'skip'
|
|
3124
|
+
? (gl.getExtension('KHR_parallel_shader_compile') as {
|
|
3125
|
+
readonly COMPLETION_STATUS_KHR: number;
|
|
3126
|
+
} | null)
|
|
3127
|
+
: null;
|
|
3027
3128
|
this.clipControl = gl.getExtension('EXT_clip_control') as ClipControlExtension | null;
|
|
3028
3129
|
if (REVERSED_DEPTH && this.clipControl !== null) {
|
|
3029
3130
|
this.clipControl.clipControlEXT(
|
|
@@ -3361,7 +3462,36 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3361
3462
|
* this before its first frame gets the right behaviour on both without asking which backend
|
|
3362
3463
|
* it has, which is the contract `RendererApi` exists to keep.
|
|
3363
3464
|
*/
|
|
3364
|
-
|
|
3465
|
+
/**
|
|
3466
|
+
* Resolve once every program a draw has started compiling has finished, adopting each: what a
|
|
3467
|
+
* loading screen awaits after drawing what its scene will use, where draws skip a compile. See
|
|
3468
|
+
* `RenderQuality.pipelineCompile`; nothing is started anywhere else here, so otherwise it is now.
|
|
3469
|
+
*
|
|
3470
|
+
* **It waits without holding the thread**, as the other backend's does: it asks the driver
|
|
3471
|
+
* whether each is done, which does not wait, and yields between asks. A driver that never says
|
|
3472
|
+
* done is asked the way that waits after `READY_MAX_POLLS`, so this cannot hang a loading screen.
|
|
3473
|
+
*/
|
|
3474
|
+
async ready(): Promise<void> {
|
|
3475
|
+
for (let polls = 0; this.modelPending.size > 0 && !this.contextLost; polls++) {
|
|
3476
|
+
for (const [key, started] of this.modelPending) {
|
|
3477
|
+
const done =
|
|
3478
|
+
polls >= READY_MAX_POLLS ||
|
|
3479
|
+
this.parallelCompile === null ||
|
|
3480
|
+
this.gl.getProgramParameter(
|
|
3481
|
+
started.program,
|
|
3482
|
+
this.parallelCompile.COMPLETION_STATUS_KHR,
|
|
3483
|
+
) === true;
|
|
3484
|
+
if (!done) continue;
|
|
3485
|
+
this.modelPending.delete(key);
|
|
3486
|
+
this.adoptModel(key, finishProgram(this.gl, started), started.label);
|
|
3487
|
+
}
|
|
3488
|
+
this.useFlatProgram();
|
|
3489
|
+
if (this.modelPending.size > 0) {
|
|
3490
|
+
// platform: browser default — a yield between two asks of the driver, in a browser module
|
|
3491
|
+
await new Promise<void>((resolve) => setTimeout(resolve, READY_POLL_MS));
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3365
3495
|
|
|
3366
3496
|
dispose(options: { releaseContext?: boolean } = {}): void {
|
|
3367
3497
|
if (this.disposed) return;
|
|
@@ -3813,6 +3943,58 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3813
3943
|
* `uScale`/`vScale` are repeats across the mesh's own UV range: geometry authored in
|
|
3814
3944
|
* metres is textured at whatever density the material wants without rebuilding it.
|
|
3815
3945
|
*/
|
|
3946
|
+
/**
|
|
3947
|
+
* Compile, off the frame, every program a draw of `mesh` in `material` takes that making the mesh
|
|
3948
|
+
* did not, so the first frame that draws it waits on none: the program of the material's shading
|
|
3949
|
+
* model for each vertex variant the mesh can be drawn with (its rig, with a palette; its targets,
|
|
3950
|
+
* with weights and without), a skin's halves under the screen-space blur, and the lit switches
|
|
3951
|
+
* the material turns on. `ready()` resolves once they have.
|
|
3952
|
+
*
|
|
3953
|
+
* **For `pipelineCompile: 'skip'` above all**, where a draw whose program is new is left out until
|
|
3954
|
+
* it lands: a figure brought on screen whole rather than without its face and hair for the frames
|
|
3955
|
+
* those take. Where draws wait, the programs compile here, at the call, rather than in a frame.
|
|
3956
|
+
* Two-sided and cut-out surfaces are draw state on this backend and need nothing; on WebGPU they
|
|
3957
|
+
* are pipelines of their own, prepared the same way.
|
|
3958
|
+
*
|
|
3959
|
+
* **Call it between frames**: it sets `material` to read it and leaves no material set after, as
|
|
3960
|
+
* `setMaterial(null)` does. `options` is the other backend's; blended draws share a program here.
|
|
3961
|
+
*/
|
|
3962
|
+
prepareMesh(mesh: Mesh, material: SurfaceMaterial | null, _options: PrepareOptions = {}): void {
|
|
3963
|
+
if (this.contextLost) return;
|
|
3964
|
+
this.setMaterial(material);
|
|
3965
|
+
const kind = material?.model?.kind ?? null;
|
|
3966
|
+
if (kind !== null) {
|
|
3967
|
+
const eight = mesh.isSkinnedEight;
|
|
3968
|
+
const morphs = mesh.morph !== null;
|
|
3969
|
+
const plain = mesh.isSkinned ? (eight ? 'skinned8' : 'skinned') : 'plain';
|
|
3970
|
+
const morphed = mesh.isSkinned ? (eight ? 'both8' : 'both') : 'morphed';
|
|
3971
|
+
for (const variant of morphs ? [plain, morphed] : [plain]) {
|
|
3972
|
+
this.modelProgram(variant, kind);
|
|
3973
|
+
/* A skin under the screen-space blur draws three halves, each a program of its own. */
|
|
3974
|
+
if (kind === 'skin' && this.skinScatter !== null && this.sceneTarget !== null) {
|
|
3975
|
+
for (const half of SPLIT_SKIN_HALVES) this.modelProgram(variant, kind, half);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
this.setMaterial(null);
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
/** `prepareMesh` for an instanced batch: its own programs, and its model's for its variant. */
|
|
3983
|
+
prepareInstanced(
|
|
3984
|
+
batch: InstancedBatch,
|
|
3985
|
+
material: SurfaceMaterial | null,
|
|
3986
|
+
_options: PrepareOptions = {},
|
|
3987
|
+
): void {
|
|
3988
|
+
if (this.contextLost) return;
|
|
3989
|
+
this.setMaterial(material);
|
|
3990
|
+
const animated = batch.animation !== null;
|
|
3991
|
+
this.ensureInstancedProgram();
|
|
3992
|
+
if (animated) this.ensureAnimatedProgram();
|
|
3993
|
+
const kind = material?.model?.kind ?? null;
|
|
3994
|
+
if (kind !== null) this.modelProgram(animated ? 'animated' : 'instanced', kind);
|
|
3995
|
+
this.setMaterial(null);
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3816
3998
|
/**
|
|
3817
3999
|
* Choose the joint palette the following `drawMesh` calls skin by, or null for none.
|
|
3818
4000
|
*
|
|
@@ -3845,18 +4027,43 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3845
4027
|
kind: SurfaceModelKind,
|
|
3846
4028
|
/** Which half of a skin it draws under the screen-space blur. See `SkinHalf`. */
|
|
3847
4029
|
half: SkinHalf = 'whole',
|
|
3848
|
-
): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } {
|
|
4030
|
+
): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } | null {
|
|
3849
4031
|
const key = `${variant}|${kind}|${half}`;
|
|
3850
4032
|
const held = this.modelFlat.get(key);
|
|
3851
4033
|
if (held !== undefined) return held;
|
|
3852
4034
|
const { gl } = this;
|
|
3853
4035
|
const label = `flat.${variant}.${kind}.${half}`;
|
|
3854
|
-
const
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
4036
|
+
const vertex = modelVertexOf(variant, kind);
|
|
4037
|
+
const fragment = skinHalfBound(modelBound(this.flatFragSource, kind), half);
|
|
4038
|
+
/*
|
|
4039
|
+
* **Where draws skip a compile, none until the driver says asking will not wait**: the program
|
|
4040
|
+
* is started at the first draw that wants it and that draw, and every one before it is ready,
|
|
4041
|
+
* is left out. See `RenderQuality.pipelineCompile`.
|
|
4042
|
+
*/
|
|
4043
|
+
if (this.parallelCompile !== null) {
|
|
4044
|
+
const started = this.modelPending.get(key);
|
|
4045
|
+
if (started === undefined) {
|
|
4046
|
+
this.modelPending.set(key, startProgram(gl, vertex, fragment, label));
|
|
4047
|
+
return null;
|
|
4048
|
+
}
|
|
4049
|
+
if (
|
|
4050
|
+
gl.getProgramParameter(started.program, this.parallelCompile.COMPLETION_STATUS_KHR) !== true
|
|
4051
|
+
) {
|
|
4052
|
+
return null;
|
|
4053
|
+
}
|
|
4054
|
+
this.modelPending.delete(key);
|
|
4055
|
+
return this.adoptModel(key, finishProgram(gl, started), label);
|
|
4056
|
+
}
|
|
4057
|
+
return this.adoptModel(key, compileProgram(gl, vertex, fragment, label), label);
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
/** A model program compiled: kept, written to by every material setter, and fed the pass. */
|
|
4061
|
+
private adoptModel(
|
|
4062
|
+
key: string,
|
|
4063
|
+
program: WebGLProgram,
|
|
4064
|
+
label: string,
|
|
4065
|
+
): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } {
|
|
4066
|
+
const { gl } = this;
|
|
3860
4067
|
const made = { program, uniforms: uniformLocations(gl, program, label) };
|
|
3861
4068
|
this.modelFlat.set(key, made);
|
|
3862
4069
|
this.flatTargets.push(made);
|
|
@@ -4182,9 +4389,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4182
4389
|
* and read neither — one decision for every program, as `setMaterial` says.
|
|
4183
4390
|
*/
|
|
4184
4391
|
const modelMap = material?.modelMap ?? null;
|
|
4392
|
+
/* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
|
|
4393
|
+
const layers = this.layersOf(material);
|
|
4394
|
+
const boundMap = modelMap ?? layerMaskMap(layers);
|
|
4185
4395
|
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
4186
4396
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
4187
|
-
if (
|
|
4397
|
+
if (boundMap !== null) boundMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
|
|
4398
|
+
packSurfaceLayers(layers, this.layerFloats);
|
|
4399
|
+
if (layers !== null) this.askLit('LAYERED');
|
|
4400
|
+
gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
|
|
4188
4401
|
gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
|
|
4189
4402
|
packModel(
|
|
4190
4403
|
material?.model ?? null,
|
|
@@ -4197,6 +4410,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4197
4410
|
gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
|
|
4198
4411
|
/* The draw's overlay, which a program born mid-pass must wear too: see `setSurfaceOverlay`. */
|
|
4199
4412
|
gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
|
|
4413
|
+
gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
|
|
4414
|
+
/* How much light from behind the surface lets through, and its colour: transmission.ts. */
|
|
4415
|
+
resolveTransmission(material, this.transmission);
|
|
4416
|
+
this.writeTransmission(u);
|
|
4417
|
+
/* Maps placed by the world, where the material asks and the lit stage has room: worldUv.ts. */
|
|
4418
|
+
const projection = layeredProjection(material?.projection, layers);
|
|
4419
|
+
if (projection !== (material?.projection ?? null)) this.warnLayersTriplanar();
|
|
4420
|
+
packSurfaceProjection(this.projectionFits(projection) ? projection : null, this.projection);
|
|
4421
|
+
if ((this.projection[0] as number) > 0) this.askLit('WORLD_UVS');
|
|
4422
|
+
gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
|
|
4200
4423
|
|
|
4201
4424
|
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
4202
4425
|
return, so a material with no albedo still leaves a complete texture on the unit. */
|
|
@@ -4371,17 +4594,121 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4371
4594
|
/** What the lit shader at this renderer's budget leaves of the limit, with no overlay. */
|
|
4372
4595
|
private overlayRoomLeft(): number {
|
|
4373
4596
|
return (
|
|
4374
|
-
this.fragmentVectorLimit() -
|
|
4597
|
+
this.fragmentVectorLimit() -
|
|
4598
|
+
countUniformVectors(this.flatFragAt(this.lightBudget, { SURFACE_OVERLAY: false }))
|
|
4375
4599
|
);
|
|
4376
4600
|
}
|
|
4377
4601
|
|
|
4378
4602
|
/** Whether the lit shader with the overlay compiled in fits this GPU: counted once. */
|
|
4379
4603
|
private overlayFits(): boolean {
|
|
4380
|
-
this.overlayRoom ??=
|
|
4381
|
-
countUniformVectors(this.flatFragAt(this.lightBudget, true)) <= this.fragmentVectorLimit();
|
|
4604
|
+
this.overlayRoom ??= this.switchFits('SURFACE_OVERLAY');
|
|
4382
4605
|
return this.overlayRoom;
|
|
4383
4606
|
}
|
|
4384
4607
|
|
|
4608
|
+
/**
|
|
4609
|
+
* Whether a material's projection can be drawn: none asked, or the lit stage has the room for its
|
|
4610
|
+
* one vector. **Refused, said once, where it has not**, as the overlay is, and the material's
|
|
4611
|
+
* maps are then laid by the mesh's coordinates, as they were before it existed.
|
|
4612
|
+
*/
|
|
4613
|
+
private projectionFits(projection: SurfaceProjection | null): boolean {
|
|
4614
|
+
if (!projects(projection)) return true;
|
|
4615
|
+
this.projectionRoom ??= this.switchFits('WORLD_UVS');
|
|
4616
|
+
if (!this.projectionRoom && !this.warnedProjectionRoom) {
|
|
4617
|
+
this.warnedProjectionRoom = true;
|
|
4618
|
+
console.warn(
|
|
4619
|
+
"Renderer: a material's projection is refused on this device. It takes one fragment " +
|
|
4620
|
+
'uniform vector and the lit shader already uses all ' +
|
|
4621
|
+
`${this.fragmentVectorLimit()} this GPU offers; its maps follow the mesh's coordinates. ` +
|
|
4622
|
+
'A lower `maxLights` makes the room, or WebGPU has it.',
|
|
4623
|
+
);
|
|
4624
|
+
}
|
|
4625
|
+
return this.projectionRoom;
|
|
4626
|
+
}
|
|
4627
|
+
|
|
4628
|
+
/**
|
|
4629
|
+
* What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
|
|
4630
|
+
* where the lit stage carries one (`MOVING_SUN`); the environment's otherwise, which every caller
|
|
4631
|
+
* drawing both layers with one matrix hands it anyway.
|
|
4632
|
+
*/
|
|
4633
|
+
private movingLightFor(env: Environment): ReadonlyMat4 {
|
|
4634
|
+
return this.movingLightKnown && this.litOn.MOVING_SUN
|
|
4635
|
+
? this.movingLightViewProj
|
|
4636
|
+
: env.lightViewProj;
|
|
4637
|
+
}
|
|
4638
|
+
|
|
4639
|
+
/**
|
|
4640
|
+
* Turn the moving layer's own matrix on the first time a frame draws that layer with one the
|
|
4641
|
+
* environment's does not share. **Refused, said once, where the lit stage has no room for its five
|
|
4642
|
+
* vectors**: the layer is then read through the environment's matrix, so its shadows land where
|
|
4643
|
+
* that matrix puts them, and a caller there draws both layers with one.
|
|
4644
|
+
*/
|
|
4645
|
+
private askMovingSun(env: Environment): void {
|
|
4646
|
+
if (this.litWanted.MOVING_SUN || !this.movingLightKnown) return;
|
|
4647
|
+
if (sameMatrix(this.movingLightViewProj, env.lightViewProj)) return;
|
|
4648
|
+
this.movingRoom ??= this.switchFits('MOVING_SUN');
|
|
4649
|
+
if (this.movingRoom) {
|
|
4650
|
+
this.askLit('MOVING_SUN');
|
|
4651
|
+
return;
|
|
4652
|
+
}
|
|
4653
|
+
if (this.warnedMovingRoom) return;
|
|
4654
|
+
this.warnedMovingRoom = true;
|
|
4655
|
+
console.warn(
|
|
4656
|
+
"Renderer: the sun's moving layer cannot be read through a matrix of its own on this " +
|
|
4657
|
+
'device; it takes five fragment uniform vectors the lit shader does not have. It is read ' +
|
|
4658
|
+
"through the environment's `lightViewProj`, so draw the dynamic shadow pass with that " +
|
|
4659
|
+
'matrix here, or lower `maxLights` to make the room. WebGPU has it.',
|
|
4660
|
+
);
|
|
4661
|
+
}
|
|
4662
|
+
|
|
4663
|
+
/**
|
|
4664
|
+
* A material's layers where they can be drawn: none asked, a model's map in the mask's place, or
|
|
4665
|
+
* no room for their two vectors each leave one layer, the last two said once each.
|
|
4666
|
+
*/
|
|
4667
|
+
private layersOf(material: SurfaceMaterial | null): SurfaceLayers<SurfaceTexture> | null {
|
|
4668
|
+
const layers = (material?.layers ?? null) as SurfaceLayers<SurfaceTexture> | null;
|
|
4669
|
+
if (layers === null || layers.repeats.length === 0) return null;
|
|
4670
|
+
if (!layersFitBeside(layers, material?.modelMap)) {
|
|
4671
|
+
if (!this.warnedLayersModelMap) {
|
|
4672
|
+
this.warnedLayersModelMap = true;
|
|
4673
|
+
console.warn(
|
|
4674
|
+
"Renderer: a material's layers are drawn as one: its mask is a map, read where a " +
|
|
4675
|
+
"model's map goes, and this material carries a `modelMap` too. A mask in the ORM " +
|
|
4676
|
+
"array (`mask: 'orm'`) or the vertex colour (`mask: 'vertex'`) leaves the slot free.",
|
|
4677
|
+
);
|
|
4678
|
+
}
|
|
4679
|
+
return null;
|
|
4680
|
+
}
|
|
4681
|
+
this.layersRoom ??= this.switchFits('LAYERED');
|
|
4682
|
+
if (this.layersRoom) return layers as SurfaceLayers<SurfaceTexture>;
|
|
4683
|
+
if (!this.warnedLayersRoom) {
|
|
4684
|
+
this.warnedLayersRoom = true;
|
|
4685
|
+
console.warn(
|
|
4686
|
+
"Renderer: a material's layers are refused on this device. They take four fragment uniform " +
|
|
4687
|
+
'vectors the lit shader does not have; the material is drawn as its base layer. A lower ' +
|
|
4688
|
+
'`maxLights` makes the room, or WebGPU has it.',
|
|
4689
|
+
);
|
|
4690
|
+
}
|
|
4691
|
+
return null;
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4694
|
+
/** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
|
|
4695
|
+
private warnLayersTriplanar(): void {
|
|
4696
|
+
if (this.warnedLayersTriplanar) return;
|
|
4697
|
+
this.warnedLayersTriplanar = true;
|
|
4698
|
+
console.warn(
|
|
4699
|
+
"Renderer: a layered material's triplanar projection is laid on the horizontal plane: every " +
|
|
4700
|
+
"layer's maps on three planes would be three times the reads. See `SurfaceMaterial.layers`.",
|
|
4701
|
+
);
|
|
4702
|
+
}
|
|
4703
|
+
|
|
4704
|
+
/** Whether the lit shader with `feature` compiled in fits this GPU's fragment uniform vectors. */
|
|
4705
|
+
private switchFits(feature: LitSwitch): boolean {
|
|
4706
|
+
const asIf: Partial<Record<LitSwitch, boolean>> = { [feature]: true };
|
|
4707
|
+
return (
|
|
4708
|
+
countUniformVectors(this.flatFragAt(this.lightBudget, asIf)) <= this.fragmentVectorLimit()
|
|
4709
|
+
);
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4385
4712
|
/**
|
|
4386
4713
|
* How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
|
|
4387
4714
|
*
|
|
@@ -4525,9 +4852,38 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4525
4852
|
this.useFlatProgram();
|
|
4526
4853
|
}
|
|
4527
4854
|
|
|
4528
|
-
/**
|
|
4855
|
+
/**
|
|
4856
|
+
* Whether the draws that follow spend their own opacity on a screen door rather than on blending:
|
|
4857
|
+
* an instance's `MeshInstances.alphas`, or a vertex's channel alpha, keeps that share of the
|
|
4858
|
+
* pixels `setDitherFade` would and draws the rest not at all. So an opaque or cut-out batch fades
|
|
4859
|
+
* one instance at a time, with no sorting and no blend, and an instance at 1 is drawn whole.
|
|
4860
|
+
*
|
|
4861
|
+
* Off is what `bindMeshPass` restores. It shares the fade's pattern, so a crossfade at `t` and
|
|
4862
|
+
* `-t` of an instance at opacity `a` covers that instance's share once, without holes. Material
|
|
4863
|
+
* state like `setDitherFade`; instanced draws read it, and shadow casters do not.
|
|
4864
|
+
*
|
|
4865
|
+
* What it gives up: a fade reads as grain while it lasts, which a temporal resolve smooths and a
|
|
4866
|
+
* plain frame does not; a blended draw that asks spends its opacity here and blends none of it;
|
|
4867
|
+
* and a lightmapped batch's opacities ride its page regions, so they fade in steps of a
|
|
4868
|
+
* sixty-fourth, which is the pattern's own resolution (`MeshInstances.lightmapRegions`).
|
|
4869
|
+
*/
|
|
4870
|
+
setDitherOpacity(on: boolean): void {
|
|
4871
|
+
if (this.contextLost) return;
|
|
4872
|
+
this.materials.dirty();
|
|
4873
|
+
this.ditherOpacity = on ? 1 : 0;
|
|
4874
|
+
for (const target of this.flatTargets) {
|
|
4875
|
+
this.gl.useProgram(target.program);
|
|
4876
|
+
this.writeMode(target.uniforms);
|
|
4877
|
+
}
|
|
4878
|
+
this.useFlatProgram();
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4881
|
+
/**
|
|
4882
|
+
* `uWriteMode`: the order-independent weight, the fade and whether the opacity is dithered, which
|
|
4883
|
+
* share one uniform row.
|
|
4884
|
+
*/
|
|
4529
4885
|
private writeMode(u: Record<string, WebGLUniformLocation>): void {
|
|
4530
|
-
this.gl.
|
|
4886
|
+
this.gl.uniform3f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade, this.ditherOpacity);
|
|
4531
4887
|
}
|
|
4532
4888
|
|
|
4533
4889
|
/**
|
|
@@ -4852,6 +5208,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4852
5208
|
modelKind === null
|
|
4853
5209
|
? null
|
|
4854
5210
|
: this.modelProgram(clip === null ? 'instanced' : 'animated', modelKind);
|
|
5211
|
+
/* Its model's program still compiling, where draws skip a compile: left out until it lands. */
|
|
5212
|
+
if (modelKind !== null && modelled === null) return;
|
|
4855
5213
|
const program =
|
|
4856
5214
|
modelled?.program ?? (clip === null ? this.flatInstancedProgram : this.flatAnimatedProgram);
|
|
4857
5215
|
const u =
|
|
@@ -4996,14 +5354,29 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4996
5354
|
/* w: this framebuffer's first row is the bottom, so up the screen is up the rows. */
|
|
4997
5355
|
const tint = options.refractTint ?? WHITE_TINT;
|
|
4998
5356
|
gl.uniform4f(u['uRefractTint'] ?? null, tint[0] ?? 1, tint[1] ?? 1, tint[2] ?? 1, 1);
|
|
4999
|
-
|
|
5357
|
+
/* The pane's colour; w is the material's light through from behind, which glass never reads. */
|
|
5358
|
+
gl.uniform4f(
|
|
5359
|
+
u['uGlassTint'] ?? null,
|
|
5360
|
+
glass.tint[0] ?? 1,
|
|
5361
|
+
glass.tint[1] ?? 1,
|
|
5362
|
+
glass.tint[2] ?? 1,
|
|
5363
|
+
this.transmission[3] as number,
|
|
5364
|
+
);
|
|
5000
5365
|
return true;
|
|
5001
5366
|
}
|
|
5002
5367
|
|
|
5368
|
+
/** `uGlassTint` as the current material leaves it. See `transmission`. */
|
|
5369
|
+
private writeTransmission(u: Record<string, WebGLUniformLocation>): void {
|
|
5370
|
+
const t = this.transmission;
|
|
5371
|
+
this.gl.uniform4f(u['uGlassTint'] ?? null, t[0] ?? -1, t[1] ?? -1, t[2] ?? -1, t[3] ?? 0);
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5003
5374
|
/** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
|
|
5004
5375
|
private unbindSeeThrough(u: Record<string, WebGLUniformLocation>): void {
|
|
5005
5376
|
const { gl } = this;
|
|
5006
5377
|
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
5378
|
+
/* The material's light through, over the pane's tint: transmission.ts. */
|
|
5379
|
+
this.writeTransmission(u);
|
|
5007
5380
|
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
5008
5381
|
/* The overlay's atlas, where one is held: it shares the unit. See `setSurfaceOverlay`. */
|
|
5009
5382
|
gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
|
|
@@ -6144,6 +6517,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6144
6517
|
this.shadowPassActive = true;
|
|
6145
6518
|
this.activeSunLayer = layer;
|
|
6146
6519
|
this.glassCasters.clear();
|
|
6520
|
+
/* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
|
|
6521
|
+
if (layer === 'dynamic') {
|
|
6522
|
+
this.movingLightViewProj.set(lightViewProj as Float32Array);
|
|
6523
|
+
this.movingLightKnown = true;
|
|
6524
|
+
}
|
|
6147
6525
|
/* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
|
|
6148
6526
|
if (layer === 'static') this.peelFilled = false;
|
|
6149
6527
|
/*
|
|
@@ -6560,7 +6938,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6560
6938
|
* throws a sentence with the device's numbers in it rather than a bare driver string.
|
|
6561
6939
|
*/
|
|
6562
6940
|
/** The lit fragment source this renderer's profile compiles, at a light budget. */
|
|
6563
|
-
private flatFragAt(
|
|
6941
|
+
private flatFragAt(
|
|
6942
|
+
budget: LightBudget,
|
|
6943
|
+
/* A switch counted as it would be, for asking whether it fits: see `switchFits`. */
|
|
6944
|
+
asIf: Partial<Record<LitSwitch, boolean>> = {},
|
|
6945
|
+
): string {
|
|
6564
6946
|
return flatFrag({
|
|
6565
6947
|
pointShadows: this.quality.pointShadows,
|
|
6566
6948
|
directionalShadows: this.quality.directionalShadows,
|
|
@@ -6573,8 +6955,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6573
6955
|
surfaceEffects: this.litOn.SURFACE_EFFECTS,
|
|
6574
6956
|
driftLight: this.litOn.DRIFT_LIGHT,
|
|
6575
6957
|
physicalSpecular: this.litOn.PHYSICAL_SPECULAR,
|
|
6576
|
-
surfaceOverlay:
|
|
6958
|
+
surfaceOverlay: asIf.SURFACE_OVERLAY ?? this.litOn.SURFACE_OVERLAY,
|
|
6577
6959
|
reflectionMaps: this.litOn.REFLECTION_MAPS,
|
|
6960
|
+
worldUvs: asIf.WORLD_UVS ?? this.litOn.WORLD_UVS,
|
|
6961
|
+
movingSun: asIf.MOVING_SUN ?? this.litOn.MOVING_SUN,
|
|
6962
|
+
layered: asIf.LAYERED ?? this.litOn.LAYERED,
|
|
6578
6963
|
maxLights: budget.maxLights,
|
|
6579
6964
|
maxAreaLights: budget.maxAreaLights,
|
|
6580
6965
|
});
|
|
@@ -6662,6 +7047,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6662
7047
|
this.clothFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
|
|
6663
7048
|
}
|
|
6664
7049
|
|
|
7050
|
+
/* Started against the source before this switch: begun again at the next draw that asks. */
|
|
7051
|
+
for (const started of this.modelPending.values()) gl.deleteProgram(started.program);
|
|
7052
|
+
this.modelPending.clear();
|
|
6665
7053
|
for (const [key, entry] of this.modelFlat) {
|
|
6666
7054
|
const [variant, kind, half] = key.split('|') as [string, SurfaceModelKind, SkinHalf];
|
|
6667
7055
|
const label = `flat.${variant}.${kind}.${half}`;
|
|
@@ -7499,6 +7887,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7499
7887
|
this.sceneTarget.setLocalExposure(strength);
|
|
7500
7888
|
}
|
|
7501
7889
|
|
|
7890
|
+
/**
|
|
7891
|
+
* Where ambient occlusion fades out with distance: whole up to `distance` metres from the eye and
|
|
7892
|
+
* gone `radius` metres past it, so the far depth buffer's steps — a sky dome kilometres off, a
|
|
7893
|
+
* mountain range — are not shaded in rings, and those pixels skip the occlusion's sampling.
|
|
7894
|
+
*
|
|
7895
|
+
* A distance that is not a finite number at or above zero is no fade, which is the default, and
|
|
7896
|
+
* `Infinity` reads as "never fades"; a radius below zero is a cut at `distance`. Set before
|
|
7897
|
+
* `endFrame` and held until changed, like `setDepthOfField`. Nothing at all when
|
|
7898
|
+
* `ambientOcclusion` is 0. See `occlusionFade.ts` for what it gives up.
|
|
7899
|
+
*/
|
|
7900
|
+
setAmbientOcclusionFade(distance: number, radius: number): void {
|
|
7901
|
+
resolveOcclusionFade(distance, radius, this.aoFade);
|
|
7902
|
+
}
|
|
7903
|
+
|
|
7502
7904
|
/**
|
|
7503
7905
|
* Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
|
|
7504
7906
|
* to take.
|
|
@@ -8152,6 +8554,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8152
8554
|
radius: number;
|
|
8153
8555
|
projScale: Float32Array;
|
|
8154
8556
|
invProjection: Float32Array;
|
|
8557
|
+
fade: Float32Array;
|
|
8155
8558
|
offset: Float32Array;
|
|
8156
8559
|
}
|
|
8157
8560
|
| undefined;
|
|
@@ -8175,6 +8578,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8175
8578
|
radius: this.quality.ambientOcclusionRadius,
|
|
8176
8579
|
projScale: this.aoProjScale,
|
|
8177
8580
|
invProjection: this.aoInvProjection as Float32Array,
|
|
8581
|
+
fade: this.aoFade,
|
|
8178
8582
|
offset: this.aoOffset,
|
|
8179
8583
|
};
|
|
8180
8584
|
}
|
|
@@ -8450,6 +8854,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8450
8854
|
ambient: env?.ambient ?? [0, 0, 0],
|
|
8451
8855
|
sunShadow,
|
|
8452
8856
|
lightViewProj: env?.lightViewProj ?? IDENTITY_MAT4,
|
|
8857
|
+
movingLightViewProj: env === null ? IDENTITY_MAT4 : this.movingLightFor(env),
|
|
8453
8858
|
/* The placeholder rather than null where a map is merely absent, for the reason
|
|
8454
8859
|
`emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
|
|
8455
8860
|
so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
|
|
@@ -8705,6 +9110,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8705
9110
|
this.currentSurfaceTexture = null;
|
|
8706
9111
|
/* Nor an overlay: the atlas's unit is given the empty texture with the refraction copy's. */
|
|
8707
9112
|
this.overlayFloats.fill(0);
|
|
9113
|
+
/* Nor a projection: maps follow the mesh's coordinates until a material asks otherwise. */
|
|
9114
|
+
this.projection.fill(0);
|
|
9115
|
+
/* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
|
|
9116
|
+
resolveTransmission(null, this.transmission);
|
|
9117
|
+
/* Nor layers: one layer until a material asks for more. */
|
|
9118
|
+
this.layerFloats.fill(0);
|
|
8708
9119
|
this.overlayMaps = null;
|
|
8709
9120
|
this.overlayTexture = null;
|
|
8710
9121
|
/* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
|
|
@@ -8718,6 +9129,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8718
9129
|
this.environmentDials[2] = 1;
|
|
8719
9130
|
this.emission[0] = env.emissiveGain;
|
|
8720
9131
|
this.emission[1] = env.nightFactor;
|
|
9132
|
+
/* The moving layer's own matrix, the first time a frame draws that layer with one. */
|
|
9133
|
+
this.askMovingSun(env);
|
|
9134
|
+
/* No dither and no weighting: every draw whole, until a crossfade or an opacity asks. */
|
|
9135
|
+
this.oitWeight = 0;
|
|
9136
|
+
this.ditherFade = 0;
|
|
9137
|
+
this.ditherOpacity = 0;
|
|
8721
9138
|
for (const target of this.flatTargets) {
|
|
8722
9139
|
this.gl.useProgram(target.program);
|
|
8723
9140
|
this.writeMeshPassState(target.uniforms, camera, env);
|
|
@@ -8871,11 +9288,21 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8871
9288
|
gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
|
|
8872
9289
|
/* The draws' overlay, none at a pass's start, and its atlas above: see `setSurfaceOverlay`. */
|
|
8873
9290
|
gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
|
|
9291
|
+
gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
|
|
9292
|
+
gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
|
|
8874
9293
|
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
8875
9294
|
`bindSeeThrough` for the four numbers in the one vector. */
|
|
8876
9295
|
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
8877
9296
|
gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
|
|
8878
|
-
|
|
9297
|
+
/* No light through from behind: no material at a pass's start. See `writeTransmission`. */
|
|
9298
|
+
const through = this.transmission;
|
|
9299
|
+
gl.uniform4f(
|
|
9300
|
+
u['uGlassTint'] ?? null,
|
|
9301
|
+
through[0] ?? -1,
|
|
9302
|
+
through[1] ?? -1,
|
|
9303
|
+
through[2] ?? -1,
|
|
9304
|
+
through[3] ?? 0,
|
|
9305
|
+
);
|
|
8879
9306
|
gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
|
|
8880
9307
|
gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
|
|
8881
9308
|
gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);
|
|
@@ -8942,10 +9369,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8942
9369
|
this.surfaceFog = FOG_RECEDE;
|
|
8943
9370
|
/* Grain on by default, so every scene written before it was a choice looks unchanged. */
|
|
8944
9371
|
gl.uniform1f(u['uGrain'] ?? null, 1);
|
|
8945
|
-
/*
|
|
8946
|
-
|
|
8947
|
-
this.
|
|
8948
|
-
gl.uniform2f(u['uWriteMode'] ?? null, 0, 0);
|
|
9372
|
+
/* The weighting and the dither as they stand, which `bindMeshPass` has just cleared: a program
|
|
9373
|
+
built in the middle of a pass takes what the caller set. See `setDitherFade`. */
|
|
9374
|
+
this.writeMode(u);
|
|
8949
9375
|
/* The draw's own ambient as it stands, which `bindMeshPass` has just cleared: a program built
|
|
8950
9376
|
in the middle of a pass takes what the caller set. See `setAmbientSH`. */
|
|
8951
9377
|
gl.uniform4fv(u['uAmbientSH[0]'] ?? null, this.ambientSH);
|
|
@@ -8993,6 +9419,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8993
9419
|
gl.uniform1f(u['uShadowStrength'] ?? null, this.sunShadows === null ? 0 : env.shadowStrength);
|
|
8994
9420
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
8995
9421
|
gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
|
|
9422
|
+
/* The moving layer's own matrix and span, where the program reads one: see `movingLightFor`. */
|
|
9423
|
+
const moving = this.movingLightFor(env);
|
|
9424
|
+
gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, moving);
|
|
9425
|
+
gl.uniform1f(u['uMovingShadowDepthSpan'] ?? null, orthographicDepthSpan(moving));
|
|
8996
9426
|
gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
|
|
8997
9427
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
8998
9428
|
gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
|
|
@@ -9330,6 +9760,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9330
9760
|
* light and its colour, each into the blur's own target.
|
|
9331
9761
|
*/
|
|
9332
9762
|
const splits = this.skinSplits();
|
|
9763
|
+
/* A split skin draws its three halves or none, where a half may still be compiling. */
|
|
9764
|
+
if (splits && this.parallelCompile !== null && !this.skinHalvesReady(mesh)) return;
|
|
9333
9765
|
/* An opaque lit draw into the frame writes its material's reflection too: see `reflectionKeeps`. */
|
|
9334
9766
|
const keeps = this.reflectionKeeps();
|
|
9335
9767
|
if (keeps) this.reflectionTargets?.on();
|
|
@@ -9410,14 +9842,25 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9410
9842
|
}
|
|
9411
9843
|
|
|
9412
9844
|
/** One draw of a mesh, as `drawMesh` describes, of the half of a skin `half` names. */
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
const
|
|
9845
|
+
/**
|
|
9846
|
+
* Whether every half a split skin draws has its program, starting any that has not: a skin drawn
|
|
9847
|
+
* without its kept halves would lose its diffuse light for the frames they take to compile.
|
|
9848
|
+
*/
|
|
9849
|
+
private skinHalvesReady(mesh: Mesh): boolean {
|
|
9850
|
+
const variant = this.drawVariantOf(mesh).name;
|
|
9851
|
+
let ready = true;
|
|
9852
|
+
for (const half of SPLIT_SKIN_HALVES) {
|
|
9853
|
+
if (this.modelProgram(variant, 'skin', half) === null) ready = false;
|
|
9854
|
+
}
|
|
9855
|
+
return ready;
|
|
9856
|
+
}
|
|
9857
|
+
|
|
9858
|
+
/**
|
|
9859
|
+
* The vertex variant a draw of `mesh` takes now, and its program and uniforms where it is not the
|
|
9860
|
+
* plain one: what a model's program is keyed by, and what `drawMeshHalf` draws with.
|
|
9861
|
+
*/
|
|
9862
|
+
private drawVariantOf(mesh: Mesh): DrawVariant {
|
|
9863
|
+
const out = this.drawVariant;
|
|
9421
9864
|
/*
|
|
9422
9865
|
* A rigged mesh takes the skinned program, an unrigged one the plain program.
|
|
9423
9866
|
*
|
|
@@ -9445,10 +9888,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9445
9888
|
* when none were asked for — an unbound sampler would default to a shadow map's unit.
|
|
9446
9889
|
*/
|
|
9447
9890
|
const cloth = wantSkin ? this.cloth : null;
|
|
9448
|
-
const
|
|
9449
|
-
|
|
9450
|
-
? undefined
|
|
9451
|
-
: this.clothFlat.get(`${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`);
|
|
9891
|
+
const clothKey = `${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`;
|
|
9892
|
+
const clothed = cloth === null ? undefined : this.clothFlat.get(clothKey);
|
|
9452
9893
|
const bothUniforms = eight ? this.flatBoth8Uniforms : this.flatBothUniforms;
|
|
9453
9894
|
const skinnedUniforms = eight ? this.flatSkinned8Uniforms : this.flatSkinnedUniforms;
|
|
9454
9895
|
const both = wantSkin && wantMorph && bothUniforms !== null;
|
|
@@ -9462,8 +9903,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9462
9903
|
: morphed
|
|
9463
9904
|
? this.flatMorphedUniforms
|
|
9464
9905
|
: null;
|
|
9465
|
-
|
|
9466
|
-
|
|
9906
|
+
out.skinned = skinned;
|
|
9907
|
+
out.morphed = morphed;
|
|
9908
|
+
out.cloth = cloth;
|
|
9909
|
+
out.clothed = clothed;
|
|
9910
|
+
out.uniforms = clothed?.uniforms ?? variant ?? this.flatUniforms;
|
|
9911
|
+
out.program =
|
|
9467
9912
|
clothed !== undefined
|
|
9468
9913
|
? clothed.program
|
|
9469
9914
|
: skinned && morphed
|
|
@@ -9477,26 +9922,41 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9477
9922
|
: morphed
|
|
9478
9923
|
? this.flatMorphedProgram
|
|
9479
9924
|
: null;
|
|
9925
|
+
out.name =
|
|
9926
|
+
clothed !== undefined
|
|
9927
|
+
? `cloth${eight ? 8 : 4}${mesh.morph !== null ? 'm' : '-'}`
|
|
9928
|
+
: skinned && morphed
|
|
9929
|
+
? eight
|
|
9930
|
+
? 'both8'
|
|
9931
|
+
: 'both'
|
|
9932
|
+
: skinned
|
|
9933
|
+
? eight
|
|
9934
|
+
? 'skinned8'
|
|
9935
|
+
: 'skinned'
|
|
9936
|
+
: morphed
|
|
9937
|
+
? 'morphed'
|
|
9938
|
+
: 'plain';
|
|
9939
|
+
return out;
|
|
9940
|
+
}
|
|
9941
|
+
|
|
9942
|
+
private drawMeshHalf(
|
|
9943
|
+
mesh: Mesh,
|
|
9944
|
+
model: ReadonlyMat4,
|
|
9945
|
+
depthLayer: number,
|
|
9946
|
+
tint: Vec3 | null,
|
|
9947
|
+
half: SkinHalf,
|
|
9948
|
+
): void {
|
|
9949
|
+
const { gl } = this;
|
|
9950
|
+
const drawn = this.drawVariantOf(mesh);
|
|
9951
|
+
const { skinned, morphed, cloth, clothed } = drawn;
|
|
9952
|
+
let u = drawn.uniforms;
|
|
9953
|
+
let program = drawn.program;
|
|
9480
9954
|
/* A material with a model takes that model's program for the same vertex variant. */
|
|
9481
9955
|
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
9482
9956
|
if (modelKind !== null) {
|
|
9483
|
-
const modelled = this.modelProgram(
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
: skinned && morphed
|
|
9487
|
-
? eight
|
|
9488
|
-
? 'both8'
|
|
9489
|
-
: 'both'
|
|
9490
|
-
: skinned
|
|
9491
|
-
? eight
|
|
9492
|
-
? 'skinned8'
|
|
9493
|
-
: 'skinned'
|
|
9494
|
-
: morphed
|
|
9495
|
-
? 'morphed'
|
|
9496
|
-
: 'plain',
|
|
9497
|
-
modelKind,
|
|
9498
|
-
half,
|
|
9499
|
-
);
|
|
9957
|
+
const modelled = this.modelProgram(drawn.name, modelKind, half);
|
|
9958
|
+
/* Still compiling, where draws skip a compile: left out until it lands. */
|
|
9959
|
+
if (modelled === null) return;
|
|
9500
9960
|
program = modelled.program;
|
|
9501
9961
|
u = modelled.uniforms;
|
|
9502
9962
|
}
|
|
@@ -9664,6 +10124,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9664
10124
|
/* A material with a model takes that model's plain program; see `modelProgram`. */
|
|
9665
10125
|
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
9666
10126
|
const modelled = modelKind === null ? null : this.modelProgram('plain', modelKind);
|
|
10127
|
+
/* Still compiling, where draws skip a compile: left out until it lands. */
|
|
10128
|
+
if (modelKind !== null && modelled === null) return;
|
|
9667
10129
|
const u = modelled?.uniforms ?? this.flatUniforms;
|
|
9668
10130
|
if (modelled === null) this.useFlatProgram();
|
|
9669
10131
|
else gl.useProgram(modelled.program);
|
|
@@ -10195,6 +10657,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
10195
10657
|
/* Uncorrected, for the reason the lit program's copy of this line carries: the shaft's
|
|
10196
10658
|
lookup does its own `* 0.5 + 0.5` and correcting the matrix as well doubles it. */
|
|
10197
10659
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
|
|
10660
|
+
gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, this.movingLightFor(env));
|
|
10198
10661
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
10199
10662
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
10200
10663
|
|