@driftengine/core 4.8.2 → 4.8.4
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 +127 -0
- package/README.md +1 -1
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +22 -1
- package/dist/math/scalar.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +7 -0
- package/dist/render/anisotropicLobe.js +33 -0
- package/dist/render/backend/api.d.ts +41 -4
- package/dist/render/backend/materialChanges.d.ts +7 -2
- package/dist/render/backend/materialChanges.js +8 -3
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
- package/dist/render/backend/webgl2/clothTextures.js +93 -0
- package/dist/render/backend/webgl2/renderer.d.ts +159 -3
- package/dist/render/backend/webgl2/renderer.js +662 -80
- package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
- package/dist/render/backend/webgl2/skinScatter.js +256 -0
- package/dist/render/backend/webgpu/buffers.d.ts +25 -11
- package/dist/render/backend/webgpu/buffers.js +98 -49
- package/dist/render/backend/webgpu/causticsPass.js +2 -1
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
- package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
- package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
- package/dist/render/backend/webgpu/clothTextures.js +145 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
- package/dist/render/backend/webgpu/compressedUpload.js +38 -0
- package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
- package/dist/render/backend/webgpu/computeHost.js +5 -0
- package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
- package/dist/render/backend/webgpu/depthPass.js +43 -12
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/filmPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
- package/dist/render/backend/webgpu/flatPass.js +111 -13
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/linePass.js +2 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/panelPass.js +2 -1
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/plumePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +15 -3
- package/dist/render/backend/webgpu/postPass.js +25 -8
- package/dist/render/backend/webgpu/renderer.d.ts +123 -6
- package/dist/render/backend/webgpu/renderer.js +534 -58
- package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
- package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
- package/dist/render/backend/webgpu/textPass.js +2 -1
- package/dist/render/backend/webgpu/waterPass.js +2 -1
- package/dist/render/backend/webgpu/windStreakPass.js +2 -1
- package/dist/render/boltBatch.js +2 -1
- package/dist/render/causticsRenderer.js +2 -1
- package/dist/render/clothBindingData.d.ts +41 -0
- package/dist/render/clothBindingData.js +72 -0
- package/dist/render/clothPlacement.d.ts +25 -0
- package/dist/render/clothPlacement.js +85 -0
- package/dist/render/compressedSource.d.ts +66 -0
- package/dist/render/compressedSource.js +138 -0
- package/dist/render/cutoutCaster.d.ts +5 -1
- package/dist/render/cutoutCaster.js +6 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- package/dist/render/decalPass.js +2 -1
- package/dist/render/eyeRefraction.d.ts +35 -0
- package/dist/render/eyeRefraction.js +40 -0
- package/dist/render/glCompressed.d.ts +27 -0
- package/dist/render/glCompressed.js +53 -0
- package/dist/render/globalMediumPass.js +3 -2
- package/dist/render/hairLobes.d.ts +70 -0
- package/dist/render/hairLobes.js +120 -0
- package/dist/render/lightBudget.d.ts +22 -0
- package/dist/render/lightBudget.js +22 -0
- package/dist/render/lineBatch.js +5 -1
- package/dist/render/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/oitPass.js +2 -1
- package/dist/render/particleBatch.js +3 -2
- package/dist/render/particlePool.d.ts +6 -2
- package/dist/render/plumeRenderer.js +3 -2
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +29 -2
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneCoverage.d.ts +54 -0
- package/dist/render/sceneCoverage.js +72 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/sdfTextRenderer.js +2 -1
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +45 -8
- package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
- package/dist/render/shaders/flat/anisotropic.js +68 -0
- package/dist/render/shaders/flat/eye.d.ts +1 -0
- package/dist/render/shaders/flat/eye.js +81 -0
- package/dist/render/shaders/flat/hair.d.ts +1 -0
- package/dist/render/shaders/flat/hair.js +101 -0
- package/dist/render/shaders/flat/index.js +42 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +113 -17
- package/dist/render/shaders/flat/models.d.ts +14 -0
- package/dist/render/shaders/flat/models.js +273 -0
- package/dist/render/shaders/flat/preamble.js +26 -24
- package/dist/render/shaders/flat/skin.d.ts +1 -0
- package/dist/render/shaders/flat/skin.js +114 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
- package/dist/render/shaders/generated/depth.wgsl.js +66 -24
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
- package/dist/render/shaders/generated/particle.wgsl.js +3 -3
- package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/rush.wgsl.js +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
- package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +14 -1
- package/dist/render/shaders/outputTransform.js +23 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
- package/dist/render/shaders/rush.js +13 -2
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/skinBlur.d.ts +1 -0
- package/dist/render/shaders/skinBlur.js +103 -0
- package/dist/render/shaders/skinning.d.ts +3 -1
- package/dist/render/shaders/skinning.js +31 -4
- package/dist/render/shaders/temporalResolve.js +12 -5
- package/dist/render/shadowCasters.d.ts +13 -1
- package/dist/render/skinBlur.d.ts +41 -0
- package/dist/render/skinBlur.js +49 -0
- package/dist/render/skinScatter.d.ts +67 -0
- package/dist/render/skinScatter.js +80 -0
- package/dist/render/skinnedClothRun.d.ts +26 -0
- package/dist/render/skinnedClothRun.js +84 -0
- package/dist/render/ssrPass.js +2 -1
- package/dist/render/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +55 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/textRenderer.js +2 -1
- package/dist/render/vertexDefaults.js +8 -0
- package/dist/render/waterRenderer.js +2 -1
- package/dist/render/windStreakRenderer.js +2 -1
- package/package.json +6 -6
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +53 -1
- package/src/math/scalar.ts +1 -1
- package/src/render/anisotropicLobe.ts +41 -0
- package/src/render/backend/api.ts +52 -3
- package/src/render/backend/materialChanges.ts +8 -3
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +750 -80
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- package/src/render/backend/webgpu/causticsPass.ts +2 -1
- package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
- package/src/render/backend/webgpu/clothTextures.ts +194 -0
- package/src/render/backend/webgpu/compressedUpload.ts +65 -0
- package/src/render/backend/webgpu/computeHost.ts +25 -0
- package/src/render/backend/webgpu/depthPass.ts +43 -10
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/filmPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +147 -8
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/linePass.ts +2 -1
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/panelPass.ts +2 -1
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/plumePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +25 -6
- package/src/render/backend/webgpu/renderer.ts +701 -52
- package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
- package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
- package/src/render/backend/webgpu/textPass.ts +2 -1
- package/src/render/backend/webgpu/waterPass.ts +2 -1
- package/src/render/backend/webgpu/windStreakPass.ts +2 -1
- package/src/render/boltBatch.ts +2 -1
- package/src/render/causticsRenderer.ts +2 -1
- package/src/render/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +20 -2
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/decalPass.ts +2 -1
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/globalMediumPass.ts +3 -2
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/lineBatch.ts +3 -1
- package/src/render/mesh.ts +23 -0
- package/src/render/oitPass.ts +2 -1
- package/src/render/particleBatch.ts +3 -2
- package/src/render/particlePool.ts +6 -2
- package/src/render/plumeRenderer.ts +3 -2
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +37 -2
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneCoverage.ts +78 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/sdfTextRenderer.ts +2 -1
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +45 -8
- package/src/render/shaders/flat/anisotropic.ts +68 -0
- package/src/render/shaders/flat/eye.ts +82 -0
- package/src/render/shaders/flat/hair.ts +102 -0
- package/src/render/shaders/flat/index.ts +42 -3
- package/src/render/shaders/flat/main.ts +113 -17
- package/src/render/shaders/flat/models.ts +286 -0
- package/src/render/shaders/flat/preamble.ts +26 -24
- package/src/render/shaders/flat/skin.ts +116 -0
- package/src/render/shaders/generated/depth.wgsl.ts +66 -24
- package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +3 -3
- package/src/render/shaders/generated/rush.wgsl.ts +1 -1
- package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/outputTransform.ts +24 -1
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
- package/src/render/shaders/rush.ts +13 -2
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- package/src/render/shaders/temporalResolve.ts +12 -5
- package/src/render/shadowCasters.ts +13 -0
- package/src/render/skinBlur.ts +53 -0
- package/src/render/skinScatter.ts +92 -0
- package/src/render/skinnedClothRun.ts +128 -0
- package/src/render/ssrPass.ts +2 -1
- package/src/render/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +134 -63
- package/src/render/textRenderer.ts +2 -1
- package/src/render/vertexDefaults.ts +8 -0
- package/src/render/waterRenderer.ts +2 -1
- package/src/render/windStreakRenderer.ts +2 -1
- package/dist/math/exact.d.ts +0 -76
- package/dist/math/exact.js +0 -327
- package/src/math/exact.ts +0 -340
|
@@ -164,6 +164,15 @@ import {
|
|
|
164
164
|
import type { TextStyle } from '../../textLayout.ts';
|
|
165
165
|
import { deviceSnappedCellSize, deviceSnappedOrigin } from '../../textLayout.ts';
|
|
166
166
|
import { GpuSurfaceTexture } from './surfaceTexturePass.ts';
|
|
167
|
+
import { gpuCompressedFormats } from './compressedUpload.ts';
|
|
168
|
+
import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.ts';
|
|
169
|
+
import type { ClothViews } from './clothTextures.ts';
|
|
170
|
+
import type { ComputeHost } from './computeHost.ts';
|
|
171
|
+
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel } from '../../surfaceModel.ts';
|
|
172
|
+
import type { SurfaceModel } from '../../surfaceModel.ts';
|
|
173
|
+
import { validateClothBinding } from '../../clothBindingData.ts';
|
|
174
|
+
import type { ClothBindingData } from '../../clothBindingData.ts';
|
|
175
|
+
import type { CompressedTextureFormat, SurfaceSource } from '../../compressedSource.ts';
|
|
167
176
|
import {
|
|
168
177
|
createSdfTextBindGroup,
|
|
169
178
|
createSdfTextBindGroupLayout,
|
|
@@ -254,6 +263,7 @@ import {
|
|
|
254
263
|
DECAL_TEXTURES,
|
|
255
264
|
DECAL_UNIFORMS,
|
|
256
265
|
PREMULTIPLIED_OVER_BLEND,
|
|
266
|
+
REFLECTION_OVER_BLEND,
|
|
257
267
|
SSR_FRAG_FIELDS,
|
|
258
268
|
SSR_FRAG_SIZE,
|
|
259
269
|
SSR_RESOLVE_FRAG_WGSL,
|
|
@@ -382,6 +392,7 @@ import {
|
|
|
382
392
|
depthPipeline,
|
|
383
393
|
} from './depthPass.ts';
|
|
384
394
|
import { PipelineCache, type LitSwitch } from './pipelineCache.ts';
|
|
395
|
+
import { SKIN_TARGET_FORMAT, SkinScatterPass } from './skinScatterPass.ts';
|
|
385
396
|
import {
|
|
386
397
|
SKY_FIELDS,
|
|
387
398
|
SKY_UNIFORM_SIZE,
|
|
@@ -492,7 +503,7 @@ import {
|
|
|
492
503
|
import { aimReflection, createGpuReflection, type GpuReflection } from './reflectionPass.ts';
|
|
493
504
|
import { FrameBudget } from '../budget.ts';
|
|
494
505
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.ts';
|
|
495
|
-
import { FOG_RECEDE, fogModeOf } from '../drawFog.ts';
|
|
506
|
+
import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.ts';
|
|
496
507
|
import { DYNAMIC_ALIGNMENT as DYNAMIC_UNIFORM_ALIGNMENT, UniformRing } from './uniformRing.ts';
|
|
497
508
|
import { DecalQueue, MAX_DRAWN_DECALS } from '../../decalQueue.ts';
|
|
498
509
|
import { DistanceFieldScene } from '../../gi/fieldScene.ts';
|
|
@@ -582,6 +593,8 @@ import type { Arena, FlushSchedule, ScheduledPass } from '../../frame/index.ts';
|
|
|
582
593
|
import { GpuInstancedBatch } from './instanced.ts';
|
|
583
594
|
import type { InstancedOptions, MeshInstances } from '../../instances.ts';
|
|
584
595
|
import { cutoutOf } from '../../cutoutCaster.ts';
|
|
596
|
+
import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.ts';
|
|
597
|
+
import type { CutoutResolve } from '../../cutoutDither.ts';
|
|
585
598
|
import type { CutoutCaster } from '../../cutoutCaster.ts';
|
|
586
599
|
import { LightField } from '../../driftLight/lightField.ts';
|
|
587
600
|
import type { LightFieldOptions, LightFieldSource } from '../../driftLight/lightField.ts';
|
|
@@ -1456,6 +1469,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1456
1469
|
private readonly skinnedGroupsBeside: (GPUBindGroup | null)[] = [];
|
|
1457
1470
|
private readonly skinnedGroupsPalette: (GPUTextureView | null)[] = [];
|
|
1458
1471
|
private readonly skinnedGroupsDeltas: (GPUTextureView | null)[] = [];
|
|
1472
|
+
/** The cloth views each skinned twin was built with, compared by identity like the palette. */
|
|
1473
|
+
private readonly skinnedGroupsCloth: (GPUTextureView | null)[] = [];
|
|
1474
|
+
private readonly skinnedGroupsClothBinding: (GPUTextureView | null)[] = [];
|
|
1475
|
+
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
1476
|
+
private cloth: { binding: GpuClothBinding; particles: GpuClothParticles } | null = null;
|
|
1477
|
+
private clothStandCache: { texture: GPUTexture; views: ClothViews } | null = null;
|
|
1459
1478
|
/**
|
|
1460
1479
|
* Which palette slot the following draws skin by, or -1 for none.
|
|
1461
1480
|
*
|
|
@@ -1871,6 +1890,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1871
1890
|
private refractSnapshotView: GPUTextureView | null = null;
|
|
1872
1891
|
/** Whether this frame has taken it. One snapshot serves every refracting draw. */
|
|
1873
1892
|
private refractSnapshotTaken = false;
|
|
1893
|
+
/**
|
|
1894
|
+
* Skin's screen-space scattering, made where the profile asks for it and has a composite to add
|
|
1895
|
+
* into, and null otherwise — which is every profile by default. See `SkinScatterPass`.
|
|
1896
|
+
*/
|
|
1897
|
+
private readonly skinScatter: SkinScatterPass | null;
|
|
1898
|
+
/** The pipelines skin's diffuse half is built for: the blur target's format, the frame's samples. */
|
|
1899
|
+
private readonly skinPipelines: PipelineCache | null;
|
|
1900
|
+
/** Whether this frame has spread its skin: once, at its first blended draw or before its end. */
|
|
1901
|
+
private skinScattered = false;
|
|
1902
|
+
private readonly skinInverseProjection = mat4.create();
|
|
1874
1903
|
private sceneColorView: GPUTextureView | null = null;
|
|
1875
1904
|
/** The multisampled twin, when the profile asks for samples. Resolved into `sceneColor`. */
|
|
1876
1905
|
private sceneColorMsaa: GPUTexture | null = null;
|
|
@@ -2051,6 +2080,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2051
2080
|
private temporalJitterX = 0;
|
|
2052
2081
|
private temporalJitterY = 0;
|
|
2053
2082
|
private temporalJittering = false;
|
|
2083
|
+
/**
|
|
2084
|
+
* The frame a dithered cutout's pattern is offset by, advanced at `beginFrame` and wrapped at 64.
|
|
2085
|
+
* See `cutoutDither.ts`.
|
|
2086
|
+
*/
|
|
2087
|
+
private cutoutFrame = 0;
|
|
2088
|
+
/**
|
|
2089
|
+
* How the current material's cutout edge is drawn this frame, decided when the material is staged
|
|
2090
|
+
* — after `bindMeshPass` has settled whether the frame is temporal — and read by every draw: a
|
|
2091
|
+
* coverage edge is pipeline state here (`|a2c`), and a blended draw tests hard.
|
|
2092
|
+
*/
|
|
2093
|
+
private cutoutResolveStaged: CutoutResolve = 'hard';
|
|
2054
2094
|
/**
|
|
2055
2095
|
* Whether this frame is being reconstructed, settled once in `bindMeshPass`.
|
|
2056
2096
|
*
|
|
@@ -2768,6 +2808,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2768
2808
|
private ormMap: GpuSurfaceTexture | null = null;
|
|
2769
2809
|
/** The caller's emissive map, or null for the stand-in. See `setMaterial`. */
|
|
2770
2810
|
private emissiveMap: GpuSurfaceTexture | null = null;
|
|
2811
|
+
/** The material's model map, or null for the stand-in. See `setMaterial` and `surfaceModel.ts`. */
|
|
2812
|
+
private modelMap: GpuSurfaceTexture | null = null;
|
|
2813
|
+
/** The material's shading model, which picks the lit pipeline: null the standard. */
|
|
2814
|
+
private materialModel: SurfaceModel | null = null;
|
|
2815
|
+
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
2816
|
+
private readonly modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
2771
2817
|
/**
|
|
2772
2818
|
* The flat group for a material, keyed on **all four** of its maps.
|
|
2773
2819
|
*
|
|
@@ -2798,31 +2844,65 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2798
2844
|
GpuSurfaceTexture | null,
|
|
2799
2845
|
Map<
|
|
2800
2846
|
GpuSurfaceTexture | null,
|
|
2801
|
-
Map<
|
|
2847
|
+
Map<
|
|
2848
|
+
GpuSurfaceTexture | null,
|
|
2849
|
+
Map<GpuSurfaceTexture | null, Map<GpuSurfaceTexture | null, GPUBindGroup>>
|
|
2850
|
+
>
|
|
2802
2851
|
>
|
|
2803
2852
|
>();
|
|
2804
2853
|
/** The group holding the one-pixel stand-in, for every draw with no texture set. */
|
|
2805
2854
|
private blankAlbedoBindGroup: GPUBindGroup;
|
|
2806
2855
|
/** Repeats across the mesh's own UV range. Per draw, because `uUvScale` is a vertex uniform. */
|
|
2807
2856
|
private readonly uvScale = new Float32Array([1, 1]);
|
|
2857
|
+
/** Where the texture starts, after the scale. Per draw, beside `uvScale`. */
|
|
2858
|
+
private readonly uvOffset = new Float32Array([0, 0]);
|
|
2859
|
+
/**
|
|
2860
|
+
* How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
|
|
2861
|
+
* each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
|
|
2862
|
+
*/
|
|
2863
|
+
private surfaceFog = FOG_RECEDE;
|
|
2808
2864
|
/** Whether the bound material is seen from both faces. See `SurfaceMaterial.doubleSided`. */
|
|
2809
2865
|
private materialDoubleSided = false;
|
|
2810
2866
|
/** Whether the bound material has a map, for the warning a mesh with no coordinates earns. */
|
|
2811
2867
|
private materialHasMaps = false;
|
|
2812
2868
|
|
|
2813
2869
|
createSurfaceTexture(
|
|
2814
|
-
source:
|
|
2870
|
+
source: SurfaceSource,
|
|
2815
2871
|
options: SurfaceTextureOptions = {},
|
|
2816
2872
|
): GpuSurfaceTexture {
|
|
2817
|
-
return new GpuSurfaceTexture(
|
|
2873
|
+
return new GpuSurfaceTexture(
|
|
2874
|
+
this.surface.device,
|
|
2875
|
+
this.pipelines,
|
|
2876
|
+
source,
|
|
2877
|
+
options,
|
|
2878
|
+
this.compressedFormats,
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
private compressedCache: readonly CompressedTextureFormat[] | null = null;
|
|
2883
|
+
|
|
2884
|
+
/**
|
|
2885
|
+
* The BC formats this device takes as blocks: every one where the device carries
|
|
2886
|
+
* `texture-compression-bc`, which `select.ts` asks for wherever the adapter offers it, and none
|
|
2887
|
+
* where it does not. See `Renderer.compressedFormats`.
|
|
2888
|
+
*/
|
|
2889
|
+
get compressedFormats(): readonly CompressedTextureFormat[] {
|
|
2890
|
+
this.compressedCache ??= gpuCompressedFormats(this.surface.device);
|
|
2891
|
+
return this.compressedCache;
|
|
2818
2892
|
}
|
|
2819
2893
|
|
|
2820
2894
|
/** Several images of one size as an array's layers. See `RendererApi.createSurfaceTextureArray`. */
|
|
2821
2895
|
createSurfaceTextureArray(
|
|
2822
|
-
sources: readonly
|
|
2896
|
+
sources: readonly SurfaceSource[],
|
|
2823
2897
|
options: SurfaceTextureOptions = {},
|
|
2824
2898
|
): GpuSurfaceTexture {
|
|
2825
|
-
return new GpuSurfaceTexture(
|
|
2899
|
+
return new GpuSurfaceTexture(
|
|
2900
|
+
this.surface.device,
|
|
2901
|
+
this.pipelines,
|
|
2902
|
+
sources,
|
|
2903
|
+
options,
|
|
2904
|
+
this.compressedFormats,
|
|
2905
|
+
);
|
|
2826
2906
|
}
|
|
2827
2907
|
|
|
2828
2908
|
/**
|
|
@@ -2870,6 +2950,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2870
2950
|
if (this.normalMap === texture) this.normalMap = null;
|
|
2871
2951
|
if (this.ormMap === texture) this.ormMap = null;
|
|
2872
2952
|
if (this.emissiveMap === texture) this.emissiveMap = null;
|
|
2953
|
+
if (this.modelMap === texture) this.modelMap = null;
|
|
2873
2954
|
this.forgetBindingsOf(texture);
|
|
2874
2955
|
texture.dispose();
|
|
2875
2956
|
}
|
|
@@ -2895,7 +2976,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2895
2976
|
this.albedo = null;
|
|
2896
2977
|
this.blankAlbedoBindGroup = this.buildFlatBindGroup();
|
|
2897
2978
|
this.albedo = held;
|
|
2898
|
-
this.bindGroup = this.flatGroupForMaps(
|
|
2979
|
+
this.bindGroup = this.flatGroupForMaps(
|
|
2980
|
+
held,
|
|
2981
|
+
this.normalMap,
|
|
2982
|
+
this.ormMap,
|
|
2983
|
+
this.emissiveMap,
|
|
2984
|
+
this.modelMap,
|
|
2985
|
+
);
|
|
2899
2986
|
}
|
|
2900
2987
|
|
|
2901
2988
|
/**
|
|
@@ -2924,6 +3011,128 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2924
3011
|
this.skinPaletteSlot = this.skinPalettes.take(this.surface.device, palette) ?? -1;
|
|
2925
3012
|
}
|
|
2926
3013
|
|
|
3014
|
+
/**
|
|
3015
|
+
* A mesh's cloth binding. See `RendererApi.createClothBinding`. The pipelines that draw it bound
|
|
3016
|
+
* are warmed here, every way this mesh can be drawn, so none compiles inside the first frame
|
|
3017
|
+
* that draws a garment.
|
|
3018
|
+
*/
|
|
3019
|
+
createClothBinding(mesh: GpuMesh, data: ClothBindingData): GpuClothBinding {
|
|
3020
|
+
if (!mesh.isSkinned) {
|
|
3021
|
+
throw new Error(
|
|
3022
|
+
'createClothBinding: this mesh has no rig, and a binding blends each vertex between its ' +
|
|
3023
|
+
'skinning and the cloth',
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
validateClothBinding(data, mesh.vertexCount);
|
|
3027
|
+
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
3028
|
+
const present = this.meshPresent.get(base);
|
|
3029
|
+
if (present !== undefined) {
|
|
3030
|
+
const skin = mesh.isSkinnedEight ? '|skin8' : '|skin';
|
|
3031
|
+
for (const morphed of mesh.morph !== null ? [false, true] : [false]) {
|
|
3032
|
+
for (const translucent of [false, true]) {
|
|
3033
|
+
void flatPipelineAsync(
|
|
3034
|
+
this.targetPipelines(),
|
|
3035
|
+
this.surface.device,
|
|
3036
|
+
this.flatLayoutFor(true, morphed),
|
|
3037
|
+
this.variant,
|
|
3038
|
+
`${base}${morphed ? '|morph' : ''}${skin}|cloth${translucent ? '|blend' : ''}`,
|
|
3039
|
+
present,
|
|
3040
|
+
translucent,
|
|
3041
|
+
true,
|
|
3042
|
+
morphed,
|
|
3043
|
+
mesh.isSkinnedEight,
|
|
3044
|
+
true,
|
|
3045
|
+
);
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
return new GpuClothBinding(this.surface.device, data);
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
/** One character's particles. See `RendererApi.createClothParticles`. */
|
|
3053
|
+
createClothParticles(count: number): GpuClothParticles {
|
|
3054
|
+
return new GpuClothParticles(this.surface.device, count);
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
/**
|
|
3058
|
+
* This frame's particles. **Once a frame, before the character's draws**: a texture write lands
|
|
3059
|
+
* ahead of every draw of the frame, so a second update between two draws would reach both.
|
|
3060
|
+
*/
|
|
3061
|
+
updateClothParticles(particles: GpuClothParticles, positions: Float32Array): void {
|
|
3062
|
+
if (this.surface.lost) return;
|
|
3063
|
+
/* A write between frames is the next frame's, as a rewritten mesh's is. See `changeFrames.ts`. */
|
|
3064
|
+
particles.update(this.surface.device, positions, frameOfChange(this.moverFrame, this.inFrame));
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
/** Place the following skinned draws by a cloth, or by none. See `RendererApi.setCloth`. */
|
|
3068
|
+
setCloth(binding: GpuClothBinding | null, particles: GpuClothParticles | null = null): void {
|
|
3069
|
+
if (binding === null || particles === null) {
|
|
3070
|
+
this.cloth = null;
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
if (binding.particles !== particles.count) {
|
|
3074
|
+
throw new Error(
|
|
3075
|
+
`setCloth: the binding names ${binding.particles} particles and these are ${particles.count}`,
|
|
3076
|
+
);
|
|
3077
|
+
}
|
|
3078
|
+
this.cloth = { binding, particles };
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
/** Retired rather than destroyed: a draw recorded earlier in this frame may still read them. */
|
|
3082
|
+
disposeClothBinding(binding: GpuClothBinding): void {
|
|
3083
|
+
this.retiredTextures.push(...binding.textures);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
disposeClothParticles(particles: GpuClothParticles): void {
|
|
3087
|
+
this.retiredTextures.push(...particles.textures);
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
/**
|
|
3091
|
+
* The device and the frame's bookkeeping, for compute a consumer opts into by importing it —
|
|
3092
|
+
* `createSkinnedCloth` — rather than for anything this renderer runs itself. See `computeHost.ts`.
|
|
3093
|
+
*/
|
|
3094
|
+
computeHost(): ComputeHost {
|
|
3095
|
+
return this.computeHostCache;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
private readonly computeHostCache: ComputeHost = {
|
|
3099
|
+
device: () => this.surface.device,
|
|
3100
|
+
lost: () => this.surface.lost,
|
|
3101
|
+
changeFrame: () => frameOfChange(this.moverFrame, this.inFrame),
|
|
3102
|
+
retire: (textures) => {
|
|
3103
|
+
this.retiredTextures.push(...textures);
|
|
3104
|
+
},
|
|
3105
|
+
};
|
|
3106
|
+
|
|
3107
|
+
/**
|
|
3108
|
+
* An eye's axis in the world for this draw, into the open material's \`uModelParams\`, and a new
|
|
3109
|
+
* material slot when it moved: the axis is per draw, and a slot already taken by an earlier draw
|
|
3110
|
+
* of the same eye must not be written under it. See \`eyeAxisInWorld\`.
|
|
3111
|
+
*/
|
|
3112
|
+
private writeEyeAxis(model: ArrayLike<number>, skinned: boolean): void {
|
|
3113
|
+
const eye = this.materialModel;
|
|
3114
|
+
if (eye?.kind !== 'eye') return;
|
|
3115
|
+
const at = this.materialField('uModelParams') + 4;
|
|
3116
|
+
const f = this.perFrameFloats;
|
|
3117
|
+
const before0 = f[at];
|
|
3118
|
+
const before1 = f[at + 1];
|
|
3119
|
+
const before2 = f[at + 2];
|
|
3120
|
+
eyeAxisInWorld(eye, model, skinned ? this.skinPaletteData : null, f, at);
|
|
3121
|
+
if (f[at] !== before0 || f[at + 1] !== before1 || f[at + 2] !== before2) this.materials.dirty();
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
/** The views the draw's cloth binds, or the stand-in's where no cloth is set. */
|
|
3125
|
+
private clothViews(): ClothViews {
|
|
3126
|
+
const cloth = this.cloth;
|
|
3127
|
+
return cloth === null ? this.clothStand() : clothViewsOf(cloth.binding, cloth.particles);
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
/** The one-texel stand-in a skinned group binds where there is no cloth, made on first need. */
|
|
3131
|
+
private clothStand(): ClothViews {
|
|
3132
|
+
this.clothStandCache ??= clothStandIn(this.surface.device);
|
|
3133
|
+
return this.clothStandCache.views;
|
|
3134
|
+
}
|
|
3135
|
+
|
|
2927
3136
|
/**
|
|
2928
3137
|
* Choose the morph weights the following `drawMesh` calls deform by, or null for none.
|
|
2929
3138
|
*
|
|
@@ -2940,19 +3149,48 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2940
3149
|
const albedo = material?.albedo ?? null;
|
|
2941
3150
|
this.uvScale[0] = material?.uScale ?? 1;
|
|
2942
3151
|
this.uvScale[1] = material?.vScale ?? 1;
|
|
3152
|
+
this.uvOffset[0] = material?.uOffset ?? 0;
|
|
3153
|
+
this.uvOffset[1] = material?.vOffset ?? 0;
|
|
2943
3154
|
this.materialDoubleSided = material?.doubleSided === true;
|
|
2944
3155
|
this.materialHasMaps = materialHasMaps(material);
|
|
2945
3156
|
const normal = (material?.normal ?? null) as GpuSurfaceTexture | null;
|
|
2946
3157
|
const orm = (material?.orm ?? null) as GpuSurfaceTexture | null;
|
|
2947
3158
|
const emissiveMap = (material?.emissive ?? null) as GpuSurfaceTexture | null;
|
|
3159
|
+
const modelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
|
|
3160
|
+
this.materialModel = material?.model ?? null;
|
|
3161
|
+
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
3162
|
+
const model = material?.model;
|
|
3163
|
+
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
3164
|
+
this.skinScatter.setProfile(
|
|
3165
|
+
model.profile,
|
|
3166
|
+
model.scatterColor[0] * model.radius,
|
|
3167
|
+
model.scatterColor[1] * model.radius,
|
|
3168
|
+
model.scatterColor[2] * model.radius,
|
|
3169
|
+
);
|
|
3170
|
+
}
|
|
2948
3171
|
this.material((f, i) => {
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
i[
|
|
3172
|
+
/* The material's four switches, one row: albedo, ORM map, emissive map, two-sided. */
|
|
3173
|
+
const flags = this.materialField('uMaterialFlags');
|
|
3174
|
+
i[flags] = albedo === null ? 0 : 1;
|
|
3175
|
+
/* The cutoff, how this frame resolves a dithered edge, and the frame its pattern moves with. */
|
|
3176
|
+
const cutoff = albedo === null ? 0 : (material?.cutout ?? 0);
|
|
3177
|
+
this.cutoutResolveStaged =
|
|
3178
|
+
cutoff > 0
|
|
3179
|
+
? resolveCutout(
|
|
3180
|
+
material?.cutoutMode ?? 'hard',
|
|
3181
|
+
this.reconstructing || this.temporalJittering,
|
|
3182
|
+
this.samples,
|
|
3183
|
+
)
|
|
3184
|
+
: 'hard';
|
|
3185
|
+
const cut = this.materialField('uCutout');
|
|
3186
|
+
f[cut] = cutoff;
|
|
3187
|
+
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
3188
|
+
f[cut + 2] = this.cutoutFrame;
|
|
3189
|
+
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
2952
3190
|
/* 0 is no map, and gates the whole block in the shader. See `uNormalStrength`. */
|
|
2953
3191
|
f[this.materialField('uNormalStrength')] =
|
|
2954
3192
|
normal === null ? 0 : (material?.normalStrength ?? 1);
|
|
2955
|
-
i[
|
|
3193
|
+
i[flags + 1] = orm === null ? 0 : 1;
|
|
2956
3194
|
/*
|
|
2957
3195
|
* Component-aligned with the map — r occlusion, g roughness, b metallic — so this order is
|
|
2958
3196
|
* the channel order rather than the field order of `SurfaceMaterial`. Written component by
|
|
@@ -2963,7 +3201,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2963
3201
|
f[scale] = material?.occlusionStrength ?? 1;
|
|
2964
3202
|
f[scale + 1] = material?.roughnessScale ?? 1;
|
|
2965
3203
|
f[scale + 2] = material?.metallicScale ?? 1;
|
|
2966
|
-
i[
|
|
3204
|
+
i[flags + 2] = emissiveMap === null ? 0 : 1;
|
|
2967
3205
|
/*
|
|
2968
3206
|
* One rather than zero on every component, because this multiplies rather than adds: an
|
|
2969
3207
|
* unwritten float is zero, and zero here would switch off every glow the mesh's own vertex
|
|
@@ -2974,6 +3212,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2974
3212
|
f[emissive] = asked?.[0] ?? 1;
|
|
2975
3213
|
f[emissive + 1] = asked?.[1] ?? 1;
|
|
2976
3214
|
f[emissive + 2] = asked?.[2] ?? 1;
|
|
3215
|
+
/* The model's numbers and whether its map is bound. See `packModel`. */
|
|
3216
|
+
packModel(material?.model ?? null, modelMap !== null, this.modelParams);
|
|
3217
|
+
f.set(this.modelParams, this.materialField('uModelParams'));
|
|
2977
3218
|
});
|
|
2978
3219
|
/*
|
|
2979
3220
|
* **One lookup for the whole material, and nothing thrown away.**
|
|
@@ -2988,7 +3229,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2988
3229
|
this.albedo === albedo &&
|
|
2989
3230
|
this.normalMap === normal &&
|
|
2990
3231
|
this.ormMap === orm &&
|
|
2991
|
-
this.emissiveMap === emissiveMap
|
|
3232
|
+
this.emissiveMap === emissiveMap &&
|
|
3233
|
+
this.modelMap === modelMap
|
|
2992
3234
|
) {
|
|
2993
3235
|
return;
|
|
2994
3236
|
}
|
|
@@ -2996,7 +3238,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2996
3238
|
this.normalMap = normal;
|
|
2997
3239
|
this.ormMap = orm;
|
|
2998
3240
|
this.emissiveMap = emissiveMap;
|
|
2999
|
-
this.
|
|
3241
|
+
this.modelMap = modelMap;
|
|
3242
|
+
this.bindGroup = this.flatGroupForMaps(albedo, normal, orm, emissiveMap, modelMap);
|
|
3000
3243
|
}
|
|
3001
3244
|
|
|
3002
3245
|
/**
|
|
@@ -3025,8 +3268,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3025
3268
|
normal: GpuSurfaceTexture | null,
|
|
3026
3269
|
orm: GpuSurfaceTexture | null,
|
|
3027
3270
|
emissive: GpuSurfaceTexture | null,
|
|
3271
|
+
/** The model map, a fifth level of the same cache. See `flatBindGroups`. */
|
|
3272
|
+
modelMap: GpuSurfaceTexture | null,
|
|
3028
3273
|
): GPUBindGroup {
|
|
3029
|
-
if (
|
|
3274
|
+
if (
|
|
3275
|
+
albedo === null &&
|
|
3276
|
+
normal === null &&
|
|
3277
|
+
orm === null &&
|
|
3278
|
+
emissive === null &&
|
|
3279
|
+
modelMap === null
|
|
3280
|
+
) {
|
|
3030
3281
|
return this.blankAlbedoBindGroup;
|
|
3031
3282
|
}
|
|
3032
3283
|
let byNormal = this.flatBindGroups.get(albedo);
|
|
@@ -3044,11 +3295,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3044
3295
|
byEmissive = new Map();
|
|
3045
3296
|
byOrm.set(orm, byEmissive);
|
|
3046
3297
|
}
|
|
3047
|
-
|
|
3298
|
+
let byModel = byEmissive.get(emissive);
|
|
3299
|
+
if (byModel === undefined) {
|
|
3300
|
+
byModel = new Map();
|
|
3301
|
+
byEmissive.set(emissive, byModel);
|
|
3302
|
+
}
|
|
3303
|
+
const existing = byModel.get(modelMap);
|
|
3048
3304
|
if (existing !== undefined) return existing;
|
|
3049
3305
|
const built = this.buildFlatBindGroup();
|
|
3050
3306
|
this.bindGroupBudget.ask();
|
|
3051
|
-
|
|
3307
|
+
byModel.set(modelMap, built);
|
|
3052
3308
|
return built;
|
|
3053
3309
|
}
|
|
3054
3310
|
|
|
@@ -3104,6 +3360,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3104
3360
|
});
|
|
3105
3361
|
}
|
|
3106
3362
|
|
|
3363
|
+
/** Whether the surfaces drawn next meet the medium. See `renderer.ts`. */
|
|
3364
|
+
setSurfaceFog(enabled: boolean): void {
|
|
3365
|
+
this.surfaceFog = enabled ? FOG_RECEDE : FOG_OFF;
|
|
3366
|
+
this.material((_f, i) => {
|
|
3367
|
+
i[this.materialField('uFogEnabled')] = this.surfaceFog;
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3107
3371
|
/**
|
|
3108
3372
|
* How much of the surfaces drawn next survives a screen-door dither. See `renderer.ts`.
|
|
3109
3373
|
*
|
|
@@ -5269,6 +5533,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5269
5533
|
private readonly shadowSkinnedGroups: (GPUBindGroup | null)[] = [];
|
|
5270
5534
|
private readonly shadowSkinnedGroupsBeside: (GPUBindGroup | null)[] = [];
|
|
5271
5535
|
private readonly shadowSkinnedGroupsPalette: (GPUTextureView | null)[] = [];
|
|
5536
|
+
private readonly shadowSkinnedGroupsCloth: (GPUTextureView | null)[] = [];
|
|
5537
|
+
private readonly shadowSkinnedGroupsClothBinding: (GPUTextureView | null)[] = [];
|
|
5272
5538
|
/** Held from construction so the skinned group can be rebuilt without them being threaded. */
|
|
5273
5539
|
private shadowBlankView: GPUTextureView | null = null;
|
|
5274
5540
|
private shadowBlankSampler: GPUSampler | null = null;
|
|
@@ -5392,18 +5658,22 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5392
5658
|
return group;
|
|
5393
5659
|
}
|
|
5394
5660
|
|
|
5395
|
-
/** The cutoff and UV scale into a cutout draw's slot, at
|
|
5661
|
+
/** The cutoff and UV scale and offset into a cutout draw's slot, at its own variant's offsets. */
|
|
5396
5662
|
private writeCutout(
|
|
5397
5663
|
slot: number,
|
|
5398
5664
|
cutout: CutoutCaster<unknown>,
|
|
5399
5665
|
fields: {
|
|
5400
5666
|
readonly uUvScale: { readonly offset: number };
|
|
5667
|
+
readonly uUvOffset: { readonly offset: number };
|
|
5401
5668
|
readonly uAlphaCutout: { readonly offset: number };
|
|
5402
5669
|
},
|
|
5403
5670
|
): void {
|
|
5404
5671
|
this.shadowDraws.writeFloat(slot, fields.uUvScale.offset, cutout.u);
|
|
5405
5672
|
this.shadowDraws.writeFloat(slot, fields.uUvScale.offset + 4, cutout.v);
|
|
5673
|
+
this.shadowDraws.writeFloat(slot, fields.uUvOffset.offset, cutout.uOffset);
|
|
5674
|
+
this.shadowDraws.writeFloat(slot, fields.uUvOffset.offset + 4, cutout.vOffset);
|
|
5406
5675
|
this.shadowDraws.writeFloat(slot, fields.uAlphaCutout.offset, cutout.cutoff);
|
|
5676
|
+
this.shadowDraws.writeFloat(slot, fields.uAlphaCutout.offset + 4, cutout.dithered);
|
|
5407
5677
|
}
|
|
5408
5678
|
|
|
5409
5679
|
private shadowGroup(): GPUBindGroup {
|
|
@@ -5419,12 +5689,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5419
5689
|
* second independently fading occluder can be recorded, and a character is not one — it is the
|
|
5420
5690
|
* thing in front. Serving it would mean a second cached group for a case nothing produces.
|
|
5421
5691
|
*/
|
|
5422
|
-
private shadowSkinnedGroup(palette: GPUTextureView, at: number): GPUBindGroup {
|
|
5692
|
+
private shadowSkinnedGroup(palette: GPUTextureView, at: number, cloth: ClothViews): GPUBindGroup {
|
|
5423
5693
|
const beside = this.shadowBindGroup;
|
|
5424
5694
|
if (
|
|
5425
5695
|
(this.shadowSkinnedGroups[at] ?? null) === null ||
|
|
5426
5696
|
this.shadowSkinnedGroupsBeside[at] !== beside ||
|
|
5427
|
-
this.shadowSkinnedGroupsPalette[at] !== palette
|
|
5697
|
+
this.shadowSkinnedGroupsPalette[at] !== palette ||
|
|
5698
|
+
this.shadowSkinnedGroupsCloth[at] !== cloth.particles ||
|
|
5699
|
+
this.shadowSkinnedGroupsClothBinding[at] !== cloth.binding
|
|
5428
5700
|
) {
|
|
5429
5701
|
this.shadowSkinnedGroups[at] = createDepthBindGroup(
|
|
5430
5702
|
this.surface.device,
|
|
@@ -5434,9 +5706,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5434
5706
|
this.shadowBlankView as GPUTextureView,
|
|
5435
5707
|
this.shadowBlankSampler as GPUSampler,
|
|
5436
5708
|
palette,
|
|
5709
|
+
null,
|
|
5710
|
+
cloth,
|
|
5437
5711
|
);
|
|
5438
5712
|
this.shadowSkinnedGroupsBeside[at] = beside;
|
|
5439
5713
|
this.shadowSkinnedGroupsPalette[at] = palette;
|
|
5714
|
+
this.shadowSkinnedGroupsCloth[at] = cloth.particles;
|
|
5715
|
+
this.shadowSkinnedGroupsClothBinding[at] = cloth.binding;
|
|
5440
5716
|
}
|
|
5441
5717
|
return this.shadowSkinnedGroups[at] as GPUBindGroup;
|
|
5442
5718
|
}
|
|
@@ -5475,6 +5751,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5475
5751
|
void this.pipelines.enable(feature);
|
|
5476
5752
|
if (this.overlayPipelines !== this.pipelines) void this.overlayPipelines.enable(feature);
|
|
5477
5753
|
if (this.latePipelines !== this.pipelines) void this.latePipelines.enable(feature);
|
|
5754
|
+
if (this.skinPipelines !== null) void this.skinPipelines.enable(feature);
|
|
5478
5755
|
}
|
|
5479
5756
|
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
5480
5757
|
private readonly glassCasterScratch: ResolvedGlass = {
|
|
@@ -5596,7 +5873,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5596
5873
|
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
5597
5874
|
pass.drawIndexed(geometry.indexCount, count);
|
|
5598
5875
|
},
|
|
5599
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
5876
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
5600
5877
|
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
5601
5878
|
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
5602
5879
|
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
@@ -5628,18 +5905,30 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5628
5905
|
|
|
5629
5906
|
const geometry = mesh as GpuMesh & { key?: string };
|
|
5630
5907
|
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0) return;
|
|
5908
|
+
/* A garment casts placed by its cloth, as it draws; `|cloth` keys the pipeline that does. */
|
|
5909
|
+
const clothed = cloth !== undefined;
|
|
5910
|
+
const clothViews = clothed
|
|
5911
|
+
? clothViewsOf(
|
|
5912
|
+
cloth.binding as unknown as GpuClothBinding,
|
|
5913
|
+
cloth.particles as unknown as GpuClothParticles,
|
|
5914
|
+
)
|
|
5915
|
+
: this.clothStand();
|
|
5631
5916
|
pass.setPipeline(
|
|
5632
5917
|
depthPipeline(
|
|
5633
5918
|
this.pipelines,
|
|
5634
5919
|
this.surface.device,
|
|
5635
5920
|
this.shadowSkinnedLayout,
|
|
5636
|
-
|
|
5921
|
+
`${geometry.isSkinnedEight ? 'depth-skinned8' : 'depth-skinned'}${clothed ? '|cloth' : ''}|${this.depthCullMode}|${geometry.key ?? ''}`,
|
|
5637
5922
|
this.presentOf(geometry),
|
|
5638
5923
|
this.depthCullMode,
|
|
5639
5924
|
true,
|
|
5925
|
+
false,
|
|
5926
|
+
false,
|
|
5927
|
+
geometry.isSkinnedEight,
|
|
5928
|
+
clothed,
|
|
5640
5929
|
),
|
|
5641
5930
|
);
|
|
5642
|
-
pass.setBindGroup(0, this.shadowSkinnedGroup(view, paletteSlot), [slot]);
|
|
5931
|
+
pass.setBindGroup(0, this.shadowSkinnedGroup(view, paletteSlot, clothViews), [slot]);
|
|
5643
5932
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
5644
5933
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index] as GPUBuffer);
|
|
5645
5934
|
}
|
|
@@ -6015,6 +6304,26 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6015
6304
|
this.pipelines.format === RECON_HISTORY_FORMAT && this.samples === 1
|
|
6016
6305
|
? this.pipelines
|
|
6017
6306
|
: new PipelineCache(device, RECON_HISTORY_FORMAT, 1, glassShadows, quality.clusteredLights);
|
|
6307
|
+
/*
|
|
6308
|
+
* Screen-space skin needs the composite: linear light to add into, and the depth it resolves.
|
|
6309
|
+
* Without one it is the pre-integrated fit alone, which is the default anyway, and said once.
|
|
6310
|
+
*/
|
|
6311
|
+
const skinScreen = quality.skinScattering === 'screen-space' && quality.screenEffects;
|
|
6312
|
+
if (quality.skinScattering === 'screen-space' && !quality.screenEffects) {
|
|
6313
|
+
console.warn(
|
|
6314
|
+
"WebGPU: skinScattering 'screen-space' needs screenEffects to add into; skin is pre-integrated",
|
|
6315
|
+
);
|
|
6316
|
+
}
|
|
6317
|
+
this.skinScatter = skinScreen ? new SkinScatterPass(device) : null;
|
|
6318
|
+
this.skinPipelines = skinScreen
|
|
6319
|
+
? new PipelineCache(
|
|
6320
|
+
device,
|
|
6321
|
+
SKIN_TARGET_FORMAT,
|
|
6322
|
+
this.samples,
|
|
6323
|
+
glassShadows,
|
|
6324
|
+
quality.clusteredLights,
|
|
6325
|
+
)
|
|
6326
|
+
: null;
|
|
6018
6327
|
this.issueBound = this.issueCommand.bind(this);
|
|
6019
6328
|
this.pipelineTargets =
|
|
6020
6329
|
this.overlayPipelines === this.pipelines
|
|
@@ -6084,7 +6393,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6084
6393
|
);
|
|
6085
6394
|
|
|
6086
6395
|
/*
|
|
6087
|
-
* A one-pixel stand-in for the albedo binding.
|
|
6396
|
+
* A one-pixel stand-in for the albedo binding. The albedo's flag is zero for untextured
|
|
6088
6397
|
* geometry, which is nearly all of it, so the shader never samples this — but WebGPU
|
|
6089
6398
|
* requires every declared binding to be filled. WebGL2 keeps an `emptyTexture2D` for
|
|
6090
6399
|
* exactly the same reason.
|
|
@@ -6303,6 +6612,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6303
6612
|
? { view: blankSurfaceView, sampler: blankSampler }
|
|
6304
6613
|
: { view: map.view, sampler: map.sampler };
|
|
6305
6614
|
}
|
|
6615
|
+
/* The material's model map, or the stand-in, for the reason `uNormalMap` gives above. */
|
|
6616
|
+
if (name === 'uModelMap') {
|
|
6617
|
+
const map = this.modelMap;
|
|
6618
|
+
return map === null
|
|
6619
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
6620
|
+
: { view: map.view, sampler: map.sampler };
|
|
6621
|
+
}
|
|
6306
6622
|
if (name === 'uOrmMap') {
|
|
6307
6623
|
const map = this.ormMap;
|
|
6308
6624
|
return map === null
|
|
@@ -7210,6 +7526,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7210
7526
|
}
|
|
7211
7527
|
/* Not a layout entry of its own — it widens `uvs` — so read from the key beside them. */
|
|
7212
7528
|
present['layers'] = key.includes(':layers');
|
|
7529
|
+
/* Nor is a dynamic mesh's split, which moves positions and normals into buffers of their own. */
|
|
7530
|
+
present['dynamic'] = key.includes(':dynamic');
|
|
7213
7531
|
return present;
|
|
7214
7532
|
}
|
|
7215
7533
|
|
|
@@ -7223,6 +7541,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7223
7541
|
*/
|
|
7224
7542
|
beginFrame(clearColor: Vec3): void {
|
|
7225
7543
|
this.cullFrame += 1;
|
|
7544
|
+
this.cutoutFrame = (this.cutoutFrame + 1) % 64;
|
|
7226
7545
|
/* Whatever an update replaced during the last frame; nothing recorded can read it now. */
|
|
7227
7546
|
for (const texture of this.retiredTextures) texture.destroy();
|
|
7228
7547
|
this.retiredTextures.length = 0;
|
|
@@ -7286,6 +7605,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7286
7605
|
this.volumeDepthTaken = false;
|
|
7287
7606
|
/* Last frame's snapshot is last frame's scene. */
|
|
7288
7607
|
this.refractSnapshotTaken = false;
|
|
7608
|
+
this.skinScattered = false;
|
|
7609
|
+
this.skinScatter?.reset();
|
|
7289
7610
|
this.plumeVerts.reset();
|
|
7290
7611
|
this.plumeFrags.reset();
|
|
7291
7612
|
this.waterVerts.reset();
|
|
@@ -7468,7 +7789,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7468
7789
|
* neither the pipeline set nor the key depends on whether the geometry lands in one call or
|
|
7469
7790
|
* over six frames.
|
|
7470
7791
|
*/
|
|
7471
|
-
private warmFlatPipelines(data: MeshData): string {
|
|
7792
|
+
private warmFlatPipelines(data: MeshData, dynamic: boolean): string {
|
|
7472
7793
|
/*
|
|
7473
7794
|
* **Every optional attribute, not the two that come to mind.** `present` decides where
|
|
7474
7795
|
* each attribute sits in the interleaved buffer, and `createGpuMesh` decides the same
|
|
@@ -7494,6 +7815,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7494
7815
|
present['layers'] = true;
|
|
7495
7816
|
key += ':layers';
|
|
7496
7817
|
}
|
|
7818
|
+
/*
|
|
7819
|
+
* A dynamic mesh keeps its positions and normals in buffers of their own (`GpuMesh.update`), so
|
|
7820
|
+
* it binds four buffers where a static one binds two: a different layout, and a key that says
|
|
7821
|
+
* so, or the two would share a pipeline whose buffers one of them does not have.
|
|
7822
|
+
*/
|
|
7823
|
+
if (dynamic) {
|
|
7824
|
+
present['dynamic'] = true;
|
|
7825
|
+
key += ':dynamic';
|
|
7826
|
+
}
|
|
7497
7827
|
const fullKey = `${this.variant}|${key}`;
|
|
7498
7828
|
/*
|
|
7499
7829
|
* **For every target this mesh can land on, which is two when the overlay is its own.**
|
|
@@ -7564,11 +7894,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7564
7894
|
* guessing which the scene will use.
|
|
7565
7895
|
*/
|
|
7566
7896
|
const skinnable = data.joints !== undefined;
|
|
7897
|
+
/* An eight-influence rig takes its own skinned pipelines, keyed `|skin8`. See skinning.ts. */
|
|
7898
|
+
const eight = skinnable && data.joints2 !== undefined;
|
|
7567
7899
|
const morphable = data.morphTargets !== undefined;
|
|
7568
7900
|
for (const wantSkin of skinnable ? [false, true] : [false]) {
|
|
7569
7901
|
for (const wantMorph of morphable ? [false, true] : [false]) {
|
|
7570
7902
|
if (!wantSkin && !wantMorph) continue;
|
|
7571
|
-
const suffix = `${wantMorph ? '|morph' : ''}${wantSkin ? '|skin' : ''}`;
|
|
7903
|
+
const suffix = `${wantMorph ? '|morph' : ''}${wantSkin ? (eight ? '|skin8' : '|skin') : ''}`;
|
|
7572
7904
|
for (const translucent of [false, true]) {
|
|
7573
7905
|
void flatPipelineAsync(
|
|
7574
7906
|
pipelines,
|
|
@@ -7580,6 +7912,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7580
7912
|
translucent,
|
|
7581
7913
|
wantSkin,
|
|
7582
7914
|
wantMorph,
|
|
7915
|
+
wantSkin && eight,
|
|
7583
7916
|
);
|
|
7584
7917
|
}
|
|
7585
7918
|
}
|
|
@@ -7603,7 +7936,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7603
7936
|
}
|
|
7604
7937
|
|
|
7605
7938
|
createMesh(data: MeshData, options: MeshOptions = {}): GpuMesh {
|
|
7606
|
-
const fullKey = this.warmFlatPipelines(data);
|
|
7939
|
+
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
7607
7940
|
return this.keyed(createGpuMesh(this.surface.device, data, options.dynamic === true), fullKey);
|
|
7608
7941
|
}
|
|
7609
7942
|
|
|
@@ -7617,7 +7950,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7617
7950
|
* can do nothing about. Done with `complete` still false is the signal that it was abandoned.
|
|
7618
7951
|
*/
|
|
7619
7952
|
createMeshIncremental(data: MeshData, options: MeshOptions = {}): IncrementalMeshHandle {
|
|
7620
|
-
const fullKey = this.warmFlatPipelines(data);
|
|
7953
|
+
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
7621
7954
|
const { mesh, upload } = createGpuMeshIncremental(
|
|
7622
7955
|
this.surface.device,
|
|
7623
7956
|
data,
|
|
@@ -7638,20 +7971,20 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7638
7971
|
/**
|
|
7639
7972
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
7640
7973
|
*
|
|
7641
|
-
* Refused in words on a mesh that did not declare itself dynamic, because
|
|
7642
|
-
*
|
|
7643
|
-
*
|
|
7974
|
+
* Refused in words on a mesh that did not declare itself dynamic, because what makes an update
|
|
7975
|
+
* cheap on this backend — positions and normals in buffers of their own — is a layout chosen at
|
|
7976
|
+
* creation. See `GpuMesh.update`.
|
|
7644
7977
|
*/
|
|
7645
7978
|
updateMesh(mesh: GpuMesh, positions: Float32Array, normals?: Float32Array): void {
|
|
7646
7979
|
if (mesh.update === null) {
|
|
7647
7980
|
throw new Error(
|
|
7648
|
-
'this mesh was not created with { dynamic: true }, so
|
|
7649
|
-
'
|
|
7650
|
-
'
|
|
7981
|
+
'this mesh was not created with { dynamic: true }, so its positions share one buffer with ' +
|
|
7982
|
+
'every other attribute and cannot be rewritten alone. Say so at `createMesh` — the flag ' +
|
|
7983
|
+
'decides the layout, and what a buffer is hinted as.',
|
|
7651
7984
|
);
|
|
7652
7985
|
}
|
|
7653
7986
|
/*
|
|
7654
|
-
* **The
|
|
7987
|
+
* **The positions before this rewrite are last frame's**, kept only for a reconstruction and only
|
|
7655
7988
|
* on the first rewrite of a frame — a second must not replace where the vertices were last
|
|
7656
7989
|
* frame with where they were a moment ago. A rewrite between frames is the next frame's. See
|
|
7657
7990
|
* `changeFrames.ts`.
|
|
@@ -7783,8 +8116,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7783
8116
|
* these per draw through the material block, the same shape `uOpacity` already uses.
|
|
7784
8117
|
*/
|
|
7785
8118
|
i[at('uLightingEnabled')] = 1;
|
|
7786
|
-
i[at('uFogEnabled')] =
|
|
7787
|
-
|
|
8119
|
+
i[at('uFogEnabled')] = FOG_RECEDE;
|
|
8120
|
+
this.surfaceFog = FOG_RECEDE;
|
|
8121
|
+
/* Every material switch off — albedo, ORM map, emissive map, two-sided — as WebGL2 resets them. */
|
|
8122
|
+
i.fill(0, at('uMaterialFlags'), at('uMaterialFlags') + 4);
|
|
8123
|
+
f[at('uCutout')] = 0;
|
|
8124
|
+
f[at('uCutout') + 1] = 0;
|
|
8125
|
+
this.cutoutResolveStaged = 'hard';
|
|
7788
8126
|
this.materialHasMaps = false;
|
|
7789
8127
|
|
|
7790
8128
|
/*
|
|
@@ -7930,7 +8268,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7930
8268
|
/*
|
|
7931
8269
|
* The ORM scales, whose base is 1 and not 0 — the value an unwritten uniform would hold.
|
|
7932
8270
|
*
|
|
7933
|
-
*
|
|
8271
|
+
* The ORM map's flag is zeroed with the others above, and zero is exactly "no map".
|
|
7934
8272
|
* These three are the opposite case, the one this block's own header is about: a scale of
|
|
7935
8273
|
* zero is a real and wrong value, so leaving them silent would not switch the map off, it
|
|
7936
8274
|
* would read every channel as its floor the moment one was bound.
|
|
@@ -8235,6 +8573,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8235
8573
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
8236
8574
|
this.uvScale[0] = 1;
|
|
8237
8575
|
this.uvScale[1] = 1;
|
|
8576
|
+
this.uvOffset[0] = 0;
|
|
8577
|
+
this.uvOffset[1] = 0;
|
|
8238
8578
|
}
|
|
8239
8579
|
|
|
8240
8580
|
/**
|
|
@@ -8319,7 +8659,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8319
8659
|
*/
|
|
8320
8660
|
if (this.tracksMotionNow()) this.recordDrawMotion(mesh, model, previousModel);
|
|
8321
8661
|
noteMapsWithoutUvs(mesh, mesh.hasUvs, this.materialHasMaps);
|
|
8322
|
-
|
|
8662
|
+
/* An opaque draw meets the medium as the dial says: `submitMesh` reads an absent `fog` as
|
|
8663
|
+
true, which is a translucent draw's default and would put every opaque draw back in the fog
|
|
8664
|
+
`setSurfaceFog(false)` took it out of. */
|
|
8665
|
+
this.submitMesh(mesh, model, tint, 1, false, { depthLayer, fog: this.surfaceFog !== FOG_OFF });
|
|
8323
8666
|
}
|
|
8324
8667
|
|
|
8325
8668
|
/**
|
|
@@ -8501,6 +8844,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8501
8844
|
/* The material's repeats, not a constant: geometry authored in metres is textured at
|
|
8502
8845
|
whatever density the material asks for without rebuilding it. */
|
|
8503
8846
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
8847
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
8504
8848
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
8505
8849
|
/*
|
|
8506
8850
|
* Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
|
|
@@ -8534,7 +8878,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8534
8878
|
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
8535
8879
|
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
8536
8880
|
const toneMapped = options.toneMapped ?? true;
|
|
8537
|
-
const unlitOrUnfogged = !lit || fog !==
|
|
8881
|
+
const unlitOrUnfogged = !lit || fog !== this.surfaceFog;
|
|
8538
8882
|
if (unlitOrUnfogged) {
|
|
8539
8883
|
if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
8540
8884
|
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
@@ -8554,10 +8898,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8554
8898
|
* before last. That is the defined state the two-backends rule asks for.
|
|
8555
8899
|
*/
|
|
8556
8900
|
const refracting = this.bindSeeThrough(options);
|
|
8901
|
+
/* A dithered cutout tests hard on a blended draw, which has nothing to average a pattern with;
|
|
8902
|
+
put back below with the rest. See `cutoutDither.ts`. */
|
|
8903
|
+
const ditheredBlend = blend && this.cutoutResolveStaged !== 'hard';
|
|
8904
|
+
if (ditheredBlend) this.perFrameFloats[this.materialField('uCutout') + 1] = 0;
|
|
8557
8905
|
/* Taken for itself when any of those differs from the pass, and put back below: the rule both
|
|
8558
8906
|
backends count material changes by. See `materialChanges.ts`. */
|
|
8559
|
-
const own =
|
|
8907
|
+
const own =
|
|
8908
|
+
ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog) || ditheredBlend;
|
|
8560
8909
|
if (own) this.materials.dirty();
|
|
8910
|
+
/* An eye's axis is the draw's, written into the open material. See `eyeAxisInWorld`. */
|
|
8911
|
+
const modelKind = this.materialModel?.kind ?? null;
|
|
8912
|
+
if (modelKind === 'eye') this.writeEyeAxis(model, this.skinPaletteSlot >= 0 && mesh.isSkinned);
|
|
8561
8913
|
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
8562
8914
|
/*
|
|
8563
8915
|
* A rigged mesh takes the skinned vertex variant, an unrigged one the plain variant, and the
|
|
@@ -8569,7 +8921,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8569
8921
|
const skinned = this.skinPaletteSlot >= 0 && mesh.isSkinned;
|
|
8570
8922
|
const morphed = this.morphWeights !== null && mesh.morph !== null;
|
|
8571
8923
|
const deltas = morphed && mesh.morph !== null ? mesh.morph.view() : null;
|
|
8572
|
-
const
|
|
8924
|
+
const eight = skinned && mesh.isSkinnedEight;
|
|
8925
|
+
/* A cloth set and a skinned draw: the pipeline built with CLOTH_BOUND on, warmed when the
|
|
8926
|
+
binding was made. See `createClothBinding`. */
|
|
8927
|
+
const clothed = skinned && this.cloth !== null;
|
|
8928
|
+
const keyed =
|
|
8929
|
+
`${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
|
|
8930
|
+
`${clothed ? '|cloth' : ''}`;
|
|
8573
8931
|
/*
|
|
8574
8932
|
* Depth writing and the overlay layer are pipeline state on this backend, so they belong in
|
|
8575
8933
|
* the key: two draws of one mesh differing only in these would otherwise share a cached
|
|
@@ -8584,6 +8942,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8584
8942
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
8585
8943
|
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
8586
8944
|
const adds = blend && options.additive === true;
|
|
8945
|
+
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
8946
|
+
/* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
|
|
8947
|
+
const splits = modelKind === 'skin' && !blend && this.skinSplits();
|
|
8587
8948
|
const key =
|
|
8588
8949
|
`${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8589
8950
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
@@ -8591,7 +8952,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8591
8952
|
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
8592
8953
|
`${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
|
|
8593
8954
|
/* Culling is pipeline state here: a two-sided surface culls nothing. */
|
|
8594
|
-
`${this.materialDoubleSided ? '|2s' : ''}
|
|
8955
|
+
`${this.materialDoubleSided ? '|2s' : ''}` +
|
|
8956
|
+
/* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
|
|
8957
|
+
`${coverage ? '|a2c' : ''}` +
|
|
8958
|
+
/* And the material's shading model, which is the fragment stage's own switch. */
|
|
8959
|
+
`${modelKind === null ? '' : `|m:${modelKind}`}` +
|
|
8960
|
+
/* And which half of a skin it draws, under the screen-space blur. */
|
|
8961
|
+
`${splits ? '|ss' : ''}`;
|
|
8595
8962
|
|
|
8596
8963
|
/*
|
|
8597
8964
|
* The morph uniforms, written only for a morphed draw and read from the *morphed* variant's
|
|
@@ -8637,6 +9004,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8637
9004
|
false,
|
|
8638
9005
|
this.oitMode,
|
|
8639
9006
|
this.materialDoubleSided,
|
|
9007
|
+
coverage,
|
|
9008
|
+
eight,
|
|
9009
|
+
clothed,
|
|
9010
|
+
modelKind,
|
|
9011
|
+
splits ? 'scene' : 'whole',
|
|
8640
9012
|
);
|
|
8641
9013
|
}
|
|
8642
9014
|
|
|
@@ -8690,6 +9062,21 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8690
9062
|
pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
|
|
8691
9063
|
pass.drawIndexed(mesh.indexCount);
|
|
8692
9064
|
}
|
|
9065
|
+
if (splits) {
|
|
9066
|
+
this.keepSkinDiffuse(
|
|
9067
|
+
mesh,
|
|
9068
|
+
key,
|
|
9069
|
+
base,
|
|
9070
|
+
skinned,
|
|
9071
|
+
morphed,
|
|
9072
|
+
deltas,
|
|
9073
|
+
layer,
|
|
9074
|
+
eight,
|
|
9075
|
+
clothed,
|
|
9076
|
+
slot,
|
|
9077
|
+
material,
|
|
9078
|
+
);
|
|
9079
|
+
}
|
|
8693
9080
|
}
|
|
8694
9081
|
/* Back to the defaults for whatever is drawn next, exactly as `renderer.ts` restores them —
|
|
8695
9082
|
unconditionally, because the scratch state above is shared by every draw after this one
|
|
@@ -8700,7 +9087,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8700
9087
|
}
|
|
8701
9088
|
if (unlitOrUnfogged) {
|
|
8702
9089
|
if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
8703
|
-
this.perFrameInts[this.materialField('uFogEnabled')] =
|
|
9090
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
|
|
8704
9091
|
}
|
|
8705
9092
|
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
8706
9093
|
shared, so a strength left set is worn by everything drawn after it. */
|
|
@@ -8708,6 +9095,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8708
9095
|
if (!toneMapped) {
|
|
8709
9096
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
8710
9097
|
}
|
|
9098
|
+
if (ditheredBlend) {
|
|
9099
|
+
this.perFrameFloats[this.materialField('uCutout') + 1] =
|
|
9100
|
+
CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
9101
|
+
}
|
|
8711
9102
|
if (own) this.materials.dirty();
|
|
8712
9103
|
}
|
|
8713
9104
|
|
|
@@ -8947,6 +9338,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8947
9338
|
lands ? this.correctedViewProj : this.viewProj,
|
|
8948
9339
|
);
|
|
8949
9340
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
9341
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
8950
9342
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
8951
9343
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
8952
9344
|
|
|
@@ -8957,7 +9349,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8957
9349
|
const lit = options.lit ?? true;
|
|
8958
9350
|
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
8959
9351
|
const toneMapped = options.toneMapped ?? true;
|
|
8960
|
-
const unlitOrUnfogged = !lit || fog !==
|
|
9352
|
+
const unlitOrUnfogged = !lit || fog !== this.surfaceFog;
|
|
8961
9353
|
if (unlitOrUnfogged) {
|
|
8962
9354
|
if (!lit) this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
8963
9355
|
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
@@ -8970,8 +9362,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8970
9362
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
8971
9363
|
/* A blended batch shows what is behind it — refraction or glass — as a single draw does. */
|
|
8972
9364
|
const refracting = blend && this.bindSeeThrough(options);
|
|
8973
|
-
|
|
9365
|
+
/* As a single draw: a blended batch tests a dithered cutout hard, an opaque one in a
|
|
9366
|
+
multisampled frame takes coverage. See `cutoutDither.ts`. */
|
|
9367
|
+
const ditheredBlend = blend && this.cutoutResolveStaged !== 'hard';
|
|
9368
|
+
if (ditheredBlend) this.perFrameFloats[this.materialField('uCutout') + 1] = 0;
|
|
9369
|
+
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
9370
|
+
const own =
|
|
9371
|
+
ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog) || ditheredBlend;
|
|
8974
9372
|
if (own) this.materials.dirty();
|
|
9373
|
+
/* A batch shades by its material's model too; an eye's axis is a placement's, not a batch's. */
|
|
9374
|
+
const modelKind = this.materialModel?.kind ?? null;
|
|
8975
9375
|
const base = (mesh as GpuMesh & { key?: string }).key ?? 'flat:s0:u0';
|
|
8976
9376
|
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
8977
9377
|
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
@@ -8980,7 +9380,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8980
9380
|
const key =
|
|
8981
9381
|
`${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8982
9382
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8983
|
-
`${this.materialDoubleSided ? '|2s' : ''}
|
|
9383
|
+
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
9384
|
+
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
8984
9385
|
|
|
8985
9386
|
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
8986
9387
|
let pipeline = pipelines.peek(key);
|
|
@@ -9004,6 +9405,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9004
9405
|
true,
|
|
9005
9406
|
'none',
|
|
9006
9407
|
this.materialDoubleSided,
|
|
9408
|
+
coverage,
|
|
9409
|
+
false,
|
|
9410
|
+
false,
|
|
9411
|
+
modelKind,
|
|
9007
9412
|
);
|
|
9008
9413
|
}
|
|
9009
9414
|
|
|
@@ -9052,12 +9457,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9052
9457
|
}
|
|
9053
9458
|
if (unlitOrUnfogged) {
|
|
9054
9459
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
9055
|
-
this.perFrameInts[this.materialField('uFogEnabled')] =
|
|
9460
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
|
|
9056
9461
|
}
|
|
9057
9462
|
if (!toneMapped) {
|
|
9058
9463
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
9059
9464
|
}
|
|
9060
9465
|
if (refracting) this.releaseSeeThrough();
|
|
9466
|
+
if (ditheredBlend) {
|
|
9467
|
+
this.perFrameFloats[this.materialField('uCutout') + 1] =
|
|
9468
|
+
CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
9469
|
+
}
|
|
9061
9470
|
if (own) this.materials.dirty();
|
|
9062
9471
|
}
|
|
9063
9472
|
|
|
@@ -9295,12 +9704,19 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9295
9704
|
this.bindSceneCasterMaterial(material);
|
|
9296
9705
|
this.drawMesh(mesh as GpuMesh, model);
|
|
9297
9706
|
},
|
|
9298
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
9707
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
9299
9708
|
this.bindSceneCasterMaterial(material);
|
|
9300
9709
|
this.setSkinPalette(palette);
|
|
9710
|
+
if (cloth !== undefined) {
|
|
9711
|
+
this.setCloth(
|
|
9712
|
+
cloth.binding as unknown as GpuClothBinding,
|
|
9713
|
+
cloth.particles as unknown as GpuClothParticles,
|
|
9714
|
+
);
|
|
9715
|
+
}
|
|
9301
9716
|
this.drawMesh(mesh as GpuMesh, model);
|
|
9302
9717
|
/* Put back, or the next rigid draw in the replay skins by whatever this one left bound. */
|
|
9303
9718
|
this.setSkinPalette(null);
|
|
9719
|
+
this.setCloth(null);
|
|
9304
9720
|
},
|
|
9305
9721
|
instanced: (batch, data, material) => {
|
|
9306
9722
|
this.bindSceneCasterMaterial(material);
|
|
@@ -10208,11 +10624,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10208
10624
|
/* Slot `k` caches at `k + 1`; index 0 is the morphed draw that is not skinned and has no
|
|
10209
10625
|
slot of its own. See `skinnedGroups`. */
|
|
10210
10626
|
const at = skinned ? this.skinPaletteSlot + 1 : 0;
|
|
10627
|
+
/* A skinned group carries the cloth's views, or the stand-in's: see `clothViews`. */
|
|
10628
|
+
const cloth = skinned ? this.clothViews() : null;
|
|
10211
10629
|
if (
|
|
10212
10630
|
(this.skinnedGroups[at] ?? null) === null ||
|
|
10213
10631
|
this.skinnedGroupsBeside[at] !== this.bindGroup ||
|
|
10214
10632
|
this.skinnedGroupsPalette[at] !== palette ||
|
|
10215
|
-
this.skinnedGroupsDeltas[at] !== deltas
|
|
10633
|
+
this.skinnedGroupsDeltas[at] !== deltas ||
|
|
10634
|
+
this.skinnedGroupsCloth[at] !== (cloth?.particles ?? null) ||
|
|
10635
|
+
this.skinnedGroupsClothBinding[at] !== (cloth?.binding ?? null)
|
|
10216
10636
|
) {
|
|
10217
10637
|
this.skinnedGroups[at] = createFlatBindGroup(
|
|
10218
10638
|
this.surface.device,
|
|
@@ -10223,10 +10643,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10223
10643
|
this.flatTextures,
|
|
10224
10644
|
palette,
|
|
10225
10645
|
deltas,
|
|
10646
|
+
cloth,
|
|
10226
10647
|
);
|
|
10227
10648
|
this.skinnedGroupsBeside[at] = this.bindGroup;
|
|
10228
10649
|
this.skinnedGroupsPalette[at] = palette;
|
|
10229
10650
|
this.skinnedGroupsDeltas[at] = deltas;
|
|
10651
|
+
this.skinnedGroupsCloth[at] = cloth?.particles ?? null;
|
|
10652
|
+
this.skinnedGroupsClothBinding[at] = cloth?.binding ?? null;
|
|
10230
10653
|
}
|
|
10231
10654
|
return this.skinnedGroups[at] as GPUBindGroup;
|
|
10232
10655
|
}
|
|
@@ -10295,21 +10718,25 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10295
10718
|
const normalMap = this.normalMap;
|
|
10296
10719
|
const ormMap = this.ormMap;
|
|
10297
10720
|
const emissiveMap = this.emissiveMap;
|
|
10721
|
+
const modelMap = this.modelMap;
|
|
10298
10722
|
this.albedo = null;
|
|
10299
10723
|
this.normalMap = null;
|
|
10300
10724
|
this.ormMap = null;
|
|
10301
10725
|
this.emissiveMap = null;
|
|
10726
|
+
this.modelMap = null;
|
|
10302
10727
|
this.blankAlbedoBindGroup = this.buildFlatBindGroup();
|
|
10303
10728
|
this.albedo = albedo;
|
|
10304
10729
|
this.normalMap = normalMap;
|
|
10305
10730
|
this.ormMap = ormMap;
|
|
10306
10731
|
this.emissiveMap = emissiveMap;
|
|
10732
|
+
this.modelMap = modelMap;
|
|
10307
10733
|
|
|
10308
10734
|
this.bindGroup = this.flatGroupForMaps(
|
|
10309
10735
|
this.albedo,
|
|
10310
10736
|
this.normalMap,
|
|
10311
10737
|
this.ormMap,
|
|
10312
10738
|
this.emissiveMap,
|
|
10739
|
+
this.modelMap,
|
|
10313
10740
|
);
|
|
10314
10741
|
/* The open material lives in a slot of a buffer that no longer exists. */
|
|
10315
10742
|
this.materials.dirty();
|
|
@@ -10352,7 +10779,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10352
10779
|
this.albedo === null &&
|
|
10353
10780
|
this.normalMap === null &&
|
|
10354
10781
|
this.ormMap === null &&
|
|
10355
|
-
this.emissiveMap === null
|
|
10782
|
+
this.emissiveMap === null &&
|
|
10783
|
+
this.modelMap === null
|
|
10356
10784
|
) {
|
|
10357
10785
|
this.blankAlbedoBindGroup = this.buildFlatBindGroup();
|
|
10358
10786
|
this.bindGroupBudget.ask();
|
|
@@ -10364,6 +10792,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10364
10792
|
this.normalMap,
|
|
10365
10793
|
this.ormMap,
|
|
10366
10794
|
this.emissiveMap,
|
|
10795
|
+
this.modelMap,
|
|
10367
10796
|
);
|
|
10368
10797
|
}
|
|
10369
10798
|
|
|
@@ -11096,6 +11525,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11096
11525
|
* the only moment they can land; everything else keeps the path it has.
|
|
11097
11526
|
*/
|
|
11098
11527
|
private recordBlended(reads: number, lands = this.drawsLate()): DrawCommand | null {
|
|
11528
|
+
/* Skin is spread and added before anything blended lands over it. See `runSkinScatter`. */
|
|
11529
|
+
if (this.skinScatter !== null && !this.skinScattered) this.runSkinScatter();
|
|
11099
11530
|
const late = this.latePass;
|
|
11100
11531
|
if (late !== null && lands) return late.take();
|
|
11101
11532
|
return this.recordDraw(reads, this.currentTarget());
|
|
@@ -11183,6 +11614,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11183
11614
|
const dynamic = mesh.motion;
|
|
11184
11615
|
const rows = dynamic?.previous ?? null;
|
|
11185
11616
|
const rewritten = rows !== null && dynamic !== null && dynamic.changed === this.moverFrame;
|
|
11617
|
+
const cloth = this.cloth;
|
|
11618
|
+
if (
|
|
11619
|
+
cloth !== null &&
|
|
11620
|
+
this.skinPaletteSlot >= 0 &&
|
|
11621
|
+
mesh.isSkinned &&
|
|
11622
|
+
mesh.skinOffsets !== null
|
|
11623
|
+
) {
|
|
11624
|
+
this.recordClothMotion(mesh, model, previous, cloth);
|
|
11625
|
+
return;
|
|
11626
|
+
}
|
|
11186
11627
|
if (previous === null) {
|
|
11187
11628
|
if (rewritten) this.motionPass?.recordDynamic(mesh, model, model, rows);
|
|
11188
11629
|
return;
|
|
@@ -11229,6 +11670,58 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11229
11670
|
this.motionPass?.recordSkinned(mesh, model, mover.previousModel, now, was);
|
|
11230
11671
|
}
|
|
11231
11672
|
|
|
11673
|
+
/**
|
|
11674
|
+
* A cloth-bound draw's motion. **Recorded even where the caller named no previous place**, as a
|
|
11675
|
+
* rewritten mesh's is: the particles carry their own past, and a garment swinging on a character
|
|
11676
|
+
* standing still is motion nobody else can see. Where the rig is a `Mover` its last pose is taken
|
|
11677
|
+
* as a skinned mover's is; otherwise the rig is taken not to have moved.
|
|
11678
|
+
*/
|
|
11679
|
+
private recordClothMotion(
|
|
11680
|
+
mesh: GpuMesh,
|
|
11681
|
+
model: ArrayLike<number>,
|
|
11682
|
+
previous: ArrayLike<number> | Mover | null,
|
|
11683
|
+
cloth: { binding: GpuClothBinding; particles: GpuClothParticles },
|
|
11684
|
+
): void {
|
|
11685
|
+
const now = this.skinPalettes.view(this.skinPaletteSlot);
|
|
11686
|
+
if (now === null) return;
|
|
11687
|
+
let was: GPUTextureView | null = now;
|
|
11688
|
+
let wasModel: ArrayLike<number> = model;
|
|
11689
|
+
if (previous !== null && isMover(previous)) {
|
|
11690
|
+
const verdict = advanceMover(
|
|
11691
|
+
previous,
|
|
11692
|
+
this.moverRenderer,
|
|
11693
|
+
this.moverFrame,
|
|
11694
|
+
model,
|
|
11695
|
+
this.skinPaletteData,
|
|
11696
|
+
);
|
|
11697
|
+
if (verdict === MOVER_TWICE) {
|
|
11698
|
+
this.warnMoverTwice();
|
|
11699
|
+
return;
|
|
11700
|
+
}
|
|
11701
|
+
if (verdict === MOVER_MOTION) {
|
|
11702
|
+
const slot = this.skinPalettes.take(this.surface.device, previous.previousPalette);
|
|
11703
|
+
was = slot === null ? null : this.skinPalettes.view(slot);
|
|
11704
|
+
wasModel = previous.previousModel;
|
|
11705
|
+
}
|
|
11706
|
+
} else if (previous !== null) {
|
|
11707
|
+
wasModel = previous;
|
|
11708
|
+
}
|
|
11709
|
+
if (was === null) return;
|
|
11710
|
+
const particlesWas = cloth.particles.previousAt(this.moverFrame);
|
|
11711
|
+
/* Nothing said it moved and nothing did: the camera's motion, derived from depth, is the answer. */
|
|
11712
|
+
if (previous === null && particlesWas === cloth.particles.current) return;
|
|
11713
|
+
this.motionPass?.recordCloth(
|
|
11714
|
+
mesh,
|
|
11715
|
+
model,
|
|
11716
|
+
wasModel,
|
|
11717
|
+
now,
|
|
11718
|
+
was,
|
|
11719
|
+
cloth.binding.bindingView,
|
|
11720
|
+
cloth.particles.current,
|
|
11721
|
+
particlesWas,
|
|
11722
|
+
);
|
|
11723
|
+
}
|
|
11724
|
+
|
|
11232
11725
|
private warnMoverTwice(): void {
|
|
11233
11726
|
if (this.warnedMoverTwice) return;
|
|
11234
11727
|
this.warnedMoverTwice = true;
|
|
@@ -11614,7 +12107,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11614
12107
|
'post.ssrResolve',
|
|
11615
12108
|
SSR_RESOLVE_FRAG_WGSL,
|
|
11616
12109
|
this.pipelines.format,
|
|
11617
|
-
|
|
12110
|
+
REFLECTION_OVER_BLEND,
|
|
11618
12111
|
),
|
|
11619
12112
|
);
|
|
11620
12113
|
resolve.setBindGroup(0, resolveGroup);
|
|
@@ -12318,6 +12811,150 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12318
12811
|
return true;
|
|
12319
12812
|
}
|
|
12320
12813
|
|
|
12814
|
+
/**
|
|
12815
|
+
* Whether a skin draw now draws its two halves: the profile asked, the frame has not spread its
|
|
12816
|
+
* skin yet, and this is the frame's own picture — not a mirror's, a probe's, an overlay after
|
|
12817
|
+
* the frame, or an order-independent set, none of which the spread reads.
|
|
12818
|
+
*/
|
|
12819
|
+
private skinSplits(): boolean {
|
|
12820
|
+
return (
|
|
12821
|
+
this.skinScatter !== null &&
|
|
12822
|
+
!this.skinScattered &&
|
|
12823
|
+
!this.reflectionPassActive &&
|
|
12824
|
+
!this.probePassActive &&
|
|
12825
|
+
!this.framePresented &&
|
|
12826
|
+
!this.overlayActive &&
|
|
12827
|
+
this.oitMode === 'none'
|
|
12828
|
+
);
|
|
12829
|
+
}
|
|
12830
|
+
|
|
12831
|
+
/**
|
|
12832
|
+
* A skin draw's diffuse half, kept for the spread: the frame's draw again, with the same slots,
|
|
12833
|
+
* buffers and bind group, through the pipeline that writes the diffuse alone into the blur's
|
|
12834
|
+
* target and tests for the depth its frame half wrote.
|
|
12835
|
+
*/
|
|
12836
|
+
private keepSkinDiffuse(
|
|
12837
|
+
mesh: GpuMesh,
|
|
12838
|
+
frameKey: string,
|
|
12839
|
+
base: string,
|
|
12840
|
+
skinned: boolean,
|
|
12841
|
+
morphed: boolean,
|
|
12842
|
+
deltas: GPUTextureView | null,
|
|
12843
|
+
layer: number,
|
|
12844
|
+
eight: boolean,
|
|
12845
|
+
clothed: boolean,
|
|
12846
|
+
slot: number,
|
|
12847
|
+
material: number,
|
|
12848
|
+
): void {
|
|
12849
|
+
const skin = this.skinScatter;
|
|
12850
|
+
const pipelines = this.skinPipelines;
|
|
12851
|
+
const present = this.meshPresent.get(base);
|
|
12852
|
+
if (skin === null || pipelines === null || present === undefined) return;
|
|
12853
|
+
const key = `${frameKey}|sd`;
|
|
12854
|
+
const pipeline =
|
|
12855
|
+
pipelines.peek(key) ??
|
|
12856
|
+
flatPipeline(
|
|
12857
|
+
pipelines,
|
|
12858
|
+
this.surface.device,
|
|
12859
|
+
this.flatLayoutFor(skinned, morphed),
|
|
12860
|
+
this.variant,
|
|
12861
|
+
key,
|
|
12862
|
+
present,
|
|
12863
|
+
false,
|
|
12864
|
+
skinned,
|
|
12865
|
+
morphed,
|
|
12866
|
+
true,
|
|
12867
|
+
layer,
|
|
12868
|
+
false,
|
|
12869
|
+
'none',
|
|
12870
|
+
this.materialDoubleSided,
|
|
12871
|
+
false,
|
|
12872
|
+
eight,
|
|
12873
|
+
clothed,
|
|
12874
|
+
'skin',
|
|
12875
|
+
'diffuse',
|
|
12876
|
+
);
|
|
12877
|
+
const command = skin.take();
|
|
12878
|
+
command.pipeline = pipeline;
|
|
12879
|
+
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
12880
|
+
command.offsetA = slot;
|
|
12881
|
+
command.offsetB = material;
|
|
12882
|
+
command.offsetCount = 2;
|
|
12883
|
+
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
12884
|
+
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
12885
|
+
}
|
|
12886
|
+
command.vertexCount = mesh.vertexBuffers.length;
|
|
12887
|
+
command.indexBuffer = mesh.indexBuffer;
|
|
12888
|
+
command.indexed = true;
|
|
12889
|
+
command.count = mesh.indexCount;
|
|
12890
|
+
command.instances = 1;
|
|
12891
|
+
command.indirect = null;
|
|
12892
|
+
}
|
|
12893
|
+
|
|
12894
|
+
/**
|
|
12895
|
+
* Spread the frame's skin and add it back: once, at the first blended draw or before the frame's
|
|
12896
|
+
* last flush. **A fifth instance of the boundary `takeRefractSnapshot` describes** — flush, end
|
|
12897
|
+
* the pass, work between passes, reopen with `loadOp: 'load'` — and refused where that one is,
|
|
12898
|
+
* for its reasons. Between the passes: the kept diffuse halves into their target against the
|
|
12899
|
+
* frame's depth, the depth resolved, and `SkinScatterPass.spread` across and then down into the
|
|
12900
|
+
* frame's colour.
|
|
12901
|
+
*/
|
|
12902
|
+
private runSkinScatter(): void {
|
|
12903
|
+
const skin = this.skinScatter;
|
|
12904
|
+
if (skin === null || this.skinScattered) return;
|
|
12905
|
+
/* Latched whether or not skin was kept: a skin drawn after this point is the whole surface. */
|
|
12906
|
+
this.skinScattered = true;
|
|
12907
|
+
if (!skin.pending) return;
|
|
12908
|
+
if (this.reflectionPassActive || this.probePassActive || this.oitReplaying) return;
|
|
12909
|
+
const encoder = this.encoder;
|
|
12910
|
+
const depth = this.depthView;
|
|
12911
|
+
const resolved = this.resolvedDepthView;
|
|
12912
|
+
const target = this.compositeTarget();
|
|
12913
|
+
const projection = this.frameProjection;
|
|
12914
|
+
if (encoder === null || depth === null || resolved === null) return;
|
|
12915
|
+
if (target === null || projection === null) return;
|
|
12916
|
+
if (this.pass === null && this.openPass() === null) return;
|
|
12917
|
+
if (this.pass === null) return;
|
|
12918
|
+
if (this.quality.frameGraph) this.flushGraph();
|
|
12919
|
+
|
|
12920
|
+
this.pass.end();
|
|
12921
|
+
const size = this.sceneSize();
|
|
12922
|
+
skin.size(size.width, size.height, this.samples);
|
|
12923
|
+
skin.drawDiffuse(encoder, depth, this.issueBound, this.gpuTimer.writesFor());
|
|
12924
|
+
this.resolveDepth(encoder);
|
|
12925
|
+
mat4.invert(this.skinInverseProjection, projection);
|
|
12926
|
+
const multisampled = this.compositeMsaa();
|
|
12927
|
+
skin.spread(
|
|
12928
|
+
encoder,
|
|
12929
|
+
this.pipelines,
|
|
12930
|
+
resolved,
|
|
12931
|
+
multisampled ?? target,
|
|
12932
|
+
multisampled === null ? null : target,
|
|
12933
|
+
this.samples,
|
|
12934
|
+
projection,
|
|
12935
|
+
this.skinInverseProjection,
|
|
12936
|
+
() => this.gpuTimer.writesFor(),
|
|
12937
|
+
);
|
|
12938
|
+
this.pass = encoder.beginRenderPass({
|
|
12939
|
+
label: 'skin.scattered',
|
|
12940
|
+
timestampWrites: this.gpuTimer.writesFor(),
|
|
12941
|
+
colorAttachments: [
|
|
12942
|
+
{
|
|
12943
|
+
view: multisampled ?? target,
|
|
12944
|
+
resolveTarget: multisampled !== null ? target : undefined,
|
|
12945
|
+
/* Loaded, not cleared: the frame so far, with its skin added. */
|
|
12946
|
+
loadOp: 'load',
|
|
12947
|
+
storeOp: resolvedStoreOp(
|
|
12948
|
+
multisampled !== null,
|
|
12949
|
+
false,
|
|
12950
|
+
this.quality.discardResolvedAttachments,
|
|
12951
|
+
),
|
|
12952
|
+
},
|
|
12953
|
+
],
|
|
12954
|
+
depthStencilAttachment: { view: depth, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
12955
|
+
});
|
|
12956
|
+
}
|
|
12957
|
+
|
|
12321
12958
|
private takeVolumeDepth(): boolean {
|
|
12322
12959
|
/*
|
|
12323
12960
|
* **Never while the mirror is open, and this is a correctness fix rather than a saving.**
|
|
@@ -12940,6 +13577,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12940
13577
|
* `beginFrame` resets it. Silent, and exactly the kind of loss the arena is designed
|
|
12941
13578
|
* against everywhere else.
|
|
12942
13579
|
*/
|
|
13580
|
+
/* Before the last flush, which may let the depth go that the spread still reads. */
|
|
13581
|
+
this.runSkinScatter();
|
|
12943
13582
|
this.flushGraph('boundary', this.liveOutFinalFlush());
|
|
12944
13583
|
/*
|
|
12945
13584
|
* `ensurePass` rather than a null check: the frame's pass is opened on demand now, so a
|
|
@@ -16112,6 +16751,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16112
16751
|
* the 2026-08-13 rule asks for — one decision, bound twice.
|
|
16113
16752
|
*/
|
|
16114
16753
|
dispose(): void {
|
|
16754
|
+
this.skinScatter?.dispose();
|
|
16115
16755
|
this.motionPass?.dispose();
|
|
16116
16756
|
this.motionPass = null;
|
|
16117
16757
|
this.latePass?.dispose();
|
|
@@ -16152,3 +16792,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16152
16792
|
this.surface.dispose();
|
|
16153
16793
|
}
|
|
16154
16794
|
}
|
|
16795
|
+
|
|
16796
|
+
/**
|
|
16797
|
+
* The views a binding and a character's particles bind as, built once a pair and kept on the
|
|
16798
|
+
* particles: a bind group is cached by its views' identity, so a fresh object a draw would rebuild
|
|
16799
|
+
* the group every draw.
|
|
16800
|
+
*/
|
|
16801
|
+
function clothViewsOf(binding: GpuClothBinding, particles: GpuClothParticles): ClothViews {
|
|
16802
|
+
return particles.viewsWith(binding);
|
|
16803
|
+
}
|