@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
|
@@ -52,6 +52,10 @@ import { createPanelBindGroup, createPanelBindGroupLayout, createPanelCorners, p
|
|
|
52
52
|
import { createGpuTextCube, createTextBindGroup, createTextBindGroupLayout, GpuText, textPipeline, TEXT_FRAG_FIELDS, TEXT_FRAG_SIZE, TEXT_VERT_FIELDS, TEXT_VERT_SIZE, } from './textPass.js';
|
|
53
53
|
import { deviceSnappedCellSize, deviceSnappedOrigin } from '../../textLayout.js';
|
|
54
54
|
import { GpuSurfaceTexture } from './surfaceTexturePass.js';
|
|
55
|
+
import { gpuCompressedFormats } from './compressedUpload.js';
|
|
56
|
+
import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.js';
|
|
57
|
+
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel } from '../../surfaceModel.js';
|
|
58
|
+
import { validateClothBinding } from '../../clothBindingData.js';
|
|
55
59
|
import { createSdfTextBindGroup, createSdfTextBindGroupLayout, GpuSdfText, sdfTextPipeline, SDF_TEXT_FRAG_FIELDS, SDF_TEXT_FRAG_SIZE, SDF_TEXT_VERT_FIELDS, SDF_TEXT_VERT_SIZE, } from './sdfTextPass.js';
|
|
56
60
|
import { createFilmBindGroup, createFilmBindGroupLayout, filmPipeline, FILM_FRAG_FIELDS, FILM_FRAG_SIZE, FILM_VERT_FIELDS, FILM_VERT_SIZE, } from './filmPass.js';
|
|
57
61
|
import { reflectionTargetSize } from '../../planarReflectionDraw.js';
|
|
@@ -61,7 +65,7 @@ import { ExposurePass } from './exposurePass.js';
|
|
|
61
65
|
import { AO_STORE } from '../../shaders/ambientOcclusion.js';
|
|
62
66
|
import { clampAutoExposure } from '../../shaders/exposure.js';
|
|
63
67
|
import { clampLocalExposure } from '../../shaders/localExposure.js';
|
|
64
|
-
import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, ADDITIVE_BLEND, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
|
|
68
|
+
import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, ADDITIVE_BLEND, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, REFLECTION_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
|
|
65
69
|
import { GRADE_PLACEHOLDER_SIZE, identityGradeLut, validateGradeLut, } from '../../colourGrade.js';
|
|
66
70
|
import { FILM_LOOK_WITHOUT_COMPOSITE, clampGrain, clampVignette, grainSeed, } from '../../shaders/filmLook.js';
|
|
67
71
|
import { createProbeMipBindGroupLayout, createProbePrefilterBindGroupLayout, PREFILTER_UNIFORM_OFFSETS, probeLevels, probeMipPipeline, probePrefilterPipeline, PROBE_FACES, PROBE_FACE_BASIS, PROBE_FAR_M, PROBE_FOV_DEG, PROBE_MIP_UNIFORM_SIZE, PROBE_NEAR_M, } from './probePass.js';
|
|
@@ -87,6 +91,7 @@ import { SkinPaletteRing } from './skinPaletteRing.js';
|
|
|
87
91
|
import { DEPTH_FORMAT, FLAT_VERT_FIELDS, FLAT_VERT_SIZE, createFlatBindGroup, createFlatBindGroupLayout, flatFragmentBindings, flatVariant, flatVertexBindings, flatPipeline, flatPipelineAsync, } from './flatPass.js';
|
|
88
92
|
import { DEPTH_FRAG_BINDING, DEPTH_FRAG_SIZE, DEPTH_FRAG_WGSL, DEPTH_PREVIOUS_BINDING, DEPTH_CUTOUT_VERT_FIELDS, DEPTH_CUTOUT_VERT_SIZE, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS, DEPTH_VERT_FIELDS, DEPTH_VERT_SIZE, SHADOW_FORMAT, createDepthBindGroup, createDepthBindGroupLayout, depthPipeline, } from './depthPass.js';
|
|
89
93
|
import { PipelineCache } from './pipelineCache.js';
|
|
94
|
+
import { SKIN_TARGET_FORMAT, SkinScatterPass } from './skinScatterPass.js';
|
|
90
95
|
import { SKY_FIELDS, SKY_UNIFORM_SIZE, createSkyBindGroup, createSkyBindGroupLayout, skyPipeline, } from './skyPass.js';
|
|
91
96
|
import { SCATTER_DEPTH_FIELDS, SCATTER_DEPTH_SIZE, SCATTER_FRAG_FIELDS, SCATTER_FRAG_SIZE, SCATTER_VERT_FIELDS, SCATTER_VERT_SIZE, createGpuScatter, createScatterBindGroup, createScatterBindGroupLayout, createScatterDepthBindGroup, createScatterDepthBindGroupLayout, scatterDepthPipeline, scatterPipeline, } from './scatterPass.js';
|
|
92
97
|
import { WATER_FRAG_FIELDS, WATER_FRAG_SIZE, WATER_VERT_FIELDS, WATER_VERT_SIZE, createGpuWater, createWaterBindGroup, createWaterBindGroupLayout, waterPipeline, } from './waterPass.js';
|
|
@@ -100,7 +105,7 @@ import { LIGHT_VOLUME_VERT_FIELDS, LIGHT_VOLUME_VERT_SIZE, createLightVolumeBind
|
|
|
100
105
|
import { aimReflection, createGpuReflection } from './reflectionPass.js';
|
|
101
106
|
import { FrameBudget } from '../budget.js';
|
|
102
107
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
|
|
103
|
-
import { FOG_RECEDE, fogModeOf } from '../drawFog.js';
|
|
108
|
+
import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.js';
|
|
104
109
|
import { DYNAMIC_ALIGNMENT as DYNAMIC_UNIFORM_ALIGNMENT, UniformRing } from './uniformRing.js';
|
|
105
110
|
import { DecalQueue, MAX_DRAWN_DECALS } from '../../decalQueue.js';
|
|
106
111
|
import { DistanceFieldScene } from '../../gi/fieldScene.js';
|
|
@@ -137,6 +142,7 @@ import { clusteredMode, createClusterLightSet, fillClusterLightSet, } from '../.
|
|
|
137
142
|
import { computeAt, createComputeRegistry, } from '../../compute.js';
|
|
138
143
|
import { GpuInstancedBatch } from './instanced.js';
|
|
139
144
|
import { cutoutOf } from '../../cutoutCaster.js';
|
|
145
|
+
import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.js';
|
|
140
146
|
import { LightField } from '../../driftLight/lightField.js';
|
|
141
147
|
import { WorldLightField } from '../../driftLight/worldLightField.js';
|
|
142
148
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.js';
|
|
@@ -864,6 +870,12 @@ export class WebGPURenderer {
|
|
|
864
870
|
skinnedGroupsBeside = [];
|
|
865
871
|
skinnedGroupsPalette = [];
|
|
866
872
|
skinnedGroupsDeltas = [];
|
|
873
|
+
/** The cloth views each skinned twin was built with, compared by identity like the palette. */
|
|
874
|
+
skinnedGroupsCloth = [];
|
|
875
|
+
skinnedGroupsClothBinding = [];
|
|
876
|
+
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
877
|
+
cloth = null;
|
|
878
|
+
clothStandCache = null;
|
|
867
879
|
/**
|
|
868
880
|
* Which palette slot the following draws skin by, or -1 for none.
|
|
869
881
|
*
|
|
@@ -1257,6 +1269,16 @@ export class WebGPURenderer {
|
|
|
1257
1269
|
refractSnapshotView = null;
|
|
1258
1270
|
/** Whether this frame has taken it. One snapshot serves every refracting draw. */
|
|
1259
1271
|
refractSnapshotTaken = false;
|
|
1272
|
+
/**
|
|
1273
|
+
* Skin's screen-space scattering, made where the profile asks for it and has a composite to add
|
|
1274
|
+
* into, and null otherwise — which is every profile by default. See `SkinScatterPass`.
|
|
1275
|
+
*/
|
|
1276
|
+
skinScatter;
|
|
1277
|
+
/** The pipelines skin's diffuse half is built for: the blur target's format, the frame's samples. */
|
|
1278
|
+
skinPipelines;
|
|
1279
|
+
/** Whether this frame has spread its skin: once, at its first blended draw or before its end. */
|
|
1280
|
+
skinScattered = false;
|
|
1281
|
+
skinInverseProjection = mat4.create();
|
|
1260
1282
|
sceneColorView = null;
|
|
1261
1283
|
/** The multisampled twin, when the profile asks for samples. Resolved into `sceneColor`. */
|
|
1262
1284
|
sceneColorMsaa = null;
|
|
@@ -1435,6 +1457,17 @@ export class WebGPURenderer {
|
|
|
1435
1457
|
temporalJitterX = 0;
|
|
1436
1458
|
temporalJitterY = 0;
|
|
1437
1459
|
temporalJittering = false;
|
|
1460
|
+
/**
|
|
1461
|
+
* The frame a dithered cutout's pattern is offset by, advanced at `beginFrame` and wrapped at 64.
|
|
1462
|
+
* See `cutoutDither.ts`.
|
|
1463
|
+
*/
|
|
1464
|
+
cutoutFrame = 0;
|
|
1465
|
+
/**
|
|
1466
|
+
* How the current material's cutout edge is drawn this frame, decided when the material is staged
|
|
1467
|
+
* — after `bindMeshPass` has settled whether the frame is temporal — and read by every draw: a
|
|
1468
|
+
* coverage edge is pipeline state here (`|a2c`), and a blended draw tests hard.
|
|
1469
|
+
*/
|
|
1470
|
+
cutoutResolveStaged = 'hard';
|
|
1438
1471
|
/**
|
|
1439
1472
|
* Whether this frame is being reconstructed, settled once in `bindMeshPass`.
|
|
1440
1473
|
*
|
|
@@ -2100,6 +2133,12 @@ export class WebGPURenderer {
|
|
|
2100
2133
|
ormMap = null;
|
|
2101
2134
|
/** The caller's emissive map, or null for the stand-in. See `setMaterial`. */
|
|
2102
2135
|
emissiveMap = null;
|
|
2136
|
+
/** The material's model map, or null for the stand-in. See `setMaterial` and `surfaceModel.ts`. */
|
|
2137
|
+
modelMap = null;
|
|
2138
|
+
/** The material's shading model, which picks the lit pipeline: null the standard. */
|
|
2139
|
+
materialModel = null;
|
|
2140
|
+
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
2141
|
+
modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
2103
2142
|
/**
|
|
2104
2143
|
* The flat group for a material, keyed on **all four** of its maps.
|
|
2105
2144
|
*
|
|
@@ -2131,16 +2170,33 @@ export class WebGPURenderer {
|
|
|
2131
2170
|
blankAlbedoBindGroup;
|
|
2132
2171
|
/** Repeats across the mesh's own UV range. Per draw, because `uUvScale` is a vertex uniform. */
|
|
2133
2172
|
uvScale = new Float32Array([1, 1]);
|
|
2173
|
+
/** Where the texture starts, after the scale. Per draw, beside `uvScale`. */
|
|
2174
|
+
uvOffset = new Float32Array([0, 0]);
|
|
2175
|
+
/**
|
|
2176
|
+
* How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
|
|
2177
|
+
* each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
|
|
2178
|
+
*/
|
|
2179
|
+
surfaceFog = FOG_RECEDE;
|
|
2134
2180
|
/** Whether the bound material is seen from both faces. See `SurfaceMaterial.doubleSided`. */
|
|
2135
2181
|
materialDoubleSided = false;
|
|
2136
2182
|
/** Whether the bound material has a map, for the warning a mesh with no coordinates earns. */
|
|
2137
2183
|
materialHasMaps = false;
|
|
2138
2184
|
createSurfaceTexture(source, options = {}) {
|
|
2139
|
-
return new GpuSurfaceTexture(this.surface.device, this.pipelines, source, options);
|
|
2185
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, source, options, this.compressedFormats);
|
|
2186
|
+
}
|
|
2187
|
+
compressedCache = null;
|
|
2188
|
+
/**
|
|
2189
|
+
* The BC formats this device takes as blocks: every one where the device carries
|
|
2190
|
+
* `texture-compression-bc`, which `select.ts` asks for wherever the adapter offers it, and none
|
|
2191
|
+
* where it does not. See `Renderer.compressedFormats`.
|
|
2192
|
+
*/
|
|
2193
|
+
get compressedFormats() {
|
|
2194
|
+
this.compressedCache ??= gpuCompressedFormats(this.surface.device);
|
|
2195
|
+
return this.compressedCache;
|
|
2140
2196
|
}
|
|
2141
2197
|
/** Several images of one size as an array's layers. See `RendererApi.createSurfaceTextureArray`. */
|
|
2142
2198
|
createSurfaceTextureArray(sources, options = {}) {
|
|
2143
|
-
return new GpuSurfaceTexture(this.surface.device, this.pipelines, sources, options);
|
|
2199
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, sources, options, this.compressedFormats);
|
|
2144
2200
|
}
|
|
2145
2201
|
/**
|
|
2146
2202
|
* GPU textures an `updateSurfaceTexture` replaced, destroyed when the next frame begins.
|
|
@@ -2191,6 +2247,8 @@ export class WebGPURenderer {
|
|
|
2191
2247
|
this.ormMap = null;
|
|
2192
2248
|
if (this.emissiveMap === texture)
|
|
2193
2249
|
this.emissiveMap = null;
|
|
2250
|
+
if (this.modelMap === texture)
|
|
2251
|
+
this.modelMap = null;
|
|
2194
2252
|
this.forgetBindingsOf(texture);
|
|
2195
2253
|
texture.dispose();
|
|
2196
2254
|
}
|
|
@@ -2215,7 +2273,7 @@ export class WebGPURenderer {
|
|
|
2215
2273
|
this.albedo = null;
|
|
2216
2274
|
this.blankAlbedoBindGroup = this.buildFlatBindGroup();
|
|
2217
2275
|
this.albedo = held;
|
|
2218
|
-
this.bindGroup = this.flatGroupForMaps(held, this.normalMap, this.ormMap, this.emissiveMap);
|
|
2276
|
+
this.bindGroup = this.flatGroupForMaps(held, this.normalMap, this.ormMap, this.emissiveMap, this.modelMap);
|
|
2219
2277
|
}
|
|
2220
2278
|
/**
|
|
2221
2279
|
* Choose the surface texture the following `drawMesh` calls sample, or null for none.
|
|
@@ -2243,6 +2301,104 @@ export class WebGPURenderer {
|
|
|
2243
2301
|
one. See `SkinPaletteRing`; a full ring declines the draw instead of mixing two rigs. */
|
|
2244
2302
|
this.skinPaletteSlot = this.skinPalettes.take(this.surface.device, palette) ?? -1;
|
|
2245
2303
|
}
|
|
2304
|
+
/**
|
|
2305
|
+
* A mesh's cloth binding. See `RendererApi.createClothBinding`. The pipelines that draw it bound
|
|
2306
|
+
* are warmed here, every way this mesh can be drawn, so none compiles inside the first frame
|
|
2307
|
+
* that draws a garment.
|
|
2308
|
+
*/
|
|
2309
|
+
createClothBinding(mesh, data) {
|
|
2310
|
+
if (!mesh.isSkinned) {
|
|
2311
|
+
throw new Error('createClothBinding: this mesh has no rig, and a binding blends each vertex between its ' +
|
|
2312
|
+
'skinning and the cloth');
|
|
2313
|
+
}
|
|
2314
|
+
validateClothBinding(data, mesh.vertexCount);
|
|
2315
|
+
const base = mesh.key ?? 'flat:s0:u0';
|
|
2316
|
+
const present = this.meshPresent.get(base);
|
|
2317
|
+
if (present !== undefined) {
|
|
2318
|
+
const skin = mesh.isSkinnedEight ? '|skin8' : '|skin';
|
|
2319
|
+
for (const morphed of mesh.morph !== null ? [false, true] : [false]) {
|
|
2320
|
+
for (const translucent of [false, true]) {
|
|
2321
|
+
void flatPipelineAsync(this.targetPipelines(), this.surface.device, this.flatLayoutFor(true, morphed), this.variant, `${base}${morphed ? '|morph' : ''}${skin}|cloth${translucent ? '|blend' : ''}`, present, translucent, true, morphed, mesh.isSkinnedEight, true);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
return new GpuClothBinding(this.surface.device, data);
|
|
2326
|
+
}
|
|
2327
|
+
/** One character's particles. See `RendererApi.createClothParticles`. */
|
|
2328
|
+
createClothParticles(count) {
|
|
2329
|
+
return new GpuClothParticles(this.surface.device, count);
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* This frame's particles. **Once a frame, before the character's draws**: a texture write lands
|
|
2333
|
+
* ahead of every draw of the frame, so a second update between two draws would reach both.
|
|
2334
|
+
*/
|
|
2335
|
+
updateClothParticles(particles, positions) {
|
|
2336
|
+
if (this.surface.lost)
|
|
2337
|
+
return;
|
|
2338
|
+
/* A write between frames is the next frame's, as a rewritten mesh's is. See `changeFrames.ts`. */
|
|
2339
|
+
particles.update(this.surface.device, positions, frameOfChange(this.moverFrame, this.inFrame));
|
|
2340
|
+
}
|
|
2341
|
+
/** Place the following skinned draws by a cloth, or by none. See `RendererApi.setCloth`. */
|
|
2342
|
+
setCloth(binding, particles = null) {
|
|
2343
|
+
if (binding === null || particles === null) {
|
|
2344
|
+
this.cloth = null;
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
if (binding.particles !== particles.count) {
|
|
2348
|
+
throw new Error(`setCloth: the binding names ${binding.particles} particles and these are ${particles.count}`);
|
|
2349
|
+
}
|
|
2350
|
+
this.cloth = { binding, particles };
|
|
2351
|
+
}
|
|
2352
|
+
/** Retired rather than destroyed: a draw recorded earlier in this frame may still read them. */
|
|
2353
|
+
disposeClothBinding(binding) {
|
|
2354
|
+
this.retiredTextures.push(...binding.textures);
|
|
2355
|
+
}
|
|
2356
|
+
disposeClothParticles(particles) {
|
|
2357
|
+
this.retiredTextures.push(...particles.textures);
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* The device and the frame's bookkeeping, for compute a consumer opts into by importing it —
|
|
2361
|
+
* `createSkinnedCloth` — rather than for anything this renderer runs itself. See `computeHost.ts`.
|
|
2362
|
+
*/
|
|
2363
|
+
computeHost() {
|
|
2364
|
+
return this.computeHostCache;
|
|
2365
|
+
}
|
|
2366
|
+
computeHostCache = {
|
|
2367
|
+
device: () => this.surface.device,
|
|
2368
|
+
lost: () => this.surface.lost,
|
|
2369
|
+
changeFrame: () => frameOfChange(this.moverFrame, this.inFrame),
|
|
2370
|
+
retire: (textures) => {
|
|
2371
|
+
this.retiredTextures.push(...textures);
|
|
2372
|
+
},
|
|
2373
|
+
};
|
|
2374
|
+
/**
|
|
2375
|
+
* An eye's axis in the world for this draw, into the open material's \`uModelParams\`, and a new
|
|
2376
|
+
* material slot when it moved: the axis is per draw, and a slot already taken by an earlier draw
|
|
2377
|
+
* of the same eye must not be written under it. See \`eyeAxisInWorld\`.
|
|
2378
|
+
*/
|
|
2379
|
+
writeEyeAxis(model, skinned) {
|
|
2380
|
+
const eye = this.materialModel;
|
|
2381
|
+
if (eye?.kind !== 'eye')
|
|
2382
|
+
return;
|
|
2383
|
+
const at = this.materialField('uModelParams') + 4;
|
|
2384
|
+
const f = this.perFrameFloats;
|
|
2385
|
+
const before0 = f[at];
|
|
2386
|
+
const before1 = f[at + 1];
|
|
2387
|
+
const before2 = f[at + 2];
|
|
2388
|
+
eyeAxisInWorld(eye, model, skinned ? this.skinPaletteData : null, f, at);
|
|
2389
|
+
if (f[at] !== before0 || f[at + 1] !== before1 || f[at + 2] !== before2)
|
|
2390
|
+
this.materials.dirty();
|
|
2391
|
+
}
|
|
2392
|
+
/** The views the draw's cloth binds, or the stand-in's where no cloth is set. */
|
|
2393
|
+
clothViews() {
|
|
2394
|
+
const cloth = this.cloth;
|
|
2395
|
+
return cloth === null ? this.clothStand() : clothViewsOf(cloth.binding, cloth.particles);
|
|
2396
|
+
}
|
|
2397
|
+
/** The one-texel stand-in a skinned group binds where there is no cloth, made on first need. */
|
|
2398
|
+
clothStand() {
|
|
2399
|
+
this.clothStandCache ??= clothStandIn(this.surface.device);
|
|
2400
|
+
return this.clothStandCache.views;
|
|
2401
|
+
}
|
|
2246
2402
|
/**
|
|
2247
2403
|
* Choose the morph weights the following `drawMesh` calls deform by, or null for none.
|
|
2248
2404
|
*
|
|
@@ -2260,19 +2416,39 @@ export class WebGPURenderer {
|
|
|
2260
2416
|
const albedo = material?.albedo ?? null;
|
|
2261
2417
|
this.uvScale[0] = material?.uScale ?? 1;
|
|
2262
2418
|
this.uvScale[1] = material?.vScale ?? 1;
|
|
2419
|
+
this.uvOffset[0] = material?.uOffset ?? 0;
|
|
2420
|
+
this.uvOffset[1] = material?.vOffset ?? 0;
|
|
2263
2421
|
this.materialDoubleSided = material?.doubleSided === true;
|
|
2264
2422
|
this.materialHasMaps = materialHasMaps(material);
|
|
2265
2423
|
const normal = (material?.normal ?? null);
|
|
2266
2424
|
const orm = (material?.orm ?? null);
|
|
2267
2425
|
const emissiveMap = (material?.emissive ?? null);
|
|
2426
|
+
const modelMap = (material?.modelMap ?? null);
|
|
2427
|
+
this.materialModel = material?.model ?? null;
|
|
2428
|
+
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
2429
|
+
const model = material?.model;
|
|
2430
|
+
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
2431
|
+
this.skinScatter.setProfile(model.profile, model.scatterColor[0] * model.radius, model.scatterColor[1] * model.radius, model.scatterColor[2] * model.radius);
|
|
2432
|
+
}
|
|
2268
2433
|
this.material((f, i) => {
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
i[
|
|
2434
|
+
/* The material's four switches, one row: albedo, ORM map, emissive map, two-sided. */
|
|
2435
|
+
const flags = this.materialField('uMaterialFlags');
|
|
2436
|
+
i[flags] = albedo === null ? 0 : 1;
|
|
2437
|
+
/* The cutoff, how this frame resolves a dithered edge, and the frame its pattern moves with. */
|
|
2438
|
+
const cutoff = albedo === null ? 0 : (material?.cutout ?? 0);
|
|
2439
|
+
this.cutoutResolveStaged =
|
|
2440
|
+
cutoff > 0
|
|
2441
|
+
? resolveCutout(material?.cutoutMode ?? 'hard', this.reconstructing || this.temporalJittering, this.samples)
|
|
2442
|
+
: 'hard';
|
|
2443
|
+
const cut = this.materialField('uCutout');
|
|
2444
|
+
f[cut] = cutoff;
|
|
2445
|
+
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
2446
|
+
f[cut + 2] = this.cutoutFrame;
|
|
2447
|
+
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
2272
2448
|
/* 0 is no map, and gates the whole block in the shader. See `uNormalStrength`. */
|
|
2273
2449
|
f[this.materialField('uNormalStrength')] =
|
|
2274
2450
|
normal === null ? 0 : (material?.normalStrength ?? 1);
|
|
2275
|
-
i[
|
|
2451
|
+
i[flags + 1] = orm === null ? 0 : 1;
|
|
2276
2452
|
/*
|
|
2277
2453
|
* Component-aligned with the map — r occlusion, g roughness, b metallic — so this order is
|
|
2278
2454
|
* the channel order rather than the field order of `SurfaceMaterial`. Written component by
|
|
@@ -2283,7 +2459,7 @@ export class WebGPURenderer {
|
|
|
2283
2459
|
f[scale] = material?.occlusionStrength ?? 1;
|
|
2284
2460
|
f[scale + 1] = material?.roughnessScale ?? 1;
|
|
2285
2461
|
f[scale + 2] = material?.metallicScale ?? 1;
|
|
2286
|
-
i[
|
|
2462
|
+
i[flags + 2] = emissiveMap === null ? 0 : 1;
|
|
2287
2463
|
/*
|
|
2288
2464
|
* One rather than zero on every component, because this multiplies rather than adds: an
|
|
2289
2465
|
* unwritten float is zero, and zero here would switch off every glow the mesh's own vertex
|
|
@@ -2294,6 +2470,9 @@ export class WebGPURenderer {
|
|
|
2294
2470
|
f[emissive] = asked?.[0] ?? 1;
|
|
2295
2471
|
f[emissive + 1] = asked?.[1] ?? 1;
|
|
2296
2472
|
f[emissive + 2] = asked?.[2] ?? 1;
|
|
2473
|
+
/* The model's numbers and whether its map is bound. See `packModel`. */
|
|
2474
|
+
packModel(material?.model ?? null, modelMap !== null, this.modelParams);
|
|
2475
|
+
f.set(this.modelParams, this.materialField('uModelParams'));
|
|
2297
2476
|
});
|
|
2298
2477
|
/*
|
|
2299
2478
|
* **One lookup for the whole material, and nothing thrown away.**
|
|
@@ -2307,14 +2486,16 @@ export class WebGPURenderer {
|
|
|
2307
2486
|
if (this.albedo === albedo &&
|
|
2308
2487
|
this.normalMap === normal &&
|
|
2309
2488
|
this.ormMap === orm &&
|
|
2310
|
-
this.emissiveMap === emissiveMap
|
|
2489
|
+
this.emissiveMap === emissiveMap &&
|
|
2490
|
+
this.modelMap === modelMap) {
|
|
2311
2491
|
return;
|
|
2312
2492
|
}
|
|
2313
2493
|
this.albedo = albedo;
|
|
2314
2494
|
this.normalMap = normal;
|
|
2315
2495
|
this.ormMap = orm;
|
|
2316
2496
|
this.emissiveMap = emissiveMap;
|
|
2317
|
-
this.
|
|
2497
|
+
this.modelMap = modelMap;
|
|
2498
|
+
this.bindGroup = this.flatGroupForMaps(albedo, normal, orm, emissiveMap, modelMap);
|
|
2318
2499
|
}
|
|
2319
2500
|
/**
|
|
2320
2501
|
* @deprecated Use `setMaterial`. See `renderer.ts` for why this stays: 56 call sites across four
|
|
@@ -2336,8 +2517,14 @@ export class WebGPURenderer {
|
|
|
2336
2517
|
* asking. Passing them in as well is deliberate: it is what makes the key and the group agree by
|
|
2337
2518
|
* construction rather than by the caller having remembered.
|
|
2338
2519
|
*/
|
|
2339
|
-
flatGroupForMaps(albedo, normal, orm, emissive
|
|
2340
|
-
|
|
2520
|
+
flatGroupForMaps(albedo, normal, orm, emissive,
|
|
2521
|
+
/** The model map, a fifth level of the same cache. See `flatBindGroups`. */
|
|
2522
|
+
modelMap) {
|
|
2523
|
+
if (albedo === null &&
|
|
2524
|
+
normal === null &&
|
|
2525
|
+
orm === null &&
|
|
2526
|
+
emissive === null &&
|
|
2527
|
+
modelMap === null) {
|
|
2341
2528
|
return this.blankAlbedoBindGroup;
|
|
2342
2529
|
}
|
|
2343
2530
|
let byNormal = this.flatBindGroups.get(albedo);
|
|
@@ -2355,12 +2542,17 @@ export class WebGPURenderer {
|
|
|
2355
2542
|
byEmissive = new Map();
|
|
2356
2543
|
byOrm.set(orm, byEmissive);
|
|
2357
2544
|
}
|
|
2358
|
-
|
|
2545
|
+
let byModel = byEmissive.get(emissive);
|
|
2546
|
+
if (byModel === undefined) {
|
|
2547
|
+
byModel = new Map();
|
|
2548
|
+
byEmissive.set(emissive, byModel);
|
|
2549
|
+
}
|
|
2550
|
+
const existing = byModel.get(modelMap);
|
|
2359
2551
|
if (existing !== undefined)
|
|
2360
2552
|
return existing;
|
|
2361
2553
|
const built = this.buildFlatBindGroup();
|
|
2362
2554
|
this.bindGroupBudget.ask();
|
|
2363
|
-
|
|
2555
|
+
byModel.set(modelMap, built);
|
|
2364
2556
|
return built;
|
|
2365
2557
|
}
|
|
2366
2558
|
/* -- Material state ------------------------------------------------------------------ */
|
|
@@ -2408,6 +2600,13 @@ export class WebGPURenderer {
|
|
|
2408
2600
|
f[this.materialField('uGrain')] = Math.min(1, Math.max(0, amount));
|
|
2409
2601
|
});
|
|
2410
2602
|
}
|
|
2603
|
+
/** Whether the surfaces drawn next meet the medium. See `renderer.ts`. */
|
|
2604
|
+
setSurfaceFog(enabled) {
|
|
2605
|
+
this.surfaceFog = enabled ? FOG_RECEDE : FOG_OFF;
|
|
2606
|
+
this.material((_f, i) => {
|
|
2607
|
+
i[this.materialField('uFogEnabled')] = this.surfaceFog;
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2411
2610
|
/**
|
|
2412
2611
|
* How much of the surfaces drawn next survives a screen-door dither. See `renderer.ts`.
|
|
2413
2612
|
*
|
|
@@ -4249,6 +4448,8 @@ export class WebGPURenderer {
|
|
|
4249
4448
|
shadowSkinnedGroups = [];
|
|
4250
4449
|
shadowSkinnedGroupsBeside = [];
|
|
4251
4450
|
shadowSkinnedGroupsPalette = [];
|
|
4451
|
+
shadowSkinnedGroupsCloth = [];
|
|
4452
|
+
shadowSkinnedGroupsClothBinding = [];
|
|
4252
4453
|
/** Held from construction so the skinned group can be rebuilt without them being threaded. */
|
|
4253
4454
|
shadowBlankView = null;
|
|
4254
4455
|
shadowBlankSampler = null;
|
|
@@ -4363,11 +4564,14 @@ export class WebGPURenderer {
|
|
|
4363
4564
|
groups.set(texture, group);
|
|
4364
4565
|
return group;
|
|
4365
4566
|
}
|
|
4366
|
-
/** The cutoff and UV scale into a cutout draw's slot, at
|
|
4567
|
+
/** The cutoff and UV scale and offset into a cutout draw's slot, at its own variant's offsets. */
|
|
4367
4568
|
writeCutout(slot, cutout, fields) {
|
|
4368
4569
|
this.shadowDraws.writeFloat(slot, fields.uUvScale.offset, cutout.u);
|
|
4369
4570
|
this.shadowDraws.writeFloat(slot, fields.uUvScale.offset + 4, cutout.v);
|
|
4571
|
+
this.shadowDraws.writeFloat(slot, fields.uUvOffset.offset, cutout.uOffset);
|
|
4572
|
+
this.shadowDraws.writeFloat(slot, fields.uUvOffset.offset + 4, cutout.vOffset);
|
|
4370
4573
|
this.shadowDraws.writeFloat(slot, fields.uAlphaCutout.offset, cutout.cutoff);
|
|
4574
|
+
this.shadowDraws.writeFloat(slot, fields.uAlphaCutout.offset + 4, cutout.dithered);
|
|
4371
4575
|
}
|
|
4372
4576
|
shadowGroup() {
|
|
4373
4577
|
return this.shadowLayerIsPeel && this.shadowPeelBindGroup !== null
|
|
@@ -4381,14 +4585,18 @@ export class WebGPURenderer {
|
|
|
4381
4585
|
* second independently fading occluder can be recorded, and a character is not one — it is the
|
|
4382
4586
|
* thing in front. Serving it would mean a second cached group for a case nothing produces.
|
|
4383
4587
|
*/
|
|
4384
|
-
shadowSkinnedGroup(palette, at) {
|
|
4588
|
+
shadowSkinnedGroup(palette, at, cloth) {
|
|
4385
4589
|
const beside = this.shadowBindGroup;
|
|
4386
4590
|
if ((this.shadowSkinnedGroups[at] ?? null) === null ||
|
|
4387
4591
|
this.shadowSkinnedGroupsBeside[at] !== beside ||
|
|
4388
|
-
this.shadowSkinnedGroupsPalette[at] !== palette
|
|
4389
|
-
this.
|
|
4592
|
+
this.shadowSkinnedGroupsPalette[at] !== palette ||
|
|
4593
|
+
this.shadowSkinnedGroupsCloth[at] !== cloth.particles ||
|
|
4594
|
+
this.shadowSkinnedGroupsClothBinding[at] !== cloth.binding) {
|
|
4595
|
+
this.shadowSkinnedGroups[at] = createDepthBindGroup(this.surface.device, this.shadowSkinnedLayout, this.shadowDraws.buffer, this.shadowPassUniforms, this.shadowBlankView, this.shadowBlankSampler, palette, null, cloth);
|
|
4390
4596
|
this.shadowSkinnedGroupsBeside[at] = beside;
|
|
4391
4597
|
this.shadowSkinnedGroupsPalette[at] = palette;
|
|
4598
|
+
this.shadowSkinnedGroupsCloth[at] = cloth.particles;
|
|
4599
|
+
this.shadowSkinnedGroupsClothBinding[at] = cloth.binding;
|
|
4392
4600
|
}
|
|
4393
4601
|
return this.shadowSkinnedGroups[at];
|
|
4394
4602
|
}
|
|
@@ -4427,6 +4635,8 @@ export class WebGPURenderer {
|
|
|
4427
4635
|
void this.overlayPipelines.enable(feature);
|
|
4428
4636
|
if (this.latePipelines !== this.pipelines)
|
|
4429
4637
|
void this.latePipelines.enable(feature);
|
|
4638
|
+
if (this.skinPipelines !== null)
|
|
4639
|
+
void this.skinPipelines.enable(feature);
|
|
4430
4640
|
}
|
|
4431
4641
|
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
4432
4642
|
glassCasterScratch = {
|
|
@@ -4517,7 +4727,7 @@ export class WebGPURenderer {
|
|
|
4517
4727
|
pass.setIndexBuffer(geometry.indexBuffer, 'uint32');
|
|
4518
4728
|
pass.drawIndexed(geometry.indexCount, count);
|
|
4519
4729
|
},
|
|
4520
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
4730
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
4521
4731
|
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
4522
4732
|
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
4523
4733
|
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
@@ -4547,8 +4757,13 @@ export class WebGPURenderer {
|
|
|
4547
4757
|
const geometry = mesh;
|
|
4548
4758
|
if (geometry.vertexBuffers === undefined || geometry.indexCount === 0)
|
|
4549
4759
|
return;
|
|
4550
|
-
|
|
4551
|
-
|
|
4760
|
+
/* A garment casts placed by its cloth, as it draws; `|cloth` keys the pipeline that does. */
|
|
4761
|
+
const clothed = cloth !== undefined;
|
|
4762
|
+
const clothViews = clothed
|
|
4763
|
+
? clothViewsOf(cloth.binding, cloth.particles)
|
|
4764
|
+
: this.clothStand();
|
|
4765
|
+
pass.setPipeline(depthPipeline(this.pipelines, this.surface.device, this.shadowSkinnedLayout, `${geometry.isSkinnedEight ? 'depth-skinned8' : 'depth-skinned'}${clothed ? '|cloth' : ''}|${this.depthCullMode}|${geometry.key ?? ''}`, this.presentOf(geometry), this.depthCullMode, true, false, false, geometry.isSkinnedEight, clothed));
|
|
4766
|
+
pass.setBindGroup(0, this.shadowSkinnedGroup(view, paletteSlot, clothViews), [slot]);
|
|
4552
4767
|
for (let index = 0; index < geometry.vertexBuffers.length; index++) {
|
|
4553
4768
|
pass.setVertexBuffer(index, geometry.vertexBuffers[index]);
|
|
4554
4769
|
}
|
|
@@ -4881,6 +5096,18 @@ export class WebGPURenderer {
|
|
|
4881
5096
|
this.pipelines.format === RECON_HISTORY_FORMAT && this.samples === 1
|
|
4882
5097
|
? this.pipelines
|
|
4883
5098
|
: new PipelineCache(device, RECON_HISTORY_FORMAT, 1, glassShadows, quality.clusteredLights);
|
|
5099
|
+
/*
|
|
5100
|
+
* Screen-space skin needs the composite: linear light to add into, and the depth it resolves.
|
|
5101
|
+
* Without one it is the pre-integrated fit alone, which is the default anyway, and said once.
|
|
5102
|
+
*/
|
|
5103
|
+
const skinScreen = quality.skinScattering === 'screen-space' && quality.screenEffects;
|
|
5104
|
+
if (quality.skinScattering === 'screen-space' && !quality.screenEffects) {
|
|
5105
|
+
console.warn("WebGPU: skinScattering 'screen-space' needs screenEffects to add into; skin is pre-integrated");
|
|
5106
|
+
}
|
|
5107
|
+
this.skinScatter = skinScreen ? new SkinScatterPass(device) : null;
|
|
5108
|
+
this.skinPipelines = skinScreen
|
|
5109
|
+
? new PipelineCache(device, SKIN_TARGET_FORMAT, this.samples, glassShadows, quality.clusteredLights)
|
|
5110
|
+
: null;
|
|
4884
5111
|
this.issueBound = this.issueCommand.bind(this);
|
|
4885
5112
|
this.pipelineTargets =
|
|
4886
5113
|
this.overlayPipelines === this.pipelines
|
|
@@ -4935,7 +5162,7 @@ export class WebGPURenderer {
|
|
|
4935
5162
|
*/
|
|
4936
5163
|
this.perFrame = new UniformRing(device, this.fragment.uniformSize, this.quality.materialChangesPerFrame, USAGE_UNIFORM_DST, 'flat.fragRing', this.materialBudget);
|
|
4937
5164
|
/*
|
|
4938
|
-
* A one-pixel stand-in for the albedo binding.
|
|
5165
|
+
* A one-pixel stand-in for the albedo binding. The albedo's flag is zero for untextured
|
|
4939
5166
|
* geometry, which is nearly all of it, so the shader never samples this — but WebGPU
|
|
4940
5167
|
* requires every declared binding to be filled. WebGL2 keeps an `emptyTexture2D` for
|
|
4941
5168
|
* exactly the same reason.
|
|
@@ -5139,6 +5366,13 @@ export class WebGPURenderer {
|
|
|
5139
5366
|
? { view: blankSurfaceView, sampler: blankSampler }
|
|
5140
5367
|
: { view: map.view, sampler: map.sampler };
|
|
5141
5368
|
}
|
|
5369
|
+
/* The material's model map, or the stand-in, for the reason `uNormalMap` gives above. */
|
|
5370
|
+
if (name === 'uModelMap') {
|
|
5371
|
+
const map = this.modelMap;
|
|
5372
|
+
return map === null
|
|
5373
|
+
? { view: blankSurfaceView, sampler: blankSampler }
|
|
5374
|
+
: { view: map.view, sampler: map.sampler };
|
|
5375
|
+
}
|
|
5142
5376
|
if (name === 'uOrmMap') {
|
|
5143
5377
|
const map = this.ormMap;
|
|
5144
5378
|
return map === null
|
|
@@ -5714,6 +5948,8 @@ export class WebGPURenderer {
|
|
|
5714
5948
|
}
|
|
5715
5949
|
/* Not a layout entry of its own — it widens `uvs` — so read from the key beside them. */
|
|
5716
5950
|
present['layers'] = key.includes(':layers');
|
|
5951
|
+
/* Nor is a dynamic mesh's split, which moves positions and normals into buffers of their own. */
|
|
5952
|
+
present['dynamic'] = key.includes(':dynamic');
|
|
5717
5953
|
return present;
|
|
5718
5954
|
}
|
|
5719
5955
|
/**
|
|
@@ -5726,6 +5962,7 @@ export class WebGPURenderer {
|
|
|
5726
5962
|
*/
|
|
5727
5963
|
beginFrame(clearColor) {
|
|
5728
5964
|
this.cullFrame += 1;
|
|
5965
|
+
this.cutoutFrame = (this.cutoutFrame + 1) % 64;
|
|
5729
5966
|
/* Whatever an update replaced during the last frame; nothing recorded can read it now. */
|
|
5730
5967
|
for (const texture of this.retiredTextures)
|
|
5731
5968
|
texture.destroy();
|
|
@@ -5789,6 +6026,8 @@ export class WebGPURenderer {
|
|
|
5789
6026
|
this.volumeDepthTaken = false;
|
|
5790
6027
|
/* Last frame's snapshot is last frame's scene. */
|
|
5791
6028
|
this.refractSnapshotTaken = false;
|
|
6029
|
+
this.skinScattered = false;
|
|
6030
|
+
this.skinScatter?.reset();
|
|
5792
6031
|
this.plumeVerts.reset();
|
|
5793
6032
|
this.plumeFrags.reset();
|
|
5794
6033
|
this.waterVerts.reset();
|
|
@@ -5965,7 +6204,7 @@ export class WebGPURenderer {
|
|
|
5965
6204
|
* neither the pipeline set nor the key depends on whether the geometry lands in one call or
|
|
5966
6205
|
* over six frames.
|
|
5967
6206
|
*/
|
|
5968
|
-
warmFlatPipelines(data) {
|
|
6207
|
+
warmFlatPipelines(data, dynamic) {
|
|
5969
6208
|
/*
|
|
5970
6209
|
* **Every optional attribute, not the two that come to mind.** `present` decides where
|
|
5971
6210
|
* each attribute sits in the interleaved buffer, and `createGpuMesh` decides the same
|
|
@@ -5992,6 +6231,15 @@ export class WebGPURenderer {
|
|
|
5992
6231
|
present['layers'] = true;
|
|
5993
6232
|
key += ':layers';
|
|
5994
6233
|
}
|
|
6234
|
+
/*
|
|
6235
|
+
* A dynamic mesh keeps its positions and normals in buffers of their own (`GpuMesh.update`), so
|
|
6236
|
+
* it binds four buffers where a static one binds two: a different layout, and a key that says
|
|
6237
|
+
* so, or the two would share a pipeline whose buffers one of them does not have.
|
|
6238
|
+
*/
|
|
6239
|
+
if (dynamic) {
|
|
6240
|
+
present['dynamic'] = true;
|
|
6241
|
+
key += ':dynamic';
|
|
6242
|
+
}
|
|
5995
6243
|
const fullKey = `${this.variant}|${key}`;
|
|
5996
6244
|
/*
|
|
5997
6245
|
* **For every target this mesh can land on, which is two when the overlay is its own.**
|
|
@@ -6047,14 +6295,16 @@ export class WebGPURenderer {
|
|
|
6047
6295
|
* guessing which the scene will use.
|
|
6048
6296
|
*/
|
|
6049
6297
|
const skinnable = data.joints !== undefined;
|
|
6298
|
+
/* An eight-influence rig takes its own skinned pipelines, keyed `|skin8`. See skinning.ts. */
|
|
6299
|
+
const eight = skinnable && data.joints2 !== undefined;
|
|
6050
6300
|
const morphable = data.morphTargets !== undefined;
|
|
6051
6301
|
for (const wantSkin of skinnable ? [false, true] : [false]) {
|
|
6052
6302
|
for (const wantMorph of morphable ? [false, true] : [false]) {
|
|
6053
6303
|
if (!wantSkin && !wantMorph)
|
|
6054
6304
|
continue;
|
|
6055
|
-
const suffix = `${wantMorph ? '|morph' : ''}${wantSkin ? '|skin' : ''}`;
|
|
6305
|
+
const suffix = `${wantMorph ? '|morph' : ''}${wantSkin ? (eight ? '|skin8' : '|skin') : ''}`;
|
|
6056
6306
|
for (const translucent of [false, true]) {
|
|
6057
|
-
void flatPipelineAsync(pipelines, this.surface.device, this.flatLayoutFor(wantSkin, wantMorph), this.variant, `${fullKey}${suffix}${translucent ? '|blend' : ''}`, present, translucent, wantSkin, wantMorph);
|
|
6307
|
+
void flatPipelineAsync(pipelines, this.surface.device, this.flatLayoutFor(wantSkin, wantMorph), this.variant, `${fullKey}${suffix}${translucent ? '|blend' : ''}`, present, translucent, wantSkin, wantMorph, wantSkin && eight);
|
|
6058
6308
|
}
|
|
6059
6309
|
}
|
|
6060
6310
|
}
|
|
@@ -6075,7 +6325,7 @@ export class WebGPURenderer {
|
|
|
6075
6325
|
return keyed;
|
|
6076
6326
|
}
|
|
6077
6327
|
createMesh(data, options = {}) {
|
|
6078
|
-
const fullKey = this.warmFlatPipelines(data);
|
|
6328
|
+
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
6079
6329
|
return this.keyed(createGpuMesh(this.surface.device, data, options.dynamic === true), fullKey);
|
|
6080
6330
|
}
|
|
6081
6331
|
/**
|
|
@@ -6088,7 +6338,7 @@ export class WebGPURenderer {
|
|
|
6088
6338
|
* can do nothing about. Done with `complete` still false is the signal that it was abandoned.
|
|
6089
6339
|
*/
|
|
6090
6340
|
createMeshIncremental(data, options = {}) {
|
|
6091
|
-
const fullKey = this.warmFlatPipelines(data);
|
|
6341
|
+
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
6092
6342
|
const { mesh, upload } = createGpuMeshIncremental(this.surface.device, data, options.dynamic === true);
|
|
6093
6343
|
const surface = this.surface;
|
|
6094
6344
|
return {
|
|
@@ -6105,18 +6355,18 @@ export class WebGPURenderer {
|
|
|
6105
6355
|
/**
|
|
6106
6356
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
6107
6357
|
*
|
|
6108
|
-
* Refused in words on a mesh that did not declare itself dynamic, because
|
|
6109
|
-
*
|
|
6110
|
-
*
|
|
6358
|
+
* Refused in words on a mesh that did not declare itself dynamic, because what makes an update
|
|
6359
|
+
* cheap on this backend — positions and normals in buffers of their own — is a layout chosen at
|
|
6360
|
+
* creation. See `GpuMesh.update`.
|
|
6111
6361
|
*/
|
|
6112
6362
|
updateMesh(mesh, positions, normals) {
|
|
6113
6363
|
if (mesh.update === null) {
|
|
6114
|
-
throw new Error('this mesh was not created with { dynamic: true }, so
|
|
6115
|
-
'
|
|
6116
|
-
'
|
|
6364
|
+
throw new Error('this mesh was not created with { dynamic: true }, so its positions share one buffer with ' +
|
|
6365
|
+
'every other attribute and cannot be rewritten alone. Say so at `createMesh` — the flag ' +
|
|
6366
|
+
'decides the layout, and what a buffer is hinted as.');
|
|
6117
6367
|
}
|
|
6118
6368
|
/*
|
|
6119
|
-
* **The
|
|
6369
|
+
* **The positions before this rewrite are last frame's**, kept only for a reconstruction and only
|
|
6120
6370
|
* on the first rewrite of a frame — a second must not replace where the vertices were last
|
|
6121
6371
|
* frame with where they were a moment ago. A rewrite between frames is the next frame's. See
|
|
6122
6372
|
* `changeFrames.ts`.
|
|
@@ -6248,8 +6498,13 @@ export class WebGPURenderer {
|
|
|
6248
6498
|
* these per draw through the material block, the same shape `uOpacity` already uses.
|
|
6249
6499
|
*/
|
|
6250
6500
|
i[at('uLightingEnabled')] = 1;
|
|
6251
|
-
i[at('uFogEnabled')] =
|
|
6252
|
-
|
|
6501
|
+
i[at('uFogEnabled')] = FOG_RECEDE;
|
|
6502
|
+
this.surfaceFog = FOG_RECEDE;
|
|
6503
|
+
/* Every material switch off — albedo, ORM map, emissive map, two-sided — as WebGL2 resets them. */
|
|
6504
|
+
i.fill(0, at('uMaterialFlags'), at('uMaterialFlags') + 4);
|
|
6505
|
+
f[at('uCutout')] = 0;
|
|
6506
|
+
f[at('uCutout') + 1] = 0;
|
|
6507
|
+
this.cutoutResolveStaged = 'hard';
|
|
6253
6508
|
this.materialHasMaps = false;
|
|
6254
6509
|
/*
|
|
6255
6510
|
* **The camera medium, which this pass did not upload until 2026-08-29.**
|
|
@@ -6380,7 +6635,7 @@ export class WebGPURenderer {
|
|
|
6380
6635
|
/*
|
|
6381
6636
|
* The ORM scales, whose base is 1 and not 0 — the value an unwritten uniform would hold.
|
|
6382
6637
|
*
|
|
6383
|
-
*
|
|
6638
|
+
* The ORM map's flag is zeroed with the others above, and zero is exactly "no map".
|
|
6384
6639
|
* These three are the opposite case, the one this block's own header is about: a scale of
|
|
6385
6640
|
* zero is a real and wrong value, so leaving them silent would not switch the map off, it
|
|
6386
6641
|
* would read every channel as its floor the moment one was bound.
|
|
@@ -6666,6 +6921,8 @@ export class WebGPURenderer {
|
|
|
6666
6921
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
6667
6922
|
this.uvScale[0] = 1;
|
|
6668
6923
|
this.uvScale[1] = 1;
|
|
6924
|
+
this.uvOffset[0] = 0;
|
|
6925
|
+
this.uvOffset[1] = 0;
|
|
6669
6926
|
}
|
|
6670
6927
|
/**
|
|
6671
6928
|
* The slot the next draw's material lives in, taking a new one when something dirtied it.
|
|
@@ -6744,7 +7001,10 @@ export class WebGPURenderer {
|
|
|
6744
7001
|
if (this.tracksMotionNow())
|
|
6745
7002
|
this.recordDrawMotion(mesh, model, previousModel);
|
|
6746
7003
|
noteMapsWithoutUvs(mesh, mesh.hasUvs, this.materialHasMaps);
|
|
6747
|
-
|
|
7004
|
+
/* An opaque draw meets the medium as the dial says: `submitMesh` reads an absent `fog` as
|
|
7005
|
+
true, which is a translucent draw's default and would put every opaque draw back in the fog
|
|
7006
|
+
`setSurfaceFog(false)` took it out of. */
|
|
7007
|
+
this.submitMesh(mesh, model, tint, 1, false, { depthLayer, fog: this.surfaceFog !== FOG_OFF });
|
|
6748
7008
|
}
|
|
6749
7009
|
/**
|
|
6750
7010
|
* Draw a mesh that lets the world through, at `opacity`.
|
|
@@ -6894,6 +7154,7 @@ export class WebGPURenderer {
|
|
|
6894
7154
|
/* The material's repeats, not a constant: geometry authored in metres is textured at
|
|
6895
7155
|
whatever density the material asks for without rebuilding it. */
|
|
6896
7156
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7157
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
6897
7158
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
6898
7159
|
/*
|
|
6899
7160
|
* Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
|
|
@@ -6926,7 +7187,7 @@ export class WebGPURenderer {
|
|
|
6926
7187
|
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
6927
7188
|
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
6928
7189
|
const toneMapped = options.toneMapped ?? true;
|
|
6929
|
-
const unlitOrUnfogged = !lit || fog !==
|
|
7190
|
+
const unlitOrUnfogged = !lit || fog !== this.surfaceFog;
|
|
6930
7191
|
if (unlitOrUnfogged) {
|
|
6931
7192
|
if (!lit)
|
|
6932
7193
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
@@ -6947,11 +7208,20 @@ export class WebGPURenderer {
|
|
|
6947
7208
|
* before last. That is the defined state the two-backends rule asks for.
|
|
6948
7209
|
*/
|
|
6949
7210
|
const refracting = this.bindSeeThrough(options);
|
|
7211
|
+
/* A dithered cutout tests hard on a blended draw, which has nothing to average a pattern with;
|
|
7212
|
+
put back below with the rest. See `cutoutDither.ts`. */
|
|
7213
|
+
const ditheredBlend = blend && this.cutoutResolveStaged !== 'hard';
|
|
7214
|
+
if (ditheredBlend)
|
|
7215
|
+
this.perFrameFloats[this.materialField('uCutout') + 1] = 0;
|
|
6950
7216
|
/* Taken for itself when any of those differs from the pass, and put back below: the rule both
|
|
6951
7217
|
backends count material changes by. See `materialChanges.ts`. */
|
|
6952
|
-
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
7218
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog) || ditheredBlend;
|
|
6953
7219
|
if (own)
|
|
6954
7220
|
this.materials.dirty();
|
|
7221
|
+
/* An eye's axis is the draw's, written into the open material. See `eyeAxisInWorld`. */
|
|
7222
|
+
const modelKind = this.materialModel?.kind ?? null;
|
|
7223
|
+
if (modelKind === 'eye')
|
|
7224
|
+
this.writeEyeAxis(model, this.skinPaletteSlot >= 0 && mesh.isSkinned);
|
|
6955
7225
|
const base = mesh.key ?? 'flat:s0:u0';
|
|
6956
7226
|
/*
|
|
6957
7227
|
* A rigged mesh takes the skinned vertex variant, an unrigged one the plain variant, and the
|
|
@@ -6963,7 +7233,12 @@ export class WebGPURenderer {
|
|
|
6963
7233
|
const skinned = this.skinPaletteSlot >= 0 && mesh.isSkinned;
|
|
6964
7234
|
const morphed = this.morphWeights !== null && mesh.morph !== null;
|
|
6965
7235
|
const deltas = morphed && mesh.morph !== null ? mesh.morph.view() : null;
|
|
6966
|
-
const
|
|
7236
|
+
const eight = skinned && mesh.isSkinnedEight;
|
|
7237
|
+
/* A cloth set and a skinned draw: the pipeline built with CLOTH_BOUND on, warmed when the
|
|
7238
|
+
binding was made. See `createClothBinding`. */
|
|
7239
|
+
const clothed = skinned && this.cloth !== null;
|
|
7240
|
+
const keyed = `${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
|
|
7241
|
+
`${clothed ? '|cloth' : ''}`;
|
|
6967
7242
|
/*
|
|
6968
7243
|
* Depth writing and the overlay layer are pipeline state on this backend, so they belong in
|
|
6969
7244
|
* the key: two draws of one mesh differing only in these would otherwise share a cached
|
|
@@ -6978,13 +7253,22 @@ export class WebGPURenderer {
|
|
|
6978
7253
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
6979
7254
|
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
6980
7255
|
const adds = blend && options.additive === true;
|
|
7256
|
+
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
7257
|
+
/* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
|
|
7258
|
+
const splits = modelKind === 'skin' && !blend && this.skinSplits();
|
|
6981
7259
|
const key = `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
6982
7260
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
6983
7261
|
/* The two order-independent buffers are two formats and two blend states, so a draw into
|
|
6984
7262
|
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
6985
7263
|
`${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
|
|
6986
7264
|
/* Culling is pipeline state here: a two-sided surface culls nothing. */
|
|
6987
|
-
`${this.materialDoubleSided ? '|2s' : ''}
|
|
7265
|
+
`${this.materialDoubleSided ? '|2s' : ''}` +
|
|
7266
|
+
/* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
|
|
7267
|
+
`${coverage ? '|a2c' : ''}` +
|
|
7268
|
+
/* And the material's shading model, which is the fragment stage's own switch. */
|
|
7269
|
+
`${modelKind === null ? '' : `|m:${modelKind}`}` +
|
|
7270
|
+
/* And which half of a skin it draws, under the screen-space blur. */
|
|
7271
|
+
`${splits ? '|ss' : ''}`;
|
|
6988
7272
|
/*
|
|
6989
7273
|
* The morph uniforms, written only for a morphed draw and read from the *morphed* variant's
|
|
6990
7274
|
* field table. `FLAT_VERT_FIELDS` is the plain variant's and does not name them at all — every
|
|
@@ -7014,7 +7298,7 @@ export class WebGPURenderer {
|
|
|
7014
7298
|
if (present === undefined) {
|
|
7015
7299
|
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
7016
7300
|
}
|
|
7017
|
-
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided);
|
|
7301
|
+
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided, coverage, eight, clothed, modelKind, splits ? 'scene' : 'whole');
|
|
7018
7302
|
}
|
|
7019
7303
|
/*
|
|
7020
7304
|
* Guards the draw call only, not the restore below.
|
|
@@ -7068,6 +7352,9 @@ export class WebGPURenderer {
|
|
|
7068
7352
|
pass.setIndexBuffer(mesh.indexBuffer, 'uint32');
|
|
7069
7353
|
pass.drawIndexed(mesh.indexCount);
|
|
7070
7354
|
}
|
|
7355
|
+
if (splits) {
|
|
7356
|
+
this.keepSkinDiffuse(mesh, key, base, skinned, morphed, deltas, layer, eight, clothed, slot, material);
|
|
7357
|
+
}
|
|
7071
7358
|
}
|
|
7072
7359
|
/* Back to the defaults for whatever is drawn next, exactly as `renderer.ts` restores them —
|
|
7073
7360
|
unconditionally, because the scratch state above is shared by every draw after this one
|
|
@@ -7079,7 +7366,7 @@ export class WebGPURenderer {
|
|
|
7079
7366
|
if (unlitOrUnfogged) {
|
|
7080
7367
|
if (!lit)
|
|
7081
7368
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
7082
|
-
this.perFrameInts[this.materialField('uFogEnabled')] =
|
|
7369
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
|
|
7083
7370
|
}
|
|
7084
7371
|
/* Back to off for every other draw in the frame, exactly as `uOpacity` is: this scratch is
|
|
7085
7372
|
shared, so a strength left set is worn by everything drawn after it. */
|
|
@@ -7088,6 +7375,10 @@ export class WebGPURenderer {
|
|
|
7088
7375
|
if (!toneMapped) {
|
|
7089
7376
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
7090
7377
|
}
|
|
7378
|
+
if (ditheredBlend) {
|
|
7379
|
+
this.perFrameFloats[this.materialField('uCutout') + 1] =
|
|
7380
|
+
CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
7381
|
+
}
|
|
7091
7382
|
if (own)
|
|
7092
7383
|
this.materials.dirty();
|
|
7093
7384
|
}
|
|
@@ -7284,6 +7575,7 @@ export class WebGPURenderer {
|
|
|
7284
7575
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7285
7576
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uViewProj.offset, lands ? this.correctedViewProj : this.viewProj);
|
|
7286
7577
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7578
|
+
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
7287
7579
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7288
7580
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
7289
7581
|
const dimmed = opacity < 1;
|
|
@@ -7293,7 +7585,7 @@ export class WebGPURenderer {
|
|
|
7293
7585
|
const lit = options.lit ?? true;
|
|
7294
7586
|
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
7295
7587
|
const toneMapped = options.toneMapped ?? true;
|
|
7296
|
-
const unlitOrUnfogged = !lit || fog !==
|
|
7588
|
+
const unlitOrUnfogged = !lit || fog !== this.surfaceFog;
|
|
7297
7589
|
if (unlitOrUnfogged) {
|
|
7298
7590
|
if (!lit)
|
|
7299
7591
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 0;
|
|
@@ -7306,9 +7598,17 @@ export class WebGPURenderer {
|
|
|
7306
7598
|
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
7307
7599
|
/* A blended batch shows what is behind it — refraction or glass — as a single draw does. */
|
|
7308
7600
|
const refracting = blend && this.bindSeeThrough(options);
|
|
7309
|
-
|
|
7601
|
+
/* As a single draw: a blended batch tests a dithered cutout hard, an opaque one in a
|
|
7602
|
+
multisampled frame takes coverage. See `cutoutDither.ts`. */
|
|
7603
|
+
const ditheredBlend = blend && this.cutoutResolveStaged !== 'hard';
|
|
7604
|
+
if (ditheredBlend)
|
|
7605
|
+
this.perFrameFloats[this.materialField('uCutout') + 1] = 0;
|
|
7606
|
+
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
7607
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog) || ditheredBlend;
|
|
7310
7608
|
if (own)
|
|
7311
7609
|
this.materials.dirty();
|
|
7610
|
+
/* A batch shades by its material's model too; an eye's axis is a placement's, not a batch's. */
|
|
7611
|
+
const modelKind = this.materialModel?.kind ?? null;
|
|
7312
7612
|
const base = mesh.key ?? 'flat:s0:u0';
|
|
7313
7613
|
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
7314
7614
|
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
@@ -7316,7 +7616,8 @@ export class WebGPURenderer {
|
|
|
7316
7616
|
const adds = blend && options.additive === true;
|
|
7317
7617
|
const key = `${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
7318
7618
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
7319
|
-
`${this.materialDoubleSided ? '|2s' : ''}
|
|
7619
|
+
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
7620
|
+
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
7320
7621
|
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
7321
7622
|
let pipeline = pipelines.peek(key);
|
|
7322
7623
|
if (pipeline === undefined) {
|
|
@@ -7324,7 +7625,7 @@ export class WebGPURenderer {
|
|
|
7324
7625
|
if (present === undefined) {
|
|
7325
7626
|
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
7326
7627
|
}
|
|
7327
|
-
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(false, false), this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided);
|
|
7628
|
+
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(false, false), this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind);
|
|
7328
7629
|
}
|
|
7329
7630
|
const material = this.materialSlotForDraw();
|
|
7330
7631
|
if (material !== null) {
|
|
@@ -7375,13 +7676,17 @@ export class WebGPURenderer {
|
|
|
7375
7676
|
}
|
|
7376
7677
|
if (unlitOrUnfogged) {
|
|
7377
7678
|
this.perFrameInts[this.materialField('uLightingEnabled')] = 1;
|
|
7378
|
-
this.perFrameInts[this.materialField('uFogEnabled')] =
|
|
7679
|
+
this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
|
|
7379
7680
|
}
|
|
7380
7681
|
if (!toneMapped) {
|
|
7381
7682
|
this.perFrameInts[this.materialField('uOutputTransform')] = this.gradeCode();
|
|
7382
7683
|
}
|
|
7383
7684
|
if (refracting)
|
|
7384
7685
|
this.releaseSeeThrough();
|
|
7686
|
+
if (ditheredBlend) {
|
|
7687
|
+
this.perFrameFloats[this.materialField('uCutout') + 1] =
|
|
7688
|
+
CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
7689
|
+
}
|
|
7385
7690
|
if (own)
|
|
7386
7691
|
this.materials.dirty();
|
|
7387
7692
|
}
|
|
@@ -7599,12 +7904,16 @@ export class WebGPURenderer {
|
|
|
7599
7904
|
this.bindSceneCasterMaterial(material);
|
|
7600
7905
|
this.drawMesh(mesh, model);
|
|
7601
7906
|
},
|
|
7602
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
7907
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
7603
7908
|
this.bindSceneCasterMaterial(material);
|
|
7604
7909
|
this.setSkinPalette(palette);
|
|
7910
|
+
if (cloth !== undefined) {
|
|
7911
|
+
this.setCloth(cloth.binding, cloth.particles);
|
|
7912
|
+
}
|
|
7605
7913
|
this.drawMesh(mesh, model);
|
|
7606
7914
|
/* Put back, or the next rigid draw in the replay skins by whatever this one left bound. */
|
|
7607
7915
|
this.setSkinPalette(null);
|
|
7916
|
+
this.setCloth(null);
|
|
7608
7917
|
},
|
|
7609
7918
|
instanced: (batch, data, material) => {
|
|
7610
7919
|
this.bindSceneCasterMaterial(material);
|
|
@@ -8354,14 +8663,20 @@ export class WebGPURenderer {
|
|
|
8354
8663
|
/* Slot `k` caches at `k + 1`; index 0 is the morphed draw that is not skinned and has no
|
|
8355
8664
|
slot of its own. See `skinnedGroups`. */
|
|
8356
8665
|
const at = skinned ? this.skinPaletteSlot + 1 : 0;
|
|
8666
|
+
/* A skinned group carries the cloth's views, or the stand-in's: see `clothViews`. */
|
|
8667
|
+
const cloth = skinned ? this.clothViews() : null;
|
|
8357
8668
|
if ((this.skinnedGroups[at] ?? null) === null ||
|
|
8358
8669
|
this.skinnedGroupsBeside[at] !== this.bindGroup ||
|
|
8359
8670
|
this.skinnedGroupsPalette[at] !== palette ||
|
|
8360
|
-
this.skinnedGroupsDeltas[at] !== deltas
|
|
8361
|
-
this.
|
|
8671
|
+
this.skinnedGroupsDeltas[at] !== deltas ||
|
|
8672
|
+
this.skinnedGroupsCloth[at] !== (cloth?.particles ?? null) ||
|
|
8673
|
+
this.skinnedGroupsClothBinding[at] !== (cloth?.binding ?? null)) {
|
|
8674
|
+
this.skinnedGroups[at] = createFlatBindGroup(this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, this.perDraw.buffer, this.perFrame.buffer, this.flatTextures, palette, deltas, cloth);
|
|
8362
8675
|
this.skinnedGroupsBeside[at] = this.bindGroup;
|
|
8363
8676
|
this.skinnedGroupsPalette[at] = palette;
|
|
8364
8677
|
this.skinnedGroupsDeltas[at] = deltas;
|
|
8678
|
+
this.skinnedGroupsCloth[at] = cloth?.particles ?? null;
|
|
8679
|
+
this.skinnedGroupsClothBinding[at] = cloth?.binding ?? null;
|
|
8365
8680
|
}
|
|
8366
8681
|
return this.skinnedGroups[at];
|
|
8367
8682
|
}
|
|
@@ -8430,16 +8745,19 @@ export class WebGPURenderer {
|
|
|
8430
8745
|
const normalMap = this.normalMap;
|
|
8431
8746
|
const ormMap = this.ormMap;
|
|
8432
8747
|
const emissiveMap = this.emissiveMap;
|
|
8748
|
+
const modelMap = this.modelMap;
|
|
8433
8749
|
this.albedo = null;
|
|
8434
8750
|
this.normalMap = null;
|
|
8435
8751
|
this.ormMap = null;
|
|
8436
8752
|
this.emissiveMap = null;
|
|
8753
|
+
this.modelMap = null;
|
|
8437
8754
|
this.blankAlbedoBindGroup = this.buildFlatBindGroup();
|
|
8438
8755
|
this.albedo = albedo;
|
|
8439
8756
|
this.normalMap = normalMap;
|
|
8440
8757
|
this.ormMap = ormMap;
|
|
8441
8758
|
this.emissiveMap = emissiveMap;
|
|
8442
|
-
this.
|
|
8759
|
+
this.modelMap = modelMap;
|
|
8760
|
+
this.bindGroup = this.flatGroupForMaps(this.albedo, this.normalMap, this.ormMap, this.emissiveMap, this.modelMap);
|
|
8443
8761
|
/* The open material lives in a slot of a buffer that no longer exists. */
|
|
8444
8762
|
this.materials.dirty();
|
|
8445
8763
|
}
|
|
@@ -8477,13 +8795,14 @@ export class WebGPURenderer {
|
|
|
8477
8795
|
if (this.albedo === null &&
|
|
8478
8796
|
this.normalMap === null &&
|
|
8479
8797
|
this.ormMap === null &&
|
|
8480
|
-
this.emissiveMap === null
|
|
8798
|
+
this.emissiveMap === null &&
|
|
8799
|
+
this.modelMap === null) {
|
|
8481
8800
|
this.blankAlbedoBindGroup = this.buildFlatBindGroup();
|
|
8482
8801
|
this.bindGroupBudget.ask();
|
|
8483
8802
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
8484
8803
|
return;
|
|
8485
8804
|
}
|
|
8486
|
-
this.bindGroup = this.flatGroupForMaps(this.albedo, this.normalMap, this.ormMap, this.emissiveMap);
|
|
8805
|
+
this.bindGroup = this.flatGroupForMaps(this.albedo, this.normalMap, this.ormMap, this.emissiveMap, this.modelMap);
|
|
8487
8806
|
}
|
|
8488
8807
|
/**
|
|
8489
8808
|
* The sky, as one triangle covering the screen with the world reconstructed per fragment.
|
|
@@ -9142,6 +9461,9 @@ export class WebGPURenderer {
|
|
|
9142
9461
|
* the only moment they can land; everything else keeps the path it has.
|
|
9143
9462
|
*/
|
|
9144
9463
|
recordBlended(reads, lands = this.drawsLate()) {
|
|
9464
|
+
/* Skin is spread and added before anything blended lands over it. See `runSkinScatter`. */
|
|
9465
|
+
if (this.skinScatter !== null && !this.skinScattered)
|
|
9466
|
+
this.runSkinScatter();
|
|
9145
9467
|
const late = this.latePass;
|
|
9146
9468
|
if (late !== null && lands)
|
|
9147
9469
|
return late.take();
|
|
@@ -9207,6 +9529,14 @@ export class WebGPURenderer {
|
|
|
9207
9529
|
const dynamic = mesh.motion;
|
|
9208
9530
|
const rows = dynamic?.previous ?? null;
|
|
9209
9531
|
const rewritten = rows !== null && dynamic !== null && dynamic.changed === this.moverFrame;
|
|
9532
|
+
const cloth = this.cloth;
|
|
9533
|
+
if (cloth !== null &&
|
|
9534
|
+
this.skinPaletteSlot >= 0 &&
|
|
9535
|
+
mesh.isSkinned &&
|
|
9536
|
+
mesh.skinOffsets !== null) {
|
|
9537
|
+
this.recordClothMotion(mesh, model, previous, cloth);
|
|
9538
|
+
return;
|
|
9539
|
+
}
|
|
9210
9540
|
if (previous === null) {
|
|
9211
9541
|
if (rewritten)
|
|
9212
9542
|
this.motionPass?.recordDynamic(mesh, model, model, rows);
|
|
@@ -9251,6 +9581,41 @@ export class WebGPURenderer {
|
|
|
9251
9581
|
return;
|
|
9252
9582
|
this.motionPass?.recordSkinned(mesh, model, mover.previousModel, now, was);
|
|
9253
9583
|
}
|
|
9584
|
+
/**
|
|
9585
|
+
* A cloth-bound draw's motion. **Recorded even where the caller named no previous place**, as a
|
|
9586
|
+
* rewritten mesh's is: the particles carry their own past, and a garment swinging on a character
|
|
9587
|
+
* standing still is motion nobody else can see. Where the rig is a `Mover` its last pose is taken
|
|
9588
|
+
* as a skinned mover's is; otherwise the rig is taken not to have moved.
|
|
9589
|
+
*/
|
|
9590
|
+
recordClothMotion(mesh, model, previous, cloth) {
|
|
9591
|
+
const now = this.skinPalettes.view(this.skinPaletteSlot);
|
|
9592
|
+
if (now === null)
|
|
9593
|
+
return;
|
|
9594
|
+
let was = now;
|
|
9595
|
+
let wasModel = model;
|
|
9596
|
+
if (previous !== null && isMover(previous)) {
|
|
9597
|
+
const verdict = advanceMover(previous, this.moverRenderer, this.moverFrame, model, this.skinPaletteData);
|
|
9598
|
+
if (verdict === MOVER_TWICE) {
|
|
9599
|
+
this.warnMoverTwice();
|
|
9600
|
+
return;
|
|
9601
|
+
}
|
|
9602
|
+
if (verdict === MOVER_MOTION) {
|
|
9603
|
+
const slot = this.skinPalettes.take(this.surface.device, previous.previousPalette);
|
|
9604
|
+
was = slot === null ? null : this.skinPalettes.view(slot);
|
|
9605
|
+
wasModel = previous.previousModel;
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
else if (previous !== null) {
|
|
9609
|
+
wasModel = previous;
|
|
9610
|
+
}
|
|
9611
|
+
if (was === null)
|
|
9612
|
+
return;
|
|
9613
|
+
const particlesWas = cloth.particles.previousAt(this.moverFrame);
|
|
9614
|
+
/* Nothing said it moved and nothing did: the camera's motion, derived from depth, is the answer. */
|
|
9615
|
+
if (previous === null && particlesWas === cloth.particles.current)
|
|
9616
|
+
return;
|
|
9617
|
+
this.motionPass?.recordCloth(mesh, model, wasModel, now, was, cloth.binding.bindingView, cloth.particles.current, particlesWas);
|
|
9618
|
+
}
|
|
9254
9619
|
warnMoverTwice() {
|
|
9255
9620
|
if (this.warnedMoverTwice)
|
|
9256
9621
|
return;
|
|
@@ -9583,7 +9948,7 @@ export class WebGPURenderer {
|
|
|
9583
9948
|
label: 'post.ssr.resolve',
|
|
9584
9949
|
colorAttachments: [{ view: scene, loadOp: 'load', storeOp: 'store' }],
|
|
9585
9950
|
});
|
|
9586
|
-
resolve.setPipeline(postPipeline(this.pipelines, device, this.ssrResolveLayout, 'post.ssrResolve', SSR_RESOLVE_FRAG_WGSL, this.pipelines.format,
|
|
9951
|
+
resolve.setPipeline(postPipeline(this.pipelines, device, this.ssrResolveLayout, 'post.ssrResolve', SSR_RESOLVE_FRAG_WGSL, this.pipelines.format, REFLECTION_OVER_BLEND));
|
|
9587
9952
|
resolve.setBindGroup(0, resolveGroup);
|
|
9588
9953
|
resolve.draw(3);
|
|
9589
9954
|
resolve.end();
|
|
@@ -10173,6 +10538,106 @@ export class WebGPURenderer {
|
|
|
10173
10538
|
this.refractSnapshotTaken = true;
|
|
10174
10539
|
return true;
|
|
10175
10540
|
}
|
|
10541
|
+
/**
|
|
10542
|
+
* Whether a skin draw now draws its two halves: the profile asked, the frame has not spread its
|
|
10543
|
+
* skin yet, and this is the frame's own picture — not a mirror's, a probe's, an overlay after
|
|
10544
|
+
* the frame, or an order-independent set, none of which the spread reads.
|
|
10545
|
+
*/
|
|
10546
|
+
skinSplits() {
|
|
10547
|
+
return (this.skinScatter !== null &&
|
|
10548
|
+
!this.skinScattered &&
|
|
10549
|
+
!this.reflectionPassActive &&
|
|
10550
|
+
!this.probePassActive &&
|
|
10551
|
+
!this.framePresented &&
|
|
10552
|
+
!this.overlayActive &&
|
|
10553
|
+
this.oitMode === 'none');
|
|
10554
|
+
}
|
|
10555
|
+
/**
|
|
10556
|
+
* A skin draw's diffuse half, kept for the spread: the frame's draw again, with the same slots,
|
|
10557
|
+
* buffers and bind group, through the pipeline that writes the diffuse alone into the blur's
|
|
10558
|
+
* target and tests for the depth its frame half wrote.
|
|
10559
|
+
*/
|
|
10560
|
+
keepSkinDiffuse(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, slot, material) {
|
|
10561
|
+
const skin = this.skinScatter;
|
|
10562
|
+
const pipelines = this.skinPipelines;
|
|
10563
|
+
const present = this.meshPresent.get(base);
|
|
10564
|
+
if (skin === null || pipelines === null || present === undefined)
|
|
10565
|
+
return;
|
|
10566
|
+
const key = `${frameKey}|sd`;
|
|
10567
|
+
const pipeline = pipelines.peek(key) ??
|
|
10568
|
+
flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, false, 'none', this.materialDoubleSided, false, eight, clothed, 'skin', 'diffuse');
|
|
10569
|
+
const command = skin.take();
|
|
10570
|
+
command.pipeline = pipeline;
|
|
10571
|
+
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
10572
|
+
command.offsetA = slot;
|
|
10573
|
+
command.offsetB = material;
|
|
10574
|
+
command.offsetCount = 2;
|
|
10575
|
+
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
10576
|
+
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
10577
|
+
}
|
|
10578
|
+
command.vertexCount = mesh.vertexBuffers.length;
|
|
10579
|
+
command.indexBuffer = mesh.indexBuffer;
|
|
10580
|
+
command.indexed = true;
|
|
10581
|
+
command.count = mesh.indexCount;
|
|
10582
|
+
command.instances = 1;
|
|
10583
|
+
command.indirect = null;
|
|
10584
|
+
}
|
|
10585
|
+
/**
|
|
10586
|
+
* Spread the frame's skin and add it back: once, at the first blended draw or before the frame's
|
|
10587
|
+
* last flush. **A fifth instance of the boundary `takeRefractSnapshot` describes** — flush, end
|
|
10588
|
+
* the pass, work between passes, reopen with `loadOp: 'load'` — and refused where that one is,
|
|
10589
|
+
* for its reasons. Between the passes: the kept diffuse halves into their target against the
|
|
10590
|
+
* frame's depth, the depth resolved, and `SkinScatterPass.spread` across and then down into the
|
|
10591
|
+
* frame's colour.
|
|
10592
|
+
*/
|
|
10593
|
+
runSkinScatter() {
|
|
10594
|
+
const skin = this.skinScatter;
|
|
10595
|
+
if (skin === null || this.skinScattered)
|
|
10596
|
+
return;
|
|
10597
|
+
/* Latched whether or not skin was kept: a skin drawn after this point is the whole surface. */
|
|
10598
|
+
this.skinScattered = true;
|
|
10599
|
+
if (!skin.pending)
|
|
10600
|
+
return;
|
|
10601
|
+
if (this.reflectionPassActive || this.probePassActive || this.oitReplaying)
|
|
10602
|
+
return;
|
|
10603
|
+
const encoder = this.encoder;
|
|
10604
|
+
const depth = this.depthView;
|
|
10605
|
+
const resolved = this.resolvedDepthView;
|
|
10606
|
+
const target = this.compositeTarget();
|
|
10607
|
+
const projection = this.frameProjection;
|
|
10608
|
+
if (encoder === null || depth === null || resolved === null)
|
|
10609
|
+
return;
|
|
10610
|
+
if (target === null || projection === null)
|
|
10611
|
+
return;
|
|
10612
|
+
if (this.pass === null && this.openPass() === null)
|
|
10613
|
+
return;
|
|
10614
|
+
if (this.pass === null)
|
|
10615
|
+
return;
|
|
10616
|
+
if (this.quality.frameGraph)
|
|
10617
|
+
this.flushGraph();
|
|
10618
|
+
this.pass.end();
|
|
10619
|
+
const size = this.sceneSize();
|
|
10620
|
+
skin.size(size.width, size.height, this.samples);
|
|
10621
|
+
skin.drawDiffuse(encoder, depth, this.issueBound, this.gpuTimer.writesFor());
|
|
10622
|
+
this.resolveDepth(encoder);
|
|
10623
|
+
mat4.invert(this.skinInverseProjection, projection);
|
|
10624
|
+
const multisampled = this.compositeMsaa();
|
|
10625
|
+
skin.spread(encoder, this.pipelines, resolved, multisampled ?? target, multisampled === null ? null : target, this.samples, projection, this.skinInverseProjection, () => this.gpuTimer.writesFor());
|
|
10626
|
+
this.pass = encoder.beginRenderPass({
|
|
10627
|
+
label: 'skin.scattered',
|
|
10628
|
+
timestampWrites: this.gpuTimer.writesFor(),
|
|
10629
|
+
colorAttachments: [
|
|
10630
|
+
{
|
|
10631
|
+
view: multisampled ?? target,
|
|
10632
|
+
resolveTarget: multisampled !== null ? target : undefined,
|
|
10633
|
+
/* Loaded, not cleared: the frame so far, with its skin added. */
|
|
10634
|
+
loadOp: 'load',
|
|
10635
|
+
storeOp: resolvedStoreOp(multisampled !== null, false, this.quality.discardResolvedAttachments),
|
|
10636
|
+
},
|
|
10637
|
+
],
|
|
10638
|
+
depthStencilAttachment: { view: depth, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
10639
|
+
});
|
|
10640
|
+
}
|
|
10176
10641
|
takeVolumeDepth() {
|
|
10177
10642
|
/*
|
|
10178
10643
|
* **Never while the mirror is open, and this is a correctness fix rather than a saving.**
|
|
@@ -10795,6 +11260,8 @@ export class WebGPURenderer {
|
|
|
10795
11260
|
* `beginFrame` resets it. Silent, and exactly the kind of loss the arena is designed
|
|
10796
11261
|
* against everywhere else.
|
|
10797
11262
|
*/
|
|
11263
|
+
/* Before the last flush, which may let the depth go that the spread still reads. */
|
|
11264
|
+
this.runSkinScatter();
|
|
10798
11265
|
this.flushGraph('boundary', this.liveOutFinalFlush());
|
|
10799
11266
|
/*
|
|
10800
11267
|
* `ensurePass` rather than a null check: the frame's pass is opened on demand now, so a
|
|
@@ -13612,6 +14079,7 @@ export class WebGPURenderer {
|
|
|
13612
14079
|
* the 2026-08-13 rule asks for — one decision, bound twice.
|
|
13613
14080
|
*/
|
|
13614
14081
|
dispose() {
|
|
14082
|
+
this.skinScatter?.dispose();
|
|
13615
14083
|
this.motionPass?.dispose();
|
|
13616
14084
|
this.motionPass = null;
|
|
13617
14085
|
this.latePass?.dispose();
|
|
@@ -13651,3 +14119,11 @@ export class WebGPURenderer {
|
|
|
13651
14119
|
this.surface.dispose();
|
|
13652
14120
|
}
|
|
13653
14121
|
}
|
|
14122
|
+
/**
|
|
14123
|
+
* The views a binding and a character's particles bind as, built once a pair and kept on the
|
|
14124
|
+
* particles: a bind group is cached by its views' identity, so a fresh object a draw would rebuild
|
|
14125
|
+
* the group every draw.
|
|
14126
|
+
*/
|
|
14127
|
+
function clothViewsOf(binding, particles) {
|
|
14128
|
+
return particles.viewsWith(binding);
|
|
14129
|
+
}
|