@driftengine/core 4.9.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +133 -11
- package/README.md +43 -38
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -4
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/backend/api.d.ts +10 -3
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +189 -4
- package/dist/render/backend/webgl2/renderer.js +614 -66
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +24 -4
- package/dist/render/backend/webgpu/buffers.js +116 -59
- 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/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
- package/dist/render/backend/webgpu/drawCommand.js +11 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +137 -20
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
- package/dist/render/backend/webgpu/pipelineCache.js +46 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +200 -35
- package/dist/render/backend/webgpu/renderer.js +993 -168
- 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/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +242 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/glCompressed.js +83 -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 +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +14 -0
- package/dist/render/mesh.js +44 -14
- package/dist/render/pointLightSelection.d.ts +4 -4
- 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/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- 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 +21 -0
- package/dist/render/shaders/flat/index.js +42 -7
- package/dist/render/shaders/flat/layered.d.ts +19 -0
- package/dist/render/shaders/flat/layered.js +115 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +58 -21
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/models.js +1 -1
- 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 +14 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +42 -20
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +59 -27
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
- package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
- 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/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceLayers.d.ts +39 -0
- package/dist/render/surfaceLayers.js +26 -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 +68 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +12 -4
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/backend/api.ts +13 -3
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +723 -79
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +195 -70
- 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/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +25 -4
- package/src/render/backend/webgpu/flatPass.ts +212 -41
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +53 -7
- package/src/render/backend/webgpu/renderer.ts +1201 -202
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +364 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/glCompressed.ts +83 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +72 -14
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- 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 +63 -7
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +58 -21
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +14 -3
- package/src/render/shaders/flat/overlay.ts +42 -20
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +59 -27
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
- 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/outputTransform.ts +30 -14
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceLayers.ts +57 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +124 -27
- package/src/render/vertexPacking.ts +109 -0
- 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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { vertexPackingOf } from '../../vertexPacking.ts';
|
|
2
|
+
import { markPacking, markPackingOfKey, packingKey } from './vertexFormats.ts';
|
|
1
3
|
import { mat4 } from 'gl-matrix';
|
|
2
4
|
import type { ProbeBakeOptions } from '../../reflectionProbe.ts';
|
|
3
5
|
import { probeFaceRange } from '../../reflectionProbe.ts';
|
|
@@ -194,15 +196,23 @@ import {
|
|
|
194
196
|
packSurfaceOverlay,
|
|
195
197
|
type SurfaceOverlay,
|
|
196
198
|
} from '../../surfaceOverlay.ts';
|
|
199
|
+
import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.ts';
|
|
200
|
+
import { orthographicDepthSpan } from '../../lightMatrix.ts';
|
|
201
|
+
import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.ts';
|
|
202
|
+
import type { SurfaceLayers } from '../../surfaceLayers.ts';
|
|
197
203
|
import {
|
|
198
204
|
frameReflectionsRefused,
|
|
199
205
|
resolveFrameReflections,
|
|
200
206
|
type ResolvedFrameReflections,
|
|
201
207
|
} from '../../frameReflections.ts';
|
|
202
|
-
import { ReflectionSurfacePass } from './reflectionSurfacePass.ts';
|
|
208
|
+
import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.ts';
|
|
203
209
|
import { validateClothBinding } from '../../clothBindingData.ts';
|
|
204
210
|
import type { ClothBindingData } from '../../clothBindingData.ts';
|
|
205
|
-
import type {
|
|
211
|
+
import type {
|
|
212
|
+
CompressedTextureFormat,
|
|
213
|
+
CompressedTextureSource,
|
|
214
|
+
SurfaceSource,
|
|
215
|
+
} from '../../compressedSource.ts';
|
|
206
216
|
import {
|
|
207
217
|
createSdfTextBindGroup,
|
|
208
218
|
createSdfTextBindGroupLayout,
|
|
@@ -412,16 +422,25 @@ import {
|
|
|
412
422
|
SUN_STATIC_LAYER,
|
|
413
423
|
} from '../../shadowMap.ts';
|
|
414
424
|
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
415
|
-
import {
|
|
425
|
+
import {
|
|
426
|
+
VERTEX_LAYOUT,
|
|
427
|
+
createGpuMesh,
|
|
428
|
+
createGpuMeshIncremental,
|
|
429
|
+
type GpuMesh,
|
|
430
|
+
type StagedRewrite,
|
|
431
|
+
} from './buffers.ts';
|
|
416
432
|
import type { GpuSurface } from './device.ts';
|
|
417
433
|
import { SkinPaletteRing } from './skinPaletteRing.ts';
|
|
418
434
|
import {
|
|
419
435
|
DEPTH_FORMAT,
|
|
436
|
+
FLAT_ANIMATED_VERT_FIELDS,
|
|
420
437
|
FLAT_VERT_FIELDS,
|
|
421
438
|
FLAT_VERT_SIZE,
|
|
439
|
+
FLAT_VIEW_FIELDS,
|
|
440
|
+
FLAT_VIEW_SIZE,
|
|
422
441
|
createFlatBindGroup,
|
|
423
442
|
createFlatBindGroupLayout,
|
|
424
|
-
|
|
443
|
+
flatFragmentScratch,
|
|
425
444
|
flatVariant,
|
|
426
445
|
flatVertexBindings,
|
|
427
446
|
type FlatVariant,
|
|
@@ -429,7 +448,15 @@ import {
|
|
|
429
448
|
flatPipelineAsync,
|
|
430
449
|
probeStageCeilings,
|
|
431
450
|
} from './flatPass.ts';
|
|
432
|
-
import type { OitTarget } from './flatPass.ts';
|
|
451
|
+
import type { AnimationViews, FlatFragmentScratch, OitTarget } from './flatPass.ts';
|
|
452
|
+
import { GpuBoneAnimation } from './boneAnimations.ts';
|
|
453
|
+
import { packBoneAnimation } from '../../boneAnimation.ts';
|
|
454
|
+
import type { BoneAnimationClip, BoneAnimationHandle } from '../../boneAnimation.ts';
|
|
455
|
+
import { MaterialStore, hashWords } from './materialStore.ts';
|
|
456
|
+
import { GpuStaticDraws, encodeBundle, takeRecorded } from './staticBundles.ts';
|
|
457
|
+
import type { StaticRecording } from './staticBundles.ts';
|
|
458
|
+
import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.ts';
|
|
459
|
+
import type { StaticDrawsHandle } from '../../staticDraws.ts';
|
|
433
460
|
import {
|
|
434
461
|
DEPTH_FRAG_BINDING,
|
|
435
462
|
DEPTH_FRAG_SIZE,
|
|
@@ -437,6 +464,8 @@ import {
|
|
|
437
464
|
DEPTH_PREVIOUS_BINDING,
|
|
438
465
|
DEPTH_CUTOUT_VERT_FIELDS,
|
|
439
466
|
DEPTH_CUTOUT_VERT_SIZE,
|
|
467
|
+
DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS,
|
|
468
|
+
DEPTH_INSTANCED_ANIMATED_VERT_FIELDS,
|
|
440
469
|
DEPTH_INSTANCED_CUTOUT_VERT_FIELDS,
|
|
441
470
|
DEPTH_VERT_FIELDS,
|
|
442
471
|
DEPTH_VERT_SIZE,
|
|
@@ -909,6 +938,14 @@ const MAX_WATER_BODIES_PER_FRAME = 16;
|
|
|
909
938
|
*/
|
|
910
939
|
const MAX_BATCHES_PER_FRAME = 8;
|
|
911
940
|
|
|
941
|
+
/**
|
|
942
|
+
* Mesh passes a frame may open before the ring grows: the frame's own, a mirror's, a capture's, and
|
|
943
|
+
* a probe face's, which gives its slot back once submitted. A frame past it loses the draws of the
|
|
944
|
+
* passes that found no slot, counted on the `materials` line, and the ring is grown for the next.
|
|
945
|
+
* Each slot is the pass's block, 3,328 to 6,656 bytes by variant, so the ring is 213 to 426 KB.
|
|
946
|
+
*/
|
|
947
|
+
const PASSES_PER_FRAME = 64;
|
|
948
|
+
|
|
912
949
|
/** Buffer usage flags, from the specification. See `buffers.ts` for why they are not read off the global. */
|
|
913
950
|
const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
|
|
914
951
|
|
|
@@ -1423,6 +1460,15 @@ interface SunTintTarget {
|
|
|
1423
1460
|
readonly mips: readonly [SnapshotMips, SnapshotMips];
|
|
1424
1461
|
}
|
|
1425
1462
|
|
|
1463
|
+
/** A skinned or morphed flat group, and what it was built with, compared by identity. */
|
|
1464
|
+
interface SkinnedTwin {
|
|
1465
|
+
readonly group: GPUBindGroup;
|
|
1466
|
+
readonly palette: GPUTextureView | null;
|
|
1467
|
+
readonly deltas: GPUTextureView | null;
|
|
1468
|
+
readonly cloth: GPUTextureView | null;
|
|
1469
|
+
readonly clothBinding: GPUTextureView | null;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1426
1472
|
export class WebGPURenderer implements RendererApi {
|
|
1427
1473
|
/**
|
|
1428
1474
|
* Real timestamp queries, where the adapter offers them.
|
|
@@ -1490,7 +1536,21 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1490
1536
|
* flight at the time, so a consumer that builds more meshes later can wait again.
|
|
1491
1537
|
*/
|
|
1492
1538
|
async ready(): Promise<void> {
|
|
1493
|
-
await Promise.all(this.
|
|
1539
|
+
await Promise.all(this.pipelineCaches().map((cache) => cache.ready()));
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/** Every distinct cache a lit draw reaches: the frame's, the canvas's, the late pass's, skin's. */
|
|
1543
|
+
private pipelineCaches(): PipelineCache[] {
|
|
1544
|
+
const caches = [...this.pipelineTargets];
|
|
1545
|
+
for (const cache of [this.latePipelines, this.skinPipelines]) {
|
|
1546
|
+
if (cache !== null && !caches.includes(cache)) caches.push(cache);
|
|
1547
|
+
}
|
|
1548
|
+
return caches;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/** Whether a lit draw whose pipeline is not built is left out while it compiles. */
|
|
1552
|
+
private skipCompiling(skips: boolean): void {
|
|
1553
|
+
for (const cache of this.pipelineCaches()) cache.skipsCompiling = skips;
|
|
1494
1554
|
}
|
|
1495
1555
|
|
|
1496
1556
|
private presentedFrameCount = 0;
|
|
@@ -1506,6 +1566,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1506
1566
|
private readonly skinnedBindGroupLayout: GPUBindGroupLayout;
|
|
1507
1567
|
private readonly morphedBindGroupLayout: GPUBindGroupLayout;
|
|
1508
1568
|
private readonly bothBindGroupLayout: GPUBindGroupLayout;
|
|
1569
|
+
/** The instanced layout of a batch playing a bone animation. See `animatedGroup`. */
|
|
1570
|
+
private readonly animatedBindGroupLayout: GPUBindGroupLayout;
|
|
1509
1571
|
/** The morph weights the following draws use, or null for none. */
|
|
1510
1572
|
private morphWeights: Float32Array | null = null;
|
|
1511
1573
|
/**
|
|
@@ -1518,14 +1580,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1518
1580
|
* group per character per frame — an allocation in the frame loop, which `AGENTS.md` forbids.
|
|
1519
1581
|
* Keyed by slot instead, the groups are built once and reused for the life of the renderer.
|
|
1520
1582
|
*/
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1583
|
+
/*
|
|
1584
|
+
* **And by material within a slot**, keyed by the unskinned group each was built beside. One
|
|
1585
|
+
* group a slot was rebuilt whenever the material beside it changed, so a character drawn in
|
|
1586
|
+
* thirty-odd sections, each its own material, built one a section a frame — about ninety a frame
|
|
1587
|
+
* for two characters, reported by a consumer. A slot's map holds one entry a material its rigs
|
|
1588
|
+
* are drawn in, and is emptied wherever the unskinned groups are, whose identities it is keyed
|
|
1589
|
+
* by.
|
|
1590
|
+
*/
|
|
1591
|
+
private readonly skinnedGroups: (Map<GPUBindGroup, SkinnedTwin> | undefined)[] = [];
|
|
1529
1592
|
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
1530
1593
|
private cloth: { binding: GpuClothBinding; particles: GpuClothParticles } | null = null;
|
|
1531
1594
|
private clothStandCache: { texture: GPUTexture; views: ClothViews } | null = null;
|
|
@@ -1595,22 +1658,51 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1595
1658
|
/** Every distinct cache above, so a mesh's pipelines are built for each. One entry when aliased. */
|
|
1596
1659
|
private readonly pipelineTargets: readonly PipelineCache[];
|
|
1597
1660
|
private readonly bindGroupLayout: GPUBindGroupLayout;
|
|
1598
|
-
/** One slot per draw, uploaded once a frame. See `UniformRing`. */
|
|
1599
|
-
private
|
|
1600
|
-
/**
|
|
1601
|
-
|
|
1661
|
+
/** One slot per draw, uploaded once a frame. See `UniformRing`. Swapped while a static list records. */
|
|
1662
|
+
private perDraw: UniformRing;
|
|
1663
|
+
/**
|
|
1664
|
+
* The lights, fog and camera, a slot per mesh pass a frame opens: `bindMeshPass` writes them, and
|
|
1665
|
+
* the first draw after it takes a slot. A ring rather than one buffer because a frame opens more
|
|
1666
|
+
* than one pass — a mirror's, a capture's, a probe face's — and a queue write does not interleave
|
|
1667
|
+
* with the draws recorded between them. See `UniformRing`.
|
|
1668
|
+
*/
|
|
1669
|
+
private passRing: UniformRing;
|
|
1670
|
+
/** Each material's block, kept across frames for as long as it does not change. */
|
|
1671
|
+
private readonly materialStore: MaterialStore;
|
|
1602
1672
|
/**
|
|
1603
|
-
*
|
|
1673
|
+
* Both blocks as the CPU holds them, the pass's then the material's, patched by name and copied
|
|
1674
|
+
* into a slot by the next draw after something changed them. See `FlatFragmentScratch`.
|
|
1604
1675
|
*
|
|
1605
1676
|
* Authoritative between draws: a setter changes one field here and marks the block dirty, and
|
|
1606
|
-
* the next `drawMesh`
|
|
1607
|
-
*
|
|
1677
|
+
* the next `drawMesh` places it. That keeps a setter free of any knowledge of slots, which is
|
|
1678
|
+
* what lets it be called anywhere a scene wants.
|
|
1608
1679
|
*/
|
|
1609
1680
|
private readonly perFrameStaging: ArrayBuffer;
|
|
1610
1681
|
private readonly perFrameFloats: Float32Array;
|
|
1611
1682
|
private readonly perFrameInts: Int32Array;
|
|
1683
|
+
/** The two halves of the scratch, as the words a slot is written from. */
|
|
1684
|
+
private readonly passInts: Int32Array;
|
|
1685
|
+
private readonly materialInts: Int32Array;
|
|
1612
1686
|
/** The slot the open material occupies, and whether the next draw must take a new one. */
|
|
1613
1687
|
private readonly materials = new MaterialChanges();
|
|
1688
|
+
/** The same for the pass's block: reopened by `bindMeshPass` and by nothing a material sets. */
|
|
1689
|
+
private readonly passBlock = new MaterialChanges();
|
|
1690
|
+
/**
|
|
1691
|
+
* The vertex stage's view block — the camera, the light's matrix, the wind — a slot a pass, and a
|
|
1692
|
+
* second, unjittered, for a blended draw landing after the upscale. Reopened with the pass, and by
|
|
1693
|
+
* `setWind` and `beginShadowPass`, which change what it holds.
|
|
1694
|
+
*/
|
|
1695
|
+
private viewRing: UniformRing;
|
|
1696
|
+
private readonly viewBlock = new MaterialChanges();
|
|
1697
|
+
private readonly lateViewBlock = new MaterialChanges();
|
|
1698
|
+
/** The view slot the draw being submitted bound, read by the halves it keeps for later passes. */
|
|
1699
|
+
private drawViewOffset = 0;
|
|
1700
|
+
/** Pass slots asked for and refused this frame, and materials refused: what grows the two. */
|
|
1701
|
+
private passesAsked = 0;
|
|
1702
|
+
private passesRefused = 0;
|
|
1703
|
+
private viewsAsked = 0;
|
|
1704
|
+
private viewsRefused = 0;
|
|
1705
|
+
private materialsRefused = 0;
|
|
1614
1706
|
|
|
1615
1707
|
/*
|
|
1616
1708
|
* The frame graph's recording, and the machinery that replays it.
|
|
@@ -1879,8 +1971,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1879
1971
|
private readonly variant: FlatVariant;
|
|
1880
1972
|
/** Whether this profile compiled the point-shadow path, and so has sentinels to write. */
|
|
1881
1973
|
private readonly pointShadowsCompiled: boolean;
|
|
1882
|
-
/** What the generator recorded about that permutation
|
|
1883
|
-
private readonly fragment:
|
|
1974
|
+
/** What the generator recorded about that permutation, as the renderer's scratch lays it out. */
|
|
1975
|
+
private readonly fragment: FlatFragmentScratch;
|
|
1884
1976
|
|
|
1885
1977
|
private depth: GPUTexture | null = null;
|
|
1886
1978
|
private depthView: GPUTextureView | null = null;
|
|
@@ -2624,7 +2716,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2624
2716
|
if (this.quality.frameGraph) recordNode(this.arena, VERB_SCOPE, 0, MIRROR_TARGET, 0, 0);
|
|
2625
2717
|
this.pass?.end();
|
|
2626
2718
|
this.flushRings();
|
|
2627
|
-
this.
|
|
2719
|
+
this.submit([this.encoder.finish()]);
|
|
2628
2720
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'reflection' });
|
|
2629
2721
|
/*
|
|
2630
2722
|
* **Opened on demand, like the frame's own.** Nothing has been recorded into the mirror yet,
|
|
@@ -2684,7 +2776,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2684
2776
|
this.windStreakFrags,
|
|
2685
2777
|
this.scatterVerts,
|
|
2686
2778
|
this.scatterFrags,
|
|
2687
|
-
this.
|
|
2779
|
+
this.passRing,
|
|
2780
|
+
this.viewRing,
|
|
2688
2781
|
this.filmVerts,
|
|
2689
2782
|
this.filmFrags,
|
|
2690
2783
|
this.insetUniforms,
|
|
@@ -2705,6 +2798,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2705
2798
|
/* Culls first: a submit that follows may carry a draw reading what they write. */
|
|
2706
2799
|
this.instanceCulls?.flush();
|
|
2707
2800
|
for (const ring of this.rings()) ring.flush();
|
|
2801
|
+
this.materialStore.flush();
|
|
2708
2802
|
}
|
|
2709
2803
|
|
|
2710
2804
|
/** Where every ring stands before work that submits an encoder of its own. */
|
|
@@ -2724,6 +2818,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2724
2818
|
private rewindRings(skinMark: number): void {
|
|
2725
2819
|
const rings = this.rings();
|
|
2726
2820
|
for (let i = 0; i < rings.length; i++) (rings[i] as UniformRing).rewind(this.ringMarks[i] ?? 0);
|
|
2821
|
+
/* The open pass may sit in a slot just given back, which the next pass would overwrite. */
|
|
2822
|
+
this.reopenPass();
|
|
2727
2823
|
this.skinPalettes.rewind(skinMark);
|
|
2728
2824
|
if (this.skinPaletteSlot >= skinMark) this.skinPaletteSlot = -1;
|
|
2729
2825
|
}
|
|
@@ -2851,6 +2947,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2851
2947
|
}
|
|
2852
2948
|
filmCommand.vertexCount = geometry.vertexBuffers.length;
|
|
2853
2949
|
filmCommand.indexBuffer = geometry.indexBuffer;
|
|
2950
|
+
filmCommand.indexFormat = geometry.indexFormat;
|
|
2854
2951
|
filmCommand.indexed = true;
|
|
2855
2952
|
filmCommand.count = geometry.indexCount;
|
|
2856
2953
|
} else {
|
|
@@ -2861,7 +2958,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2861
2958
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
2862
2959
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
2863
2960
|
}
|
|
2864
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
2961
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
2865
2962
|
pass.drawIndexed(geometry.indexCount);
|
|
2866
2963
|
}
|
|
2867
2964
|
}
|
|
@@ -2898,6 +2995,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2898
2995
|
private modelMap: GpuSurfaceTexture | null = null;
|
|
2899
2996
|
/** The material's shading model, which picks the lit pipeline: null the standard. */
|
|
2900
2997
|
private materialModel: SurfaceModel | null = null;
|
|
2998
|
+
/** `uWorldUv`, the material's projection packed by `packSurfaceProjection`. */
|
|
2999
|
+
private readonly projection = new Float32Array(PROJECTION_FLOATS);
|
|
3000
|
+
/** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
|
|
3001
|
+
private readonly layerFloats = new Float32Array(LAYER_FLOATS);
|
|
3002
|
+
private warnedLayersModelMap = false;
|
|
2901
3003
|
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
2902
3004
|
private readonly modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
2903
3005
|
/**
|
|
@@ -2926,7 +3028,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2926
3028
|
* ones that move something *every* group holds: the shadow cubemaps, the probe fence, the cookie
|
|
2927
3029
|
* atlas and the IES rows. None of those is a frame path.
|
|
2928
3030
|
*/
|
|
2929
|
-
private
|
|
3031
|
+
private flatBindGroups = new Map<
|
|
2930
3032
|
GpuSurfaceTexture | null,
|
|
2931
3033
|
Map<
|
|
2932
3034
|
GpuSurfaceTexture | null,
|
|
@@ -3097,7 +3199,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3097
3199
|
pass.end();
|
|
3098
3200
|
this.pass = null;
|
|
3099
3201
|
this.flushRings();
|
|
3100
|
-
|
|
3202
|
+
this.submit([encoder.finish()]);
|
|
3101
3203
|
this.rewindRings(skinMark);
|
|
3102
3204
|
this.encoderEpoch = frameEpoch;
|
|
3103
3205
|
this.encoder = savedEncoder;
|
|
@@ -3105,6 +3207,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3105
3207
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
3106
3208
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
3107
3209
|
this.materials.dirty();
|
|
3210
|
+
this.reopenPass();
|
|
3108
3211
|
this.capturePassActive = false;
|
|
3109
3212
|
}
|
|
3110
3213
|
return true;
|
|
@@ -3119,11 +3222,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3119
3222
|
*/
|
|
3120
3223
|
private readonly retiredTextures: GPUTexture[] = [];
|
|
3121
3224
|
|
|
3122
|
-
updateSurfaceTexture(
|
|
3225
|
+
updateSurfaceTexture(
|
|
3226
|
+
texture: GpuSurfaceTexture,
|
|
3227
|
+
source: TexImageSource | CompressedTextureSource,
|
|
3228
|
+
): void {
|
|
3123
3229
|
if (this.surface.lost) return;
|
|
3124
|
-
const replaced = texture.update(source);
|
|
3230
|
+
const replaced = texture.update(source, this.compressedFormats);
|
|
3125
3231
|
if (replaced === null) return;
|
|
3126
|
-
/* A new size is a new view, so every group holding the old one is dropped — see
|
|
3232
|
+
/* A new size or format is a new view, so every group holding the old one is dropped — see
|
|
3233
|
+
below. */
|
|
3127
3234
|
this.retiredTextures.push(replaced);
|
|
3128
3235
|
this.forgetBindingsOf(texture);
|
|
3129
3236
|
}
|
|
@@ -3175,7 +3282,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3175
3282
|
/* Same reasoning, for whichever SDF labels were bound against this atlas. */
|
|
3176
3283
|
this.sdfTextBindGroups.delete(texture);
|
|
3177
3284
|
this.flatBindGroups.clear();
|
|
3285
|
+
this.flatGroupEpoch += 1;
|
|
3178
3286
|
this.overlayGroups.clear();
|
|
3287
|
+
this.skinnedGroups.length = 0;
|
|
3179
3288
|
/*
|
|
3180
3289
|
* The blank one is built with the albedo held aside, because `buildFlatBindGroup` reads
|
|
3181
3290
|
* `this.albedo` for that slot — leaving it in place would put a real colour map into the
|
|
@@ -3365,11 +3474,21 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3365
3474
|
const normal = (material?.normal ?? null) as GpuSurfaceTexture | null;
|
|
3366
3475
|
const orm = (material?.orm ?? null) as GpuSurfaceTexture | null;
|
|
3367
3476
|
const emissiveMap = (material?.emissive ?? null) as GpuSurfaceTexture | null;
|
|
3368
|
-
const
|
|
3477
|
+
const ownModelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
|
|
3478
|
+
/* A layered material's mask goes where a model's map would: see `SurfaceMaterial.layers`. */
|
|
3479
|
+
const layers = this.layersOf(material);
|
|
3480
|
+
const modelMap = ownModelMap ?? ((layers?.mask ?? null) as GpuSurfaceTexture | null);
|
|
3369
3481
|
this.materialModel = material?.model ?? null;
|
|
3482
|
+
const reflectivity = material?.reflectivity;
|
|
3483
|
+
const environmentGain = material?.environmentGain;
|
|
3484
|
+
this.materialReflectivity =
|
|
3485
|
+
reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
|
|
3486
|
+
this.materialEnvironmentGain =
|
|
3487
|
+
environmentGain === undefined ? null : Math.max(0, environmentGain);
|
|
3488
|
+
this.writeEnvironmentDials();
|
|
3370
3489
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
3371
3490
|
const model = material?.model;
|
|
3372
|
-
warnUnpagedLightmap(model,
|
|
3491
|
+
warnUnpagedLightmap(model, ownModelMap);
|
|
3373
3492
|
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
3374
3493
|
this.skinScatter.setProfile(
|
|
3375
3494
|
model.profile,
|
|
@@ -3395,7 +3514,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3395
3514
|
const cut = this.materialField('uCutout');
|
|
3396
3515
|
f[cut] = cutoff;
|
|
3397
3516
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
3398
|
-
|
|
3517
|
+
/* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
|
|
3518
|
+
f[cut + 2] = 0;
|
|
3399
3519
|
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
3400
3520
|
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
3401
3521
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
@@ -3427,12 +3547,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3427
3547
|
/* The model's numbers and whether its map is bound. See `packModel`. */
|
|
3428
3548
|
packModel(
|
|
3429
3549
|
material?.model ?? null,
|
|
3430
|
-
|
|
3550
|
+
ownModelMap !== null,
|
|
3431
3551
|
this.modelParams,
|
|
3432
3552
|
material?.physicalSpecular === true,
|
|
3433
3553
|
);
|
|
3434
3554
|
/* The first physical highlight asked for is when the lit pipelines start carrying one. */
|
|
3435
3555
|
if (material?.physicalSpecular === true) this.enableLit('PHYSICAL_SPECULAR');
|
|
3556
|
+
/* Maps placed by the world: the first material that asks is when lit pipelines carry it. */
|
|
3557
|
+
packSurfaceProjection(material?.projection, this.projection);
|
|
3558
|
+
f.set(this.projection, this.materialField('uWorldUv'));
|
|
3559
|
+
/* How much light from behind it lets through, in the glass tint's spare lane. */
|
|
3560
|
+
f[this.materialField('uGlassTint') + 3] =
|
|
3561
|
+
Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
|
|
3562
|
+
if ((this.projection[0] as number) > 0) this.enableLit('WORLD_UVS');
|
|
3563
|
+
/* And its layers, the first time a material blends any: layered.ts. */
|
|
3564
|
+
packSurfaceLayers(layers, this.layerFloats);
|
|
3565
|
+
f.set(this.layerFloats, this.materialField('uLayers'));
|
|
3566
|
+
if (layers !== null) this.enableLit('LAYERED');
|
|
3436
3567
|
f.set(this.modelParams, this.materialField('uModelParams'));
|
|
3437
3568
|
});
|
|
3438
3569
|
/*
|
|
@@ -3585,15 +3716,31 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3585
3716
|
|
|
3586
3717
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
3587
3718
|
setSurfaceReflectivity(amount: number): void {
|
|
3588
|
-
this.
|
|
3589
|
-
|
|
3590
|
-
});
|
|
3719
|
+
this.passReflectivity = Math.min(1, Math.max(0, amount));
|
|
3720
|
+
this.writeEnvironmentDials();
|
|
3591
3721
|
}
|
|
3592
3722
|
|
|
3593
3723
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
3594
3724
|
setEnvironmentGain(gain: number): void {
|
|
3725
|
+
this.passEnvironmentGain = Math.max(0, gain);
|
|
3726
|
+
this.writeEnvironmentDials();
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
/**
|
|
3730
|
+
* The reflectivity and environment gain the setters gave the pass, and the ones the material
|
|
3731
|
+
* set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
|
|
3732
|
+
* material's where it has one and the pass's otherwise, the rule `renderer.ts` keeps too.
|
|
3733
|
+
*/
|
|
3734
|
+
private passReflectivity = 0;
|
|
3735
|
+
private passEnvironmentGain = 1;
|
|
3736
|
+
private materialReflectivity: number | null = null;
|
|
3737
|
+
private materialEnvironmentGain: number | null = null;
|
|
3738
|
+
|
|
3739
|
+
private writeEnvironmentDials(): void {
|
|
3595
3740
|
this.material((f) => {
|
|
3596
|
-
|
|
3741
|
+
const dials = this.materialField('uEnvironmentDials');
|
|
3742
|
+
f[dials + 1] = this.materialReflectivity ?? this.passReflectivity;
|
|
3743
|
+
f[dials + 2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
|
|
3597
3744
|
});
|
|
3598
3745
|
}
|
|
3599
3746
|
|
|
@@ -3624,6 +3771,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3624
3771
|
this.materials.dirty();
|
|
3625
3772
|
}
|
|
3626
3773
|
|
|
3774
|
+
/** Whether the draws that follow spend their own opacity on that screen door. See `renderer.ts`. */
|
|
3775
|
+
setDitherOpacity(on: boolean): void {
|
|
3776
|
+
if (this.surface.lost) return;
|
|
3777
|
+
/* The third lane of `uWriteMode`, beside the fade. */
|
|
3778
|
+
this.perFrameFloats[this.materialField('uWriteMode') + 2] = on ? 1 : 0;
|
|
3779
|
+
this.materials.dirty();
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3627
3782
|
/**
|
|
3628
3783
|
* How strong the microscopic relief on the surfaces drawn next is, and how coarse.
|
|
3629
3784
|
*
|
|
@@ -4112,7 +4267,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4112
4267
|
}
|
|
4113
4268
|
this.pass?.end();
|
|
4114
4269
|
this.flushRings();
|
|
4115
|
-
this.
|
|
4270
|
+
this.submit([this.encoder.finish()]);
|
|
4116
4271
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'frame.resumed' });
|
|
4117
4272
|
this.reflectionPassActive = false;
|
|
4118
4273
|
this.reflectionReady = true;
|
|
@@ -5121,6 +5276,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5121
5276
|
at('uLightViewProj'),
|
|
5122
5277
|
env === undefined ? NO_LIGHT_MATRIX : (env.lightViewProj as Float32Array),
|
|
5123
5278
|
);
|
|
5279
|
+
ring.writeFloats(
|
|
5280
|
+
fragmentSlot,
|
|
5281
|
+
at('uMovingLightViewProj'),
|
|
5282
|
+
env === undefined ? NO_LIGHT_MATRIX : (this.movingLightFor(env) as Float32Array),
|
|
5283
|
+
);
|
|
5124
5284
|
ring.writeFloat(fragmentSlot, at('uShadowMapSize'), this.quality.directionalShadowMapSize);
|
|
5125
5285
|
/* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
|
|
5126
5286
|
ring.writeInt(fragmentSlot, at('uPeeledShadowEnabled'), this.peelFilled ? 1 : 0);
|
|
@@ -5160,6 +5320,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5160
5320
|
}
|
|
5161
5321
|
volumeCommand.vertexCount = mesh.vertexBuffers.length;
|
|
5162
5322
|
volumeCommand.indexBuffer = mesh.indexBuffer;
|
|
5323
|
+
volumeCommand.indexFormat = mesh.indexFormat;
|
|
5163
5324
|
volumeCommand.indexed = true;
|
|
5164
5325
|
volumeCommand.count = mesh.indexCount;
|
|
5165
5326
|
} else {
|
|
@@ -5168,7 +5329,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5168
5329
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
5169
5330
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
|
|
5170
5331
|
}
|
|
5171
|
-
pass.setIndexBuffer(mesh.indexBuffer,
|
|
5332
|
+
pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
|
|
5172
5333
|
pass.drawIndexed(mesh.indexCount);
|
|
5173
5334
|
}
|
|
5174
5335
|
}
|
|
@@ -5795,6 +5956,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5795
5956
|
*/
|
|
5796
5957
|
setWind(windX: number, windZ: number, windGust: number, timeSeconds: number): void {
|
|
5797
5958
|
resolveScatterDeform(windX, windZ, windGust, timeSeconds, null, this.frameWind);
|
|
5959
|
+
this.reopenViews();
|
|
5798
5960
|
}
|
|
5799
5961
|
|
|
5800
5962
|
/**
|
|
@@ -5814,22 +5976,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5814
5976
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
5815
5977
|
}
|
|
5816
5978
|
|
|
5817
|
-
/**
|
|
5818
|
-
* The five wind fields of one per-draw slot.
|
|
5819
|
-
*
|
|
5820
|
-
* **Never called for an instanced draw.** That variant declares none of these, and
|
|
5821
|
-
* `flatPass.ts` records what writing a field a variant does not declare costs: the block is
|
|
5822
|
-
* shorter, so the write lands on whatever occupies that offset instead.
|
|
5823
|
-
*/
|
|
5824
|
-
private writeWind(slot: number): void {
|
|
5825
|
-
const d = this.frameWind;
|
|
5826
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindDirection.offset, d.direction);
|
|
5827
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindSpeed.offset, d.bend);
|
|
5828
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindGust.offset, d.gust);
|
|
5829
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindTime.offset, d.time);
|
|
5830
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
5831
|
-
}
|
|
5832
|
-
|
|
5833
5979
|
/** Refilled per frame rather than allocated; see `resolvePointLights`. */
|
|
5834
5980
|
private readonly lights: ResolvedPointLights = {
|
|
5835
5981
|
count: 0,
|
|
@@ -5984,6 +6130,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5984
6130
|
* at submit, and a texture's view is new when an update changes its size.
|
|
5985
6131
|
*/
|
|
5986
6132
|
private readonly shadowCutoutLayout: GPUBindGroupLayout;
|
|
6133
|
+
/** A crowd's casters: the plain and cutout layouts with a bone animation's three textures. */
|
|
6134
|
+
private readonly shadowAnimatedLayout: GPUBindGroupLayout;
|
|
6135
|
+
private readonly shadowAnimatedCutoutLayout: GPUBindGroupLayout;
|
|
5987
6136
|
private readonly cutoutGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5988
6137
|
private readonly cutoutPeelGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5989
6138
|
private shadowPeelPassUniforms: GPUBuffer | null = null;
|
|
@@ -6021,6 +6170,22 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6021
6170
|
* the texel-for-texel comparison that settled it.
|
|
6022
6171
|
*/
|
|
6023
6172
|
private readonly correctedLightViewProj = new Float32Array(16);
|
|
6173
|
+
/**
|
|
6174
|
+
* What a receiver projects through to read the sun's static layers: the environment's
|
|
6175
|
+
* `lightViewProj`, taken at `bindMeshPass` as WebGL2 takes it there.
|
|
6176
|
+
*
|
|
6177
|
+
* **It was the last shadow pass's matrix**, which is the environment's wherever both layers are
|
|
6178
|
+
* drawn with one and the moving layer's wherever they are not: the static layer was then read
|
|
6179
|
+
* through the moving layer's tight square, and a distant pillar lost its shadow on WebGPU and kept
|
|
6180
|
+
* it on WebGL2, measured on demo/dev/sunLayers.html?moving=tight.
|
|
6181
|
+
*/
|
|
6182
|
+
private readonly receiverLightViewProj = new Float32Array(16);
|
|
6183
|
+
/**
|
|
6184
|
+
* The matrix the sun's dynamic shadow pass was last drawn with, uncorrected as the lit stage
|
|
6185
|
+
* projects with, and whether one has been. See `movingLightFor`.
|
|
6186
|
+
*/
|
|
6187
|
+
private readonly movingLightViewProj = new Float32Array(16);
|
|
6188
|
+
private movingLightKnown = false;
|
|
6024
6189
|
/**
|
|
6025
6190
|
* The same matrix *uncorrected*, which is what the flat shader must project with.
|
|
6026
6191
|
*
|
|
@@ -6151,6 +6316,48 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6151
6316
|
this.enableLit('GLASS_SHADOWS');
|
|
6152
6317
|
}
|
|
6153
6318
|
|
|
6319
|
+
/**
|
|
6320
|
+
* A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
|
|
6321
|
+
* said once, leave one layer.
|
|
6322
|
+
*/
|
|
6323
|
+
private layersOf(
|
|
6324
|
+
material: SurfaceMaterial<GpuSurfaceTexture> | null,
|
|
6325
|
+
): SurfaceLayers<GpuSurfaceTexture> | null {
|
|
6326
|
+
const layers = material?.layers ?? null;
|
|
6327
|
+
if (layers === null || layers.repeats.length === 0) return null;
|
|
6328
|
+
if ((material?.modelMap ?? null) === null) return layers;
|
|
6329
|
+
if (!this.warnedLayersModelMap) {
|
|
6330
|
+
this.warnedLayersModelMap = true;
|
|
6331
|
+
console.warn(
|
|
6332
|
+
"WebGPU: a material's layers are drawn as one: its mask is read where a model's map goes, " +
|
|
6333
|
+
'and this material carries a `modelMap` too.',
|
|
6334
|
+
);
|
|
6335
|
+
}
|
|
6336
|
+
return null;
|
|
6337
|
+
}
|
|
6338
|
+
|
|
6339
|
+
/**
|
|
6340
|
+
* What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
|
|
6341
|
+
* where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
|
|
6342
|
+
* drawing both layers with one matrix hands it anyway.
|
|
6343
|
+
*/
|
|
6344
|
+
private movingLightFor(env: Environment): ReadonlyMat4 {
|
|
6345
|
+
return this.movingLightKnown && this.pipelines.litSwitches.MOVING_SUN
|
|
6346
|
+
? this.movingLightViewProj
|
|
6347
|
+
: env.lightViewProj;
|
|
6348
|
+
}
|
|
6349
|
+
|
|
6350
|
+
/** The moving layer's own matrix on, the first time a frame draws that layer with one. */
|
|
6351
|
+
private enableMovingSun(env: Environment): void {
|
|
6352
|
+
if (this.pipelines.litSwitches.MOVING_SUN || !this.movingLightKnown) return;
|
|
6353
|
+
for (let k = 0; k < 16; k++) {
|
|
6354
|
+
if (this.movingLightViewProj[k] !== env.lightViewProj[k]) {
|
|
6355
|
+
this.enableLit('MOVING_SUN');
|
|
6356
|
+
return;
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
|
|
6154
6361
|
/** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
|
|
6155
6362
|
private enableLit(feature: LitSwitch): void {
|
|
6156
6363
|
if (this.pipelines.litSwitches[feature]) return;
|
|
@@ -6216,7 +6423,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6216
6423
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
6217
6424
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
6218
6425
|
}
|
|
6219
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
6426
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
6220
6427
|
pass.drawIndexed(geometry.indexCount);
|
|
6221
6428
|
},
|
|
6222
6429
|
/**
|
|
@@ -6239,8 +6446,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6239
6446
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
6240
6447
|
if (count === 0) return;
|
|
6241
6448
|
const cutout = cutoutOf(material);
|
|
6449
|
+
/* A crowd casts where its clip has moved it, through the animated variant. */
|
|
6450
|
+
const animation = gpuBatch.animation === null ? null : this.animationViews(gpuBatch);
|
|
6451
|
+
if (gpuBatch.animation !== null && animation === null) return;
|
|
6452
|
+
const cutoutTexture =
|
|
6453
|
+
cutout === null ? null : (cutout.albedo as unknown as GpuSurfaceTexture);
|
|
6242
6454
|
const cutoutGroup =
|
|
6243
|
-
|
|
6455
|
+
animation !== null
|
|
6456
|
+
? this.animatedDepthGroup(gpuBatch, animation, cutoutTexture)
|
|
6457
|
+
: cutoutTexture === null
|
|
6458
|
+
? null
|
|
6459
|
+
: this.cutoutGroup(cutoutTexture);
|
|
6460
|
+
if (animation !== null && cutoutGroup === null) return;
|
|
6244
6461
|
|
|
6245
6462
|
const slot = this.shadowDraws.allocate();
|
|
6246
6463
|
if (slot === null) return;
|
|
@@ -6251,23 +6468,43 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6251
6468
|
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
6252
6469
|
this.correctedLightViewProj,
|
|
6253
6470
|
);
|
|
6254
|
-
|
|
6471
|
+
const cuts = cutoutTexture !== null && cutoutGroup !== null;
|
|
6472
|
+
if (cutout !== null && cuts) {
|
|
6255
6473
|
this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
6256
6474
|
}
|
|
6475
|
+
const clip = gpuBatch.animation;
|
|
6476
|
+
if (animation !== null && clip !== null) {
|
|
6477
|
+
const fields = cuts
|
|
6478
|
+
? DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS
|
|
6479
|
+
: DEPTH_INSTANCED_ANIMATED_VERT_FIELDS;
|
|
6480
|
+
this.shadowDraws.writeFloat(slot, fields.uSceneTime.offset, this.animationTime);
|
|
6481
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset, clip.framesPerSecond);
|
|
6482
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset + 4, clip.boneScale);
|
|
6483
|
+
}
|
|
6257
6484
|
|
|
6258
|
-
const cuts = cutoutGroup !== null;
|
|
6259
6485
|
const cull = material?.doubleSided === true ? 'none' : this.depthCullMode;
|
|
6486
|
+
const animated = animation !== null;
|
|
6260
6487
|
pass.setPipeline(
|
|
6261
6488
|
depthPipeline(
|
|
6262
6489
|
this.pipelines,
|
|
6263
6490
|
this.surface.device,
|
|
6264
|
-
|
|
6265
|
-
|
|
6491
|
+
animated
|
|
6492
|
+
? cuts
|
|
6493
|
+
? this.shadowAnimatedCutoutLayout
|
|
6494
|
+
: this.shadowAnimatedLayout
|
|
6495
|
+
: cuts
|
|
6496
|
+
? this.shadowCutoutLayout
|
|
6497
|
+
: this.shadowLayout,
|
|
6498
|
+
`${cuts ? 'depth-instanced-cutout' : 'depth-instanced'}${animated ? '-animated' : ''}` +
|
|
6499
|
+
`|${cull}|${geometry.key ?? ''}`,
|
|
6266
6500
|
this.presentOf(geometry),
|
|
6267
6501
|
cull,
|
|
6268
6502
|
false,
|
|
6269
6503
|
true,
|
|
6270
6504
|
cuts,
|
|
6505
|
+
false,
|
|
6506
|
+
false,
|
|
6507
|
+
animated,
|
|
6271
6508
|
),
|
|
6272
6509
|
);
|
|
6273
6510
|
pass.setBindGroup(0, cutoutGroup ?? this.shadowGroup(), [slot]);
|
|
@@ -6276,7 +6513,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6276
6513
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
6277
6514
|
}
|
|
6278
6515
|
pass.setVertexBuffer(index, gpuBatch.buffer);
|
|
6279
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
6516
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
6280
6517
|
pass.drawIndexed(geometry.indexCount, count);
|
|
6281
6518
|
},
|
|
6282
6519
|
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
@@ -6338,7 +6575,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6338
6575
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
6339
6576
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
6340
6577
|
}
|
|
6341
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
6578
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
6342
6579
|
pass.drawIndexed(geometry.indexCount);
|
|
6343
6580
|
},
|
|
6344
6581
|
/**
|
|
@@ -6761,6 +6998,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6761
6998
|
this.overlayPipelines === this.pipelines
|
|
6762
6999
|
? [this.pipelines]
|
|
6763
7000
|
: [this.pipelines, this.overlayPipelines];
|
|
7001
|
+
/* A lit draw first needed inside a frame waits for its compile or is left out while it runs. */
|
|
7002
|
+
this.skipCompiling(quality.pipelineCompile === 'skip');
|
|
6764
7003
|
this.variant = flatVariant({
|
|
6765
7004
|
directionalShadows: quality.directionalShadows,
|
|
6766
7005
|
/* No `bakeReflectionProbe` here; see the field. */
|
|
@@ -6781,7 +7020,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6781
7020
|
pointShadows: quality.pointShadows,
|
|
6782
7021
|
});
|
|
6783
7022
|
this.pointShadowsCompiled = quality.pointShadows;
|
|
6784
|
-
this.fragment =
|
|
7023
|
+
this.fragment = flatFragmentScratch(this.variant);
|
|
6785
7024
|
this.bindGroupLayout = createFlatBindGroupLayout(device, this.variant);
|
|
6786
7025
|
/*
|
|
6787
7026
|
* The skinned twin, built eagerly because it is a descriptor rather than a compile — WebGPU
|
|
@@ -6791,6 +7030,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6791
7030
|
this.skinnedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true);
|
|
6792
7031
|
this.morphedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, true);
|
|
6793
7032
|
this.bothBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true, true);
|
|
7033
|
+
/* And the instanced twin that plays a bone animation: its clip's and clocks' textures. */
|
|
7034
|
+
this.animatedBindGroupLayout = createFlatBindGroupLayout(
|
|
7035
|
+
device,
|
|
7036
|
+
this.variant,
|
|
7037
|
+
false,
|
|
7038
|
+
false,
|
|
7039
|
+
true,
|
|
7040
|
+
);
|
|
6794
7041
|
this.perDraw = new UniformRing(
|
|
6795
7042
|
device,
|
|
6796
7043
|
FLAT_VERT_SIZE,
|
|
@@ -6799,30 +7046,48 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6799
7046
|
'flat.vertRing',
|
|
6800
7047
|
this.drawBudget,
|
|
6801
7048
|
);
|
|
6802
|
-
|
|
7049
|
+
const { passSize, materialSize } = this.fragment;
|
|
7050
|
+
this.perFrameStaging = new ArrayBuffer(passSize + materialSize);
|
|
6803
7051
|
this.perFrameFloats = new Float32Array(this.perFrameStaging);
|
|
6804
7052
|
this.perFrameInts = new Int32Array(this.perFrameStaging);
|
|
7053
|
+
this.passInts = new Int32Array(this.perFrameStaging, 0, passSize / 4);
|
|
7054
|
+
this.materialInts = new Int32Array(this.perFrameStaging, passSize, materialSize / 4);
|
|
6805
7055
|
/*
|
|
6806
|
-
* **
|
|
7056
|
+
* **Two blocks, because they change at two rates.** `setSurfaceTexture`, `setSurfaceGrain` and
|
|
7057
|
+
* their siblings are pass state on both backends — a material covers many draws — and on WebGL2
|
|
7058
|
+
* each is a `uniform1f` that lands immediately. Here they live in a block, and
|
|
7059
|
+
* `queue.writeBuffer` does not interleave with recorded commands, so one buffer rewritten between
|
|
7060
|
+
* two draws gives *both* the second value: every surface would wear the last material set, which
|
|
7061
|
+
* is bug 7's shape at pass scale. So each draw binds a slot.
|
|
6807
7062
|
*
|
|
6808
|
-
*
|
|
6809
|
-
*
|
|
6810
|
-
*
|
|
6811
|
-
*
|
|
6812
|
-
* commands, so one buffer rewritten between two draws gives *both* the second value. Every
|
|
6813
|
-
* surface in a scene would wear the last material set, which is bug 7's shape at pass scale.
|
|
6814
|
-
*
|
|
6815
|
-
* A slot per *material change* rather than per draw: `drawMesh` reuses the open slot until
|
|
6816
|
-
* something dirties it, so a scene drawing a hundred meshes of one material spends one.
|
|
7063
|
+
* It was one block until 4.10.0, lights and all, copied whole into a ring slot per material
|
|
7064
|
+
* change and uploaded every frame: a stage of four hundred materials sent 4.8 MB a frame to say
|
|
7065
|
+
* what it had said the frame before. Now the pass's block takes a ring slot per pass, and a
|
|
7066
|
+
* material's block a slot of the store, where an unchanged material stays from frame to frame.
|
|
6817
7067
|
*/
|
|
6818
|
-
this.
|
|
7068
|
+
this.passRing = new UniformRing(
|
|
6819
7069
|
device,
|
|
6820
|
-
|
|
7070
|
+
passSize,
|
|
7071
|
+
PASSES_PER_FRAME,
|
|
7072
|
+
USAGE_UNIFORM_DST,
|
|
7073
|
+
'flat.passRing',
|
|
7074
|
+
);
|
|
7075
|
+
this.materialStore = new MaterialStore(
|
|
7076
|
+
device,
|
|
7077
|
+
materialSize,
|
|
6821
7078
|
this.quality.materialChangesPerFrame,
|
|
6822
7079
|
USAGE_UNIFORM_DST,
|
|
6823
|
-
'flat.
|
|
7080
|
+
'flat.materials',
|
|
6824
7081
|
this.materialBudget,
|
|
6825
7082
|
);
|
|
7083
|
+
/* Two a pass at most: the jittered view and the unjittered one. */
|
|
7084
|
+
this.viewRing = new UniformRing(
|
|
7085
|
+
device,
|
|
7086
|
+
FLAT_VIEW_SIZE,
|
|
7087
|
+
2 * PASSES_PER_FRAME,
|
|
7088
|
+
USAGE_UNIFORM_DST,
|
|
7089
|
+
'flat.viewRing',
|
|
7090
|
+
);
|
|
6826
7091
|
|
|
6827
7092
|
/*
|
|
6828
7093
|
* A one-pixel stand-in for the albedo binding. The albedo's flag is zero for untextured
|
|
@@ -6971,7 +7236,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6971
7236
|
})
|
|
6972
7237
|
.end();
|
|
6973
7238
|
}
|
|
6974
|
-
|
|
7239
|
+
this.submit([clear.finish()]);
|
|
6975
7240
|
/*
|
|
6976
7241
|
* `nearest` on a depth texture sampled as an ordinary float, matching what
|
|
6977
7242
|
* `shadowMap.ts` asks WebGL2 for. A filtering sampler cannot be used on a depth format
|
|
@@ -7584,6 +7849,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7584
7849
|
this.shadowLayout = createDepthBindGroupLayout(device);
|
|
7585
7850
|
this.shadowSkinnedLayout = createDepthBindGroupLayout(device, true);
|
|
7586
7851
|
this.shadowCutoutLayout = createDepthBindGroupLayout(device, false, true);
|
|
7852
|
+
this.shadowAnimatedLayout = createDepthBindGroupLayout(device, false, false, true);
|
|
7853
|
+
this.shadowAnimatedCutoutLayout = createDepthBindGroupLayout(device, false, true, true);
|
|
7587
7854
|
this.shadowDraws = new UniformRing(
|
|
7588
7855
|
device,
|
|
7589
7856
|
/* The cutout block is the plain one and two fields more, and a slot must hold either. */
|
|
@@ -7985,6 +8252,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7985
8252
|
present['layers'] = key.includes(':layers');
|
|
7986
8253
|
/* Nor is a dynamic mesh's split, which moves positions and normals into buffers of their own. */
|
|
7987
8254
|
present['dynamic'] = key.includes(':dynamic');
|
|
8255
|
+
/* Nor how each attribute travels, which moves every field after a packed one. */
|
|
8256
|
+
markPackingOfKey(present, key);
|
|
7988
8257
|
return present;
|
|
7989
8258
|
}
|
|
7990
8259
|
|
|
@@ -8095,9 +8364,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8095
8364
|
this.lineVerts.reset();
|
|
8096
8365
|
this.lineFrags.reset();
|
|
8097
8366
|
this.skyUniforms.reset();
|
|
8098
|
-
/* The
|
|
8099
|
-
|
|
8367
|
+
/* The pass ring, and the open slots with it: last frame's pass slots are gone. Its materials
|
|
8368
|
+
stay in the store, which a draw finds them in again. */
|
|
8369
|
+
this.passRing.reset();
|
|
8370
|
+
this.viewRing.reset();
|
|
8371
|
+
this.materialStore.beginFrame();
|
|
8372
|
+
this.passesAsked = 0;
|
|
8373
|
+
this.passesRefused = 0;
|
|
8374
|
+
this.viewsAsked = 0;
|
|
8375
|
+
this.viewsRefused = 0;
|
|
8376
|
+
this.materialsRefused = 0;
|
|
8100
8377
|
this.materials.dirty();
|
|
8378
|
+
this.reopenPass();
|
|
8101
8379
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
8102
8380
|
this.motionPass?.reset();
|
|
8103
8381
|
this.latePass?.reset();
|
|
@@ -8285,6 +8563,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8285
8563
|
present['dynamic'] = true;
|
|
8286
8564
|
key += ':dynamic';
|
|
8287
8565
|
}
|
|
8566
|
+
/*
|
|
8567
|
+
* And how each attribute travels (`vertexPacking.ts`): a field read as sixteen bits rather than
|
|
8568
|
+
* thirty-two, or not interleaved at all, moves every field after it, so the key says so.
|
|
8569
|
+
*/
|
|
8570
|
+
const packing = vertexPackingOf(data, dynamic);
|
|
8571
|
+
markPacking(present, packing);
|
|
8572
|
+
key += packingKey(packing);
|
|
8288
8573
|
const fullKey = `${this.variant}|${key}`;
|
|
8289
8574
|
/*
|
|
8290
8575
|
* **For every target this mesh can land on, which is two when the overlay is its own.**
|
|
@@ -8432,6 +8717,40 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8432
8717
|
};
|
|
8433
8718
|
}
|
|
8434
8719
|
|
|
8720
|
+
/**
|
|
8721
|
+
* The copies this frame's mesh rewrites owe (`StagedRewrite`), recorded since the last submit and
|
|
8722
|
+
* sent at the head of the next one: one encoder however many meshes are rewritten. Null while
|
|
8723
|
+
* nothing is owed.
|
|
8724
|
+
*/
|
|
8725
|
+
private pendingRewrites: GPUCommandEncoder | null = null;
|
|
8726
|
+
/** One record handed to every staged rewrite, refilled each call, so a rewrite allocates nothing. */
|
|
8727
|
+
private readonly stagedRewrite: StagedRewrite = {
|
|
8728
|
+
encoder: null as unknown as GPUCommandEncoder,
|
|
8729
|
+
staging: null as unknown as GPUBuffer,
|
|
8730
|
+
previous: null,
|
|
8731
|
+
};
|
|
8732
|
+
|
|
8733
|
+
private rewriteCopies(): GPUCommandEncoder {
|
|
8734
|
+
this.pendingRewrites ??= this.surface.device.createCommandEncoder({
|
|
8735
|
+
label: 'mesh.rewriteCopies',
|
|
8736
|
+
});
|
|
8737
|
+
return this.pendingRewrites;
|
|
8738
|
+
}
|
|
8739
|
+
|
|
8740
|
+
/**
|
|
8741
|
+
* Submit `buffers`, led by any copies the frame's mesh rewrites owe. **Every submit of this
|
|
8742
|
+
* renderer goes through here**, because any of them may draw a rewritten mesh — the frame's own,
|
|
8743
|
+
* a mirror's, a shadow's, a probe's — and the copies must be ahead of the first that does.
|
|
8744
|
+
*/
|
|
8745
|
+
private submit(buffers: GPUCommandBuffer[]): void {
|
|
8746
|
+
const rewrites = this.pendingRewrites;
|
|
8747
|
+
if (rewrites !== null) {
|
|
8748
|
+
this.pendingRewrites = null;
|
|
8749
|
+
buffers.unshift(rewrites.finish());
|
|
8750
|
+
}
|
|
8751
|
+
this.surface.device.queue.submit(buffers);
|
|
8752
|
+
}
|
|
8753
|
+
|
|
8435
8754
|
/**
|
|
8436
8755
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
8437
8756
|
*
|
|
@@ -8454,20 +8773,35 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8454
8773
|
* `changeFrames.ts`.
|
|
8455
8774
|
*/
|
|
8456
8775
|
const motion = mesh.motion;
|
|
8457
|
-
let
|
|
8776
|
+
let staged: StagedRewrite | null = null;
|
|
8458
8777
|
if (motion !== null && this.reconstructionWanted) {
|
|
8459
8778
|
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
8779
|
+
const size = mesh.vertexBuffers[0]?.size ?? 0;
|
|
8780
|
+
let previous: GPUBuffer | null = null;
|
|
8460
8781
|
if (keepsPrevious(motion.changed, frame)) {
|
|
8461
8782
|
motion.previous ??= this.surface.device.createBuffer({
|
|
8462
8783
|
label: 'recon.dynamicPrevious',
|
|
8463
|
-
size
|
|
8784
|
+
size,
|
|
8464
8785
|
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
8465
8786
|
});
|
|
8466
8787
|
previous = motion.previous;
|
|
8467
8788
|
}
|
|
8468
8789
|
motion.changed = frame;
|
|
8790
|
+
/*
|
|
8791
|
+
* **Staged, and copied in behind the copy aside, in an encoder the frame's next submit leads
|
|
8792
|
+
* with**: no submit of the rewrite's own. See `StagedRewrite` and `submit`.
|
|
8793
|
+
*/
|
|
8794
|
+
motion.staging ??= this.surface.device.createBuffer({
|
|
8795
|
+
label: 'mesh.stagedPositions',
|
|
8796
|
+
size,
|
|
8797
|
+
usage: 0x0004 | 0x0008, // COPY_SRC | COPY_DST
|
|
8798
|
+
});
|
|
8799
|
+
staged = this.stagedRewrite;
|
|
8800
|
+
staged.encoder = this.rewriteCopies();
|
|
8801
|
+
staged.staging = motion.staging;
|
|
8802
|
+
staged.previous = previous;
|
|
8469
8803
|
}
|
|
8470
|
-
mesh.update(this.surface.device, positions, normals,
|
|
8804
|
+
mesh.update(this.surface.device, positions, normals, staged);
|
|
8471
8805
|
}
|
|
8472
8806
|
|
|
8473
8807
|
/**
|
|
@@ -8480,6 +8814,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8480
8814
|
*/
|
|
8481
8815
|
bindMeshPass(camera: Camera, env: Environment): void {
|
|
8482
8816
|
if (this.surface.lost) return;
|
|
8817
|
+
/* The matrix every receiver projects through: the environment's, as WebGL2's. See the field. */
|
|
8818
|
+
this.receiverLightViewProj.set(env.lightViewProj as Float32Array);
|
|
8483
8819
|
|
|
8484
8820
|
/* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
|
|
8485
8821
|
if (
|
|
@@ -8598,6 +8934,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8598
8934
|
this.surfaceFog = FOG_RECEDE;
|
|
8599
8935
|
/* Every material switch off — albedo, ORM map, emissive map, two-sided — as WebGL2 resets them. */
|
|
8600
8936
|
i.fill(0, at('uMaterialFlags'), at('uMaterialFlags') + 4);
|
|
8937
|
+
/* And no projection: maps follow the mesh's coordinates until a material asks otherwise. */
|
|
8938
|
+
f.fill(0, at('uWorldUv'), at('uWorldUv') + 4);
|
|
8939
|
+
/* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
|
|
8940
|
+
f[at('uGlassTint') + 3] = 0;
|
|
8941
|
+
/* Nor layers: one layer until a material asks for more. */
|
|
8942
|
+
f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
|
|
8601
8943
|
f[at('uCutout')] = 0;
|
|
8602
8944
|
f[at('uCutout') + 1] = 0;
|
|
8603
8945
|
this.cutoutResolveStaged = 'hard';
|
|
@@ -8671,10 +9013,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8671
9013
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
8672
9014
|
*/
|
|
8673
9015
|
f[at('uGrain')] = 1;
|
|
9016
|
+
/* No reflection and an environment at its own brightness, from the pass and from no material. */
|
|
9017
|
+
this.passReflectivity = 0;
|
|
9018
|
+
this.passEnvironmentGain = 1;
|
|
9019
|
+
this.materialReflectivity = null;
|
|
9020
|
+
this.materialEnvironmentGain = null;
|
|
8674
9021
|
f[at('uEnvironmentDials') + 1] = 0;
|
|
8675
9022
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
8676
9023
|
f[at('uWriteMode')] = 0;
|
|
8677
9024
|
f[at('uWriteMode') + 1] = 0;
|
|
9025
|
+
f[at('uWriteMode') + 2] = 0;
|
|
8678
9026
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
8679
9027
|
f[at('uEnvironmentDials') + 2] = 1;
|
|
8680
9028
|
/* No overlay until a draw sets one. See `setSurfaceOverlay`. */
|
|
@@ -8785,6 +9133,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8785
9133
|
*/
|
|
8786
9134
|
if (this.quality.nightEmissive) f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
8787
9135
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
9136
|
+
/* The frame the cutout dither moves with, in the near corner's w. See `uHighlightMin`. */
|
|
9137
|
+
f[at('uHighlightMin') + 3] = this.cutoutFrame;
|
|
8788
9138
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
8789
9139
|
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
8790
9140
|
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
@@ -8966,6 +9316,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8966
9316
|
f[at('uShadowStrength')] = env.shadowStrength;
|
|
8967
9317
|
f[at('uShadowMapSize')] = this.quality.directionalShadowMapSize;
|
|
8968
9318
|
f[at('uShadowDepthSpan')] = env.shadowDepthSpan;
|
|
9319
|
+
/* The moving layer's own matrix and span, where the lit stage reads one: `movingLightFor`. */
|
|
9320
|
+
this.enableMovingSun(env);
|
|
9321
|
+
const moving = this.movingLightFor(env);
|
|
9322
|
+
f.set(moving as Float32Array, at('uMovingLightViewProj'));
|
|
9323
|
+
f[at('uMovingShadowDepthSpan')] = orthographicDepthSpan(moving);
|
|
8969
9324
|
f[at('uShadowMaxDistance')] = this.quality.directionalShadowMaxDistance;
|
|
8970
9325
|
f[at('uShadowMaxSlope')] = this.quality.directionalShadowMaxSlope;
|
|
8971
9326
|
/* Sampled only where a pass filled it — see `peelFilled` for why existing is not enough. */
|
|
@@ -9057,6 +9412,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9057
9412
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
9058
9413
|
*/
|
|
9059
9414
|
this.materials.dirty();
|
|
9415
|
+
this.reopenPass();
|
|
9060
9416
|
/*
|
|
9061
9417
|
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
9062
9418
|
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
@@ -9081,31 +9437,114 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9081
9437
|
}
|
|
9082
9438
|
|
|
9083
9439
|
/**
|
|
9084
|
-
* The slot the next draw's material lives in,
|
|
9440
|
+
* The slot the next draw's material lives in, placing it when something dirtied it, with the
|
|
9441
|
+
* pass's slot taken first where `bindMeshPass` reopened it: `passOffset` is that one's offset.
|
|
9085
9442
|
*
|
|
9086
|
-
* **A
|
|
9087
|
-
* material
|
|
9088
|
-
*
|
|
9443
|
+
* **A placement per material change, not per draw.** A scene drawing a hundred meshes of one
|
|
9444
|
+
* material places it once; a scene switching material every draw places one each, and a material
|
|
9445
|
+
* the store already holds — the same numbers as a draw before it, this frame or an earlier one —
|
|
9446
|
+
* is bound where it is and uploads nothing.
|
|
9089
9447
|
*/
|
|
9090
9448
|
private materialSlotForDraw(): number | null {
|
|
9449
|
+
if (!this.passBlock.open) {
|
|
9450
|
+
this.passesAsked += 1;
|
|
9451
|
+
const pass = this.passRing.allocate();
|
|
9452
|
+
if (pass === null) {
|
|
9453
|
+
this.passesRefused += 1;
|
|
9454
|
+
this.materialBudget.ask();
|
|
9455
|
+
this.materialBudget.drop();
|
|
9456
|
+
if (!this.warnedPassesFull) {
|
|
9457
|
+
this.warnedPassesFull = true;
|
|
9458
|
+
console.warn(
|
|
9459
|
+
`WebGPU: more than ${this.passRing.slots} mesh passes drew in one frame; the draws of the rest are skipped this frame and the ring grows for the next.`,
|
|
9460
|
+
);
|
|
9461
|
+
}
|
|
9462
|
+
return null;
|
|
9463
|
+
}
|
|
9464
|
+
this.passRing.writeBlock(pass, this.passInts);
|
|
9465
|
+
this.passBlock.slot = pass;
|
|
9466
|
+
}
|
|
9091
9467
|
if (this.materials.open) return this.materials.slot;
|
|
9092
|
-
const slot = this.
|
|
9468
|
+
const slot = this.materialStore.place(this.materialInts);
|
|
9093
9469
|
if (slot === null) {
|
|
9094
|
-
|
|
9095
|
-
|
|
9470
|
+
this.materialsRefused += 1;
|
|
9471
|
+
this.warnMaterialsFull();
|
|
9472
|
+
return null;
|
|
9473
|
+
}
|
|
9474
|
+
this.materials.slot = slot;
|
|
9475
|
+
return slot;
|
|
9476
|
+
}
|
|
9477
|
+
|
|
9478
|
+
/** The pass slot `materialSlotForDraw` left open, read beside the material's by every lit draw. */
|
|
9479
|
+
private get passOffset(): number {
|
|
9480
|
+
return this.passBlock.slot;
|
|
9481
|
+
}
|
|
9482
|
+
|
|
9483
|
+
/** A new pass: its block, and both its views, are taken afresh by the next draw. */
|
|
9484
|
+
private reopenPass(): void {
|
|
9485
|
+
this.passBlock.dirty();
|
|
9486
|
+
this.reopenViews();
|
|
9487
|
+
}
|
|
9488
|
+
|
|
9489
|
+
/** What a view holds changed — the wind, the light's matrix — so the next draw takes a new one. */
|
|
9490
|
+
private reopenViews(): void {
|
|
9491
|
+
this.viewBlock.dirty();
|
|
9492
|
+
this.lateViewBlock.dirty();
|
|
9493
|
+
}
|
|
9494
|
+
|
|
9495
|
+
/**
|
|
9496
|
+
* The view slot a lit draw reads its camera from: the pass's, or its unjittered twin for a blended
|
|
9497
|
+
* draw landing after the upscale. Taken by the first draw that needs it after the pass, the wind or
|
|
9498
|
+
* the light's matrix changed, and shared by every draw until then. Null when the ring is full, which
|
|
9499
|
+
* skips the draw and grows the ring for the next frame, as the pass ring does.
|
|
9500
|
+
*/
|
|
9501
|
+
private viewSlotFor(late: boolean, viewProj: Float32Array): number | null {
|
|
9502
|
+
const block = late ? this.lateViewBlock : this.viewBlock;
|
|
9503
|
+
if (block.open) return block.slot;
|
|
9504
|
+
this.viewsAsked += 1;
|
|
9505
|
+
const slot = this.viewRing.allocate();
|
|
9506
|
+
if (slot === null) {
|
|
9507
|
+
this.viewsRefused += 1;
|
|
9508
|
+
this.materialBudget.ask();
|
|
9509
|
+
this.materialBudget.drop();
|
|
9510
|
+
if (!this.warnedPassesFull) {
|
|
9511
|
+
this.warnedPassesFull = true;
|
|
9096
9512
|
console.warn(
|
|
9097
|
-
`WebGPU: more than ${this.
|
|
9513
|
+
`WebGPU: more than ${this.viewRing.slots} views drew in one frame; the draws of the rest are skipped this frame and the ring grows for the next.`,
|
|
9098
9514
|
);
|
|
9099
9515
|
}
|
|
9100
9516
|
return null;
|
|
9101
9517
|
}
|
|
9102
|
-
this.
|
|
9103
|
-
|
|
9518
|
+
this.writeView(this.viewRing, slot, viewProj);
|
|
9519
|
+
block.slot = slot;
|
|
9104
9520
|
return slot;
|
|
9105
9521
|
}
|
|
9106
9522
|
|
|
9523
|
+
/** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
|
|
9524
|
+
private writeView(ring: UniformRing, slot: number, viewProj: Float32Array): void {
|
|
9525
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
|
|
9526
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.receiverLightViewProj);
|
|
9527
|
+
/* The scene's clock, which a bone animation's instances read their moment from. */
|
|
9528
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
|
|
9529
|
+
const d = this.frameWind;
|
|
9530
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindDirection.offset, d.direction);
|
|
9531
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindSpeed.offset, d.bend);
|
|
9532
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindGust.offset, d.gust);
|
|
9533
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindTime.offset, d.time);
|
|
9534
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
9535
|
+
}
|
|
9536
|
+
|
|
9537
|
+
private warnMaterialsFull(): void {
|
|
9538
|
+
if (this.warnedMaterialsFull) return;
|
|
9539
|
+
this.warnedMaterialsFull = true;
|
|
9540
|
+
console.warn(
|
|
9541
|
+
`WebGPU: more than ${this.quality.materialChangesPerFrame} distinct materials in a frame; the draws asking for the rest are skipped. renderer.frameBudget names the line and the count.`,
|
|
9542
|
+
);
|
|
9543
|
+
}
|
|
9544
|
+
|
|
9107
9545
|
/** Said once rather than every frame, for the reason `warnedFull` gives. */
|
|
9108
9546
|
private warnedMaterialsFull = false;
|
|
9547
|
+
private warnedPassesFull = false;
|
|
9109
9548
|
private warnedSkyFull = false;
|
|
9110
9549
|
private warnedParticlesTwice = false;
|
|
9111
9550
|
/**
|
|
@@ -9146,7 +9585,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9146
9585
|
* What this saves is the GPU's share, which for a heavy mesh is most of the cost and for a
|
|
9147
9586
|
* trivial one is very little.
|
|
9148
9587
|
*/
|
|
9149
|
-
|
|
9588
|
+
/* A static list draws every entry in every view, since a bundle cannot leave one out. */
|
|
9589
|
+
if (
|
|
9590
|
+
this.quality.cullDraws &&
|
|
9591
|
+
this.staticRecording === null &&
|
|
9592
|
+
!this.visible(mesh.bounds, model as ReadonlyMat4)
|
|
9593
|
+
) {
|
|
9594
|
+
return;
|
|
9595
|
+
}
|
|
9150
9596
|
/*
|
|
9151
9597
|
* And the other half of the same question, where the profile asked for one. Gated on
|
|
9152
9598
|
* `cullDraws` alongside the frustum test, because both are the renderer doing a consumer's
|
|
@@ -9337,21 +9783,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9337
9783
|
`TranslucentMeshOptions.reconstructed`. */
|
|
9338
9784
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
9339
9785
|
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
);
|
|
9786
|
+
/* The camera, the light's matrix and the wind are the pass's, in its view block. */
|
|
9787
|
+
const view = this.viewSlotFor(late, late ? this.correctedViewProj : this.viewProj);
|
|
9788
|
+
if (view === null) return;
|
|
9789
|
+
this.drawViewOffset = view;
|
|
9345
9790
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
9346
9791
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
9347
|
-
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
9348
|
-
instanced one, which declares none of these fields. */
|
|
9349
|
-
this.writeWind(slot);
|
|
9350
9792
|
/* The material's repeats, not a constant: geometry authored in metres is textured at
|
|
9351
9793
|
whatever density the material asks for without rebuilding it. */
|
|
9352
9794
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
9353
9795
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
9354
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
9355
9796
|
/*
|
|
9356
9797
|
* Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
|
|
9357
9798
|
* is always backed — by the caller's data or by the constant buffer — which makes the binding
|
|
@@ -9489,8 +9930,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9489
9930
|
* thrown error in the middle of one.
|
|
9490
9931
|
*/
|
|
9491
9932
|
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
9492
|
-
let pipeline = pipelines.peek(key);
|
|
9493
|
-
if (pipeline ===
|
|
9933
|
+
let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
|
|
9934
|
+
if (pipeline === null) {
|
|
9494
9935
|
const present = this.meshPresent.get(base);
|
|
9495
9936
|
if (present === undefined) {
|
|
9496
9937
|
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
@@ -9517,6 +9958,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9517
9958
|
splits ? 'scene' : 'whole',
|
|
9518
9959
|
);
|
|
9519
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
|
+
}
|
|
9520
9969
|
|
|
9521
9970
|
/*
|
|
9522
9971
|
* Guards the draw call only, not the restore below.
|
|
@@ -9534,8 +9983,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9534
9983
|
* close to the 256-slot budget before the gateway, the play controls, the waveform, the
|
|
9535
9984
|
* label handles and the sparkles are even counted.
|
|
9536
9985
|
*/
|
|
9537
|
-
|
|
9538
|
-
|
|
9986
|
+
/* A draw whose pipeline is still compiling is left out, its state restored all the same. */
|
|
9987
|
+
const material = pipeline === null ? null : this.materialSlotForDraw();
|
|
9988
|
+
if (pipeline !== null && material !== null) {
|
|
9539
9989
|
/*
|
|
9540
9990
|
* Record rather than issue. Every per-draw value is already in `slot` and `material` —
|
|
9541
9991
|
* the uniform rings are the state arena this design would otherwise have had to build —
|
|
@@ -9548,24 +9998,32 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9548
9998
|
command.pipeline = pipeline;
|
|
9549
9999
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
9550
10000
|
command.offsetA = slot;
|
|
9551
|
-
command.offsetB =
|
|
9552
|
-
command.
|
|
10001
|
+
command.offsetB = this.passOffset;
|
|
10002
|
+
command.offsetC = material;
|
|
10003
|
+
command.offsetD = this.drawViewOffset;
|
|
10004
|
+
command.offsetCount = 4;
|
|
9553
10005
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
9554
10006
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
9555
10007
|
}
|
|
9556
10008
|
command.vertexCount = mesh.vertexBuffers.length;
|
|
9557
10009
|
command.indexBuffer = mesh.indexBuffer;
|
|
10010
|
+
command.indexFormat = mesh.indexFormat;
|
|
9558
10011
|
command.indexed = true;
|
|
9559
10012
|
command.count = mesh.indexCount;
|
|
9560
10013
|
} else {
|
|
9561
10014
|
const pass = this.openPass();
|
|
9562
10015
|
if (pass === null) return;
|
|
9563
10016
|
pass.setPipeline(pipeline);
|
|
9564
|
-
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
10017
|
+
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
10018
|
+
slot,
|
|
10019
|
+
this.passOffset,
|
|
10020
|
+
material,
|
|
10021
|
+
this.drawViewOffset,
|
|
10022
|
+
]);
|
|
9565
10023
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
9566
10024
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
|
|
9567
10025
|
}
|
|
9568
|
-
pass.setIndexBuffer(mesh.indexBuffer,
|
|
10026
|
+
pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
|
|
9569
10027
|
pass.drawIndexed(mesh.indexCount);
|
|
9570
10028
|
}
|
|
9571
10029
|
if (splits) {
|
|
@@ -9677,16 +10135,127 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9677
10135
|
'instanced pipeline.',
|
|
9678
10136
|
);
|
|
9679
10137
|
}
|
|
10138
|
+
const animation = (options.animation ?? null) as GpuBoneAnimation | null;
|
|
10139
|
+
/* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
|
|
10140
|
+
lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
|
|
10141
|
+
if (animation !== null && !this.presentOf(mesh as GpuMesh & { key?: string })['grain']) {
|
|
10142
|
+
throw new Error(
|
|
10143
|
+
'WebGPU: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
|
|
10144
|
+
'(`MeshData.lightmapUvs`), and this mesh has none.',
|
|
10145
|
+
);
|
|
10146
|
+
}
|
|
9680
10147
|
const batch = new GpuInstancedBatch(
|
|
9681
10148
|
this.surface.device,
|
|
9682
10149
|
mesh,
|
|
9683
10150
|
capacity,
|
|
9684
10151
|
`flat.instances:${(mesh as GpuMesh & { key?: string }).key ?? 'mesh'}`,
|
|
9685
10152
|
options.cull ?? false,
|
|
10153
|
+
animation,
|
|
9686
10154
|
);
|
|
9687
10155
|
return batch;
|
|
9688
10156
|
}
|
|
9689
10157
|
|
|
10158
|
+
/** See `Renderer.setAnimationTime`. Every view taken after it reads the new clock. */
|
|
10159
|
+
setAnimationTime(seconds: number): void {
|
|
10160
|
+
if (!Number.isFinite(seconds) || seconds === this.animationTime) return;
|
|
10161
|
+
this.animationTime = seconds;
|
|
10162
|
+
this.reopenViews();
|
|
10163
|
+
}
|
|
10164
|
+
|
|
10165
|
+
/** The clock `setAnimationTime` set, 0 until it is. */
|
|
10166
|
+
private animationTime = 0;
|
|
10167
|
+
|
|
10168
|
+
/** See `Renderer.createBoneAnimation`. */
|
|
10169
|
+
createBoneAnimation(clip: BoneAnimationClip): BoneAnimationHandle {
|
|
10170
|
+
return new GpuBoneAnimation(this.surface.device, packBoneAnimation(clip));
|
|
10171
|
+
}
|
|
10172
|
+
|
|
10173
|
+
/** See `Renderer.disposeBoneAnimation`. A batch still playing it draws nothing after. */
|
|
10174
|
+
disposeBoneAnimation(animation: BoneAnimationHandle): void {
|
|
10175
|
+
const clip = animation as GpuBoneAnimation;
|
|
10176
|
+
if (!clip.disposed) clip.dispose();
|
|
10177
|
+
}
|
|
10178
|
+
|
|
10179
|
+
/** An animated batch's clip and clocks, as its groups bind them. */
|
|
10180
|
+
private animationViews(batch: GpuInstancedBatch): AnimationViews | null {
|
|
10181
|
+
const animation = batch.animation;
|
|
10182
|
+
const clocks = batch.clocks;
|
|
10183
|
+
if (animation === null || clocks === null || animation.disposed) return null;
|
|
10184
|
+
return { places: animation.placesView, turns: animation.turnsView, clocks: clocks.view };
|
|
10185
|
+
}
|
|
10186
|
+
|
|
10187
|
+
/** An animated batch's groups, dropped wherever the renderer drops its own. */
|
|
10188
|
+
private freshAnimatedGroups(batch: GpuInstancedBatch): void {
|
|
10189
|
+
if (batch.groupEpoch === this.flatGroupEpoch) return;
|
|
10190
|
+
batch.litGroups.clear();
|
|
10191
|
+
batch.depthGroups.clear();
|
|
10192
|
+
batch.depthPeelGroups.clear();
|
|
10193
|
+
batch.groupEpoch = this.flatGroupEpoch;
|
|
10194
|
+
}
|
|
10195
|
+
|
|
10196
|
+
/**
|
|
10197
|
+
* The lit group an animated batch draws with: the material's group, or its overlay twin, again
|
|
10198
|
+
* with the clip's and the clocks' textures — kept on the batch by the group it was built beside,
|
|
10199
|
+
* as a skinned twin is kept by its palette slot.
|
|
10200
|
+
*/
|
|
10201
|
+
private animatedGroup(batch: GpuInstancedBatch, views: AnimationViews): GPUBindGroup {
|
|
10202
|
+
this.freshAnimatedGroups(batch);
|
|
10203
|
+
const base = this.overlayGroup();
|
|
10204
|
+
let group = batch.litGroups.get(base);
|
|
10205
|
+
if (group === undefined) {
|
|
10206
|
+
this.overlayBinding = base !== this.bindGroup;
|
|
10207
|
+
group = createFlatBindGroup(
|
|
10208
|
+
this.surface.device,
|
|
10209
|
+
this.animatedBindGroupLayout,
|
|
10210
|
+
this.variant,
|
|
10211
|
+
this.perDraw.buffer,
|
|
10212
|
+
this.passRing.buffer,
|
|
10213
|
+
this.materialStore.buffer,
|
|
10214
|
+
this.viewRing.buffer,
|
|
10215
|
+
this.flatTextures,
|
|
10216
|
+
null,
|
|
10217
|
+
null,
|
|
10218
|
+
null,
|
|
10219
|
+
views,
|
|
10220
|
+
);
|
|
10221
|
+
this.overlayBinding = false;
|
|
10222
|
+
this.bindGroupBudget.ask();
|
|
10223
|
+
batch.litGroups.set(base, group);
|
|
10224
|
+
}
|
|
10225
|
+
return group;
|
|
10226
|
+
}
|
|
10227
|
+
|
|
10228
|
+
/** An animated caster's shadow group, by what it cuts with and whether this is the peel. */
|
|
10229
|
+
private animatedDepthGroup(
|
|
10230
|
+
batch: GpuInstancedBatch,
|
|
10231
|
+
views: AnimationViews,
|
|
10232
|
+
cutout: GpuSurfaceTexture | null,
|
|
10233
|
+
): GPUBindGroup | null {
|
|
10234
|
+
this.freshAnimatedGroups(batch);
|
|
10235
|
+
const peel = this.shadowLayerIsPeel && this.shadowPeelPassUniforms !== null;
|
|
10236
|
+
const groups = peel ? batch.depthPeelGroups : batch.depthGroups;
|
|
10237
|
+
const held = groups.get(cutout);
|
|
10238
|
+
if (held !== undefined) return held;
|
|
10239
|
+
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
10240
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
10241
|
+
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
10242
|
+
if (passUniforms === null || previousView === null || previousSampler === null) return null;
|
|
10243
|
+
const group = createDepthBindGroup(
|
|
10244
|
+
this.surface.device,
|
|
10245
|
+
cutout === null ? this.shadowAnimatedLayout : this.shadowAnimatedCutoutLayout,
|
|
10246
|
+
this.shadowDraws.buffer,
|
|
10247
|
+
passUniforms,
|
|
10248
|
+
previousView,
|
|
10249
|
+
previousSampler,
|
|
10250
|
+
null,
|
|
10251
|
+
cutout === null ? null : { view: cutout.view, sampler: cutout.sampler },
|
|
10252
|
+
null,
|
|
10253
|
+
views,
|
|
10254
|
+
);
|
|
10255
|
+
groups.set(cutout, group);
|
|
10256
|
+
return group;
|
|
10257
|
+
}
|
|
10258
|
+
|
|
9690
10259
|
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
9691
10260
|
private instanceCulls: InstanceCullPass | null = null;
|
|
9692
10261
|
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
@@ -9720,7 +10289,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9720
10289
|
this.submitInstanced(batch, data, 1, false, {});
|
|
9721
10290
|
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
9722
10291
|
const previous = batch.previousBuffer;
|
|
9723
|
-
if (
|
|
10292
|
+
if (
|
|
10293
|
+
previous !== null &&
|
|
10294
|
+
this.staticRecording === null &&
|
|
10295
|
+
batch.changed === this.moverFrame &&
|
|
10296
|
+
this.tracksMotionNow()
|
|
10297
|
+
) {
|
|
9724
10298
|
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
9725
10299
|
if (paired > 0) this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
9726
10300
|
}
|
|
@@ -9767,6 +10341,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9767
10341
|
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
9768
10342
|
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
9769
10343
|
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
10344
|
+
/* w: this framebuffer's first row is the top, so up the screen is down the rows. */
|
|
10345
|
+
this.perFrameFloats[tintAt + 3] = -1;
|
|
9770
10346
|
const glassTintAt = this.materialField('uGlassTint');
|
|
9771
10347
|
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
9772
10348
|
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
@@ -9842,10 +10418,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9842
10418
|
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
9843
10419
|
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
9844
10420
|
instance and their order. See `InstancedOptions.cull`. */
|
|
9845
|
-
const culling = !blend && batch.cull;
|
|
10421
|
+
const culling = !blend && batch.cull && this.staticRecording === null;
|
|
9846
10422
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
10423
|
+
/* A batch playing a bone animation reads its clocks by instance index, which a compaction
|
|
10424
|
+
would reorder: it is culled whole and never instance by instance. */
|
|
10425
|
+
const animation = batch.animation === null ? null : this.animationViews(batch);
|
|
10426
|
+
if (batch.animation !== null && animation === null) return;
|
|
9847
10427
|
const cut =
|
|
9848
|
-
culling && cullsInstances(count, mesh.indexCount)
|
|
10428
|
+
culling && animation === null && cullsInstances(count, mesh.indexCount)
|
|
10429
|
+
? this.takeCullSlot(batch, count)
|
|
10430
|
+
: null;
|
|
9849
10431
|
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
9850
10432
|
|
|
9851
10433
|
const slot = this.perDraw.allocate();
|
|
@@ -9861,15 +10443,22 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9861
10443
|
|
|
9862
10444
|
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
9863
10445
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
9864
|
-
this.
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
lands ? this.correctedViewProj : this.viewProj,
|
|
9868
|
-
);
|
|
10446
|
+
const view = this.viewSlotFor(lands, lands ? this.correctedViewProj : this.viewProj);
|
|
10447
|
+
if (view === null) return;
|
|
10448
|
+
this.drawViewOffset = view;
|
|
9869
10449
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
9870
10450
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
9871
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
9872
10451
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
10452
|
+
/* The clip's speed and bone scale, read by the animated variant's own field. */
|
|
10453
|
+
const clip = batch.animation;
|
|
10454
|
+
if (animation !== null && clip !== null) {
|
|
10455
|
+
this.perDraw.writeFloat(
|
|
10456
|
+
slot,
|
|
10457
|
+
FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset,
|
|
10458
|
+
clip.framesPerSecond,
|
|
10459
|
+
);
|
|
10460
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset + 4, clip.boneScale);
|
|
10461
|
+
}
|
|
9873
10462
|
|
|
9874
10463
|
const dimmed = opacity < 1;
|
|
9875
10464
|
if (dimmed) {
|
|
@@ -9907,14 +10496,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9907
10496
|
plain one would hand a draw the wrong layout. */
|
|
9908
10497
|
const adds = blend && options.additive === true;
|
|
9909
10498
|
const key =
|
|
9910
|
-
`${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
10499
|
+
`${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
9911
10500
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
9912
10501
|
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
9913
10502
|
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
9914
10503
|
|
|
9915
10504
|
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
9916
|
-
let pipeline = pipelines.peek(key);
|
|
9917
|
-
if (pipeline ===
|
|
10505
|
+
let pipeline: GPURenderPipeline | null = pipelines.peek(key) ?? null;
|
|
10506
|
+
if (pipeline === null) {
|
|
9918
10507
|
const present = this.meshPresent.get(base);
|
|
9919
10508
|
if (present === undefined) {
|
|
9920
10509
|
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
@@ -9922,7 +10511,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9922
10511
|
pipeline = flatPipeline(
|
|
9923
10512
|
pipelines,
|
|
9924
10513
|
this.surface.device,
|
|
9925
|
-
this.flatLayoutFor(false, false),
|
|
10514
|
+
animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout,
|
|
9926
10515
|
this.variant,
|
|
9927
10516
|
key,
|
|
9928
10517
|
present,
|
|
@@ -9938,20 +10527,29 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9938
10527
|
false,
|
|
9939
10528
|
false,
|
|
9940
10529
|
modelKind,
|
|
10530
|
+
'whole',
|
|
10531
|
+
false,
|
|
10532
|
+
animation !== null,
|
|
9941
10533
|
);
|
|
9942
10534
|
}
|
|
9943
10535
|
|
|
9944
|
-
|
|
9945
|
-
|
|
10536
|
+
/* A batch whose pipeline is still compiling is left out, as a mesh is. */
|
|
10537
|
+
const material = pipeline === null ? null : this.materialSlotForDraw();
|
|
10538
|
+
if (pipeline !== null && material !== null) {
|
|
9946
10539
|
const command = blend
|
|
9947
10540
|
? this.recordBlended(0, lands)
|
|
9948
10541
|
: this.recordDraw(0, this.currentTarget());
|
|
9949
10542
|
if (command !== null) {
|
|
9950
10543
|
command.pipeline = pipeline;
|
|
9951
|
-
command.bindGroup =
|
|
10544
|
+
command.bindGroup =
|
|
10545
|
+
animation === null
|
|
10546
|
+
? this.flatBindGroupFor(false, false, null)
|
|
10547
|
+
: this.animatedGroup(batch, animation);
|
|
9952
10548
|
command.offsetA = slot;
|
|
9953
|
-
command.offsetB =
|
|
9954
|
-
command.
|
|
10549
|
+
command.offsetB = this.passOffset;
|
|
10550
|
+
command.offsetC = material;
|
|
10551
|
+
command.offsetD = this.drawViewOffset;
|
|
10552
|
+
command.offsetCount = 4;
|
|
9955
10553
|
let index = 0;
|
|
9956
10554
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
9957
10555
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
@@ -9959,6 +10557,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9959
10557
|
command.vertexBuffers[index] = instanceBuffer;
|
|
9960
10558
|
command.vertexCount = mesh.vertexBuffers.length + 1;
|
|
9961
10559
|
command.indexBuffer = mesh.indexBuffer;
|
|
10560
|
+
command.indexFormat = mesh.indexFormat;
|
|
9962
10561
|
command.indexed = true;
|
|
9963
10562
|
command.count = mesh.indexCount;
|
|
9964
10563
|
command.instances = count;
|
|
@@ -9967,13 +10566,19 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9967
10566
|
const pass = this.openPass();
|
|
9968
10567
|
if (pass === null) return;
|
|
9969
10568
|
pass.setPipeline(pipeline);
|
|
9970
|
-
pass.setBindGroup(
|
|
10569
|
+
pass.setBindGroup(
|
|
10570
|
+
0,
|
|
10571
|
+
animation === null
|
|
10572
|
+
? this.flatBindGroupFor(false, false, null)
|
|
10573
|
+
: this.animatedGroup(batch, animation),
|
|
10574
|
+
[slot, this.passOffset, material, this.drawViewOffset],
|
|
10575
|
+
);
|
|
9971
10576
|
let index = 0;
|
|
9972
10577
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
9973
10578
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
|
|
9974
10579
|
}
|
|
9975
10580
|
pass.setVertexBuffer(index, instanceBuffer);
|
|
9976
|
-
pass.setIndexBuffer(mesh.indexBuffer,
|
|
10581
|
+
pass.setIndexBuffer(mesh.indexBuffer, mesh.indexFormat);
|
|
9977
10582
|
if (cut === null) pass.drawIndexed(mesh.indexCount, count);
|
|
9978
10583
|
else pass.drawIndexedIndirect(cut.args, 0);
|
|
9979
10584
|
}
|
|
@@ -9995,6 +10600,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9995
10600
|
instanceBuffer,
|
|
9996
10601
|
count,
|
|
9997
10602
|
cut === null ? null : cut.args,
|
|
10603
|
+
animation === null ? null : batch,
|
|
9998
10604
|
);
|
|
9999
10605
|
}
|
|
10000
10606
|
}
|
|
@@ -10207,6 +10813,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10207
10813
|
if (view === null) return false;
|
|
10208
10814
|
|
|
10209
10815
|
this.lightViewProj.set(lightViewProj as Float32Array);
|
|
10816
|
+
/* The moving layer is read through the matrix it is drawn with: see `movingLightFor`. */
|
|
10817
|
+
if (dynamic) {
|
|
10818
|
+
this.movingLightViewProj.set(lightViewProj as Float32Array);
|
|
10819
|
+
this.movingLightKnown = true;
|
|
10820
|
+
}
|
|
10821
|
+
this.reopenViews();
|
|
10210
10822
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
10211
10823
|
this.shadowLayerIsPeel = peel;
|
|
10212
10824
|
this.shadowLayerIsDynamic = dynamic;
|
|
@@ -10319,6 +10931,267 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10319
10931
|
casters(this.sceneCasterSink);
|
|
10320
10932
|
}
|
|
10321
10933
|
|
|
10934
|
+
/* -- Static draws ---------------------------------------------------------------- */
|
|
10935
|
+
|
|
10936
|
+
/** See `Renderer.createStaticDraws`. Recorded on its first draw, into each view it is drawn in. */
|
|
10937
|
+
createStaticDraws(casters: ShadowCasters): StaticDrawsHandle {
|
|
10938
|
+
return new GpuStaticDraws(
|
|
10939
|
+
this.surface.device,
|
|
10940
|
+
captureStaticDraws(casters),
|
|
10941
|
+
this.fragment.passSize,
|
|
10942
|
+
FLAT_VIEW_SIZE,
|
|
10943
|
+
FLAT_VERT_SIZE,
|
|
10944
|
+
);
|
|
10945
|
+
}
|
|
10946
|
+
|
|
10947
|
+
/**
|
|
10948
|
+
* See `Renderer.drawStaticDraws`. One bundle executed, after a pass block and a view block are
|
|
10949
|
+
* written for this view; the list is recorded first where it has no recording that is current for
|
|
10950
|
+
* this target and starting state. See `staticBundles.ts`.
|
|
10951
|
+
*/
|
|
10952
|
+
drawStaticDraws(draws: StaticDrawsHandle): void {
|
|
10953
|
+
if (this.surface.lost) return;
|
|
10954
|
+
const list = draws as GpuStaticDraws;
|
|
10955
|
+
if (list.disposed) {
|
|
10956
|
+
this.warnStaticDisposed();
|
|
10957
|
+
return;
|
|
10958
|
+
}
|
|
10959
|
+
if (!this.canDraw() || this.viewProj === null) return;
|
|
10960
|
+
/* A list's draws are rigid and wear no overlay: put back to none, as `bindMeshPass` does. */
|
|
10961
|
+
this.setSkinPalette(null);
|
|
10962
|
+
this.setMorphWeights(null);
|
|
10963
|
+
this.setCloth(null);
|
|
10964
|
+
this.setSurfaceOverlay(null);
|
|
10965
|
+
this.setMaterial(null);
|
|
10966
|
+
this.sceneCasterMaterial = undefined;
|
|
10967
|
+
/* A probe face gives its slots back after every face; a list replays there draw by draw. */
|
|
10968
|
+
if (this.probePassActive) {
|
|
10969
|
+
this.replayStaticImmediately(list);
|
|
10970
|
+
return;
|
|
10971
|
+
}
|
|
10972
|
+
/* The copy first: taking one may grow the list's rings, which no recording before can bind. */
|
|
10973
|
+
const copy = list.takeCopy(this.moverFrame);
|
|
10974
|
+
const current = this.staticRecordingFor(list);
|
|
10975
|
+
if (current === null || current.immediate) {
|
|
10976
|
+
this.replayStaticImmediately(list);
|
|
10977
|
+
return;
|
|
10978
|
+
}
|
|
10979
|
+
current.usedIn = this.moverFrame;
|
|
10980
|
+
list.writePass(copy, this.passInts);
|
|
10981
|
+
this.writeView(list.views, list.viewOffset(copy), this.viewProj);
|
|
10982
|
+
list.uploadCopy(copy);
|
|
10983
|
+
|
|
10984
|
+
const cache = current.cache;
|
|
10985
|
+
let frame = current.frameBundles[copy];
|
|
10986
|
+
if (frame === undefined || frame === null) {
|
|
10987
|
+
frame = encodeBundle(
|
|
10988
|
+
this.surface.device,
|
|
10989
|
+
{
|
|
10990
|
+
colorFormats: [cache.format],
|
|
10991
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
10992
|
+
sampleCount: cache.sampleCount,
|
|
10993
|
+
depthReadOnly: false,
|
|
10994
|
+
},
|
|
10995
|
+
current.frame,
|
|
10996
|
+
list.passOffset(copy),
|
|
10997
|
+
list.viewOffset(copy),
|
|
10998
|
+
'static.frame',
|
|
10999
|
+
);
|
|
11000
|
+
current.frameBundles[copy] = frame;
|
|
11001
|
+
}
|
|
11002
|
+
const command = this.recordDraw(0, this.currentTarget());
|
|
11003
|
+
if (command !== null) {
|
|
11004
|
+
command.bundles = frame;
|
|
11005
|
+
command.pipeline = null;
|
|
11006
|
+
command.bindGroup = null;
|
|
11007
|
+
} else {
|
|
11008
|
+
this.openPass()?.executeBundles(frame);
|
|
11009
|
+
}
|
|
11010
|
+
const surface = this.reflectionSurface;
|
|
11011
|
+
if (current.surfaceKept && surface !== null && current.surface.taken > 0) {
|
|
11012
|
+
let halves = current.surfaceBundles[copy];
|
|
11013
|
+
if (halves === undefined || halves === null) {
|
|
11014
|
+
halves = encodeBundle(
|
|
11015
|
+
this.surface.device,
|
|
11016
|
+
{
|
|
11017
|
+
colorFormats: [null, REFLECTION_SURFACE_FORMAT, REFLECTION_SURFACE_FORMAT],
|
|
11018
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
11019
|
+
sampleCount: 1,
|
|
11020
|
+
depthReadOnly: true,
|
|
11021
|
+
},
|
|
11022
|
+
current.surface,
|
|
11023
|
+
list.passOffset(copy),
|
|
11024
|
+
list.viewOffset(copy),
|
|
11025
|
+
'static.surface',
|
|
11026
|
+
);
|
|
11027
|
+
current.surfaceBundles[copy] = halves;
|
|
11028
|
+
}
|
|
11029
|
+
const kept = surface.take();
|
|
11030
|
+
kept.bundles = halves;
|
|
11031
|
+
}
|
|
11032
|
+
/* Counted as the draws would have been, so a frame reports the same on both backends. */
|
|
11033
|
+
for (let draw = 0; draw < current.frame.taken; draw++) this.drawBudget.ask();
|
|
11034
|
+
if (!this.staticJustRecorded) {
|
|
11035
|
+
for (let ask = 0; ask < current.materialAsks; ask++) this.materialBudget.ask();
|
|
11036
|
+
}
|
|
11037
|
+
this.staticJustRecorded = false;
|
|
11038
|
+
}
|
|
11039
|
+
|
|
11040
|
+
/** A list drawn the way `drawSceneCasters` draws, a draw at a time, for where a bundle cannot be. */
|
|
11041
|
+
private replayStaticImmediately(list: GpuStaticDraws): void {
|
|
11042
|
+
this.staticJustRecorded = false;
|
|
11043
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
11044
|
+
this.setMaterial(null);
|
|
11045
|
+
this.sceneCasterMaterial = undefined;
|
|
11046
|
+
}
|
|
11047
|
+
|
|
11048
|
+
/** See `Renderer.disposeStaticDraws`. Its pins let go; its meshes and materials are the caller's. */
|
|
11049
|
+
disposeStaticDraws(draws: StaticDrawsHandle): void {
|
|
11050
|
+
const list = draws as GpuStaticDraws;
|
|
11051
|
+
if (list.disposed) return;
|
|
11052
|
+
list.dispose(this.unpinFor(list));
|
|
11053
|
+
}
|
|
11054
|
+
|
|
11055
|
+
/** The recording to replay `list` from here, made now if none is current; null if it cannot be. */
|
|
11056
|
+
private staticRecordingFor(list: GpuStaticDraws): StaticRecording | null {
|
|
11057
|
+
/* Geometry still arriving is not drawn yet, so a recording now would leave it out for good. */
|
|
11058
|
+
for (const entry of list.entries) {
|
|
11059
|
+
const mesh = entry.kind === 'mesh' ? entry.mesh : (entry.batch as GpuInstancedBatch).mesh;
|
|
11060
|
+
if (!mesh.complete) return null;
|
|
11061
|
+
}
|
|
11062
|
+
const cache = this.targetPipelines();
|
|
11063
|
+
const start = this.materialInts;
|
|
11064
|
+
const startHash = hashWords(start, start.length);
|
|
11065
|
+
const surfaceKept = this.reflectionSurfaceKeeps();
|
|
11066
|
+
let recording = list.find(cache, start, startHash, this.cutoutResolveStaged, surfaceKept);
|
|
11067
|
+
if (
|
|
11068
|
+
recording !== null &&
|
|
11069
|
+
recording.cacheVersion === cache.version &&
|
|
11070
|
+
recording.groupEpoch === this.flatGroupEpoch &&
|
|
11071
|
+
recording.storeEpoch === this.materialStore.epoch &&
|
|
11072
|
+
recording.ringEpoch === list.ringEpoch
|
|
11073
|
+
) {
|
|
11074
|
+
return recording;
|
|
11075
|
+
}
|
|
11076
|
+
const unpin = this.unpinFor(list);
|
|
11077
|
+
if (recording === null) {
|
|
11078
|
+
recording = list.open(cache, start, startHash, this.cutoutResolveStaged, surfaceKept, unpin);
|
|
11079
|
+
} else {
|
|
11080
|
+
list.clear(recording, recording.storeEpoch === this.materialStore.epoch ? unpin : () => {});
|
|
11081
|
+
}
|
|
11082
|
+
this.recordStatic(list, recording);
|
|
11083
|
+
return recording;
|
|
11084
|
+
}
|
|
11085
|
+
|
|
11086
|
+
/** How a list's pins are let go: through the store, unless it has forgotten them since. */
|
|
11087
|
+
private unpinFor(list: GpuStaticDraws): (offset: number) => void {
|
|
11088
|
+
const epoch = this.materialStore.epoch;
|
|
11089
|
+
return (offset) => {
|
|
11090
|
+
if (this.materialStore.epoch === epoch && list.pinEpoch === epoch) {
|
|
11091
|
+
this.materialStore.unpin(offset);
|
|
11092
|
+
}
|
|
11093
|
+
};
|
|
11094
|
+
}
|
|
11095
|
+
|
|
11096
|
+
/**
|
|
11097
|
+
* Record `list` into `recording`: its entries through the scene-caster path, with the list's
|
|
11098
|
+
* rings and group cache swapped in for the frame's and the pass and view slots held at the list's
|
|
11099
|
+
* first copy, so every command the path records binds the list's buffers and offsets the bundle
|
|
11100
|
+
* can rewrite per copy. Everything swapped is put back, whatever the path throws.
|
|
11101
|
+
*/
|
|
11102
|
+
private recordStatic(list: GpuStaticDraws, recording: StaticRecording): void {
|
|
11103
|
+
if (list.groupEpoch !== this.flatGroupEpoch) {
|
|
11104
|
+
list.groups = new Map();
|
|
11105
|
+
list.blank = null;
|
|
11106
|
+
list.groupEpoch = this.flatGroupEpoch;
|
|
11107
|
+
}
|
|
11108
|
+
list.drawRing.reset();
|
|
11109
|
+
const perDraw = this.perDraw;
|
|
11110
|
+
const passRing = this.passRing;
|
|
11111
|
+
const viewRing = this.viewRing;
|
|
11112
|
+
const groups = this.flatBindGroups;
|
|
11113
|
+
const blank = this.blankAlbedoBindGroup;
|
|
11114
|
+
const bindGroup = this.bindGroup;
|
|
11115
|
+
const passSlot = this.passBlock.slot;
|
|
11116
|
+
const viewSlot = this.viewBlock.slot;
|
|
11117
|
+
const lateSlot = this.lateViewBlock.slot;
|
|
11118
|
+
const asked = this.materialBudget.used;
|
|
11119
|
+
this.perDraw = list.drawRing;
|
|
11120
|
+
this.passRing = list.passes;
|
|
11121
|
+
this.viewRing = list.views;
|
|
11122
|
+
this.flatBindGroups = list.groups as typeof groups;
|
|
11123
|
+
/* The blank group over the list's rings, built as the frame's is: with no maps held. */
|
|
11124
|
+
list.blank ??= this.buildFlatBindGroup();
|
|
11125
|
+
this.blankAlbedoBindGroup = list.blank;
|
|
11126
|
+
this.bindGroup = this.flatGroupForMaps(null, null, null, null, null);
|
|
11127
|
+
this.passBlock.slot = list.passOffset(0);
|
|
11128
|
+
this.viewBlock.slot = list.viewOffset(0);
|
|
11129
|
+
this.lateViewBlock.slot = list.viewOffset(0);
|
|
11130
|
+
this.materials.dirty();
|
|
11131
|
+
this.staticRecording = recording;
|
|
11132
|
+
/* A recorded list keeps what it recorded, so every draw of it waits for its pipeline. */
|
|
11133
|
+
this.skipCompiling(false);
|
|
11134
|
+
try {
|
|
11135
|
+
this.sceneCasterMaterial = undefined;
|
|
11136
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
11137
|
+
} finally {
|
|
11138
|
+
this.skipCompiling(this.quality.pipelineCompile === 'skip');
|
|
11139
|
+
this.staticRecording = null;
|
|
11140
|
+
this.perDraw = perDraw;
|
|
11141
|
+
this.passRing = passRing;
|
|
11142
|
+
this.viewRing = viewRing;
|
|
11143
|
+
this.flatBindGroups = groups;
|
|
11144
|
+
this.blankAlbedoBindGroup = blank;
|
|
11145
|
+
this.bindGroup = bindGroup;
|
|
11146
|
+
this.passBlock.slot = passSlot;
|
|
11147
|
+
this.viewBlock.slot = viewSlot;
|
|
11148
|
+
this.lateViewBlock.slot = lateSlot;
|
|
11149
|
+
this.materials.dirty();
|
|
11150
|
+
this.sceneCasterMaterial = undefined;
|
|
11151
|
+
this.setMaterial(null);
|
|
11152
|
+
}
|
|
11153
|
+
recording.materialAsks = this.materialBudget.used - asked;
|
|
11154
|
+
const store = this.materialStore;
|
|
11155
|
+
recording.cacheVersion = recording.cache.version;
|
|
11156
|
+
recording.groupEpoch = this.flatGroupEpoch;
|
|
11157
|
+
recording.storeEpoch = store.epoch;
|
|
11158
|
+
recording.ringEpoch = list.ringEpoch;
|
|
11159
|
+
/* A skin split into halves is replayed draw by draw, so nothing recorded is kept or pinned. */
|
|
11160
|
+
if (recording.immediate) {
|
|
11161
|
+
resetPool(recording.frame);
|
|
11162
|
+
resetPool(recording.surface);
|
|
11163
|
+
return;
|
|
11164
|
+
}
|
|
11165
|
+
/* Every material the commands bind, pinned once, so nothing displaces what a bundle bakes. */
|
|
11166
|
+
for (const pool of [recording.frame, recording.surface]) {
|
|
11167
|
+
for (let i = 0; i < pool.taken; i++) {
|
|
11168
|
+
const offset = (pool.commands[i] as DrawCommand).offsetC;
|
|
11169
|
+
if (!recording.pins.includes(offset)) {
|
|
11170
|
+
store.pin(offset);
|
|
11171
|
+
recording.pins.push(offset);
|
|
11172
|
+
}
|
|
11173
|
+
}
|
|
11174
|
+
}
|
|
11175
|
+
list.pinEpoch = store.epoch;
|
|
11176
|
+
list.uploadDraws();
|
|
11177
|
+
this.staticJustRecorded = true;
|
|
11178
|
+
}
|
|
11179
|
+
|
|
11180
|
+
/** The recording under way, which `recordDraw` and the side passes take their commands into. */
|
|
11181
|
+
private staticRecording: StaticRecording | null = null;
|
|
11182
|
+
/** Whether the replay about to count its material changes recorded them, and so counted them. */
|
|
11183
|
+
private staticJustRecorded = false;
|
|
11184
|
+
|
|
11185
|
+
/** A disposed list drawn, said once: the frame loop does not throw. */
|
|
11186
|
+
private warnStaticDisposed(): void {
|
|
11187
|
+
if (this.warnedStaticDisposed) return;
|
|
11188
|
+
this.warnedStaticDisposed = true;
|
|
11189
|
+
console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
|
|
11190
|
+
}
|
|
11191
|
+
private warnedStaticDisposed = false;
|
|
11192
|
+
/** Bumped wherever the lit groups are dropped, which a static list's own groups follow. */
|
|
11193
|
+
private flatGroupEpoch = 0;
|
|
11194
|
+
|
|
10322
11195
|
drawShadowCasters(casters: ShadowCasters): void {
|
|
10323
11196
|
if (this.surface.lost || this.shadowPass === null) return;
|
|
10324
11197
|
casters(this.casterSink);
|
|
@@ -10569,7 +11442,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10569
11442
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
10570
11443
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
10571
11444
|
}
|
|
10572
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
11445
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
10573
11446
|
pass.drawIndexed(geometry.indexCount);
|
|
10574
11447
|
},
|
|
10575
11448
|
instanced: (batch, data, material, glass) => {
|
|
@@ -10614,7 +11487,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10614
11487
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
10615
11488
|
}
|
|
10616
11489
|
pass.setVertexBuffer(index, gpuBatch.buffer);
|
|
10617
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
11490
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
10618
11491
|
pass.drawIndexed(geometry.indexCount, count);
|
|
10619
11492
|
},
|
|
10620
11493
|
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
@@ -10650,7 +11523,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10650
11523
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
10651
11524
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
10652
11525
|
}
|
|
10653
|
-
pass.setIndexBuffer(geometry.indexBuffer,
|
|
11526
|
+
pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
|
|
10654
11527
|
pass.drawIndexed(geometry.indexCount);
|
|
10655
11528
|
},
|
|
10656
11529
|
};
|
|
@@ -10664,7 +11537,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10664
11537
|
}
|
|
10665
11538
|
this.shadowDraws.flush();
|
|
10666
11539
|
this.scatterDepthDraws.flush();
|
|
10667
|
-
this.
|
|
11540
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
10668
11541
|
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
10669
11542
|
this.retiredSunTexture?.destroy();
|
|
10670
11543
|
this.retiredSunTexture = null;
|
|
@@ -10954,7 +11827,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10954
11827
|
* its own slot.
|
|
10955
11828
|
*/
|
|
10956
11829
|
this.pointShadowArray?.flush();
|
|
10957
|
-
this.
|
|
11830
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
10958
11831
|
this.shadowEncoder = null;
|
|
10959
11832
|
this.shadowPass = null;
|
|
10960
11833
|
this.gpuTimer.end();
|
|
@@ -11161,7 +12034,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11161
12034
|
this.bindGroupLayout,
|
|
11162
12035
|
this.variant,
|
|
11163
12036
|
this.perDraw.buffer,
|
|
11164
|
-
this.
|
|
12037
|
+
this.passRing.buffer,
|
|
12038
|
+
this.materialStore.buffer,
|
|
12039
|
+
this.viewRing.buffer,
|
|
11165
12040
|
this.flatTextures,
|
|
11166
12041
|
);
|
|
11167
12042
|
}
|
|
@@ -11206,11 +12081,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11206
12081
|
/**
|
|
11207
12082
|
* The bind group a draw uses, skinned or not.
|
|
11208
12083
|
*
|
|
11209
|
-
* **The skinned twin is
|
|
11210
|
-
*
|
|
11211
|
-
*
|
|
11212
|
-
*
|
|
11213
|
-
* missed invalidation is not possible rather than merely unlikely, and it is why
|
|
12084
|
+
* **The skinned twin is kept by palette slot and by the unskinned group it was built beside**,
|
|
12085
|
+
* which is one a material, and compared against the views it holds — the palette's, which
|
|
12086
|
+
* changes only when the slot's texture grows, the morph deltas and the cloth's — rather than
|
|
12087
|
+
* against a flag somebody has to remember to clear at six call sites. Comparing object identity
|
|
12088
|
+
* means a missed invalidation is not possible rather than merely unlikely, and it is why
|
|
11214
12089
|
* `SkinPaletteTexture.view()` caches: a fresh view every call never compares equal and would
|
|
11215
12090
|
* rebuild this every draw.
|
|
11216
12091
|
*/
|
|
@@ -11227,35 +12102,45 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11227
12102
|
const at = skinned ? this.skinPaletteSlot + 1 : 0;
|
|
11228
12103
|
/* A skinned group carries the cloth's views, or the stand-in's: see `clothViews`. */
|
|
11229
12104
|
const cloth = skinned ? this.clothViews() : null;
|
|
12105
|
+
let byMaterial = this.skinnedGroups[at];
|
|
12106
|
+
if (byMaterial === undefined) {
|
|
12107
|
+
byMaterial = new Map();
|
|
12108
|
+
this.skinnedGroups[at] = byMaterial;
|
|
12109
|
+
}
|
|
12110
|
+
let twin = byMaterial.get(base);
|
|
11230
12111
|
if (
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
this.skinnedGroupsClothBinding[at] !== (cloth?.binding ?? null)
|
|
12112
|
+
twin === undefined ||
|
|
12113
|
+
twin.palette !== palette ||
|
|
12114
|
+
twin.deltas !== deltas ||
|
|
12115
|
+
twin.cloth !== (cloth?.particles ?? null) ||
|
|
12116
|
+
twin.clothBinding !== (cloth?.binding ?? null)
|
|
11237
12117
|
) {
|
|
11238
12118
|
/* Built from the live material, with the overlay's atlas where `base` is its twin. */
|
|
11239
12119
|
this.overlayBinding = base !== this.bindGroup;
|
|
11240
|
-
|
|
12120
|
+
const group = createFlatBindGroup(
|
|
11241
12121
|
this.surface.device,
|
|
11242
12122
|
this.flatLayoutFor(skinned, morphed),
|
|
11243
12123
|
this.variant,
|
|
11244
12124
|
this.perDraw.buffer,
|
|
11245
|
-
this.
|
|
12125
|
+
this.passRing.buffer,
|
|
12126
|
+
this.materialStore.buffer,
|
|
12127
|
+
this.viewRing.buffer,
|
|
11246
12128
|
this.flatTextures,
|
|
11247
12129
|
palette,
|
|
11248
12130
|
deltas,
|
|
11249
12131
|
cloth,
|
|
11250
12132
|
);
|
|
11251
12133
|
this.overlayBinding = false;
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
12134
|
+
twin = {
|
|
12135
|
+
group,
|
|
12136
|
+
palette,
|
|
12137
|
+
deltas,
|
|
12138
|
+
cloth: cloth?.particles ?? null,
|
|
12139
|
+
clothBinding: cloth?.binding ?? null,
|
|
12140
|
+
};
|
|
12141
|
+
byMaterial.set(base, twin);
|
|
11257
12142
|
}
|
|
11258
|
-
return
|
|
12143
|
+
return twin.group;
|
|
11259
12144
|
}
|
|
11260
12145
|
|
|
11261
12146
|
/** The layout matching a pair of vertex flags. A pipeline and its bind group must agree. */
|
|
@@ -11293,8 +12178,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11293
12178
|
if (this.drawBudget.dropped > 0) {
|
|
11294
12179
|
grew = this.perDraw.growTo(roomFor(this.drawBudget.used)) || grew;
|
|
11295
12180
|
}
|
|
11296
|
-
if (this.
|
|
11297
|
-
grew = this.
|
|
12181
|
+
if (this.materialsRefused > 0) {
|
|
12182
|
+
grew = this.materialStore.growTo(roomFor(this.materialBudget.used)) || grew;
|
|
12183
|
+
}
|
|
12184
|
+
if (this.passesRefused > 0) {
|
|
12185
|
+
grew = this.passRing.growTo(roomFor(this.passesAsked)) || grew;
|
|
12186
|
+
}
|
|
12187
|
+
if (this.viewsRefused > 0) {
|
|
12188
|
+
grew = this.viewRing.growTo(roomFor(this.viewsAsked)) || grew;
|
|
11298
12189
|
}
|
|
11299
12190
|
if (grew) this.rebuildFlatGroupsForNewRings();
|
|
11300
12191
|
}
|
|
@@ -11315,9 +12206,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11315
12206
|
*/
|
|
11316
12207
|
private rebuildFlatGroupsForNewRings(): void {
|
|
11317
12208
|
this.flatBindGroups.clear();
|
|
12209
|
+
this.flatGroupEpoch += 1;
|
|
11318
12210
|
this.overlayGroups.clear();
|
|
11319
|
-
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
11320
12211
|
this.skinnedGroups.length = 0;
|
|
12212
|
+
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
11321
12213
|
|
|
11322
12214
|
const albedo = this.albedo;
|
|
11323
12215
|
const normalMap = this.normalMap;
|
|
@@ -11345,6 +12237,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11345
12237
|
);
|
|
11346
12238
|
/* The open material lives in a slot of a buffer that no longer exists. */
|
|
11347
12239
|
this.materials.dirty();
|
|
12240
|
+
this.reopenPass();
|
|
11348
12241
|
}
|
|
11349
12242
|
|
|
11350
12243
|
/**
|
|
@@ -11379,7 +12272,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11379
12272
|
*/
|
|
11380
12273
|
private rebuildFlatBindGroup(): void {
|
|
11381
12274
|
this.flatBindGroups.clear();
|
|
12275
|
+
this.flatGroupEpoch += 1;
|
|
11382
12276
|
this.overlayGroups.clear();
|
|
12277
|
+
this.skinnedGroups.length = 0;
|
|
11383
12278
|
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
11384
12279
|
if (
|
|
11385
12280
|
this.albedo === null &&
|
|
@@ -13127,6 +14022,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13127
14022
|
/* Uncorrected, for the reason the shaft's own copy of this line carries: the shader does its
|
|
13128
14023
|
own `* 0.5 + 0.5`, and correcting the matrix as well doubles it. */
|
|
13129
14024
|
f.set(env?.lightViewProj ?? this.mediumIdentity, at('uLightViewProj'));
|
|
14025
|
+
f.set(
|
|
14026
|
+
(env === null ? this.mediumIdentity : this.movingLightFor(env)) as Float32Array,
|
|
14027
|
+
at('uMovingLightViewProj'),
|
|
14028
|
+
);
|
|
13130
14029
|
ints[at('uPeeledShadowEnabled')] = this.peelView === null || !this.peelFilled ? 0 : 1;
|
|
13131
14030
|
device.queue.writeBuffer(this.mediumUniforms, 0, this.mediumStaging);
|
|
13132
14031
|
|
|
@@ -13618,17 +14517,20 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13618
14517
|
instanceBuffer: GPUBuffer | null,
|
|
13619
14518
|
instances: number,
|
|
13620
14519
|
indirect: GPUBuffer | null,
|
|
14520
|
+
/** A batch playing a bone animation, whose surface half moves as its draw does. */
|
|
14521
|
+
animated: GpuInstancedBatch | null = null,
|
|
13621
14522
|
): void {
|
|
13622
14523
|
const surface = this.reflectionSurface;
|
|
13623
14524
|
const present = this.meshPresent.get(base);
|
|
13624
14525
|
if (surface === null || present === undefined) return;
|
|
14526
|
+
const animation = animated === null ? null : this.animationViews(animated);
|
|
13625
14527
|
const key = `${frameKey}|rs`;
|
|
13626
14528
|
const pipeline =
|
|
13627
14529
|
this.pipelines.peek(key) ??
|
|
13628
14530
|
flatPipeline(
|
|
13629
14531
|
this.pipelines,
|
|
13630
14532
|
this.surface.device,
|
|
13631
|
-
this.flatLayoutFor(skinned, morphed),
|
|
14533
|
+
animation === null ? this.flatLayoutFor(skinned, morphed) : this.animatedBindGroupLayout,
|
|
13632
14534
|
this.variant,
|
|
13633
14535
|
key,
|
|
13634
14536
|
present,
|
|
@@ -13646,13 +14548,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13646
14548
|
modelKind,
|
|
13647
14549
|
'whole',
|
|
13648
14550
|
true,
|
|
14551
|
+
animation !== null,
|
|
13649
14552
|
);
|
|
13650
|
-
|
|
14553
|
+
/* Still compiling where draws skip: the probe stands in for this surface until it lands. */
|
|
14554
|
+
if (pipeline === null) return;
|
|
14555
|
+
/* A static list's surface halves go into its own pool, beside its draws. */
|
|
14556
|
+
const command =
|
|
14557
|
+
this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
|
|
13651
14558
|
command.pipeline = pipeline;
|
|
13652
|
-
command.bindGroup =
|
|
14559
|
+
command.bindGroup =
|
|
14560
|
+
animated === null || animation === null
|
|
14561
|
+
? this.flatBindGroupFor(skinned, morphed, deltas)
|
|
14562
|
+
: this.animatedGroup(animated, animation);
|
|
13653
14563
|
command.offsetA = slot;
|
|
13654
|
-
command.offsetB =
|
|
13655
|
-
command.
|
|
14564
|
+
command.offsetB = this.passOffset;
|
|
14565
|
+
command.offsetC = material;
|
|
14566
|
+
command.offsetD = this.drawViewOffset;
|
|
14567
|
+
command.offsetCount = 4;
|
|
13656
14568
|
let index = 0;
|
|
13657
14569
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
13658
14570
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
@@ -13660,6 +14572,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13660
14572
|
if (instanceBuffer !== null) command.vertexBuffers[index++] = instanceBuffer;
|
|
13661
14573
|
command.vertexCount = index;
|
|
13662
14574
|
command.indexBuffer = mesh.indexBuffer;
|
|
14575
|
+
command.indexFormat = mesh.indexFormat;
|
|
13663
14576
|
command.indexed = true;
|
|
13664
14577
|
command.count = mesh.indexCount;
|
|
13665
14578
|
command.instances = instances;
|
|
@@ -13672,6 +14585,78 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13672
14585
|
* light alone and its colour alone into the blur's targets and test for the depth its frame half
|
|
13673
14586
|
* wrote.
|
|
13674
14587
|
*/
|
|
14588
|
+
/**
|
|
14589
|
+
* Whether a split skin draw's two kept halves have pipelines, starting the compile of either that
|
|
14590
|
+
* has not: a skin drawn without them would lose its diffuse light for the frames they take, so a
|
|
14591
|
+
* split skin draws all three halves or none. See `PipelineCache.lit`.
|
|
14592
|
+
*/
|
|
14593
|
+
private skinHalvesReady(
|
|
14594
|
+
frameKey: string,
|
|
14595
|
+
base: string,
|
|
14596
|
+
skinned: boolean,
|
|
14597
|
+
morphed: boolean,
|
|
14598
|
+
layer: number,
|
|
14599
|
+
eight: boolean,
|
|
14600
|
+
clothed: boolean,
|
|
14601
|
+
): boolean {
|
|
14602
|
+
let ready = true;
|
|
14603
|
+
for (const half of SKIN_KEPT_HALVES) {
|
|
14604
|
+
const pipeline = this.skinHalfPipeline(
|
|
14605
|
+
half,
|
|
14606
|
+
frameKey,
|
|
14607
|
+
base,
|
|
14608
|
+
skinned,
|
|
14609
|
+
morphed,
|
|
14610
|
+
layer,
|
|
14611
|
+
eight,
|
|
14612
|
+
clothed,
|
|
14613
|
+
);
|
|
14614
|
+
if (pipeline === null) ready = false;
|
|
14615
|
+
}
|
|
14616
|
+
return ready;
|
|
14617
|
+
}
|
|
14618
|
+
|
|
14619
|
+
/** One kept half's pipeline, or none where it is still compiling: see `PipelineCache.lit`. */
|
|
14620
|
+
private skinHalfPipeline(
|
|
14621
|
+
half: (typeof SKIN_KEPT_HALVES)[number],
|
|
14622
|
+
frameKey: string,
|
|
14623
|
+
base: string,
|
|
14624
|
+
skinned: boolean,
|
|
14625
|
+
morphed: boolean,
|
|
14626
|
+
layer: number,
|
|
14627
|
+
eight: boolean,
|
|
14628
|
+
clothed: boolean,
|
|
14629
|
+
): GPURenderPipeline | null {
|
|
14630
|
+
const pipelines = this.skinPipelines;
|
|
14631
|
+
const present = this.meshPresent.get(base);
|
|
14632
|
+
if (pipelines === null || present === undefined) return null;
|
|
14633
|
+
const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
|
|
14634
|
+
return (
|
|
14635
|
+
pipelines.peek(key) ??
|
|
14636
|
+
flatPipeline(
|
|
14637
|
+
pipelines,
|
|
14638
|
+
this.surface.device,
|
|
14639
|
+
this.flatLayoutFor(skinned, morphed),
|
|
14640
|
+
this.variant,
|
|
14641
|
+
key,
|
|
14642
|
+
present,
|
|
14643
|
+
false,
|
|
14644
|
+
skinned,
|
|
14645
|
+
morphed,
|
|
14646
|
+
true,
|
|
14647
|
+
layer,
|
|
14648
|
+
false,
|
|
14649
|
+
'none',
|
|
14650
|
+
this.materialDoubleSided,
|
|
14651
|
+
false,
|
|
14652
|
+
eight,
|
|
14653
|
+
clothed,
|
|
14654
|
+
'skin',
|
|
14655
|
+
half,
|
|
14656
|
+
)
|
|
14657
|
+
);
|
|
14658
|
+
}
|
|
14659
|
+
|
|
13675
14660
|
private keepSkinDiffuse(
|
|
13676
14661
|
mesh: GpuMesh,
|
|
13677
14662
|
frameKey: string,
|
|
@@ -13685,6 +14670,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13685
14670
|
slot: number,
|
|
13686
14671
|
material: number,
|
|
13687
14672
|
): void {
|
|
14673
|
+
/* A skin's halves are not bundled: a static list holding one replays draw by draw. */
|
|
14674
|
+
if (this.staticRecording !== null) {
|
|
14675
|
+
this.staticRecording.immediate = true;
|
|
14676
|
+
return;
|
|
14677
|
+
}
|
|
13688
14678
|
const skin = this.skinScatter;
|
|
13689
14679
|
const pipelines = this.skinPipelines;
|
|
13690
14680
|
const present = this.meshPresent.get(base);
|
|
@@ -13692,41 +14682,32 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13692
14682
|
const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
13693
14683
|
/* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
|
|
13694
14684
|
for (const half of SKIN_KEPT_HALVES) {
|
|
13695
|
-
const
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
morphed,
|
|
13708
|
-
true,
|
|
13709
|
-
layer,
|
|
13710
|
-
false,
|
|
13711
|
-
'none',
|
|
13712
|
-
this.materialDoubleSided,
|
|
13713
|
-
false,
|
|
13714
|
-
eight,
|
|
13715
|
-
clothed,
|
|
13716
|
-
'skin',
|
|
13717
|
-
half,
|
|
13718
|
-
);
|
|
14685
|
+
const pipeline = this.skinHalfPipeline(
|
|
14686
|
+
half,
|
|
14687
|
+
frameKey,
|
|
14688
|
+
base,
|
|
14689
|
+
skinned,
|
|
14690
|
+
morphed,
|
|
14691
|
+
layer,
|
|
14692
|
+
eight,
|
|
14693
|
+
clothed,
|
|
14694
|
+
);
|
|
14695
|
+
/* Both landed before the draw was let through: see `skinHalvesReady`. */
|
|
14696
|
+
if (pipeline === null) return;
|
|
13719
14697
|
const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
|
|
13720
14698
|
command.pipeline = pipeline;
|
|
13721
14699
|
command.bindGroup = bindGroup;
|
|
13722
14700
|
command.offsetA = slot;
|
|
13723
|
-
command.offsetB =
|
|
13724
|
-
command.
|
|
14701
|
+
command.offsetB = this.passOffset;
|
|
14702
|
+
command.offsetC = material;
|
|
14703
|
+
command.offsetD = this.drawViewOffset;
|
|
14704
|
+
command.offsetCount = 4;
|
|
13725
14705
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
13726
14706
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
13727
14707
|
}
|
|
13728
14708
|
command.vertexCount = mesh.vertexBuffers.length;
|
|
13729
14709
|
command.indexBuffer = mesh.indexBuffer;
|
|
14710
|
+
command.indexFormat = mesh.indexFormat;
|
|
13730
14711
|
command.indexed = true;
|
|
13731
14712
|
command.count = mesh.indexCount;
|
|
13732
14713
|
command.instances = 1;
|
|
@@ -14167,6 +15148,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14167
15148
|
* site reads as one `if` either way.
|
|
14168
15149
|
*/
|
|
14169
15150
|
private recordDraw(reads: number, writes: number): DrawCommand | null {
|
|
15151
|
+
/* A static list recording takes its draws into its own pool, never the frame graph's. */
|
|
15152
|
+
if (this.staticRecording !== null) return takeRecorded(this.staticRecording.frame);
|
|
14170
15153
|
/* The order-independent replay owns its pass and issues directly into it; a recorded command
|
|
14171
15154
|
would be replayed into the frame's passes instead, which is not where these belong. */
|
|
14172
15155
|
if (this.oitEncoder !== null) return null;
|
|
@@ -14178,6 +15161,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14178
15161
|
command.offsetCount = 0;
|
|
14179
15162
|
command.vertexCount = 0;
|
|
14180
15163
|
command.indexBuffer = null;
|
|
15164
|
+
command.indexFormat = 'uint32';
|
|
14181
15165
|
command.indexed = false;
|
|
14182
15166
|
command.instances = 1;
|
|
14183
15167
|
command.indirect = null;
|
|
@@ -14209,9 +15193,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14209
15193
|
|
|
14210
15194
|
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
14211
15195
|
private issueCommand(pass: GPURenderPassEncoder, command: DrawCommand): void {
|
|
15196
|
+
/* A static list, recorded once: one call, and the pass's bindings are its to set again. */
|
|
15197
|
+
if (command.bundles !== null) {
|
|
15198
|
+
pass.executeBundles(command.bundles);
|
|
15199
|
+
return;
|
|
15200
|
+
}
|
|
14212
15201
|
if (command.pipeline === null || command.bindGroup === null) return;
|
|
14213
15202
|
pass.setPipeline(command.pipeline);
|
|
14214
|
-
if (command.offsetCount ===
|
|
15203
|
+
if (command.offsetCount === 4) {
|
|
15204
|
+
pass.setBindGroup(0, command.bindGroup, [
|
|
15205
|
+
command.offsetA,
|
|
15206
|
+
command.offsetB,
|
|
15207
|
+
command.offsetC,
|
|
15208
|
+
command.offsetD,
|
|
15209
|
+
]);
|
|
15210
|
+
} else if (command.offsetCount === 3) {
|
|
15211
|
+
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB, command.offsetC]);
|
|
15212
|
+
} else if (command.offsetCount === 2) {
|
|
14215
15213
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
14216
15214
|
} else if (command.offsetCount === 1) {
|
|
14217
15215
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA]);
|
|
@@ -14223,7 +15221,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14223
15221
|
if (buffer != null) pass.setVertexBuffer(i, buffer);
|
|
14224
15222
|
}
|
|
14225
15223
|
if (command.indexed && command.indexBuffer !== null) {
|
|
14226
|
-
pass.setIndexBuffer(command.indexBuffer,
|
|
15224
|
+
pass.setIndexBuffer(command.indexBuffer, command.indexFormat);
|
|
14227
15225
|
if (command.indirect !== null) pass.drawIndexedIndirect(command.indirect, 0);
|
|
14228
15226
|
else pass.drawIndexed(command.count, command.instances);
|
|
14229
15227
|
} else {
|
|
@@ -14473,7 +15471,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14473
15471
|
this.flushRings();
|
|
14474
15472
|
this.pass?.end();
|
|
14475
15473
|
this.pass = null;
|
|
14476
|
-
if (!this.surface.lost) this.
|
|
15474
|
+
if (!this.surface.lost) this.submit([this.encoder.finish()]);
|
|
14477
15475
|
this.encoder = null;
|
|
14478
15476
|
}
|
|
14479
15477
|
|
|
@@ -14533,7 +15531,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14533
15531
|
* there would have nothing to ride on. See `GpuTimestamps.resolve`.
|
|
14534
15532
|
*/
|
|
14535
15533
|
this.gpuTimer.resolve(this.encoder);
|
|
14536
|
-
this.
|
|
15534
|
+
this.submit([this.encoder.finish()]);
|
|
14537
15535
|
this.encoder = null;
|
|
14538
15536
|
this.framePresented = true;
|
|
14539
15537
|
/* Latched rather than assigned: `framePresented` goes back to false every `beginFrame`. */
|
|
@@ -15275,7 +16273,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15275
16273
|
definition.dispatch(this.computeContext as ComputeContext);
|
|
15276
16274
|
pass.end();
|
|
15277
16275
|
this.flushRings();
|
|
15278
|
-
|
|
16276
|
+
this.submit([encoder.finish()]);
|
|
15279
16277
|
}
|
|
15280
16278
|
|
|
15281
16279
|
/** Let go of a definition. A handle kept past this dispatches nothing; see `ComputeHandle`. */
|
|
@@ -16434,7 +17432,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16434
17432
|
.end();
|
|
16435
17433
|
}
|
|
16436
17434
|
}
|
|
16437
|
-
|
|
17435
|
+
this.submit([clear.finish()]);
|
|
16438
17436
|
}
|
|
16439
17437
|
|
|
16440
17438
|
/**
|
|
@@ -16665,7 +17663,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16665
17663
|
const whole = endFace === PROBE_FACES.length;
|
|
16666
17664
|
if (whole) this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
16667
17665
|
this.flushRings();
|
|
16668
|
-
|
|
17666
|
+
this.submit([encoder.finish()]);
|
|
16669
17667
|
this.rewindRings(skinMark);
|
|
16670
17668
|
this.encoderEpoch = frameEpoch;
|
|
16671
17669
|
|
|
@@ -16675,6 +17673,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16675
17673
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
16676
17674
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
16677
17675
|
this.materials.dirty();
|
|
17676
|
+
this.reopenPass();
|
|
16678
17677
|
this.probePassActive = false;
|
|
16679
17678
|
this.probeBounce = false;
|
|
16680
17679
|
this.rebindEnvironment();
|
|
@@ -16732,7 +17731,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16732
17731
|
this.uploadProbeFaces(probe, image);
|
|
16733
17732
|
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
16734
17733
|
this.buildProbeChain(encoder, probe, 0);
|
|
16735
|
-
|
|
17734
|
+
this.submit([encoder.finish()]);
|
|
16736
17735
|
|
|
16737
17736
|
this.markProbeFilled(0);
|
|
16738
17737
|
this.probeAmbient = options?.irradiance ?? true;
|
|
@@ -16770,7 +17769,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16770
17769
|
const { device } = this.surface;
|
|
16771
17770
|
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
16772
17771
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
16773
|
-
|
|
17772
|
+
this.submit([encoder.finish()]);
|
|
16774
17773
|
this.markProbeFilled(layer);
|
|
16775
17774
|
this.probeAmbient = options?.irradiance ?? true;
|
|
16776
17775
|
this.rebuildFlatBindGroup();
|