@driftengine/core 4.8.7 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +123 -11
- package/README.md +43 -38
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -3
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +19 -4
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +180 -4
- package/dist/render/backend/webgl2/renderer.js +550 -40
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +19 -4
- package/dist/render/backend/webgpu/buffers.js +18 -11
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
- package/dist/render/backend/webgpu/drawCommand.js +10 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
- package/dist/render/backend/webgpu/flatPass.js +191 -43
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
- package/dist/render/backend/webgpu/pipelineCache.js +7 -0
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +283 -37
- package/dist/render/backend/webgpu/renderer.js +1363 -202
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +241 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- package/dist/render/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +5 -0
- package/dist/render/mesh.js +6 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +39 -8
- package/dist/render/renderQuality.js +4 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +24 -1
- package/dist/render/screenSpaceReflection.d.ts +7 -1
- package/dist/render/screenSpaceReflection.js +5 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +2 -0
- package/dist/render/shaders/flat/index.d.ts +16 -0
- package/dist/render/shaders/flat/index.js +33 -5
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +43 -5
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +3 -3
- package/dist/render/shaders/flat/preamble.js +38 -24
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
- package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
- 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 +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
- package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/water.wgsl.js +1 -1
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +26 -0
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +106 -1
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +57 -7
- package/dist/render/surfaceTexture.js +125 -17
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +24 -2
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +22 -3
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +672 -37
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +42 -16
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/drawCommand.ts +22 -4
- package/src/render/backend/webgpu/flatPass.ts +250 -40
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/pipelineCache.ts +7 -0
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +1655 -230
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +363 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/displayRange.ts +61 -0
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/glCompressed.ts +83 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +6 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +44 -8
- package/src/render/sceneTarget.ts +30 -2
- package/src/render/screenSpaceReflection.ts +7 -1
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/glassTint.ts +5 -1
- package/src/render/shaders/flat/index.ts +49 -5
- package/src/render/shaders/flat/main.ts +43 -5
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +3 -3
- package/src/render/shaders/flat/preamble.ts +38 -24
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
- package/src/render/shaders/generated/water.wgsl.ts +1 -1
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +26 -0
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/ssrPass.ts +138 -1
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +161 -27
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
|
@@ -59,17 +59,23 @@ import { sceneCaptureTexels } from '../../sceneCapture.js';
|
|
|
59
59
|
import { gpuCompressedFormats } from './compressedUpload.js';
|
|
60
60
|
import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.js';
|
|
61
61
|
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
|
|
62
|
+
import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
|
|
63
|
+
import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
|
|
64
|
+
import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.js';
|
|
62
65
|
import { validateClothBinding } from '../../clothBindingData.js';
|
|
63
66
|
import { createSdfTextBindGroup, createSdfTextBindGroupLayout, GpuSdfText, sdfTextPipeline, SDF_TEXT_FRAG_FIELDS, SDF_TEXT_FRAG_SIZE, SDF_TEXT_VERT_FIELDS, SDF_TEXT_VERT_SIZE, } from './sdfTextPass.js';
|
|
64
67
|
import { createFilmBindGroup, createFilmBindGroupLayout, filmPipeline, FILM_FRAG_FIELDS, FILM_FRAG_SIZE, FILM_VERT_FIELDS, FILM_VERT_SIZE, } from './filmPass.js';
|
|
65
68
|
import { reflectionTargetSize } from '../../planarReflectionDraw.js';
|
|
66
69
|
import { drawingBufferSize } from '../../drawingBuffer.js';
|
|
67
|
-
import { MOTION_BLUR_MAX_UV, OUTPUT_TRANSFORM_CODE, RUSH_REACH_UV } from '../../vertexDefaults.js';
|
|
70
|
+
import { MOTION_BLUR_MAX_UV, OBJECT_BLUR_UNKEPT, OUTPUT_TRANSFORM_CODE, RUSH_REACH_UV, clampMotionShare, forwardTransformCode, } from '../../vertexDefaults.js';
|
|
71
|
+
import { FRINGE_FLOATS, resolveFringe } from '../../shaders/fringe.js';
|
|
72
|
+
import { displayHeadroom } from '../../displayRange.js';
|
|
73
|
+
import { DEFAULT_FILMIC_CURVE, FILMIC_CLAMPED, FILMIC_CONSTANTS, FILMIC_WITHOUT_COMPOSITE, resolveFilmicCurve, } from '../../filmicCurve.js';
|
|
68
74
|
import { ExposurePass } from './exposurePass.js';
|
|
69
75
|
import { AO_STORE } from '../../shaders/ambientOcclusion.js';
|
|
70
76
|
import { clampAutoExposure } from '../../shaders/exposure.js';
|
|
71
77
|
import { clampLocalExposure } from '../../shaders/localExposure.js';
|
|
72
|
-
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, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, TINTED_ADD_BLEND, 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';
|
|
78
|
+
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, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, TINTED_ADD_BLEND, 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, FRAME_REFLECTION_BLEND, REFLECTION_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_MATERIAL_FIELDS, SSR_MATERIAL_RESOLVE_FRAG_WGSL, SSR_MATERIAL_SIZE, SSR_MATERIAL_TEXTURES, SSR_MATERIAL_UNIFORMS, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
|
|
73
79
|
import { GRADE_PLACEHOLDER_SIZE, identityGradeLut, validateGradeLut, } from '../../colourGrade.js';
|
|
74
80
|
import { FILM_LOOK_WITHOUT_COMPOSITE, clampGrain, clampVignette, grainSeed, } from '../../shaders/filmLook.js';
|
|
75
81
|
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';
|
|
@@ -91,10 +97,15 @@ import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js'
|
|
|
91
97
|
import { GlassCasterList } from '../../glassCasters.js';
|
|
92
98
|
import { GpuGlassTint } from './glassTintPass.js';
|
|
93
99
|
import { SUN_DYNAMIC_LAYER, SUN_GLASS_MOVING_LAYER, SUN_GLASS_STATIC_LAYER, SUN_PEELED_LAYER, SUN_STATIC_LAYER, } from '../../shadowMap.js';
|
|
94
|
-
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental } from './buffers.js';
|
|
100
|
+
import { VERTEX_LAYOUT, createGpuMesh, createGpuMeshIncremental, } from './buffers.js';
|
|
95
101
|
import { SkinPaletteRing } from './skinPaletteRing.js';
|
|
96
|
-
import { DEPTH_FORMAT, FLAT_VERT_FIELDS, FLAT_VERT_SIZE, createFlatBindGroup, createFlatBindGroupLayout,
|
|
97
|
-
import {
|
|
102
|
+
import { DEPTH_FORMAT, FLAT_ANIMATED_VERT_FIELDS, FLAT_VERT_FIELDS, FLAT_VERT_SIZE, FLAT_VIEW_FIELDS, FLAT_VIEW_SIZE, createFlatBindGroup, createFlatBindGroupLayout, flatFragmentScratch, flatVariant, flatVertexBindings, flatPipeline, flatPipelineAsync, probeStageCeilings, } from './flatPass.js';
|
|
103
|
+
import { GpuBoneAnimation } from './boneAnimations.js';
|
|
104
|
+
import { packBoneAnimation } from '../../boneAnimation.js';
|
|
105
|
+
import { MaterialStore, hashWords } from './materialStore.js';
|
|
106
|
+
import { GpuStaticDraws, encodeBundle, takeRecorded } from './staticBundles.js';
|
|
107
|
+
import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.js';
|
|
108
|
+
import { DEPTH_FRAG_BINDING, DEPTH_FRAG_SIZE, DEPTH_FRAG_WGSL, DEPTH_PREVIOUS_BINDING, DEPTH_CUTOUT_VERT_FIELDS, DEPTH_CUTOUT_VERT_SIZE, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS, DEPTH_INSTANCED_ANIMATED_VERT_FIELDS, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS, DEPTH_VERT_FIELDS, DEPTH_VERT_SIZE, SHADOW_FORMAT, createDepthBindGroup, createDepthBindGroupLayout, depthPipeline, } from './depthPass.js';
|
|
98
109
|
import { PipelineCache } from './pipelineCache.js';
|
|
99
110
|
import { SKIN_TARGET_FORMAT, SkinScatterPass } from './skinScatterPass.js';
|
|
100
111
|
import { SKY_FIELDS, SKY_UNIFORM_SIZE, createSkyBindGroup, createSkyBindGroupLayout, skyPipeline, } from './skyPass.js';
|
|
@@ -215,34 +226,20 @@ const MAX_SKY_DRAWS = 64;
|
|
|
215
226
|
/**
|
|
216
227
|
* Whether the environment probe's permutation fits this device's per-stage binding limits.
|
|
217
228
|
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
229
|
+
* **Two ceilings, counted separately**: sampled textures and samplers, since textures that share a
|
|
230
|
+
* sampler (`// wgsl:share` in `flat/preamble.ts`) count once against the second. The widest variant
|
|
231
|
+
* binds sixteen textures and twelve samplers, which is exactly WebGPU's default texture ceiling, and
|
|
232
|
+
* `select.ts` asks for the adapter's own — 48 here — so the probe does not depend on that request.
|
|
222
233
|
*/
|
|
223
234
|
function probeFits(device) {
|
|
224
235
|
/*
|
|
225
|
-
* **
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* it
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
*
|
|
233
|
-
* It used to declare seventeen *samplers* too, and that was the binding constraint: this
|
|
234
|
-
* adapter offers exactly sixteen and no request can raise it, so the probe stood down and
|
|
235
|
-
* reflective surfaces kept a gradient. The shadow bindings share one declaration — see
|
|
236
|
-
* `// wgsl:share shadow` in `flat/preamble.ts`, which costs nothing because they were already
|
|
237
|
-
* one object — so the count is four, and the fourth is the normal map's: it is a colour image
|
|
238
|
-
* and wants filtering, which a non-filtering shadow sampler cannot give it.
|
|
239
|
-
*
|
|
240
|
-
* **Counted from the generated bindings rather than typed**: `FLAT_BINDINGS.flatFrag`'s
|
|
241
|
-
* widest variant is `directionalShadows+environmentProbe+pointShadows`, seven textures and
|
|
242
|
-
* four samplers. A stale number here reads as a device declining a feature it can run.
|
|
243
|
-
*/
|
|
244
|
-
const TEXTURES = 7;
|
|
245
|
-
const SAMPLERS = 4;
|
|
236
|
+
* **Counted from the generated bindings rather than typed** (`probeStageCeilings`). It was typed
|
|
237
|
+
* here as seven textures and four samplers while the widest variant grew to sixteen and twelve
|
|
238
|
+
* with the material maps, glass, the fixture atlas and DriftLight: a stale number reads as a
|
|
239
|
+
* device declining a feature it can run, or, the way this one had drifted, as one admitted to a
|
|
240
|
+
* permutation it cannot bind.
|
|
241
|
+
*/
|
|
242
|
+
const { textures: TEXTURES, samplers: SAMPLERS } = probeStageCeilings();
|
|
246
243
|
const fits = device.limits.maxSampledTexturesPerShaderStage >= TEXTURES &&
|
|
247
244
|
device.limits.maxSamplersPerShaderStage >= SAMPLERS;
|
|
248
245
|
if (!fits) {
|
|
@@ -279,6 +276,7 @@ const VERB_PASS = 3;
|
|
|
279
276
|
/** What the depth snapshot copies from, and what it copies into. */
|
|
280
277
|
const DEPTH_SOURCE = maskOf('sceneDepth');
|
|
281
278
|
const DEPTH_SNAPSHOT = maskOf('depthSnapshot');
|
|
279
|
+
const COLOR_SNAPSHOT = maskOf('colorSnapshot');
|
|
282
280
|
/** The mirror's own colour and depth. */
|
|
283
281
|
const MIRROR_TARGET = maskOf('mirrorColor', 'mirrorDepth');
|
|
284
282
|
/**
|
|
@@ -366,6 +364,13 @@ const MAX_WATER_BODIES_PER_FRAME = 16;
|
|
|
366
364
|
* numbers, because that is precisely the defect the ring exists to end.
|
|
367
365
|
*/
|
|
368
366
|
const MAX_BATCHES_PER_FRAME = 8;
|
|
367
|
+
/**
|
|
368
|
+
* Mesh passes a frame may open before the ring grows: the frame's own, a mirror's, a capture's, and
|
|
369
|
+
* a probe face's, which gives its slot back once submitted. A frame past it loses the draws of the
|
|
370
|
+
* passes that found no slot, counted on the `materials` line, and the ring is grown for the next.
|
|
371
|
+
* Each slot is the pass's block, 3,328 to 6,656 bytes by variant, so the ring is 213 to 426 KB.
|
|
372
|
+
*/
|
|
373
|
+
const PASSES_PER_FRAME = 64;
|
|
369
374
|
/** Buffer usage flags, from the specification. See `buffers.ts` for why they are not read off the global. */
|
|
370
375
|
const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
|
|
371
376
|
/**
|
|
@@ -866,6 +871,8 @@ export class WebGPURenderer {
|
|
|
866
871
|
skinnedBindGroupLayout;
|
|
867
872
|
morphedBindGroupLayout;
|
|
868
873
|
bothBindGroupLayout;
|
|
874
|
+
/** The instanced layout of a batch playing a bone animation. See `animatedGroup`. */
|
|
875
|
+
animatedBindGroupLayout;
|
|
869
876
|
/** The morph weights the following draws use, or null for none. */
|
|
870
877
|
morphWeights = null;
|
|
871
878
|
/**
|
|
@@ -878,14 +885,15 @@ export class WebGPURenderer {
|
|
|
878
885
|
* group per character per frame — an allocation in the frame loop, which `AGENTS.md` forbids.
|
|
879
886
|
* Keyed by slot instead, the groups are built once and reused for the life of the renderer.
|
|
880
887
|
*/
|
|
888
|
+
/*
|
|
889
|
+
* **And by material within a slot**, keyed by the unskinned group each was built beside. One
|
|
890
|
+
* group a slot was rebuilt whenever the material beside it changed, so a character drawn in
|
|
891
|
+
* thirty-odd sections, each its own material, built one a section a frame — about ninety a frame
|
|
892
|
+
* for two characters, reported by a consumer. A slot's map holds one entry a material its rigs
|
|
893
|
+
* are drawn in, and is emptied wherever the unskinned groups are, whose identities it is keyed
|
|
894
|
+
* by.
|
|
895
|
+
*/
|
|
881
896
|
skinnedGroups = [];
|
|
882
|
-
/** The unskinned group each was built beside, for identity-based invalidation. */
|
|
883
|
-
skinnedGroupsBeside = [];
|
|
884
|
-
skinnedGroupsPalette = [];
|
|
885
|
-
skinnedGroupsDeltas = [];
|
|
886
|
-
/** The cloth views each skinned twin was built with, compared by identity like the palette. */
|
|
887
|
-
skinnedGroupsCloth = [];
|
|
888
|
-
skinnedGroupsClothBinding = [];
|
|
889
897
|
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
890
898
|
cloth = null;
|
|
891
899
|
clothStandCache = null;
|
|
@@ -953,22 +961,51 @@ export class WebGPURenderer {
|
|
|
953
961
|
/** Every distinct cache above, so a mesh's pipelines are built for each. One entry when aliased. */
|
|
954
962
|
pipelineTargets;
|
|
955
963
|
bindGroupLayout;
|
|
956
|
-
/** One slot per draw, uploaded once a frame. See `UniformRing`. */
|
|
964
|
+
/** One slot per draw, uploaded once a frame. See `UniformRing`. Swapped while a static list records. */
|
|
957
965
|
perDraw;
|
|
958
|
-
/** The lights, fog and camera, settled once per frame by `bindMeshPass`. */
|
|
959
|
-
perFrame;
|
|
960
966
|
/**
|
|
961
|
-
* The
|
|
967
|
+
* The lights, fog and camera, a slot per mesh pass a frame opens: `bindMeshPass` writes them, and
|
|
968
|
+
* the first draw after it takes a slot. A ring rather than one buffer because a frame opens more
|
|
969
|
+
* than one pass — a mirror's, a capture's, a probe face's — and a queue write does not interleave
|
|
970
|
+
* with the draws recorded between them. See `UniformRing`.
|
|
971
|
+
*/
|
|
972
|
+
passRing;
|
|
973
|
+
/** Each material's block, kept across frames for as long as it does not change. */
|
|
974
|
+
materialStore;
|
|
975
|
+
/**
|
|
976
|
+
* Both blocks as the CPU holds them, the pass's then the material's, patched by name and copied
|
|
977
|
+
* into a slot by the next draw after something changed them. See `FlatFragmentScratch`.
|
|
962
978
|
*
|
|
963
979
|
* Authoritative between draws: a setter changes one field here and marks the block dirty, and
|
|
964
|
-
* the next `drawMesh`
|
|
965
|
-
*
|
|
980
|
+
* the next `drawMesh` places it. That keeps a setter free of any knowledge of slots, which is
|
|
981
|
+
* what lets it be called anywhere a scene wants.
|
|
966
982
|
*/
|
|
967
983
|
perFrameStaging;
|
|
968
984
|
perFrameFloats;
|
|
969
985
|
perFrameInts;
|
|
986
|
+
/** The two halves of the scratch, as the words a slot is written from. */
|
|
987
|
+
passInts;
|
|
988
|
+
materialInts;
|
|
970
989
|
/** The slot the open material occupies, and whether the next draw must take a new one. */
|
|
971
990
|
materials = new MaterialChanges();
|
|
991
|
+
/** The same for the pass's block: reopened by `bindMeshPass` and by nothing a material sets. */
|
|
992
|
+
passBlock = new MaterialChanges();
|
|
993
|
+
/**
|
|
994
|
+
* The vertex stage's view block — the camera, the light's matrix, the wind — a slot a pass, and a
|
|
995
|
+
* second, unjittered, for a blended draw landing after the upscale. Reopened with the pass, and by
|
|
996
|
+
* `setWind` and `beginShadowPass`, which change what it holds.
|
|
997
|
+
*/
|
|
998
|
+
viewRing;
|
|
999
|
+
viewBlock = new MaterialChanges();
|
|
1000
|
+
lateViewBlock = new MaterialChanges();
|
|
1001
|
+
/** The view slot the draw being submitted bound, read by the halves it keeps for later passes. */
|
|
1002
|
+
drawViewOffset = 0;
|
|
1003
|
+
/** Pass slots asked for and refused this frame, and materials refused: what grows the two. */
|
|
1004
|
+
passesAsked = 0;
|
|
1005
|
+
passesRefused = 0;
|
|
1006
|
+
viewsAsked = 0;
|
|
1007
|
+
viewsRefused = 0;
|
|
1008
|
+
materialsRefused = 0;
|
|
972
1009
|
/*
|
|
973
1010
|
* The frame graph's recording, and the machinery that replays it.
|
|
974
1011
|
*
|
|
@@ -1221,7 +1258,7 @@ export class WebGPURenderer {
|
|
|
1221
1258
|
variant;
|
|
1222
1259
|
/** Whether this profile compiled the point-shadow path, and so has sentinels to write. */
|
|
1223
1260
|
pointShadowsCompiled;
|
|
1224
|
-
/** What the generator recorded about that permutation
|
|
1261
|
+
/** What the generator recorded about that permutation, as the renderer's scratch lays it out. */
|
|
1225
1262
|
fragment;
|
|
1226
1263
|
depth = null;
|
|
1227
1264
|
depthView = null;
|
|
@@ -1280,6 +1317,17 @@ export class WebGPURenderer {
|
|
|
1280
1317
|
*/
|
|
1281
1318
|
refractSnapshot = null;
|
|
1282
1319
|
refractSnapshotView = null;
|
|
1320
|
+
/**
|
|
1321
|
+
* The frame's colour as a registered pass reads it (`reads: ['colorSnapshot']`): the scene's own
|
|
1322
|
+
* size and format, refilled at each such pass's draw. Allocated only once a pass declares the
|
|
1323
|
+
* read, beside the frame's targets, and never in the frame loop. See `takePassColour`.
|
|
1324
|
+
*/
|
|
1325
|
+
passColour = null;
|
|
1326
|
+
passColourView = null;
|
|
1327
|
+
/** Registered passes that read the colour, queued this frame for after the upscale. */
|
|
1328
|
+
latePasses = [];
|
|
1329
|
+
/** A late pass draws unjittered, as every late draw does. */
|
|
1330
|
+
latePassJitter = new Float32Array(2);
|
|
1283
1331
|
/** Whether this frame has taken it. One snapshot serves every refracting draw. */
|
|
1284
1332
|
refractSnapshotTaken = false;
|
|
1285
1333
|
/**
|
|
@@ -1782,8 +1830,18 @@ export class WebGPURenderer {
|
|
|
1782
1830
|
reflectionQueue = new ReflectionQueue();
|
|
1783
1831
|
ssrLayout;
|
|
1784
1832
|
ssrResolveLayout;
|
|
1833
|
+
/** The frame's materials' reflections, resolved; null where off. See `frameReflections.ts`. */
|
|
1834
|
+
frameReflections;
|
|
1835
|
+
/** Their surface half, null where they are off or cannot be drawn: `reflectionSurfacePass.ts`. */
|
|
1836
|
+
reflectionSurface;
|
|
1837
|
+
/** The material resolve: its layout, its one block (the blur) and its group over the three maps. */
|
|
1838
|
+
ssrMaterialLayout;
|
|
1839
|
+
ssrMaterialUniforms;
|
|
1840
|
+
ssrMaterialFloats = new Float32Array(SSR_MATERIAL_SIZE / 4);
|
|
1841
|
+
ssrMaterialGroup = null;
|
|
1785
1842
|
ssrUniforms;
|
|
1786
|
-
|
|
1843
|
+
/* One slot a surface, and one more for the frame's materials' trace. */
|
|
1844
|
+
ssrStaging = new ArrayBuffer(SSR_SLOT * (MAX_REFLECTIVE_SURFACES + 1));
|
|
1787
1845
|
ssrFloats = new Float32Array(this.ssrStaging);
|
|
1788
1846
|
ssrReflection = null;
|
|
1789
1847
|
ssrReflectionView = null;
|
|
@@ -1920,7 +1978,7 @@ export class WebGPURenderer {
|
|
|
1920
1978
|
recordNode(this.arena, VERB_SCOPE, 0, MIRROR_TARGET, 0, 0);
|
|
1921
1979
|
this.pass?.end();
|
|
1922
1980
|
this.flushRings();
|
|
1923
|
-
this.
|
|
1981
|
+
this.submit([this.encoder.finish()]);
|
|
1924
1982
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'reflection' });
|
|
1925
1983
|
/*
|
|
1926
1984
|
* **Opened on demand, like the frame's own.** Nothing has been recorded into the mirror yet,
|
|
@@ -1978,7 +2036,8 @@ export class WebGPURenderer {
|
|
|
1978
2036
|
this.windStreakFrags,
|
|
1979
2037
|
this.scatterVerts,
|
|
1980
2038
|
this.scatterFrags,
|
|
1981
|
-
this.
|
|
2039
|
+
this.passRing,
|
|
2040
|
+
this.viewRing,
|
|
1982
2041
|
this.filmVerts,
|
|
1983
2042
|
this.filmFrags,
|
|
1984
2043
|
this.insetUniforms,
|
|
@@ -1999,6 +2058,7 @@ export class WebGPURenderer {
|
|
|
1999
2058
|
this.instanceCulls?.flush();
|
|
2000
2059
|
for (const ring of this.rings())
|
|
2001
2060
|
ring.flush();
|
|
2061
|
+
this.materialStore.flush();
|
|
2002
2062
|
}
|
|
2003
2063
|
/** Where every ring stands before work that submits an encoder of its own. */
|
|
2004
2064
|
ringMarks = [];
|
|
@@ -2018,6 +2078,8 @@ export class WebGPURenderer {
|
|
|
2018
2078
|
const rings = this.rings();
|
|
2019
2079
|
for (let i = 0; i < rings.length; i++)
|
|
2020
2080
|
rings[i].rewind(this.ringMarks[i] ?? 0);
|
|
2081
|
+
/* The open pass may sit in a slot just given back, which the next pass would overwrite. */
|
|
2082
|
+
this.reopenPass();
|
|
2021
2083
|
this.skinPalettes.rewind(skinMark);
|
|
2022
2084
|
if (this.skinPaletteSlot >= skinMark)
|
|
2023
2085
|
this.skinPaletteSlot = -1;
|
|
@@ -2263,9 +2325,16 @@ export class WebGPURenderer {
|
|
|
2263
2325
|
camera.updateMatrices(target.width / target.height);
|
|
2264
2326
|
const { device } = this.surface;
|
|
2265
2327
|
const { depth, colorMsaa } = target.attachments(this.samples, this.pipelines.format);
|
|
2266
|
-
|
|
2328
|
+
/*
|
|
2329
|
+
* **Flushed, and only then set aside.** The flush replays what the frame has recorded, and
|
|
2330
|
+
* replaying opens the frame's pass where none is open yet; saved before it, the pass given back
|
|
2331
|
+
* afterwards was the null that stood there, while the one the flush opened was left open on the
|
|
2332
|
+
* frame's encoder and every later pass on it was refused — a black frame for any capture taken
|
|
2333
|
+
* after the frame had drawn something. `scripts/capture-check.mjs` takes one at three points.
|
|
2334
|
+
*/
|
|
2267
2335
|
if (this.quality.frameGraph)
|
|
2268
2336
|
this.flushGraph();
|
|
2337
|
+
const savedPass = this.pass;
|
|
2269
2338
|
const savedEncoder = this.encoder;
|
|
2270
2339
|
this.markRings();
|
|
2271
2340
|
const skinMark = this.skinPalettes.mark();
|
|
@@ -2308,7 +2377,7 @@ export class WebGPURenderer {
|
|
|
2308
2377
|
pass.end();
|
|
2309
2378
|
this.pass = null;
|
|
2310
2379
|
this.flushRings();
|
|
2311
|
-
|
|
2380
|
+
this.submit([encoder.finish()]);
|
|
2312
2381
|
this.rewindRings(skinMark);
|
|
2313
2382
|
this.encoderEpoch = frameEpoch;
|
|
2314
2383
|
this.encoder = savedEncoder;
|
|
@@ -2316,6 +2385,7 @@ export class WebGPURenderer {
|
|
|
2316
2385
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
2317
2386
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
2318
2387
|
this.materials.dirty();
|
|
2388
|
+
this.reopenPass();
|
|
2319
2389
|
this.capturePassActive = false;
|
|
2320
2390
|
}
|
|
2321
2391
|
return true;
|
|
@@ -2331,10 +2401,11 @@ export class WebGPURenderer {
|
|
|
2331
2401
|
updateSurfaceTexture(texture, source) {
|
|
2332
2402
|
if (this.surface.lost)
|
|
2333
2403
|
return;
|
|
2334
|
-
const replaced = texture.update(source);
|
|
2404
|
+
const replaced = texture.update(source, this.compressedFormats);
|
|
2335
2405
|
if (replaced === null)
|
|
2336
2406
|
return;
|
|
2337
|
-
/* A new size is a new view, so every group holding the old one is dropped — see
|
|
2407
|
+
/* A new size or format is a new view, so every group holding the old one is dropped — see
|
|
2408
|
+
below. */
|
|
2338
2409
|
this.retiredTextures.push(replaced);
|
|
2339
2410
|
this.forgetBindingsOf(texture);
|
|
2340
2411
|
}
|
|
@@ -2371,6 +2442,8 @@ export class WebGPURenderer {
|
|
|
2371
2442
|
this.emissiveMap = null;
|
|
2372
2443
|
if (this.modelMap === texture)
|
|
2373
2444
|
this.modelMap = null;
|
|
2445
|
+
if (this.overlayMaps === texture)
|
|
2446
|
+
this.overlayMaps = null;
|
|
2374
2447
|
this.forgetBindingsOf(texture);
|
|
2375
2448
|
texture.dispose();
|
|
2376
2449
|
this.captureTargets.get(texture)?.dispose();
|
|
@@ -2388,6 +2461,9 @@ export class WebGPURenderer {
|
|
|
2388
2461
|
/* Same reasoning, for whichever SDF labels were bound against this atlas. */
|
|
2389
2462
|
this.sdfTextBindGroups.delete(texture);
|
|
2390
2463
|
this.flatBindGroups.clear();
|
|
2464
|
+
this.flatGroupEpoch += 1;
|
|
2465
|
+
this.overlayGroups.clear();
|
|
2466
|
+
this.skinnedGroups.length = 0;
|
|
2391
2467
|
/*
|
|
2392
2468
|
* The blank one is built with the albedo held aside, because `buildFlatBindGroup` reads
|
|
2393
2469
|
* `this.albedo` for that slot — leaving it in place would put a real colour map into the
|
|
@@ -2549,6 +2625,13 @@ export class WebGPURenderer {
|
|
|
2549
2625
|
const emissiveMap = (material?.emissive ?? null);
|
|
2550
2626
|
const modelMap = (material?.modelMap ?? null);
|
|
2551
2627
|
this.materialModel = material?.model ?? null;
|
|
2628
|
+
const reflectivity = material?.reflectivity;
|
|
2629
|
+
const environmentGain = material?.environmentGain;
|
|
2630
|
+
this.materialReflectivity =
|
|
2631
|
+
reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
|
|
2632
|
+
this.materialEnvironmentGain =
|
|
2633
|
+
environmentGain === undefined ? null : Math.max(0, environmentGain);
|
|
2634
|
+
this.writeEnvironmentDials();
|
|
2552
2635
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
2553
2636
|
const model = material?.model;
|
|
2554
2637
|
warnUnpagedLightmap(model, modelMap);
|
|
@@ -2568,7 +2651,8 @@ export class WebGPURenderer {
|
|
|
2568
2651
|
const cut = this.materialField('uCutout');
|
|
2569
2652
|
f[cut] = cutoff;
|
|
2570
2653
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
2571
|
-
|
|
2654
|
+
/* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
|
|
2655
|
+
f[cut + 2] = 0;
|
|
2572
2656
|
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
2573
2657
|
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
2574
2658
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
@@ -2723,16 +2807,45 @@ export class WebGPURenderer {
|
|
|
2723
2807
|
packAmbientSH(coefficients, this.ambientSH);
|
|
2724
2808
|
this.material((f) => f.set(this.ambientSH, this.materialField('uAmbientSH')));
|
|
2725
2809
|
}
|
|
2810
|
+
/**
|
|
2811
|
+
* The overlay the following draws wear, or null for none: pass state, put back to none by
|
|
2812
|
+
* `bindMeshPass`. See `renderer.ts` and `surfaceOverlay.ts`. The numbers go in the material block,
|
|
2813
|
+
* which every variant declares; the atlas goes in a twin of each flat group (`overlayGroup`).
|
|
2814
|
+
*/
|
|
2815
|
+
setSurfaceOverlay(overlay) {
|
|
2816
|
+
if (this.surface.lost)
|
|
2817
|
+
return;
|
|
2818
|
+
packSurfaceOverlay(overlay, this.overlayFloats);
|
|
2819
|
+
/* The first overlay laid is when the lit pipelines start carrying one. */
|
|
2820
|
+
if (overlayLays(overlay))
|
|
2821
|
+
this.enableLit('SURFACE_OVERLAY');
|
|
2822
|
+
this.overlayMaps = (overlay?.maps ?? null);
|
|
2823
|
+
this.material((f) => f.set(this.overlayFloats, this.materialField('uOverlay')));
|
|
2824
|
+
}
|
|
2726
2825
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
2727
2826
|
setSurfaceReflectivity(amount) {
|
|
2728
|
-
this.
|
|
2729
|
-
|
|
2730
|
-
});
|
|
2827
|
+
this.passReflectivity = Math.min(1, Math.max(0, amount));
|
|
2828
|
+
this.writeEnvironmentDials();
|
|
2731
2829
|
}
|
|
2732
2830
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
2733
2831
|
setEnvironmentGain(gain) {
|
|
2832
|
+
this.passEnvironmentGain = Math.max(0, gain);
|
|
2833
|
+
this.writeEnvironmentDials();
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* The reflectivity and environment gain the setters gave the pass, and the ones the material
|
|
2837
|
+
* set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
|
|
2838
|
+
* material's where it has one and the pass's otherwise, the rule `renderer.ts` keeps too.
|
|
2839
|
+
*/
|
|
2840
|
+
passReflectivity = 0;
|
|
2841
|
+
passEnvironmentGain = 1;
|
|
2842
|
+
materialReflectivity = null;
|
|
2843
|
+
materialEnvironmentGain = null;
|
|
2844
|
+
writeEnvironmentDials() {
|
|
2734
2845
|
this.material((f) => {
|
|
2735
|
-
|
|
2846
|
+
const dials = this.materialField('uEnvironmentDials');
|
|
2847
|
+
f[dials + 1] = this.materialReflectivity ?? this.passReflectivity;
|
|
2848
|
+
f[dials + 2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
|
|
2736
2849
|
});
|
|
2737
2850
|
}
|
|
2738
2851
|
setSurfaceGrain(amount) {
|
|
@@ -3203,7 +3316,7 @@ export class WebGPURenderer {
|
|
|
3203
3316
|
}
|
|
3204
3317
|
this.pass?.end();
|
|
3205
3318
|
this.flushRings();
|
|
3206
|
-
this.
|
|
3319
|
+
this.submit([this.encoder.finish()]);
|
|
3207
3320
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'frame.resumed' });
|
|
3208
3321
|
this.reflectionPassActive = false;
|
|
3209
3322
|
this.reflectionReady = true;
|
|
@@ -3393,7 +3506,7 @@ export class WebGPURenderer {
|
|
|
3393
3506
|
*/
|
|
3394
3507
|
gradeCode() {
|
|
3395
3508
|
return !this.hasComposite || !this.quality.hdrScene
|
|
3396
|
-
? (
|
|
3509
|
+
? forwardTransformCode(this.quality.outputTransform)
|
|
3397
3510
|
: 0;
|
|
3398
3511
|
}
|
|
3399
3512
|
gradeExposure() {
|
|
@@ -4587,6 +4700,7 @@ export class WebGPURenderer {
|
|
|
4587
4700
|
*/
|
|
4588
4701
|
setWind(windX, windZ, windGust, timeSeconds) {
|
|
4589
4702
|
resolveScatterDeform(windX, windZ, windGust, timeSeconds, null, this.frameWind);
|
|
4703
|
+
this.reopenViews();
|
|
4590
4704
|
}
|
|
4591
4705
|
/**
|
|
4592
4706
|
* The five wind fields of one shadow-draw slot.
|
|
@@ -4604,21 +4718,6 @@ export class WebGPURenderer {
|
|
|
4604
4718
|
this.shadowDraws.writeFloat(slot, DEPTH_VERT_FIELDS.uWindTime.offset, d.time);
|
|
4605
4719
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
4606
4720
|
}
|
|
4607
|
-
/**
|
|
4608
|
-
* The five wind fields of one per-draw slot.
|
|
4609
|
-
*
|
|
4610
|
-
* **Never called for an instanced draw.** That variant declares none of these, and
|
|
4611
|
-
* `flatPass.ts` records what writing a field a variant does not declare costs: the block is
|
|
4612
|
-
* shorter, so the write lands on whatever occupies that offset instead.
|
|
4613
|
-
*/
|
|
4614
|
-
writeWind(slot) {
|
|
4615
|
-
const d = this.frameWind;
|
|
4616
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindDirection.offset, d.direction);
|
|
4617
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindSpeed.offset, d.bend);
|
|
4618
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindGust.offset, d.gust);
|
|
4619
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindTime.offset, d.time);
|
|
4620
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
4621
|
-
}
|
|
4622
4721
|
/** Refilled per frame rather than allocated; see `resolvePointLights`. */
|
|
4623
4722
|
lights = {
|
|
4624
4723
|
count: 0,
|
|
@@ -4768,6 +4867,9 @@ export class WebGPURenderer {
|
|
|
4768
4867
|
* at submit, and a texture's view is new when an update changes its size.
|
|
4769
4868
|
*/
|
|
4770
4869
|
shadowCutoutLayout;
|
|
4870
|
+
/** A crowd's casters: the plain and cutout layouts with a bone animation's three textures. */
|
|
4871
|
+
shadowAnimatedLayout;
|
|
4872
|
+
shadowAnimatedCutoutLayout;
|
|
4771
4873
|
cutoutGroups = new Map();
|
|
4772
4874
|
cutoutPeelGroups = new Map();
|
|
4773
4875
|
shadowPeelPassUniforms = null;
|
|
@@ -4980,19 +5082,47 @@ export class WebGPURenderer {
|
|
|
4980
5082
|
if (count === 0)
|
|
4981
5083
|
return;
|
|
4982
5084
|
const cutout = cutoutOf(material);
|
|
4983
|
-
|
|
5085
|
+
/* A crowd casts where its clip has moved it, through the animated variant. */
|
|
5086
|
+
const animation = gpuBatch.animation === null ? null : this.animationViews(gpuBatch);
|
|
5087
|
+
if (gpuBatch.animation !== null && animation === null)
|
|
5088
|
+
return;
|
|
5089
|
+
const cutoutTexture = cutout === null ? null : cutout.albedo;
|
|
5090
|
+
const cutoutGroup = animation !== null
|
|
5091
|
+
? this.animatedDepthGroup(gpuBatch, animation, cutoutTexture)
|
|
5092
|
+
: cutoutTexture === null
|
|
5093
|
+
? null
|
|
5094
|
+
: this.cutoutGroup(cutoutTexture);
|
|
5095
|
+
if (animation !== null && cutoutGroup === null)
|
|
5096
|
+
return;
|
|
4984
5097
|
const slot = this.shadowDraws.allocate();
|
|
4985
5098
|
if (slot === null)
|
|
4986
5099
|
return;
|
|
4987
5100
|
/* Only the light matrix: the instanced depth variant has no uModel, its placement being
|
|
4988
5101
|
the four attribute columns the batch's buffer supplies. */
|
|
4989
5102
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uLightViewProj.offset, this.correctedLightViewProj);
|
|
4990
|
-
|
|
5103
|
+
const cuts = cutoutTexture !== null && cutoutGroup !== null;
|
|
5104
|
+
if (cutout !== null && cuts) {
|
|
4991
5105
|
this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
4992
5106
|
}
|
|
4993
|
-
const
|
|
5107
|
+
const clip = gpuBatch.animation;
|
|
5108
|
+
if (animation !== null && clip !== null) {
|
|
5109
|
+
const fields = cuts
|
|
5110
|
+
? DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS
|
|
5111
|
+
: DEPTH_INSTANCED_ANIMATED_VERT_FIELDS;
|
|
5112
|
+
this.shadowDraws.writeFloat(slot, fields.uSceneTime.offset, this.animationTime);
|
|
5113
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset, clip.framesPerSecond);
|
|
5114
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset + 4, clip.boneScale);
|
|
5115
|
+
}
|
|
4994
5116
|
const cull = material?.doubleSided === true ? 'none' : this.depthCullMode;
|
|
4995
|
-
|
|
5117
|
+
const animated = animation !== null;
|
|
5118
|
+
pass.setPipeline(depthPipeline(this.pipelines, this.surface.device, animated
|
|
5119
|
+
? cuts
|
|
5120
|
+
? this.shadowAnimatedCutoutLayout
|
|
5121
|
+
: this.shadowAnimatedLayout
|
|
5122
|
+
: cuts
|
|
5123
|
+
? this.shadowCutoutLayout
|
|
5124
|
+
: this.shadowLayout, `${cuts ? 'depth-instanced-cutout' : 'depth-instanced'}${animated ? '-animated' : ''}` +
|
|
5125
|
+
`|${cull}|${geometry.key ?? ''}`, this.presentOf(geometry), cull, false, true, cuts, false, false, animated));
|
|
4996
5126
|
pass.setBindGroup(0, cutoutGroup ?? this.shadowGroup(), [slot]);
|
|
4997
5127
|
let index = 0;
|
|
4998
5128
|
for (; index < geometry.vertexBuffers.length; index++) {
|
|
@@ -5313,6 +5443,10 @@ export class WebGPURenderer {
|
|
|
5313
5443
|
this.skinPalettes = new SkinPaletteRing(quality.drawsPerFrame);
|
|
5314
5444
|
this.maxDrawingBufferPixels = quality.maxDrawingBufferPixels;
|
|
5315
5445
|
this.exposure = quality.outputExposure;
|
|
5446
|
+
resolveFilmicCurve(DEFAULT_FILMIC_CURVE, this.filmConstants);
|
|
5447
|
+
if (quality.outputTransform === 'filmic' && !(quality.screenEffects && quality.hdrScene)) {
|
|
5448
|
+
console.warn(FILMIC_WITHOUT_COMPOSITE);
|
|
5449
|
+
}
|
|
5316
5450
|
this.flushSchedule =
|
|
5317
5451
|
quality.frameGraph && quality.identifierGraph
|
|
5318
5452
|
? createFlushSchedule(MAX_DRAWS_PER_FRAME)
|
|
@@ -5382,6 +5516,25 @@ export class WebGPURenderer {
|
|
|
5382
5516
|
console.warn("WebGPU: skinScattering 'screen-space' needs screenEffects to add into; skin is pre-integrated");
|
|
5383
5517
|
}
|
|
5384
5518
|
this.skinScatter = skinScreen ? new SkinScatterPass(device) : null;
|
|
5519
|
+
/*
|
|
5520
|
+
* The frame's materials' reflections, where the frame can carry them: scene light to swap the
|
|
5521
|
+
* probe's share in, and one sample, which the trace's depth copy and both maps are. Said once
|
|
5522
|
+
* where it cannot. See `frameReflections.ts`.
|
|
5523
|
+
*/
|
|
5524
|
+
this.frameReflections = resolveFrameReflections(quality.screenSpaceReflections);
|
|
5525
|
+
const reflectionsRefusal = this.frameReflections === null
|
|
5526
|
+
? null
|
|
5527
|
+
: !quality.screenEffects || !quality.hdrScene
|
|
5528
|
+
? 'the frame keeps no scene light to swap the probe in (`screenEffects` and `hdrScene`)'
|
|
5529
|
+
: this.samples > 1
|
|
5530
|
+
? 'a multisampled frame has no single depth or colour to march through'
|
|
5531
|
+
: null;
|
|
5532
|
+
if (reflectionsRefusal !== null)
|
|
5533
|
+
console.warn(`WebGPU: ${frameReflectionsRefused(reflectionsRefusal)}`);
|
|
5534
|
+
this.reflectionSurface =
|
|
5535
|
+
this.frameReflections !== null && reflectionsRefusal === null
|
|
5536
|
+
? new ReflectionSurfacePass(device)
|
|
5537
|
+
: null;
|
|
5385
5538
|
this.skinPipelines = skinScreen
|
|
5386
5539
|
? new PipelineCache(device, SKIN_TARGET_FORMAT, this.samples, glassShadows, quality.clusteredLights)
|
|
5387
5540
|
: null;
|
|
@@ -5410,7 +5563,7 @@ export class WebGPURenderer {
|
|
|
5410
5563
|
pointShadows: quality.pointShadows,
|
|
5411
5564
|
});
|
|
5412
5565
|
this.pointShadowsCompiled = quality.pointShadows;
|
|
5413
|
-
this.fragment =
|
|
5566
|
+
this.fragment = flatFragmentScratch(this.variant);
|
|
5414
5567
|
this.bindGroupLayout = createFlatBindGroupLayout(device, this.variant);
|
|
5415
5568
|
/*
|
|
5416
5569
|
* The skinned twin, built eagerly because it is a descriptor rather than a compile — WebGPU
|
|
@@ -5420,24 +5573,32 @@ export class WebGPURenderer {
|
|
|
5420
5573
|
this.skinnedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true);
|
|
5421
5574
|
this.morphedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, true);
|
|
5422
5575
|
this.bothBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true, true);
|
|
5576
|
+
/* And the instanced twin that plays a bone animation: its clip's and clocks' textures. */
|
|
5577
|
+
this.animatedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, false, true);
|
|
5423
5578
|
this.perDraw = new UniformRing(device, FLAT_VERT_SIZE, quality.drawsPerFrame, USAGE_UNIFORM_DST, 'flat.vertRing', this.drawBudget);
|
|
5424
|
-
|
|
5579
|
+
const { passSize, materialSize } = this.fragment;
|
|
5580
|
+
this.perFrameStaging = new ArrayBuffer(passSize + materialSize);
|
|
5425
5581
|
this.perFrameFloats = new Float32Array(this.perFrameStaging);
|
|
5426
5582
|
this.perFrameInts = new Int32Array(this.perFrameStaging);
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
*
|
|
5431
|
-
* — a material covers many draws
|
|
5432
|
-
*
|
|
5433
|
-
*
|
|
5434
|
-
*
|
|
5435
|
-
*
|
|
5436
|
-
*
|
|
5437
|
-
*
|
|
5438
|
-
*
|
|
5439
|
-
|
|
5440
|
-
|
|
5583
|
+
this.passInts = new Int32Array(this.perFrameStaging, 0, passSize / 4);
|
|
5584
|
+
this.materialInts = new Int32Array(this.perFrameStaging, passSize, materialSize / 4);
|
|
5585
|
+
/*
|
|
5586
|
+
* **Two blocks, because they change at two rates.** `setSurfaceTexture`, `setSurfaceGrain` and
|
|
5587
|
+
* their siblings are pass state on both backends — a material covers many draws — and on WebGL2
|
|
5588
|
+
* each is a `uniform1f` that lands immediately. Here they live in a block, and
|
|
5589
|
+
* `queue.writeBuffer` does not interleave with recorded commands, so one buffer rewritten between
|
|
5590
|
+
* two draws gives *both* the second value: every surface would wear the last material set, which
|
|
5591
|
+
* is bug 7's shape at pass scale. So each draw binds a slot.
|
|
5592
|
+
*
|
|
5593
|
+
* It was one block until 4.10.0, lights and all, copied whole into a ring slot per material
|
|
5594
|
+
* change and uploaded every frame: a stage of four hundred materials sent 4.8 MB a frame to say
|
|
5595
|
+
* what it had said the frame before. Now the pass's block takes a ring slot per pass, and a
|
|
5596
|
+
* material's block a slot of the store, where an unchanged material stays from frame to frame.
|
|
5597
|
+
*/
|
|
5598
|
+
this.passRing = new UniformRing(device, passSize, PASSES_PER_FRAME, USAGE_UNIFORM_DST, 'flat.passRing');
|
|
5599
|
+
this.materialStore = new MaterialStore(device, materialSize, this.quality.materialChangesPerFrame, USAGE_UNIFORM_DST, 'flat.materials', this.materialBudget);
|
|
5600
|
+
/* Two a pass at most: the jittered view and the unjittered one. */
|
|
5601
|
+
this.viewRing = new UniformRing(device, FLAT_VIEW_SIZE, 2 * PASSES_PER_FRAME, USAGE_UNIFORM_DST, 'flat.viewRing');
|
|
5441
5602
|
/*
|
|
5442
5603
|
* A one-pixel stand-in for the albedo binding. The albedo's flag is zero for untextured
|
|
5443
5604
|
* geometry, which is nearly all of it, so the shader never samples this — but WebGPU
|
|
@@ -5570,7 +5731,7 @@ export class WebGPURenderer {
|
|
|
5570
5731
|
})
|
|
5571
5732
|
.end();
|
|
5572
5733
|
}
|
|
5573
|
-
|
|
5734
|
+
this.submit([clear.finish()]);
|
|
5574
5735
|
/*
|
|
5575
5736
|
* `nearest` on a depth texture sampled as an ordinary float, matching what
|
|
5576
5737
|
* `shadowMap.ts` asks WebGL2 for. A filtering sampler cannot be used on a depth format
|
|
@@ -5737,6 +5898,11 @@ export class WebGPURenderer {
|
|
|
5737
5898
|
addressModeU: 'clamp-to-edge',
|
|
5738
5899
|
addressModeV: 'clamp-to-edge',
|
|
5739
5900
|
});
|
|
5901
|
+
/* Or a surface overlay's atlas, in the twin a draw wearing one binds: `overlayGroup`. */
|
|
5902
|
+
const maps = this.overlayBinding ? this.overlayMaps : null;
|
|
5903
|
+
if (maps !== null) {
|
|
5904
|
+
return { view: maps.layerView() ?? blankView, sampler: this.refractSampler };
|
|
5905
|
+
}
|
|
5740
5906
|
return { view: this.refractSnapshotView ?? blankView, sampler: this.refractSampler };
|
|
5741
5907
|
}
|
|
5742
5908
|
/*
|
|
@@ -5867,6 +6033,8 @@ export class WebGPURenderer {
|
|
|
5867
6033
|
{ binding: RUSH_TEXTURES.uExposureHeld },
|
|
5868
6034
|
/* Local exposure's grid, read filtered between tiles. See `localExposure.ts`. */
|
|
5869
6035
|
{ binding: RUSH_TEXTURES.uExposureLocal },
|
|
6036
|
+
/* Each mover's own motion: the reconstruction's target, or the scene where there is none. */
|
|
6037
|
+
{ binding: RUSH_TEXTURES.uMotion },
|
|
5870
6038
|
]);
|
|
5871
6039
|
this.rushUniforms = device.createBuffer({
|
|
5872
6040
|
label: 'post.rushUniforms',
|
|
@@ -5884,11 +6052,24 @@ export class WebGPURenderer {
|
|
|
5884
6052
|
this.ssrLayout = createPostStageLayout(device, SSR_UNIFORMS, SSR_FRAG_SIZE, [
|
|
5885
6053
|
{ binding: SSR_TEXTURES.uSsrDepth, filterable: false },
|
|
5886
6054
|
{ binding: SSR_TEXTURES.uSsrScene },
|
|
6055
|
+
/* The frame's materials, for the trace that reads them: `frameReflections.ts`. */
|
|
6056
|
+
{ binding: SSR_TEXTURES.uSsrProbeMap },
|
|
6057
|
+
{ binding: SSR_TEXTURES.uSsrTintMap },
|
|
5887
6058
|
]);
|
|
5888
6059
|
this.ssrResolveLayout = createSsrResolveLayout(device);
|
|
6060
|
+
this.ssrMaterialLayout = createPostStageLayout(device, SSR_MATERIAL_UNIFORMS, SSR_MATERIAL_SIZE, [
|
|
6061
|
+
{ binding: SSR_MATERIAL_TEXTURES.uSsrReflection },
|
|
6062
|
+
{ binding: SSR_MATERIAL_TEXTURES.uSsrProbeMap },
|
|
6063
|
+
{ binding: SSR_MATERIAL_TEXTURES.uSsrTintMap },
|
|
6064
|
+
]);
|
|
6065
|
+
this.ssrMaterialUniforms = device.createBuffer({
|
|
6066
|
+
label: 'post.ssrMaterialUniforms',
|
|
6067
|
+
size: SSR_MATERIAL_SIZE,
|
|
6068
|
+
usage: USAGE_UNIFORM_DST,
|
|
6069
|
+
});
|
|
5889
6070
|
this.ssrUniforms = device.createBuffer({
|
|
5890
6071
|
label: 'post.ssrUniforms',
|
|
5891
|
-
size: SSR_SLOT * MAX_REFLECTIVE_SURFACES,
|
|
6072
|
+
size: SSR_SLOT * (MAX_REFLECTIVE_SURFACES + 1),
|
|
5892
6073
|
usage: USAGE_UNIFORM_DST,
|
|
5893
6074
|
});
|
|
5894
6075
|
this.decalUniforms = device.createBuffer({
|
|
@@ -6038,6 +6219,8 @@ export class WebGPURenderer {
|
|
|
6038
6219
|
this.shadowLayout = createDepthBindGroupLayout(device);
|
|
6039
6220
|
this.shadowSkinnedLayout = createDepthBindGroupLayout(device, true);
|
|
6040
6221
|
this.shadowCutoutLayout = createDepthBindGroupLayout(device, false, true);
|
|
6222
|
+
this.shadowAnimatedLayout = createDepthBindGroupLayout(device, false, false, true);
|
|
6223
|
+
this.shadowAnimatedCutoutLayout = createDepthBindGroupLayout(device, false, true, true);
|
|
6041
6224
|
this.shadowDraws = new UniformRing(device,
|
|
6042
6225
|
/* The cutout block is the plain one and two fields more, and a slot must hold either. */
|
|
6043
6226
|
Math.max(DEPTH_VERT_SIZE, DEPTH_CUTOUT_VERT_SIZE), quality.drawsPerFrame, USAGE_UNIFORM_DST, 'shadow.drawRing', this.shadowDrawBudget);
|
|
@@ -6304,7 +6487,11 @@ export class WebGPURenderer {
|
|
|
6304
6487
|
/* Last frame's snapshot is last frame's scene. */
|
|
6305
6488
|
this.refractSnapshotTaken = false;
|
|
6306
6489
|
this.skinScattered = false;
|
|
6490
|
+
/* One frame of the jitter's sequence however many times the frame binds its camera. */
|
|
6491
|
+
this.temporalHistory.nextFrame();
|
|
6492
|
+
this.latePasses.length = 0;
|
|
6307
6493
|
this.skinScatter?.reset();
|
|
6494
|
+
this.reflectionSurface?.reset();
|
|
6308
6495
|
this.plumeVerts.reset();
|
|
6309
6496
|
this.plumeFrags.reset();
|
|
6310
6497
|
this.waterVerts.reset();
|
|
@@ -6332,9 +6519,18 @@ export class WebGPURenderer {
|
|
|
6332
6519
|
this.lineVerts.reset();
|
|
6333
6520
|
this.lineFrags.reset();
|
|
6334
6521
|
this.skyUniforms.reset();
|
|
6335
|
-
/* The
|
|
6336
|
-
|
|
6522
|
+
/* The pass ring, and the open slots with it: last frame's pass slots are gone. Its materials
|
|
6523
|
+
stay in the store, which a draw finds them in again. */
|
|
6524
|
+
this.passRing.reset();
|
|
6525
|
+
this.viewRing.reset();
|
|
6526
|
+
this.materialStore.beginFrame();
|
|
6527
|
+
this.passesAsked = 0;
|
|
6528
|
+
this.passesRefused = 0;
|
|
6529
|
+
this.viewsAsked = 0;
|
|
6530
|
+
this.viewsRefused = 0;
|
|
6531
|
+
this.materialsRefused = 0;
|
|
6337
6532
|
this.materials.dirty();
|
|
6533
|
+
this.reopenPass();
|
|
6338
6534
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
6339
6535
|
this.motionPass?.reset();
|
|
6340
6536
|
this.latePass?.reset();
|
|
@@ -6632,6 +6828,37 @@ export class WebGPURenderer {
|
|
|
6632
6828
|
},
|
|
6633
6829
|
};
|
|
6634
6830
|
}
|
|
6831
|
+
/**
|
|
6832
|
+
* The copies this frame's mesh rewrites owe (`StagedRewrite`), recorded since the last submit and
|
|
6833
|
+
* sent at the head of the next one: one encoder however many meshes are rewritten. Null while
|
|
6834
|
+
* nothing is owed.
|
|
6835
|
+
*/
|
|
6836
|
+
pendingRewrites = null;
|
|
6837
|
+
/** One record handed to every staged rewrite, refilled each call, so a rewrite allocates nothing. */
|
|
6838
|
+
stagedRewrite = {
|
|
6839
|
+
encoder: null,
|
|
6840
|
+
staging: null,
|
|
6841
|
+
previous: null,
|
|
6842
|
+
};
|
|
6843
|
+
rewriteCopies() {
|
|
6844
|
+
this.pendingRewrites ??= this.surface.device.createCommandEncoder({
|
|
6845
|
+
label: 'mesh.rewriteCopies',
|
|
6846
|
+
});
|
|
6847
|
+
return this.pendingRewrites;
|
|
6848
|
+
}
|
|
6849
|
+
/**
|
|
6850
|
+
* Submit `buffers`, led by any copies the frame's mesh rewrites owe. **Every submit of this
|
|
6851
|
+
* renderer goes through here**, because any of them may draw a rewritten mesh — the frame's own,
|
|
6852
|
+
* a mirror's, a shadow's, a probe's — and the copies must be ahead of the first that does.
|
|
6853
|
+
*/
|
|
6854
|
+
submit(buffers) {
|
|
6855
|
+
const rewrites = this.pendingRewrites;
|
|
6856
|
+
if (rewrites !== null) {
|
|
6857
|
+
this.pendingRewrites = null;
|
|
6858
|
+
buffers.unshift(rewrites.finish());
|
|
6859
|
+
}
|
|
6860
|
+
this.surface.device.queue.submit(buffers);
|
|
6861
|
+
}
|
|
6635
6862
|
/**
|
|
6636
6863
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
6637
6864
|
*
|
|
@@ -6652,20 +6879,35 @@ export class WebGPURenderer {
|
|
|
6652
6879
|
* `changeFrames.ts`.
|
|
6653
6880
|
*/
|
|
6654
6881
|
const motion = mesh.motion;
|
|
6655
|
-
let
|
|
6882
|
+
let staged = null;
|
|
6656
6883
|
if (motion !== null && this.reconstructionWanted) {
|
|
6657
6884
|
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
6885
|
+
const size = mesh.vertexBuffers[0]?.size ?? 0;
|
|
6886
|
+
let previous = null;
|
|
6658
6887
|
if (keepsPrevious(motion.changed, frame)) {
|
|
6659
6888
|
motion.previous ??= this.surface.device.createBuffer({
|
|
6660
6889
|
label: 'recon.dynamicPrevious',
|
|
6661
|
-
size
|
|
6890
|
+
size,
|
|
6662
6891
|
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
6663
6892
|
});
|
|
6664
6893
|
previous = motion.previous;
|
|
6665
6894
|
}
|
|
6666
6895
|
motion.changed = frame;
|
|
6896
|
+
/*
|
|
6897
|
+
* **Staged, and copied in behind the copy aside, in an encoder the frame's next submit leads
|
|
6898
|
+
* with**: no submit of the rewrite's own. See `StagedRewrite` and `submit`.
|
|
6899
|
+
*/
|
|
6900
|
+
motion.staging ??= this.surface.device.createBuffer({
|
|
6901
|
+
label: 'mesh.stagedPositions',
|
|
6902
|
+
size,
|
|
6903
|
+
usage: 0x0004 | 0x0008, // COPY_SRC | COPY_DST
|
|
6904
|
+
});
|
|
6905
|
+
staged = this.stagedRewrite;
|
|
6906
|
+
staged.encoder = this.rewriteCopies();
|
|
6907
|
+
staged.staging = motion.staging;
|
|
6908
|
+
staged.previous = previous;
|
|
6667
6909
|
}
|
|
6668
|
-
mesh.update(this.surface.device, positions, normals,
|
|
6910
|
+
mesh.update(this.surface.device, positions, normals, staged);
|
|
6669
6911
|
}
|
|
6670
6912
|
/**
|
|
6671
6913
|
* Settle everything the frame shares: the camera, the lights and the fog.
|
|
@@ -6855,12 +7097,20 @@ export class WebGPURenderer {
|
|
|
6855
7097
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
6856
7098
|
*/
|
|
6857
7099
|
f[at('uGrain')] = 1;
|
|
7100
|
+
/* No reflection and an environment at its own brightness, from the pass and from no material. */
|
|
7101
|
+
this.passReflectivity = 0;
|
|
7102
|
+
this.passEnvironmentGain = 1;
|
|
7103
|
+
this.materialReflectivity = null;
|
|
7104
|
+
this.materialEnvironmentGain = null;
|
|
6858
7105
|
f[at('uEnvironmentDials') + 1] = 0;
|
|
6859
7106
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
6860
7107
|
f[at('uWriteMode')] = 0;
|
|
6861
7108
|
f[at('uWriteMode') + 1] = 0;
|
|
6862
7109
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
6863
7110
|
f[at('uEnvironmentDials') + 2] = 1;
|
|
7111
|
+
/* No overlay until a draw sets one. See `setSurfaceOverlay`. */
|
|
7112
|
+
this.overlayFloats.fill(0);
|
|
7113
|
+
f.fill(0, at('uOverlay'), at('uOverlay') + OVERLAY_FLOATS);
|
|
6864
7114
|
/* The frame's own ambient until a draw asks for its own. See `setAmbientSH`. */
|
|
6865
7115
|
f.fill(0, at('uAmbientSH'), at('uAmbientSH') + AMBIENT_SH_FLOATS);
|
|
6866
7116
|
/*
|
|
@@ -6962,6 +7212,8 @@ export class WebGPURenderer {
|
|
|
6962
7212
|
if (this.quality.nightEmissive)
|
|
6963
7213
|
f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
6964
7214
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
7215
|
+
/* The frame the cutout dither moves with, in the near corner's w. See `uHighlightMin`. */
|
|
7216
|
+
f[at('uHighlightMin') + 3] = this.cutoutFrame;
|
|
6965
7217
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
6966
7218
|
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
6967
7219
|
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
@@ -7214,6 +7466,7 @@ export class WebGPURenderer {
|
|
|
7214
7466
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
7215
7467
|
*/
|
|
7216
7468
|
this.materials.dirty();
|
|
7469
|
+
this.reopenPass();
|
|
7217
7470
|
/*
|
|
7218
7471
|
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
7219
7472
|
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
@@ -7229,35 +7482,112 @@ export class WebGPURenderer {
|
|
|
7229
7482
|
this.emissiveMap = null;
|
|
7230
7483
|
this.modelMap = null;
|
|
7231
7484
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
7485
|
+
/* Nor an overlay; its numbers are zeroed with the block's other defaults. */
|
|
7486
|
+
this.overlayMaps = null;
|
|
7232
7487
|
this.uvScale[0] = 1;
|
|
7233
7488
|
this.uvScale[1] = 1;
|
|
7234
7489
|
this.uvOffset[0] = 0;
|
|
7235
7490
|
this.uvOffset[1] = 0;
|
|
7236
7491
|
}
|
|
7237
7492
|
/**
|
|
7238
|
-
* The slot the next draw's material lives in,
|
|
7493
|
+
* The slot the next draw's material lives in, placing it when something dirtied it, with the
|
|
7494
|
+
* pass's slot taken first where `bindMeshPass` reopened it: `passOffset` is that one's offset.
|
|
7239
7495
|
*
|
|
7240
|
-
* **A
|
|
7241
|
-
* material
|
|
7242
|
-
*
|
|
7496
|
+
* **A placement per material change, not per draw.** A scene drawing a hundred meshes of one
|
|
7497
|
+
* material places it once; a scene switching material every draw places one each, and a material
|
|
7498
|
+
* the store already holds — the same numbers as a draw before it, this frame or an earlier one —
|
|
7499
|
+
* is bound where it is and uploads nothing.
|
|
7243
7500
|
*/
|
|
7244
7501
|
materialSlotForDraw() {
|
|
7502
|
+
if (!this.passBlock.open) {
|
|
7503
|
+
this.passesAsked += 1;
|
|
7504
|
+
const pass = this.passRing.allocate();
|
|
7505
|
+
if (pass === null) {
|
|
7506
|
+
this.passesRefused += 1;
|
|
7507
|
+
this.materialBudget.ask();
|
|
7508
|
+
this.materialBudget.drop();
|
|
7509
|
+
if (!this.warnedPassesFull) {
|
|
7510
|
+
this.warnedPassesFull = true;
|
|
7511
|
+
console.warn(`WebGPU: more than ${this.passRing.slots} mesh passes drew in one frame; the draws of the rest are skipped this frame and the ring grows for the next.`);
|
|
7512
|
+
}
|
|
7513
|
+
return null;
|
|
7514
|
+
}
|
|
7515
|
+
this.passRing.writeBlock(pass, this.passInts);
|
|
7516
|
+
this.passBlock.slot = pass;
|
|
7517
|
+
}
|
|
7245
7518
|
if (this.materials.open)
|
|
7246
7519
|
return this.materials.slot;
|
|
7247
|
-
const slot = this.
|
|
7520
|
+
const slot = this.materialStore.place(this.materialInts);
|
|
7248
7521
|
if (slot === null) {
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
console.warn(`WebGPU: more than ${this.quality.materialChangesPerFrame} material changes in a frame; the draws asking for the rest are skipped. renderer.frameBudget names the line and the count.`);
|
|
7252
|
-
}
|
|
7522
|
+
this.materialsRefused += 1;
|
|
7523
|
+
this.warnMaterialsFull();
|
|
7253
7524
|
return null;
|
|
7254
7525
|
}
|
|
7255
|
-
this.perFrame.writeBlock(slot, this.perFrameInts);
|
|
7256
7526
|
this.materials.slot = slot;
|
|
7257
7527
|
return slot;
|
|
7258
7528
|
}
|
|
7529
|
+
/** The pass slot `materialSlotForDraw` left open, read beside the material's by every lit draw. */
|
|
7530
|
+
get passOffset() {
|
|
7531
|
+
return this.passBlock.slot;
|
|
7532
|
+
}
|
|
7533
|
+
/** A new pass: its block, and both its views, are taken afresh by the next draw. */
|
|
7534
|
+
reopenPass() {
|
|
7535
|
+
this.passBlock.dirty();
|
|
7536
|
+
this.reopenViews();
|
|
7537
|
+
}
|
|
7538
|
+
/** What a view holds changed — the wind, the light's matrix — so the next draw takes a new one. */
|
|
7539
|
+
reopenViews() {
|
|
7540
|
+
this.viewBlock.dirty();
|
|
7541
|
+
this.lateViewBlock.dirty();
|
|
7542
|
+
}
|
|
7543
|
+
/**
|
|
7544
|
+
* The view slot a lit draw reads its camera from: the pass's, or its unjittered twin for a blended
|
|
7545
|
+
* draw landing after the upscale. Taken by the first draw that needs it after the pass, the wind or
|
|
7546
|
+
* the light's matrix changed, and shared by every draw until then. Null when the ring is full, which
|
|
7547
|
+
* skips the draw and grows the ring for the next frame, as the pass ring does.
|
|
7548
|
+
*/
|
|
7549
|
+
viewSlotFor(late, viewProj) {
|
|
7550
|
+
const block = late ? this.lateViewBlock : this.viewBlock;
|
|
7551
|
+
if (block.open)
|
|
7552
|
+
return block.slot;
|
|
7553
|
+
this.viewsAsked += 1;
|
|
7554
|
+
const slot = this.viewRing.allocate();
|
|
7555
|
+
if (slot === null) {
|
|
7556
|
+
this.viewsRefused += 1;
|
|
7557
|
+
this.materialBudget.ask();
|
|
7558
|
+
this.materialBudget.drop();
|
|
7559
|
+
if (!this.warnedPassesFull) {
|
|
7560
|
+
this.warnedPassesFull = true;
|
|
7561
|
+
console.warn(`WebGPU: more than ${this.viewRing.slots} views drew in one frame; the draws of the rest are skipped this frame and the ring grows for the next.`);
|
|
7562
|
+
}
|
|
7563
|
+
return null;
|
|
7564
|
+
}
|
|
7565
|
+
this.writeView(this.viewRing, slot, viewProj);
|
|
7566
|
+
block.slot = slot;
|
|
7567
|
+
return slot;
|
|
7568
|
+
}
|
|
7569
|
+
/** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
|
|
7570
|
+
writeView(ring, slot, viewProj) {
|
|
7571
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
|
|
7572
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7573
|
+
/* The scene's clock, which a bone animation's instances read their moment from. */
|
|
7574
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
|
|
7575
|
+
const d = this.frameWind;
|
|
7576
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindDirection.offset, d.direction);
|
|
7577
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindSpeed.offset, d.bend);
|
|
7578
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindGust.offset, d.gust);
|
|
7579
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindTime.offset, d.time);
|
|
7580
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
7581
|
+
}
|
|
7582
|
+
warnMaterialsFull() {
|
|
7583
|
+
if (this.warnedMaterialsFull)
|
|
7584
|
+
return;
|
|
7585
|
+
this.warnedMaterialsFull = true;
|
|
7586
|
+
console.warn(`WebGPU: more than ${this.quality.materialChangesPerFrame} distinct materials in a frame; the draws asking for the rest are skipped. renderer.frameBudget names the line and the count.`);
|
|
7587
|
+
}
|
|
7259
7588
|
/** Said once rather than every frame, for the reason `warnedFull` gives. */
|
|
7260
7589
|
warnedMaterialsFull = false;
|
|
7590
|
+
warnedPassesFull = false;
|
|
7261
7591
|
warnedSkyFull = false;
|
|
7262
7592
|
warnedParticlesTwice = false;
|
|
7263
7593
|
/**
|
|
@@ -7292,8 +7622,12 @@ export class WebGPURenderer {
|
|
|
7292
7622
|
* What this saves is the GPU's share, which for a heavy mesh is most of the cost and for a
|
|
7293
7623
|
* trivial one is very little.
|
|
7294
7624
|
*/
|
|
7295
|
-
|
|
7625
|
+
/* A static list draws every entry in every view, since a bundle cannot leave one out. */
|
|
7626
|
+
if (this.quality.cullDraws &&
|
|
7627
|
+
this.staticRecording === null &&
|
|
7628
|
+
!this.visible(mesh.bounds, model)) {
|
|
7296
7629
|
return;
|
|
7630
|
+
}
|
|
7297
7631
|
/*
|
|
7298
7632
|
* And the other half of the same question, where the profile asked for one. Gated on
|
|
7299
7633
|
* `cullDraws` alongside the frustum test, because both are the renderer doing a consumer's
|
|
@@ -7310,6 +7644,9 @@ export class WebGPURenderer {
|
|
|
7310
7644
|
*/
|
|
7311
7645
|
if (this.tracksMotionNow())
|
|
7312
7646
|
this.recordDrawMotion(mesh, model, previousModel);
|
|
7647
|
+
else if (previousModel !== null && this.quality.cameraMotionBlur * this.motionBlurScale > 0) {
|
|
7648
|
+
this.warnObjectBlur();
|
|
7649
|
+
}
|
|
7313
7650
|
noteMapsWithoutUvs(mesh, mesh.hasUvs, this.materialHasMaps);
|
|
7314
7651
|
/* An opaque draw meets the medium as the dial says: `submitMesh` reads an absent `fog` as
|
|
7315
7652
|
true, which is a translucent draw's default and would put every opaque draw back in the fog
|
|
@@ -7455,17 +7792,17 @@ export class WebGPURenderer {
|
|
|
7455
7792
|
`TranslucentMeshOptions.reconstructed`. */
|
|
7456
7793
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7457
7794
|
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
7458
|
-
|
|
7795
|
+
/* The camera, the light's matrix and the wind are the pass's, in its view block. */
|
|
7796
|
+
const view = this.viewSlotFor(late, late ? this.correctedViewProj : this.viewProj);
|
|
7797
|
+
if (view === null)
|
|
7798
|
+
return;
|
|
7799
|
+
this.drawViewOffset = view;
|
|
7459
7800
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
7460
7801
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
7461
|
-
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
7462
|
-
instanced one, which declares none of these fields. */
|
|
7463
|
-
this.writeWind(slot);
|
|
7464
7802
|
/* The material's repeats, not a constant: geometry authored in metres is textured at
|
|
7465
7803
|
whatever density the material asks for without rebuilding it. */
|
|
7466
7804
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7467
7805
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
7468
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7469
7806
|
/*
|
|
7470
7807
|
* Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
|
|
7471
7808
|
* is always backed — by the caller's data or by the constant buffer — which makes the binding
|
|
@@ -7640,8 +7977,10 @@ export class WebGPURenderer {
|
|
|
7640
7977
|
command.pipeline = pipeline;
|
|
7641
7978
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
7642
7979
|
command.offsetA = slot;
|
|
7643
|
-
command.offsetB =
|
|
7644
|
-
command.
|
|
7980
|
+
command.offsetB = this.passOffset;
|
|
7981
|
+
command.offsetC = material;
|
|
7982
|
+
command.offsetD = this.drawViewOffset;
|
|
7983
|
+
command.offsetCount = 4;
|
|
7645
7984
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
7646
7985
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
7647
7986
|
}
|
|
@@ -7655,7 +7994,12 @@ export class WebGPURenderer {
|
|
|
7655
7994
|
if (pass === null)
|
|
7656
7995
|
return;
|
|
7657
7996
|
pass.setPipeline(pipeline);
|
|
7658
|
-
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
7997
|
+
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
7998
|
+
slot,
|
|
7999
|
+
this.passOffset,
|
|
8000
|
+
material,
|
|
8001
|
+
this.drawViewOffset,
|
|
8002
|
+
]);
|
|
7659
8003
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
7660
8004
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
7661
8005
|
}
|
|
@@ -7665,6 +8009,10 @@ export class WebGPURenderer {
|
|
|
7665
8009
|
if (splits) {
|
|
7666
8010
|
this.keepSkinDiffuse(mesh, key, base, skinned, morphed, deltas, layer, eight, clothed, slot, material);
|
|
7667
8011
|
}
|
|
8012
|
+
/* And its surface half, for the frame's materials' reflections. See `keepReflectionSurface`. */
|
|
8013
|
+
if (!blend && lit && depthWrite && this.reflectionSurfaceKeeps()) {
|
|
8014
|
+
this.keepReflectionSurface(mesh, key, base, skinned, morphed, deltas, layer, eight, clothed, modelKind, slot, material, null, 1, null);
|
|
8015
|
+
}
|
|
7668
8016
|
}
|
|
7669
8017
|
/* Back to the defaults for whatever is drawn next, exactly as `renderer.ts` restores them —
|
|
7670
8018
|
unconditionally, because the scratch state above is shared by every draw after this one
|
|
@@ -7731,9 +8079,87 @@ export class WebGPURenderer {
|
|
|
7731
8079
|
'an ordinary mesh, or drop the channel — the sway, sky and alpha lanes cannot reach an ' +
|
|
7732
8080
|
'instanced pipeline.');
|
|
7733
8081
|
}
|
|
7734
|
-
const
|
|
8082
|
+
const animation = (options.animation ?? null);
|
|
8083
|
+
/* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
|
|
8084
|
+
lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
|
|
8085
|
+
if (animation !== null && !this.presentOf(mesh)['grain']) {
|
|
8086
|
+
throw new Error('WebGPU: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
|
|
8087
|
+
'(`MeshData.lightmapUvs`), and this mesh has none.');
|
|
8088
|
+
}
|
|
8089
|
+
const batch = new GpuInstancedBatch(this.surface.device, mesh, capacity, `flat.instances:${mesh.key ?? 'mesh'}`, options.cull ?? false, animation);
|
|
7735
8090
|
return batch;
|
|
7736
8091
|
}
|
|
8092
|
+
/** See `Renderer.setAnimationTime`. Every view taken after it reads the new clock. */
|
|
8093
|
+
setAnimationTime(seconds) {
|
|
8094
|
+
if (!Number.isFinite(seconds) || seconds === this.animationTime)
|
|
8095
|
+
return;
|
|
8096
|
+
this.animationTime = seconds;
|
|
8097
|
+
this.reopenViews();
|
|
8098
|
+
}
|
|
8099
|
+
/** The clock `setAnimationTime` set, 0 until it is. */
|
|
8100
|
+
animationTime = 0;
|
|
8101
|
+
/** See `Renderer.createBoneAnimation`. */
|
|
8102
|
+
createBoneAnimation(clip) {
|
|
8103
|
+
return new GpuBoneAnimation(this.surface.device, packBoneAnimation(clip));
|
|
8104
|
+
}
|
|
8105
|
+
/** See `Renderer.disposeBoneAnimation`. A batch still playing it draws nothing after. */
|
|
8106
|
+
disposeBoneAnimation(animation) {
|
|
8107
|
+
const clip = animation;
|
|
8108
|
+
if (!clip.disposed)
|
|
8109
|
+
clip.dispose();
|
|
8110
|
+
}
|
|
8111
|
+
/** An animated batch's clip and clocks, as its groups bind them. */
|
|
8112
|
+
animationViews(batch) {
|
|
8113
|
+
const animation = batch.animation;
|
|
8114
|
+
const clocks = batch.clocks;
|
|
8115
|
+
if (animation === null || clocks === null || animation.disposed)
|
|
8116
|
+
return null;
|
|
8117
|
+
return { places: animation.placesView, turns: animation.turnsView, clocks: clocks.view };
|
|
8118
|
+
}
|
|
8119
|
+
/** An animated batch's groups, dropped wherever the renderer drops its own. */
|
|
8120
|
+
freshAnimatedGroups(batch) {
|
|
8121
|
+
if (batch.groupEpoch === this.flatGroupEpoch)
|
|
8122
|
+
return;
|
|
8123
|
+
batch.litGroups.clear();
|
|
8124
|
+
batch.depthGroups.clear();
|
|
8125
|
+
batch.depthPeelGroups.clear();
|
|
8126
|
+
batch.groupEpoch = this.flatGroupEpoch;
|
|
8127
|
+
}
|
|
8128
|
+
/**
|
|
8129
|
+
* The lit group an animated batch draws with: the material's group, or its overlay twin, again
|
|
8130
|
+
* with the clip's and the clocks' textures — kept on the batch by the group it was built beside,
|
|
8131
|
+
* as a skinned twin is kept by its palette slot.
|
|
8132
|
+
*/
|
|
8133
|
+
animatedGroup(batch, views) {
|
|
8134
|
+
this.freshAnimatedGroups(batch);
|
|
8135
|
+
const base = this.overlayGroup();
|
|
8136
|
+
let group = batch.litGroups.get(base);
|
|
8137
|
+
if (group === undefined) {
|
|
8138
|
+
this.overlayBinding = base !== this.bindGroup;
|
|
8139
|
+
group = createFlatBindGroup(this.surface.device, this.animatedBindGroupLayout, this.variant, this.perDraw.buffer, this.passRing.buffer, this.materialStore.buffer, this.viewRing.buffer, this.flatTextures, null, null, null, views);
|
|
8140
|
+
this.overlayBinding = false;
|
|
8141
|
+
this.bindGroupBudget.ask();
|
|
8142
|
+
batch.litGroups.set(base, group);
|
|
8143
|
+
}
|
|
8144
|
+
return group;
|
|
8145
|
+
}
|
|
8146
|
+
/** An animated caster's shadow group, by what it cuts with and whether this is the peel. */
|
|
8147
|
+
animatedDepthGroup(batch, views, cutout) {
|
|
8148
|
+
this.freshAnimatedGroups(batch);
|
|
8149
|
+
const peel = this.shadowLayerIsPeel && this.shadowPeelPassUniforms !== null;
|
|
8150
|
+
const groups = peel ? batch.depthPeelGroups : batch.depthGroups;
|
|
8151
|
+
const held = groups.get(cutout);
|
|
8152
|
+
if (held !== undefined)
|
|
8153
|
+
return held;
|
|
8154
|
+
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
8155
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
8156
|
+
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
8157
|
+
if (passUniforms === null || previousView === null || previousSampler === null)
|
|
8158
|
+
return null;
|
|
8159
|
+
const group = createDepthBindGroup(this.surface.device, cutout === null ? this.shadowAnimatedLayout : this.shadowAnimatedCutoutLayout, this.shadowDraws.buffer, passUniforms, previousView, previousSampler, null, cutout === null ? null : { view: cutout.view, sampler: cutout.sampler }, null, views);
|
|
8160
|
+
groups.set(cutout, group);
|
|
8161
|
+
return group;
|
|
8162
|
+
}
|
|
7737
8163
|
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
7738
8164
|
instanceCulls = null;
|
|
7739
8165
|
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
@@ -7766,7 +8192,10 @@ export class WebGPURenderer {
|
|
|
7766
8192
|
this.submitInstanced(batch, data, 1, false, {});
|
|
7767
8193
|
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
7768
8194
|
const previous = batch.previousBuffer;
|
|
7769
|
-
if (previous !== null &&
|
|
8195
|
+
if (previous !== null &&
|
|
8196
|
+
this.staticRecording === null &&
|
|
8197
|
+
batch.changed === this.moverFrame &&
|
|
8198
|
+
this.tracksMotionNow()) {
|
|
7770
8199
|
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
7771
8200
|
if (paired > 0)
|
|
7772
8201
|
this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
@@ -7794,6 +8223,7 @@ export class WebGPURenderer {
|
|
|
7794
8223
|
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
7795
8224
|
if ((refraction <= 0 && !glassy) || !this.takeRefractSnapshot())
|
|
7796
8225
|
return false;
|
|
8226
|
+
this.seeThroughBound = true;
|
|
7797
8227
|
const glass = this.glassScratch;
|
|
7798
8228
|
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
7799
8229
|
const seeAt = this.materialField('uSeeThrough');
|
|
@@ -7806,6 +8236,8 @@ export class WebGPURenderer {
|
|
|
7806
8236
|
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
7807
8237
|
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
7808
8238
|
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
8239
|
+
/* w: this framebuffer's first row is the top, so up the screen is down the rows. */
|
|
8240
|
+
this.perFrameFloats[tintAt + 3] = -1;
|
|
7809
8241
|
const glassTintAt = this.materialField('uGlassTint');
|
|
7810
8242
|
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
7811
8243
|
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
@@ -7814,6 +8246,7 @@ export class WebGPURenderer {
|
|
|
7814
8246
|
}
|
|
7815
8247
|
/** Put the scratch back after `bindSeeThrough`: refraction and glass off. */
|
|
7816
8248
|
releaseSeeThrough() {
|
|
8249
|
+
this.seeThroughBound = false;
|
|
7817
8250
|
const seeAt = this.materialField('uSeeThrough');
|
|
7818
8251
|
this.perFrameFloats[seeAt] = 0;
|
|
7819
8252
|
this.perFrameFloats[seeAt + 2] = 0;
|
|
@@ -7869,10 +8302,17 @@ export class WebGPURenderer {
|
|
|
7869
8302
|
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
7870
8303
|
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
7871
8304
|
instance and their order. See `InstancedOptions.cull`. */
|
|
7872
|
-
const culling = !blend && batch.cull;
|
|
8305
|
+
const culling = !blend && batch.cull && this.staticRecording === null;
|
|
7873
8306
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
|
|
7874
8307
|
return;
|
|
7875
|
-
|
|
8308
|
+
/* A batch playing a bone animation reads its clocks by instance index, which a compaction
|
|
8309
|
+
would reorder: it is culled whole and never instance by instance. */
|
|
8310
|
+
const animation = batch.animation === null ? null : this.animationViews(batch);
|
|
8311
|
+
if (batch.animation !== null && animation === null)
|
|
8312
|
+
return;
|
|
8313
|
+
const cut = culling && animation === null && cullsInstances(count, mesh.indexCount)
|
|
8314
|
+
? this.takeCullSlot(batch, count)
|
|
8315
|
+
: null;
|
|
7876
8316
|
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
7877
8317
|
const slot = this.perDraw.allocate();
|
|
7878
8318
|
if (slot === null) {
|
|
@@ -7884,11 +8324,19 @@ export class WebGPURenderer {
|
|
|
7884
8324
|
}
|
|
7885
8325
|
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
7886
8326
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
7887
|
-
this.
|
|
8327
|
+
const view = this.viewSlotFor(lands, lands ? this.correctedViewProj : this.viewProj);
|
|
8328
|
+
if (view === null)
|
|
8329
|
+
return;
|
|
8330
|
+
this.drawViewOffset = view;
|
|
7888
8331
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
7889
8332
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
7890
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
7891
8333
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
8334
|
+
/* The clip's speed and bone scale, read by the animated variant's own field. */
|
|
8335
|
+
const clip = batch.animation;
|
|
8336
|
+
if (animation !== null && clip !== null) {
|
|
8337
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset, clip.framesPerSecond);
|
|
8338
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset + 4, clip.boneScale);
|
|
8339
|
+
}
|
|
7892
8340
|
const dimmed = opacity < 1;
|
|
7893
8341
|
if (dimmed) {
|
|
7894
8342
|
this.perFrameFloats[this.materialField('uOpacity')] = opacity;
|
|
@@ -7925,7 +8373,7 @@ export class WebGPURenderer {
|
|
|
7925
8373
|
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
7926
8374
|
plain one would hand a draw the wrong layout. */
|
|
7927
8375
|
const adds = blend && options.additive === true;
|
|
7928
|
-
const key = `${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8376
|
+
const key = `${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
7929
8377
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
7930
8378
|
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
7931
8379
|
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
@@ -7936,7 +8384,7 @@ export class WebGPURenderer {
|
|
|
7936
8384
|
if (present === undefined) {
|
|
7937
8385
|
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
7938
8386
|
}
|
|
7939
|
-
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);
|
|
8387
|
+
pipeline = flatPipeline(pipelines, this.surface.device, animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout, this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind, 'whole', false, animation !== null);
|
|
7940
8388
|
}
|
|
7941
8389
|
const material = this.materialSlotForDraw();
|
|
7942
8390
|
if (material !== null) {
|
|
@@ -7945,10 +8393,15 @@ export class WebGPURenderer {
|
|
|
7945
8393
|
: this.recordDraw(0, this.currentTarget());
|
|
7946
8394
|
if (command !== null) {
|
|
7947
8395
|
command.pipeline = pipeline;
|
|
7948
|
-
command.bindGroup =
|
|
8396
|
+
command.bindGroup =
|
|
8397
|
+
animation === null
|
|
8398
|
+
? this.flatBindGroupFor(false, false, null)
|
|
8399
|
+
: this.animatedGroup(batch, animation);
|
|
7949
8400
|
command.offsetA = slot;
|
|
7950
|
-
command.offsetB =
|
|
7951
|
-
command.
|
|
8401
|
+
command.offsetB = this.passOffset;
|
|
8402
|
+
command.offsetC = material;
|
|
8403
|
+
command.offsetD = this.drawViewOffset;
|
|
8404
|
+
command.offsetCount = 4;
|
|
7952
8405
|
let index = 0;
|
|
7953
8406
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
7954
8407
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
@@ -7966,7 +8419,9 @@ export class WebGPURenderer {
|
|
|
7966
8419
|
if (pass === null)
|
|
7967
8420
|
return;
|
|
7968
8421
|
pass.setPipeline(pipeline);
|
|
7969
|
-
pass.setBindGroup(0,
|
|
8422
|
+
pass.setBindGroup(0, animation === null
|
|
8423
|
+
? this.flatBindGroupFor(false, false, null)
|
|
8424
|
+
: this.animatedGroup(batch, animation), [slot, this.passOffset, material, this.drawViewOffset]);
|
|
7970
8425
|
let index = 0;
|
|
7971
8426
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
7972
8427
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index]);
|
|
@@ -7978,6 +8433,10 @@ export class WebGPURenderer {
|
|
|
7978
8433
|
else
|
|
7979
8434
|
pass.drawIndexedIndirect(cut.args, 0);
|
|
7980
8435
|
}
|
|
8436
|
+
/* And its surface half, for the frame's materials' reflections, with the same survivors. */
|
|
8437
|
+
if (!blend && lit && depthWrite && this.reflectionSurfaceKeeps()) {
|
|
8438
|
+
this.keepReflectionSurface(mesh, key, base, false, false, null, layer, false, false, modelKind, slot, material, instanceBuffer, count, cut === null ? null : cut.args, animation === null ? null : batch);
|
|
8439
|
+
}
|
|
7981
8440
|
}
|
|
7982
8441
|
/* Back to the defaults, unconditionally and for the reason `drawMesh` gives at length: this
|
|
7983
8442
|
scratch block is cumulative state shared by every draw after this one, and an early return
|
|
@@ -8169,6 +8628,7 @@ export class WebGPURenderer {
|
|
|
8169
8628
|
if (view === null)
|
|
8170
8629
|
return false;
|
|
8171
8630
|
this.lightViewProj.set(lightViewProj);
|
|
8631
|
+
this.reopenViews();
|
|
8172
8632
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
8173
8633
|
this.shadowLayerIsPeel = peel;
|
|
8174
8634
|
this.shadowLayerIsDynamic = dynamic;
|
|
@@ -8276,6 +8736,241 @@ export class WebGPURenderer {
|
|
|
8276
8736
|
this.sceneCasterMaterial = undefined;
|
|
8277
8737
|
casters(this.sceneCasterSink);
|
|
8278
8738
|
}
|
|
8739
|
+
/* -- Static draws ---------------------------------------------------------------- */
|
|
8740
|
+
/** See `Renderer.createStaticDraws`. Recorded on its first draw, into each view it is drawn in. */
|
|
8741
|
+
createStaticDraws(casters) {
|
|
8742
|
+
return new GpuStaticDraws(this.surface.device, captureStaticDraws(casters), this.fragment.passSize, FLAT_VIEW_SIZE, FLAT_VERT_SIZE);
|
|
8743
|
+
}
|
|
8744
|
+
/**
|
|
8745
|
+
* See `Renderer.drawStaticDraws`. One bundle executed, after a pass block and a view block are
|
|
8746
|
+
* written for this view; the list is recorded first where it has no recording that is current for
|
|
8747
|
+
* this target and starting state. See `staticBundles.ts`.
|
|
8748
|
+
*/
|
|
8749
|
+
drawStaticDraws(draws) {
|
|
8750
|
+
if (this.surface.lost)
|
|
8751
|
+
return;
|
|
8752
|
+
const list = draws;
|
|
8753
|
+
if (list.disposed) {
|
|
8754
|
+
this.warnStaticDisposed();
|
|
8755
|
+
return;
|
|
8756
|
+
}
|
|
8757
|
+
if (!this.canDraw() || this.viewProj === null)
|
|
8758
|
+
return;
|
|
8759
|
+
/* A list's draws are rigid and wear no overlay: put back to none, as `bindMeshPass` does. */
|
|
8760
|
+
this.setSkinPalette(null);
|
|
8761
|
+
this.setMorphWeights(null);
|
|
8762
|
+
this.setCloth(null);
|
|
8763
|
+
this.setSurfaceOverlay(null);
|
|
8764
|
+
this.setMaterial(null);
|
|
8765
|
+
this.sceneCasterMaterial = undefined;
|
|
8766
|
+
/* A probe face gives its slots back after every face; a list replays there draw by draw. */
|
|
8767
|
+
if (this.probePassActive) {
|
|
8768
|
+
this.replayStaticImmediately(list);
|
|
8769
|
+
return;
|
|
8770
|
+
}
|
|
8771
|
+
/* The copy first: taking one may grow the list's rings, which no recording before can bind. */
|
|
8772
|
+
const copy = list.takeCopy(this.moverFrame);
|
|
8773
|
+
const current = this.staticRecordingFor(list);
|
|
8774
|
+
if (current === null || current.immediate) {
|
|
8775
|
+
this.replayStaticImmediately(list);
|
|
8776
|
+
return;
|
|
8777
|
+
}
|
|
8778
|
+
current.usedIn = this.moverFrame;
|
|
8779
|
+
list.writePass(copy, this.passInts);
|
|
8780
|
+
this.writeView(list.views, list.viewOffset(copy), this.viewProj);
|
|
8781
|
+
list.uploadCopy(copy);
|
|
8782
|
+
const cache = current.cache;
|
|
8783
|
+
let frame = current.frameBundles[copy];
|
|
8784
|
+
if (frame === undefined || frame === null) {
|
|
8785
|
+
frame = encodeBundle(this.surface.device, {
|
|
8786
|
+
colorFormats: [cache.format],
|
|
8787
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
8788
|
+
sampleCount: cache.sampleCount,
|
|
8789
|
+
depthReadOnly: false,
|
|
8790
|
+
}, current.frame, list.passOffset(copy), list.viewOffset(copy), 'static.frame');
|
|
8791
|
+
current.frameBundles[copy] = frame;
|
|
8792
|
+
}
|
|
8793
|
+
const command = this.recordDraw(0, this.currentTarget());
|
|
8794
|
+
if (command !== null) {
|
|
8795
|
+
command.bundles = frame;
|
|
8796
|
+
command.pipeline = null;
|
|
8797
|
+
command.bindGroup = null;
|
|
8798
|
+
}
|
|
8799
|
+
else {
|
|
8800
|
+
this.openPass()?.executeBundles(frame);
|
|
8801
|
+
}
|
|
8802
|
+
const surface = this.reflectionSurface;
|
|
8803
|
+
if (current.surfaceKept && surface !== null && current.surface.taken > 0) {
|
|
8804
|
+
let halves = current.surfaceBundles[copy];
|
|
8805
|
+
if (halves === undefined || halves === null) {
|
|
8806
|
+
halves = encodeBundle(this.surface.device, {
|
|
8807
|
+
colorFormats: [null, REFLECTION_SURFACE_FORMAT, REFLECTION_SURFACE_FORMAT],
|
|
8808
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
8809
|
+
sampleCount: 1,
|
|
8810
|
+
depthReadOnly: true,
|
|
8811
|
+
}, current.surface, list.passOffset(copy), list.viewOffset(copy), 'static.surface');
|
|
8812
|
+
current.surfaceBundles[copy] = halves;
|
|
8813
|
+
}
|
|
8814
|
+
const kept = surface.take();
|
|
8815
|
+
kept.bundles = halves;
|
|
8816
|
+
}
|
|
8817
|
+
/* Counted as the draws would have been, so a frame reports the same on both backends. */
|
|
8818
|
+
for (let draw = 0; draw < current.frame.taken; draw++)
|
|
8819
|
+
this.drawBudget.ask();
|
|
8820
|
+
if (!this.staticJustRecorded) {
|
|
8821
|
+
for (let ask = 0; ask < current.materialAsks; ask++)
|
|
8822
|
+
this.materialBudget.ask();
|
|
8823
|
+
}
|
|
8824
|
+
this.staticJustRecorded = false;
|
|
8825
|
+
}
|
|
8826
|
+
/** A list drawn the way `drawSceneCasters` draws, a draw at a time, for where a bundle cannot be. */
|
|
8827
|
+
replayStaticImmediately(list) {
|
|
8828
|
+
this.staticJustRecorded = false;
|
|
8829
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
8830
|
+
this.setMaterial(null);
|
|
8831
|
+
this.sceneCasterMaterial = undefined;
|
|
8832
|
+
}
|
|
8833
|
+
/** See `Renderer.disposeStaticDraws`. Its pins let go; its meshes and materials are the caller's. */
|
|
8834
|
+
disposeStaticDraws(draws) {
|
|
8835
|
+
const list = draws;
|
|
8836
|
+
if (list.disposed)
|
|
8837
|
+
return;
|
|
8838
|
+
list.dispose(this.unpinFor(list));
|
|
8839
|
+
}
|
|
8840
|
+
/** The recording to replay `list` from here, made now if none is current; null if it cannot be. */
|
|
8841
|
+
staticRecordingFor(list) {
|
|
8842
|
+
/* Geometry still arriving is not drawn yet, so a recording now would leave it out for good. */
|
|
8843
|
+
for (const entry of list.entries) {
|
|
8844
|
+
const mesh = entry.kind === 'mesh' ? entry.mesh : entry.batch.mesh;
|
|
8845
|
+
if (!mesh.complete)
|
|
8846
|
+
return null;
|
|
8847
|
+
}
|
|
8848
|
+
const cache = this.targetPipelines();
|
|
8849
|
+
const start = this.materialInts;
|
|
8850
|
+
const startHash = hashWords(start, start.length);
|
|
8851
|
+
const surfaceKept = this.reflectionSurfaceKeeps();
|
|
8852
|
+
let recording = list.find(cache, start, startHash, this.cutoutResolveStaged, surfaceKept);
|
|
8853
|
+
if (recording !== null &&
|
|
8854
|
+
recording.cacheVersion === cache.version &&
|
|
8855
|
+
recording.groupEpoch === this.flatGroupEpoch &&
|
|
8856
|
+
recording.storeEpoch === this.materialStore.epoch &&
|
|
8857
|
+
recording.ringEpoch === list.ringEpoch) {
|
|
8858
|
+
return recording;
|
|
8859
|
+
}
|
|
8860
|
+
const unpin = this.unpinFor(list);
|
|
8861
|
+
if (recording === null) {
|
|
8862
|
+
recording = list.open(cache, start, startHash, this.cutoutResolveStaged, surfaceKept, unpin);
|
|
8863
|
+
}
|
|
8864
|
+
else {
|
|
8865
|
+
list.clear(recording, recording.storeEpoch === this.materialStore.epoch ? unpin : () => { });
|
|
8866
|
+
}
|
|
8867
|
+
this.recordStatic(list, recording);
|
|
8868
|
+
return recording;
|
|
8869
|
+
}
|
|
8870
|
+
/** How a list's pins are let go: through the store, unless it has forgotten them since. */
|
|
8871
|
+
unpinFor(list) {
|
|
8872
|
+
const epoch = this.materialStore.epoch;
|
|
8873
|
+
return (offset) => {
|
|
8874
|
+
if (this.materialStore.epoch === epoch && list.pinEpoch === epoch) {
|
|
8875
|
+
this.materialStore.unpin(offset);
|
|
8876
|
+
}
|
|
8877
|
+
};
|
|
8878
|
+
}
|
|
8879
|
+
/**
|
|
8880
|
+
* Record `list` into `recording`: its entries through the scene-caster path, with the list's
|
|
8881
|
+
* rings and group cache swapped in for the frame's and the pass and view slots held at the list's
|
|
8882
|
+
* first copy, so every command the path records binds the list's buffers and offsets the bundle
|
|
8883
|
+
* can rewrite per copy. Everything swapped is put back, whatever the path throws.
|
|
8884
|
+
*/
|
|
8885
|
+
recordStatic(list, recording) {
|
|
8886
|
+
if (list.groupEpoch !== this.flatGroupEpoch) {
|
|
8887
|
+
list.groups = new Map();
|
|
8888
|
+
list.blank = null;
|
|
8889
|
+
list.groupEpoch = this.flatGroupEpoch;
|
|
8890
|
+
}
|
|
8891
|
+
list.drawRing.reset();
|
|
8892
|
+
const perDraw = this.perDraw;
|
|
8893
|
+
const passRing = this.passRing;
|
|
8894
|
+
const viewRing = this.viewRing;
|
|
8895
|
+
const groups = this.flatBindGroups;
|
|
8896
|
+
const blank = this.blankAlbedoBindGroup;
|
|
8897
|
+
const bindGroup = this.bindGroup;
|
|
8898
|
+
const passSlot = this.passBlock.slot;
|
|
8899
|
+
const viewSlot = this.viewBlock.slot;
|
|
8900
|
+
const lateSlot = this.lateViewBlock.slot;
|
|
8901
|
+
const asked = this.materialBudget.used;
|
|
8902
|
+
this.perDraw = list.drawRing;
|
|
8903
|
+
this.passRing = list.passes;
|
|
8904
|
+
this.viewRing = list.views;
|
|
8905
|
+
this.flatBindGroups = list.groups;
|
|
8906
|
+
/* The blank group over the list's rings, built as the frame's is: with no maps held. */
|
|
8907
|
+
list.blank ??= this.buildFlatBindGroup();
|
|
8908
|
+
this.blankAlbedoBindGroup = list.blank;
|
|
8909
|
+
this.bindGroup = this.flatGroupForMaps(null, null, null, null, null);
|
|
8910
|
+
this.passBlock.slot = list.passOffset(0);
|
|
8911
|
+
this.viewBlock.slot = list.viewOffset(0);
|
|
8912
|
+
this.lateViewBlock.slot = list.viewOffset(0);
|
|
8913
|
+
this.materials.dirty();
|
|
8914
|
+
this.staticRecording = recording;
|
|
8915
|
+
try {
|
|
8916
|
+
this.sceneCasterMaterial = undefined;
|
|
8917
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
8918
|
+
}
|
|
8919
|
+
finally {
|
|
8920
|
+
this.staticRecording = null;
|
|
8921
|
+
this.perDraw = perDraw;
|
|
8922
|
+
this.passRing = passRing;
|
|
8923
|
+
this.viewRing = viewRing;
|
|
8924
|
+
this.flatBindGroups = groups;
|
|
8925
|
+
this.blankAlbedoBindGroup = blank;
|
|
8926
|
+
this.bindGroup = bindGroup;
|
|
8927
|
+
this.passBlock.slot = passSlot;
|
|
8928
|
+
this.viewBlock.slot = viewSlot;
|
|
8929
|
+
this.lateViewBlock.slot = lateSlot;
|
|
8930
|
+
this.materials.dirty();
|
|
8931
|
+
this.sceneCasterMaterial = undefined;
|
|
8932
|
+
this.setMaterial(null);
|
|
8933
|
+
}
|
|
8934
|
+
recording.materialAsks = this.materialBudget.used - asked;
|
|
8935
|
+
const store = this.materialStore;
|
|
8936
|
+
recording.cacheVersion = recording.cache.version;
|
|
8937
|
+
recording.groupEpoch = this.flatGroupEpoch;
|
|
8938
|
+
recording.storeEpoch = store.epoch;
|
|
8939
|
+
recording.ringEpoch = list.ringEpoch;
|
|
8940
|
+
/* A skin split into halves is replayed draw by draw, so nothing recorded is kept or pinned. */
|
|
8941
|
+
if (recording.immediate) {
|
|
8942
|
+
resetPool(recording.frame);
|
|
8943
|
+
resetPool(recording.surface);
|
|
8944
|
+
return;
|
|
8945
|
+
}
|
|
8946
|
+
/* Every material the commands bind, pinned once, so nothing displaces what a bundle bakes. */
|
|
8947
|
+
for (const pool of [recording.frame, recording.surface]) {
|
|
8948
|
+
for (let i = 0; i < pool.taken; i++) {
|
|
8949
|
+
const offset = pool.commands[i].offsetC;
|
|
8950
|
+
if (!recording.pins.includes(offset)) {
|
|
8951
|
+
store.pin(offset);
|
|
8952
|
+
recording.pins.push(offset);
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
list.pinEpoch = store.epoch;
|
|
8957
|
+
list.uploadDraws();
|
|
8958
|
+
this.staticJustRecorded = true;
|
|
8959
|
+
}
|
|
8960
|
+
/** The recording under way, which `recordDraw` and the side passes take their commands into. */
|
|
8961
|
+
staticRecording = null;
|
|
8962
|
+
/** Whether the replay about to count its material changes recorded them, and so counted them. */
|
|
8963
|
+
staticJustRecorded = false;
|
|
8964
|
+
/** A disposed list drawn, said once: the frame loop does not throw. */
|
|
8965
|
+
warnStaticDisposed() {
|
|
8966
|
+
if (this.warnedStaticDisposed)
|
|
8967
|
+
return;
|
|
8968
|
+
this.warnedStaticDisposed = true;
|
|
8969
|
+
console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
|
|
8970
|
+
}
|
|
8971
|
+
warnedStaticDisposed = false;
|
|
8972
|
+
/** Bumped wherever the lit groups are dropped, which a static list's own groups follow. */
|
|
8973
|
+
flatGroupEpoch = 0;
|
|
8279
8974
|
drawShadowCasters(casters) {
|
|
8280
8975
|
if (this.surface.lost || this.shadowPass === null)
|
|
8281
8976
|
return;
|
|
@@ -8567,7 +9262,7 @@ export class WebGPURenderer {
|
|
|
8567
9262
|
}
|
|
8568
9263
|
this.shadowDraws.flush();
|
|
8569
9264
|
this.scatterDepthDraws.flush();
|
|
8570
|
-
this.
|
|
9265
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
8571
9266
|
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
8572
9267
|
this.retiredSunTexture?.destroy();
|
|
8573
9268
|
this.retiredSunTexture = null;
|
|
@@ -8606,6 +9301,20 @@ export class WebGPURenderer {
|
|
|
8606
9301
|
warnedNoPointShadowArray = false;
|
|
8607
9302
|
/** Said once, for the retired ambient dial. */
|
|
8608
9303
|
warnedEnvironmentAmbient = false;
|
|
9304
|
+
/** The overlay the draws wear, `uOverlay`: see `setSurfaceOverlay`. */
|
|
9305
|
+
overlayFloats = new Float32Array(OVERLAY_FLOATS);
|
|
9306
|
+
/** Its atlas, bound where the refraction copy goes in a twin of each flat group. */
|
|
9307
|
+
overlayMaps = null;
|
|
9308
|
+
/**
|
|
9309
|
+
* Each flat group's twin with the atlas in the refraction slot, by the group and the atlas, built
|
|
9310
|
+
* on the first draw that wears it and dropped wherever `flatBindGroups` is.
|
|
9311
|
+
*/
|
|
9312
|
+
overlayGroups = new Map();
|
|
9313
|
+
/** True while a twin is built: what `flatTextures` hands the refraction slot then. */
|
|
9314
|
+
overlayBinding = false;
|
|
9315
|
+
/** True between `bindSeeThrough` and `releaseSeeThrough`: that draw reads the copy, not the atlas. */
|
|
9316
|
+
seeThroughBound = false;
|
|
9317
|
+
warnedOverlayRefraction = false;
|
|
8609
9318
|
castingWorldIndices = new Int32Array(POINT_SHADOW_POOL).fill(-1);
|
|
8610
9319
|
resolvedPointShadows = createResolvedPointShadows(MAX_POINT_LIGHTS);
|
|
8611
9320
|
/** One face's view-projection, rebuilt per face rather than allocated. */
|
|
@@ -8787,7 +9496,7 @@ export class WebGPURenderer {
|
|
|
8787
9496
|
* its own slot.
|
|
8788
9497
|
*/
|
|
8789
9498
|
this.pointShadowArray?.flush();
|
|
8790
|
-
this.
|
|
9499
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
8791
9500
|
this.shadowEncoder = null;
|
|
8792
9501
|
this.shadowPass = null;
|
|
8793
9502
|
this.gpuTimer.end();
|
|
@@ -8954,42 +9663,89 @@ export class WebGPURenderer {
|
|
|
8954
9663
|
tintTarget.resolve(encoder, layer, face, offered);
|
|
8955
9664
|
}
|
|
8956
9665
|
buildFlatBindGroup() {
|
|
8957
|
-
return createFlatBindGroup(this.surface.device, this.bindGroupLayout, this.variant, this.perDraw.buffer, this.
|
|
9666
|
+
return createFlatBindGroup(this.surface.device, this.bindGroupLayout, this.variant, this.perDraw.buffer, this.passRing.buffer, this.materialStore.buffer, this.viewRing.buffer, this.flatTextures);
|
|
9667
|
+
}
|
|
9668
|
+
/**
|
|
9669
|
+
* The open material's flat group, or its twin with the surface overlay's atlas in the refraction
|
|
9670
|
+
* slot where the draw wears one and does not refract. A twin is built from the live material,
|
|
9671
|
+
* which `bindGroup` always is, and kept by the group and the atlas.
|
|
9672
|
+
*
|
|
9673
|
+
* **A draw that refracts, or is glass, keeps the frame's copy** and its overlay reads no image —
|
|
9674
|
+
* the shader asks the same of `uSeeThrough` (`overlayImagesBound`) — said once.
|
|
9675
|
+
*/
|
|
9676
|
+
overlayGroup() {
|
|
9677
|
+
const maps = this.overlayMaps;
|
|
9678
|
+
if (maps === null)
|
|
9679
|
+
return this.bindGroup;
|
|
9680
|
+
if (this.seeThroughBound) {
|
|
9681
|
+
if (!this.warnedOverlayRefraction) {
|
|
9682
|
+
this.warnedOverlayRefraction = true;
|
|
9683
|
+
console.warn('WebGPU: a draw that refracts or is glass wears a surface overlay with maps. The frame ' +
|
|
9684
|
+
"copy it reads takes the overlay's slot, so its rim, dissolve and wrinkles read no image.");
|
|
9685
|
+
}
|
|
9686
|
+
return this.bindGroup;
|
|
9687
|
+
}
|
|
9688
|
+
let byMaps = this.overlayGroups.get(this.bindGroup);
|
|
9689
|
+
if (byMaps === undefined) {
|
|
9690
|
+
byMaps = new Map();
|
|
9691
|
+
this.overlayGroups.set(this.bindGroup, byMaps);
|
|
9692
|
+
}
|
|
9693
|
+
let twin = byMaps.get(maps);
|
|
9694
|
+
if (twin === undefined) {
|
|
9695
|
+
this.overlayBinding = true;
|
|
9696
|
+
twin = this.buildFlatBindGroup();
|
|
9697
|
+
this.overlayBinding = false;
|
|
9698
|
+
this.bindGroupBudget.ask();
|
|
9699
|
+
byMaps.set(maps, twin);
|
|
9700
|
+
}
|
|
9701
|
+
return twin;
|
|
8958
9702
|
}
|
|
8959
9703
|
/**
|
|
8960
9704
|
* The bind group a draw uses, skinned or not.
|
|
8961
9705
|
*
|
|
8962
|
-
* **The skinned twin is
|
|
8963
|
-
*
|
|
8964
|
-
*
|
|
8965
|
-
*
|
|
8966
|
-
* missed invalidation is not possible rather than merely unlikely, and it is why
|
|
9706
|
+
* **The skinned twin is kept by palette slot and by the unskinned group it was built beside**,
|
|
9707
|
+
* which is one a material, and compared against the views it holds — the palette's, which
|
|
9708
|
+
* changes only when the slot's texture grows, the morph deltas and the cloth's — rather than
|
|
9709
|
+
* against a flag somebody has to remember to clear at six call sites. Comparing object identity
|
|
9710
|
+
* means a missed invalidation is not possible rather than merely unlikely, and it is why
|
|
8967
9711
|
* `SkinPaletteTexture.view()` caches: a fresh view every call never compares equal and would
|
|
8968
9712
|
* rebuild this every draw.
|
|
8969
9713
|
*/
|
|
8970
9714
|
flatBindGroupFor(skinned, morphed, deltas) {
|
|
9715
|
+
const base = this.overlayGroup();
|
|
8971
9716
|
if (!skinned && !morphed)
|
|
8972
|
-
return
|
|
9717
|
+
return base;
|
|
8973
9718
|
const palette = skinned ? this.skinPalettes.view(this.skinPaletteSlot) : null;
|
|
8974
9719
|
/* Slot `k` caches at `k + 1`; index 0 is the morphed draw that is not skinned and has no
|
|
8975
9720
|
slot of its own. See `skinnedGroups`. */
|
|
8976
9721
|
const at = skinned ? this.skinPaletteSlot + 1 : 0;
|
|
8977
9722
|
/* A skinned group carries the cloth's views, or the stand-in's: see `clothViews`. */
|
|
8978
9723
|
const cloth = skinned ? this.clothViews() : null;
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
this.
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
9724
|
+
let byMaterial = this.skinnedGroups[at];
|
|
9725
|
+
if (byMaterial === undefined) {
|
|
9726
|
+
byMaterial = new Map();
|
|
9727
|
+
this.skinnedGroups[at] = byMaterial;
|
|
9728
|
+
}
|
|
9729
|
+
let twin = byMaterial.get(base);
|
|
9730
|
+
if (twin === undefined ||
|
|
9731
|
+
twin.palette !== palette ||
|
|
9732
|
+
twin.deltas !== deltas ||
|
|
9733
|
+
twin.cloth !== (cloth?.particles ?? null) ||
|
|
9734
|
+
twin.clothBinding !== (cloth?.binding ?? null)) {
|
|
9735
|
+
/* Built from the live material, with the overlay's atlas where `base` is its twin. */
|
|
9736
|
+
this.overlayBinding = base !== this.bindGroup;
|
|
9737
|
+
const group = createFlatBindGroup(this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, this.perDraw.buffer, this.passRing.buffer, this.materialStore.buffer, this.viewRing.buffer, this.flatTextures, palette, deltas, cloth);
|
|
9738
|
+
this.overlayBinding = false;
|
|
9739
|
+
twin = {
|
|
9740
|
+
group,
|
|
9741
|
+
palette,
|
|
9742
|
+
deltas,
|
|
9743
|
+
cloth: cloth?.particles ?? null,
|
|
9744
|
+
clothBinding: cloth?.binding ?? null,
|
|
9745
|
+
};
|
|
9746
|
+
byMaterial.set(base, twin);
|
|
9747
|
+
}
|
|
9748
|
+
return twin.group;
|
|
8993
9749
|
}
|
|
8994
9750
|
/** The layout matching a pair of vertex flags. A pipeline and its bind group must agree. */
|
|
8995
9751
|
flatLayoutFor(skinned, morphed) {
|
|
@@ -9028,8 +9784,14 @@ export class WebGPURenderer {
|
|
|
9028
9784
|
if (this.drawBudget.dropped > 0) {
|
|
9029
9785
|
grew = this.perDraw.growTo(roomFor(this.drawBudget.used)) || grew;
|
|
9030
9786
|
}
|
|
9031
|
-
if (this.
|
|
9032
|
-
grew = this.
|
|
9787
|
+
if (this.materialsRefused > 0) {
|
|
9788
|
+
grew = this.materialStore.growTo(roomFor(this.materialBudget.used)) || grew;
|
|
9789
|
+
}
|
|
9790
|
+
if (this.passesRefused > 0) {
|
|
9791
|
+
grew = this.passRing.growTo(roomFor(this.passesAsked)) || grew;
|
|
9792
|
+
}
|
|
9793
|
+
if (this.viewsRefused > 0) {
|
|
9794
|
+
grew = this.viewRing.growTo(roomFor(this.viewsAsked)) || grew;
|
|
9033
9795
|
}
|
|
9034
9796
|
if (grew)
|
|
9035
9797
|
this.rebuildFlatGroupsForNewRings();
|
|
@@ -9050,8 +9812,10 @@ export class WebGPURenderer {
|
|
|
9050
9812
|
*/
|
|
9051
9813
|
rebuildFlatGroupsForNewRings() {
|
|
9052
9814
|
this.flatBindGroups.clear();
|
|
9053
|
-
this.
|
|
9815
|
+
this.flatGroupEpoch += 1;
|
|
9816
|
+
this.overlayGroups.clear();
|
|
9054
9817
|
this.skinnedGroups.length = 0;
|
|
9818
|
+
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
9055
9819
|
const albedo = this.albedo;
|
|
9056
9820
|
const normalMap = this.normalMap;
|
|
9057
9821
|
const ormMap = this.ormMap;
|
|
@@ -9071,6 +9835,7 @@ export class WebGPURenderer {
|
|
|
9071
9835
|
this.bindGroup = this.flatGroupForMaps(this.albedo, this.normalMap, this.ormMap, this.emissiveMap, this.modelMap);
|
|
9072
9836
|
/* The open material lives in a slot of a buffer that no longer exists. */
|
|
9073
9837
|
this.materials.dirty();
|
|
9838
|
+
this.reopenPass();
|
|
9074
9839
|
}
|
|
9075
9840
|
/**
|
|
9076
9841
|
* The environment view the cached flat groups bind: the grid, or the blank stand-in while the
|
|
@@ -9102,6 +9867,9 @@ export class WebGPURenderer {
|
|
|
9102
9867
|
*/
|
|
9103
9868
|
rebuildFlatBindGroup() {
|
|
9104
9869
|
this.flatBindGroups.clear();
|
|
9870
|
+
this.flatGroupEpoch += 1;
|
|
9871
|
+
this.overlayGroups.clear();
|
|
9872
|
+
this.skinnedGroups.length = 0;
|
|
9105
9873
|
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
9106
9874
|
if (this.albedo === null &&
|
|
9107
9875
|
this.normalMap === null &&
|
|
@@ -9327,9 +10095,14 @@ export class WebGPURenderer {
|
|
|
9327
10095
|
? (OUTPUT_TRANSFORM_CODE[this.quality.outputTransform] ?? 0)
|
|
9328
10096
|
: 0;
|
|
9329
10097
|
f[at('uOutputExposure')] = graded ? this.exposure : 1;
|
|
10098
|
+
f.set(this.filmA, at('uFilmA'));
|
|
10099
|
+
f.set(this.filmB, at('uFilmB'));
|
|
10100
|
+
f[at('uDisplayHeadroom')] = this.headroom;
|
|
9330
10101
|
f.set(this.reprojection, at('uReprojection'));
|
|
9331
10102
|
f[at('uMotionStrength')] = motionStrength;
|
|
9332
|
-
f[at('uMotionMax')] =
|
|
10103
|
+
f[at('uMotionMax')] = this.motionBlurMax;
|
|
10104
|
+
/* Each mover's own motion, where the reconstruction drew its target this frame. */
|
|
10105
|
+
f[at('uObjectMotion')] = this.reconstructing && this.reconMotionView !== null ? 1 : 0;
|
|
9333
10106
|
f[at('uAoStrength')] = this.frameProjection === null ? 0 : aoStrength;
|
|
9334
10107
|
/*
|
|
9335
10108
|
* The occlusion read where this frame's jitter put it (`uAoOffset`): measured from the jittered
|
|
@@ -9373,6 +10146,7 @@ export class WebGPURenderer {
|
|
|
9373
10146
|
f[at('uGradeSize')] = this.gradeSize;
|
|
9374
10147
|
/* The lens and the print. See `setVignette` and `setFilmGrain`. */
|
|
9375
10148
|
f[at('uVignette')] = this.vignette;
|
|
10149
|
+
f.set(this.fringe, at('uFringe'));
|
|
9376
10150
|
f[at('uAutoExposure')] = adapting ? this.autoExposure : 0;
|
|
9377
10151
|
f[at('uLocalExposure')] = measuring && localising ? this.localExposure : 0;
|
|
9378
10152
|
f[at('uGrain')] = this.grain;
|
|
@@ -9410,7 +10184,7 @@ export class WebGPURenderer {
|
|
|
9410
10184
|
return;
|
|
9411
10185
|
if (this.aoTargetView === null)
|
|
9412
10186
|
return;
|
|
9413
|
-
this.rushBindGroup = this.buildRushBindGroup('post.rushBindGroup', this.sceneColorView);
|
|
10187
|
+
this.rushBindGroup = this.buildRushBindGroup('post.rushBindGroup', this.sceneColorView, this.sceneColorView);
|
|
9414
10188
|
/*
|
|
9415
10189
|
* **And the reconstruction's twin, from the same three call sites.** A colour grade replaces
|
|
9416
10190
|
* the lookup table and a resize replaces the depth and the occlusion target, and both groups
|
|
@@ -9418,11 +10192,13 @@ export class WebGPURenderer {
|
|
|
9418
10192
|
* come to read a stale view on exactly the frames a consumer changed something.
|
|
9419
10193
|
*/
|
|
9420
10194
|
if (this.reconShownView !== null) {
|
|
9421
|
-
this.reconRushGroup = this.buildRushBindGroup('recon.rushBindGroup', this.reconShownView);
|
|
10195
|
+
this.reconRushGroup = this.buildRushBindGroup('recon.rushBindGroup', this.reconShownView, this.reconMotionView ?? this.reconShownView);
|
|
9422
10196
|
}
|
|
9423
10197
|
}
|
|
9424
10198
|
/** The composite's group over one scene source. See `rebuildRushBindGroup`. */
|
|
9425
|
-
buildRushBindGroup(label, sceneView
|
|
10199
|
+
buildRushBindGroup(label, sceneView,
|
|
10200
|
+
/** Each mover's own motion, or a stand-in the shader is told not to read. */
|
|
10201
|
+
motionView) {
|
|
9426
10202
|
const { device } = this.surface;
|
|
9427
10203
|
if (this.resolvedDepthView === null || this.aoTargetView === null)
|
|
9428
10204
|
return null;
|
|
@@ -9451,6 +10227,7 @@ export class WebGPURenderer {
|
|
|
9451
10227
|
and a declared binding still needs a complete texture, as the bloom fallback says. */
|
|
9452
10228
|
...entry(RUSH_TEXTURES.uExposureHeld, this.exposurePass?.heldView ?? sceneView),
|
|
9453
10229
|
...entry(RUSH_TEXTURES.uExposureLocal, this.exposurePass?.localView ?? sceneView),
|
|
10230
|
+
...entry(RUSH_TEXTURES.uMotion, motionView),
|
|
9454
10231
|
],
|
|
9455
10232
|
layout: this.rushLayout,
|
|
9456
10233
|
});
|
|
@@ -9707,7 +10484,7 @@ export class WebGPURenderer {
|
|
|
9707
10484
|
this.reconSharpenGroups[write] = build(`recon.sharpen${String(write)}`, target, this.reconShownView, records, nextRecords, swings, nextSwings);
|
|
9708
10485
|
}
|
|
9709
10486
|
/* The composite's own group, over the resolved picture instead of the scene target. */
|
|
9710
|
-
this.reconRushGroup = this.buildRushBindGroup('recon.rushBindGroup', this.reconShownView);
|
|
10487
|
+
this.reconRushGroup = this.buildRushBindGroup('recon.rushBindGroup', this.reconShownView, this.reconMotionView ?? this.reconShownView);
|
|
9711
10488
|
/* And the scene's, because `ensureComposite` built it before this one existed. */
|
|
9712
10489
|
this.rebuildRushBindGroup();
|
|
9713
10490
|
/* And the bloom's, which blooms the resolved picture: see `rebuildBloomGroups`. */
|
|
@@ -9796,7 +10573,7 @@ export class WebGPURenderer {
|
|
|
9796
10573
|
if (!this.reconstructing || late === null || shown === null || depth === null)
|
|
9797
10574
|
return;
|
|
9798
10575
|
const oitSet = this.oitActive && this.translucentQueue.length > 0;
|
|
9799
|
-
if (late.pending === 0 && !oitSet)
|
|
10576
|
+
if (late.pending === 0 && !oitSet && this.latePasses.length === 0)
|
|
9800
10577
|
return;
|
|
9801
10578
|
const width = Math.max(1, this.surface.canvas.width);
|
|
9802
10579
|
const height = Math.max(1, this.surface.canvas.height);
|
|
@@ -9808,21 +10585,60 @@ export class WebGPURenderer {
|
|
|
9808
10585
|
this.refractSnapshotTaken = true;
|
|
9809
10586
|
}
|
|
9810
10587
|
this.lateRefractWanted = false;
|
|
9811
|
-
if (late.pending
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
}
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
10588
|
+
if (late.pending > 0) {
|
|
10589
|
+
const pass = encoder.beginRenderPass({
|
|
10590
|
+
label: 'recon.late',
|
|
10591
|
+
timestampWrites: this.gpuTimer.writesFor(),
|
|
10592
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
10593
|
+
depthStencilAttachment: {
|
|
10594
|
+
view: late.depthView(width, height),
|
|
10595
|
+
depthLoadOp: 'load',
|
|
10596
|
+
/* Kept: the order-independent set replays against it after this pass. */
|
|
10597
|
+
depthStoreOp: 'store',
|
|
10598
|
+
},
|
|
10599
|
+
});
|
|
10600
|
+
late.replay(pass, this.issueBound);
|
|
10601
|
+
pass.end();
|
|
10602
|
+
}
|
|
10603
|
+
this.runLatePasses(encoder, shown, late.depthView(width, height));
|
|
10604
|
+
}
|
|
10605
|
+
/**
|
|
10606
|
+
* The registered passes `drawPass` queued for after the upscale: each handed a fresh copy of the
|
|
10607
|
+
* reconstructed picture with every late draw in it, in the output-size texture the late
|
|
10608
|
+
* refraction keeps (its own reads are done by now: they were recorded before this copy), then
|
|
10609
|
+
* drawn over the picture in a pass of its own.
|
|
10610
|
+
*/
|
|
10611
|
+
runLatePasses(encoder, shown, depth) {
|
|
10612
|
+
const copy = this.refractSnapshot;
|
|
10613
|
+
const source = this.reconShown;
|
|
10614
|
+
for (const handle of this.latePasses) {
|
|
10615
|
+
const definition = passAt(this.passes, handle);
|
|
10616
|
+
if (definition === undefined)
|
|
10617
|
+
continue;
|
|
10618
|
+
if (copy !== null && source !== null) {
|
|
10619
|
+
encoder.copyTextureToTexture({ texture: source }, { texture: copy }, [
|
|
10620
|
+
copy.width,
|
|
10621
|
+
copy.height,
|
|
10622
|
+
]);
|
|
10623
|
+
}
|
|
10624
|
+
const pass = encoder.beginRenderPass({
|
|
10625
|
+
label: 'recon.latePass',
|
|
10626
|
+
timestampWrites: this.gpuTimer.writesFor(),
|
|
10627
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
10628
|
+
depthStencilAttachment: { view: depth, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
10629
|
+
});
|
|
10630
|
+
const context = this.passContext;
|
|
10631
|
+
context.pass = pass;
|
|
10632
|
+
context.outputTransform = this.gradeCode();
|
|
10633
|
+
context.outputExposure = this.gradeExposure();
|
|
10634
|
+
context.jitter = this.latePassJitter;
|
|
10635
|
+
context.format = RECON_HISTORY_FORMAT;
|
|
10636
|
+
context.depthFormat = DEPTH_FORMAT;
|
|
10637
|
+
context.samples = 1;
|
|
10638
|
+
definition.draw(context);
|
|
10639
|
+
context.jitter = this.passJitter;
|
|
10640
|
+
pass.end();
|
|
10641
|
+
}
|
|
9826
10642
|
}
|
|
9827
10643
|
/** Whether this draw lands in the reconstructed picture. See `lateRouting.ts`. */
|
|
9828
10644
|
tracksMotionNow() {
|
|
@@ -10189,7 +11005,10 @@ export class WebGPURenderer {
|
|
|
10189
11005
|
* other is the disagreement the parity rule exists to prevent. Said once rather than dropped.
|
|
10190
11006
|
*/
|
|
10191
11007
|
runReflections(encoder) {
|
|
10192
|
-
|
|
11008
|
+
const frameReflections = this.frameReflections;
|
|
11009
|
+
const surfaceHalf = this.reflectionSurface;
|
|
11010
|
+
const materials = frameReflections !== null && surfaceHalf !== null;
|
|
11011
|
+
if (this.reflectionQueue.length === 0 && !materials)
|
|
10193
11012
|
return;
|
|
10194
11013
|
const scene = this.sceneColorView;
|
|
10195
11014
|
const reflection = this.ssrReflectionView;
|
|
@@ -10234,9 +11053,63 @@ export class WebGPURenderer {
|
|
|
10234
11053
|
f[base + at('uSsrThickness')] = surface.thicknessM;
|
|
10235
11054
|
f[base + at('uSsrSteps')] = surface.steps;
|
|
10236
11055
|
f[base + at('uSsrEdgeFade')] = REFLECTION_EDGE_FADE;
|
|
11056
|
+
f[base + at('uSsrMaterial')] = 0;
|
|
10237
11057
|
slot++;
|
|
10238
11058
|
});
|
|
11059
|
+
const regions = slot;
|
|
11060
|
+
/* The frame's materials' trace takes the slot after the surfaces': the box, its axis and its
|
|
11061
|
+
strength unread, the march and the ceiling its own. See `frameReflections.ts`. */
|
|
11062
|
+
if (materials) {
|
|
11063
|
+
const base = (slot * SSR_SLOT) / 4;
|
|
11064
|
+
f.set(this.ssrDepthToWorld, base + at('uSsrDepthToWorld'));
|
|
11065
|
+
f.set(this.ssrViewProj, base + at('uSsrViewProj'));
|
|
11066
|
+
f.set(this.frameEye, base + at('uSsrEye'));
|
|
11067
|
+
f.fill(0, base + at('uSsrAxis'), base + at('uSsrAxis') + 3);
|
|
11068
|
+
f.fill(1, base + at('uSsrTint'), base + at('uSsrTint') + 3);
|
|
11069
|
+
f[base + at('uSsrStrength')] = 1;
|
|
11070
|
+
f[base + at('uSsrFresnel')] = 0;
|
|
11071
|
+
f[base + at('uSsrFacingCos')] = -1;
|
|
11072
|
+
f[base + at('uSsrReach')] = frameReflections.reachM;
|
|
11073
|
+
f[base + at('uSsrThickness')] = frameReflections.thicknessM;
|
|
11074
|
+
f[base + at('uSsrSteps')] = frameReflections.steps;
|
|
11075
|
+
f[base + at('uSsrEdgeFade')] = REFLECTION_EDGE_FADE;
|
|
11076
|
+
f[base + at('uSsrMaterial')] = 1;
|
|
11077
|
+
f[base + at('uSsrMaxRoughness')] = frameReflections.maxRoughness;
|
|
11078
|
+
slot++;
|
|
11079
|
+
}
|
|
10239
11080
|
device.queue.writeBuffer(this.ssrUniforms, 0, this.ssrStaging, 0, slot * SSR_SLOT);
|
|
11081
|
+
const tracePipeline = postPipeline(this.pipelines, device, this.ssrLayout, 'post.ssrTrace', SSR_TRACE_FRAG_WGSL, this.pipelines.format, PREMULTIPLIED_OVER_BLEND);
|
|
11082
|
+
if (materials && this.depthView !== null && this.ssrMaterialGroup !== null) {
|
|
11083
|
+
/* The frame's opaque lit draws again, into what the trace and the resolve read. */
|
|
11084
|
+
surfaceHalf.draw(encoder, this.depthView, this.issueBound);
|
|
11085
|
+
const trace = encoder.beginRenderPass({
|
|
11086
|
+
label: 'post.ssr.materialTrace',
|
|
11087
|
+
colorAttachments: [
|
|
11088
|
+
{ view: reflection, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
11089
|
+
],
|
|
11090
|
+
});
|
|
11091
|
+
trace.setPipeline(tracePipeline);
|
|
11092
|
+
trace.setBindGroup(0, group, [regions * SSR_SLOT]);
|
|
11093
|
+
trace.draw(3);
|
|
11094
|
+
trace.end();
|
|
11095
|
+
/* The blur's texel and its radius at roughness 1, as a share of the frame's height. */
|
|
11096
|
+
const m = this.ssrMaterialFloats;
|
|
11097
|
+
const blurAt = this.postField(SSR_MATERIAL_FIELDS, 'uSsrBlur');
|
|
11098
|
+
m[blurAt] = 1 / Math.max(width, 1);
|
|
11099
|
+
m[blurAt + 1] = 1 / Math.max(height, 1);
|
|
11100
|
+
m[blurAt + 2] = frameReflections.blur * height;
|
|
11101
|
+
device.queue.writeBuffer(this.ssrMaterialUniforms, 0, m);
|
|
11102
|
+
const resolve = encoder.beginRenderPass({
|
|
11103
|
+
label: 'post.ssr.materialResolve',
|
|
11104
|
+
colorAttachments: [{ view: scene, loadOp: 'load', storeOp: 'store' }],
|
|
11105
|
+
});
|
|
11106
|
+
resolve.setPipeline(postPipeline(this.pipelines, device, this.ssrMaterialLayout, 'post.ssrMaterialResolve', SSR_MATERIAL_RESOLVE_FRAG_WGSL, this.pipelines.format, FRAME_REFLECTION_BLEND));
|
|
11107
|
+
resolve.setBindGroup(0, this.ssrMaterialGroup, [0]);
|
|
11108
|
+
resolve.draw(3);
|
|
11109
|
+
resolve.end();
|
|
11110
|
+
}
|
|
11111
|
+
if (regions === 0)
|
|
11112
|
+
return;
|
|
10240
11113
|
/* Cleared whole and then scissored per surface: the resolve reads every pixel, and one left
|
|
10241
11114
|
from the last frame would composite that reflection over this frame's scene. */
|
|
10242
11115
|
const trace = encoder.beginRenderPass({
|
|
@@ -10245,7 +11118,7 @@ export class WebGPURenderer {
|
|
|
10245
11118
|
{ view: reflection, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
10246
11119
|
],
|
|
10247
11120
|
});
|
|
10248
|
-
trace.setPipeline(
|
|
11121
|
+
trace.setPipeline(tracePipeline);
|
|
10249
11122
|
slot = 0;
|
|
10250
11123
|
this.reflectionQueue.replay((surface) => {
|
|
10251
11124
|
const index = slot++;
|
|
@@ -10877,6 +11750,59 @@ export class WebGPURenderer {
|
|
|
10877
11750
|
!this.overlayActive &&
|
|
10878
11751
|
this.oitMode === 'none');
|
|
10879
11752
|
}
|
|
11753
|
+
/** Whether a draw now is the frame's own, and so has a surface half to keep. */
|
|
11754
|
+
reflectionSurfaceKeeps() {
|
|
11755
|
+
return (this.reflectionSurface !== null &&
|
|
11756
|
+
!this.reflectionPassActive &&
|
|
11757
|
+
!this.probePassActive &&
|
|
11758
|
+
!this.capturePassActive &&
|
|
11759
|
+
!this.framePresented &&
|
|
11760
|
+
!this.overlayActive &&
|
|
11761
|
+
this.oitMode === 'none');
|
|
11762
|
+
}
|
|
11763
|
+
/**
|
|
11764
|
+
* An opaque lit draw's surface half, kept for the frame's materials' reflections: the draw again,
|
|
11765
|
+
* with the same slots, buffers and bind group, through the pipeline with `REFLECTION_SURFACE` on —
|
|
11766
|
+
* which writes what the pixel reflects of its environment into the two targets
|
|
11767
|
+
* `reflectionSurfacePass.ts` holds and tests for the depth its frame draw wrote. Replayed when the
|
|
11768
|
+
* frame's pass has ended, before the trace reads them. See `shaders/flat/reflectionSurface.ts`.
|
|
11769
|
+
*/
|
|
11770
|
+
keepReflectionSurface(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, modelKind, slot, material, instanceBuffer, instances, indirect,
|
|
11771
|
+
/** A batch playing a bone animation, whose surface half moves as its draw does. */
|
|
11772
|
+
animated = null) {
|
|
11773
|
+
const surface = this.reflectionSurface;
|
|
11774
|
+
const present = this.meshPresent.get(base);
|
|
11775
|
+
if (surface === null || present === undefined)
|
|
11776
|
+
return;
|
|
11777
|
+
const animation = animated === null ? null : this.animationViews(animated);
|
|
11778
|
+
const key = `${frameKey}|rs`;
|
|
11779
|
+
const pipeline = this.pipelines.peek(key) ??
|
|
11780
|
+
flatPipeline(this.pipelines, this.surface.device, animation === null ? this.flatLayoutFor(skinned, morphed) : this.animatedBindGroupLayout, this.variant, key, present, false, skinned, morphed, true, layer, instanceBuffer !== null, 'none', this.materialDoubleSided, false, eight, clothed, modelKind, 'whole', true, animation !== null);
|
|
11781
|
+
/* A static list's surface halves go into its own pool, beside its draws. */
|
|
11782
|
+
const command = this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
|
|
11783
|
+
command.pipeline = pipeline;
|
|
11784
|
+
command.bindGroup =
|
|
11785
|
+
animated === null || animation === null
|
|
11786
|
+
? this.flatBindGroupFor(skinned, morphed, deltas)
|
|
11787
|
+
: this.animatedGroup(animated, animation);
|
|
11788
|
+
command.offsetA = slot;
|
|
11789
|
+
command.offsetB = this.passOffset;
|
|
11790
|
+
command.offsetC = material;
|
|
11791
|
+
command.offsetD = this.drawViewOffset;
|
|
11792
|
+
command.offsetCount = 4;
|
|
11793
|
+
let index = 0;
|
|
11794
|
+
for (; index < mesh.vertexBuffers.length; index++) {
|
|
11795
|
+
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
11796
|
+
}
|
|
11797
|
+
if (instanceBuffer !== null)
|
|
11798
|
+
command.vertexBuffers[index++] = instanceBuffer;
|
|
11799
|
+
command.vertexCount = index;
|
|
11800
|
+
command.indexBuffer = mesh.indexBuffer;
|
|
11801
|
+
command.indexed = true;
|
|
11802
|
+
command.count = mesh.indexCount;
|
|
11803
|
+
command.instances = instances;
|
|
11804
|
+
command.indirect = indirect;
|
|
11805
|
+
}
|
|
10880
11806
|
/**
|
|
10881
11807
|
* A skin draw's diffuse and colour halves, kept for the spread: the frame's draw again, twice,
|
|
10882
11808
|
* with the same slots, buffers and bind group, through the pipelines that write the diffuse's
|
|
@@ -10884,6 +11810,11 @@ export class WebGPURenderer {
|
|
|
10884
11810
|
* wrote.
|
|
10885
11811
|
*/
|
|
10886
11812
|
keepSkinDiffuse(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, slot, material) {
|
|
11813
|
+
/* A skin's halves are not bundled: a static list holding one replays draw by draw. */
|
|
11814
|
+
if (this.staticRecording !== null) {
|
|
11815
|
+
this.staticRecording.immediate = true;
|
|
11816
|
+
return;
|
|
11817
|
+
}
|
|
10887
11818
|
const skin = this.skinScatter;
|
|
10888
11819
|
const pipelines = this.skinPipelines;
|
|
10889
11820
|
const present = this.meshPresent.get(base);
|
|
@@ -10899,8 +11830,10 @@ export class WebGPURenderer {
|
|
|
10899
11830
|
command.pipeline = pipeline;
|
|
10900
11831
|
command.bindGroup = bindGroup;
|
|
10901
11832
|
command.offsetA = slot;
|
|
10902
|
-
command.offsetB =
|
|
10903
|
-
command.
|
|
11833
|
+
command.offsetB = this.passOffset;
|
|
11834
|
+
command.offsetC = material;
|
|
11835
|
+
command.offsetD = this.drawViewOffset;
|
|
11836
|
+
command.offsetCount = 4;
|
|
10904
11837
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
10905
11838
|
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
10906
11839
|
}
|
|
@@ -10924,13 +11857,19 @@ export class WebGPURenderer {
|
|
|
10924
11857
|
const skin = this.skinScatter;
|
|
10925
11858
|
if (skin === null || this.skinScattered)
|
|
10926
11859
|
return;
|
|
11860
|
+
/*
|
|
11861
|
+
* **Not the frame's picture, so not the frame's moment**: a blended draw in a mirror, a probe
|
|
11862
|
+
* bake or a scene capture lands over none of the frame's skin. Latching there switched the
|
|
11863
|
+
* spread off for the frame, so a capture with a glowing sign in it left every face drawn after
|
|
11864
|
+
* it flat. `scripts/capture-check.mjs`.
|
|
11865
|
+
*/
|
|
11866
|
+
if (this.reflectionPassActive || this.probePassActive || this.capturePassActive)
|
|
11867
|
+
return;
|
|
10927
11868
|
/* Latched whether or not skin was kept: a skin drawn after this point is the whole surface. */
|
|
10928
11869
|
this.skinScattered = true;
|
|
10929
11870
|
if (!skin.pending)
|
|
10930
11871
|
return;
|
|
10931
|
-
if (this.
|
|
10932
|
-
return;
|
|
10933
|
-
if (this.capturePassActive)
|
|
11872
|
+
if (this.oitReplaying)
|
|
10934
11873
|
return;
|
|
10935
11874
|
const encoder = this.encoder;
|
|
10936
11875
|
const depth = this.depthView;
|
|
@@ -11045,6 +11984,66 @@ export class WebGPURenderer {
|
|
|
11045
11984
|
this.volumeDepthTaken = true;
|
|
11046
11985
|
return true;
|
|
11047
11986
|
}
|
|
11987
|
+
/** The colour copy a registered pass reads, at the scene's size and format. See `passColour`. */
|
|
11988
|
+
allocatePassColour() {
|
|
11989
|
+
const source = this.sceneColor;
|
|
11990
|
+
if (source === null || this.passColour !== null)
|
|
11991
|
+
return;
|
|
11992
|
+
this.passColour = this.surface.device.createTexture({
|
|
11993
|
+
label: 'pass.sceneColor',
|
|
11994
|
+
size: [source.width, source.height],
|
|
11995
|
+
format: this.pipelines.format,
|
|
11996
|
+
usage: 0x4 | 0x2, // TEXTURE_BINDING | COPY_DST
|
|
11997
|
+
});
|
|
11998
|
+
this.passColourView = this.passColour.createView();
|
|
11999
|
+
}
|
|
12000
|
+
/**
|
|
12001
|
+
* The frame's colour so far, into `passColour`, for a registered pass that reads it: the
|
|
12002
|
+
* boundary `takeRefractSnapshot` describes — flush, end the pass, copy what its end resolved,
|
|
12003
|
+
* reopen with `load` — and refused where that one is, in a mirror, a probe bake or a capture,
|
|
12004
|
+
* whose pictures are not the frame's. **Not latched**, unlike refraction's: each reading pass
|
|
12005
|
+
* sees the passes drawn before it, at a full-frame copy each.
|
|
12006
|
+
*/
|
|
12007
|
+
takePassColour() {
|
|
12008
|
+
if (this.reflectionPassActive || this.probePassActive || this.capturePassActive)
|
|
12009
|
+
return;
|
|
12010
|
+
const copy = this.passColour;
|
|
12011
|
+
const source = this.sceneColor;
|
|
12012
|
+
const encoder = this.encoder;
|
|
12013
|
+
if (copy === null || source === null || encoder === null || this.pass === null)
|
|
12014
|
+
return;
|
|
12015
|
+
if (this.quality.frameGraph)
|
|
12016
|
+
this.flushGraph();
|
|
12017
|
+
this.pass.end();
|
|
12018
|
+
encoder.copyTextureToTexture({ texture: source }, { texture: copy }, [copy.width, copy.height]);
|
|
12019
|
+
this.pass = this.reopenFramePass('pass.colourTaken');
|
|
12020
|
+
}
|
|
12021
|
+
/**
|
|
12022
|
+
* The frame's pass again after a copy between passes, loading everything already drawn: the same
|
|
12023
|
+
* attachments the copy's caller ended, with nothing cleared.
|
|
12024
|
+
*/
|
|
12025
|
+
reopenFramePass(label) {
|
|
12026
|
+
const encoder = this.encoder;
|
|
12027
|
+
const target = this.compositeTarget();
|
|
12028
|
+
if (encoder === null || target === null)
|
|
12029
|
+
return null;
|
|
12030
|
+
const multisampled = this.compositeMsaa() !== null;
|
|
12031
|
+
return encoder.beginRenderPass({
|
|
12032
|
+
label,
|
|
12033
|
+
timestampWrites: this.gpuTimer.writesFor(),
|
|
12034
|
+
colorAttachments: [
|
|
12035
|
+
{
|
|
12036
|
+
view: this.compositeMsaa() ?? target,
|
|
12037
|
+
resolveTarget: multisampled ? target : undefined,
|
|
12038
|
+
loadOp: 'load',
|
|
12039
|
+
storeOp: resolvedStoreOp(multisampled, false, this.quality.discardResolvedAttachments),
|
|
12040
|
+
},
|
|
12041
|
+
],
|
|
12042
|
+
depthStencilAttachment: this.depthView === null
|
|
12043
|
+
? undefined
|
|
12044
|
+
: { view: this.depthView, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
12045
|
+
});
|
|
12046
|
+
}
|
|
11048
12047
|
/**
|
|
11049
12048
|
* The pass to record into, reopening one on the canvas if the frame has already gone.
|
|
11050
12049
|
*
|
|
@@ -11226,6 +12225,8 @@ export class WebGPURenderer {
|
|
|
11226
12225
|
/* And a march reads the same copy, for the same reason and with the same failure if it is
|
|
11227
12226
|
discarded: a depth of zero is the far plane reversed, so every ray finds nothing. */
|
|
11228
12227
|
this.reflectionQueue.length > 0 ||
|
|
12228
|
+
/* And the frame's materials' reflections, whose surface half tests against the depth too. */
|
|
12229
|
+
this.reflectionSurface !== null ||
|
|
11229
12230
|
/* And the medium's own march, which stops where the frame's depth says a surface is. Left
|
|
11230
12231
|
out of this, every ray runs to `maxDistance` through the walls and the fog is drawn on
|
|
11231
12232
|
top of the room rather than in it. */
|
|
@@ -11259,6 +12260,9 @@ export class WebGPURenderer {
|
|
|
11259
12260
|
* site reads as one `if` either way.
|
|
11260
12261
|
*/
|
|
11261
12262
|
recordDraw(reads, writes) {
|
|
12263
|
+
/* A static list recording takes its draws into its own pool, never the frame graph's. */
|
|
12264
|
+
if (this.staticRecording !== null)
|
|
12265
|
+
return takeRecorded(this.staticRecording.frame);
|
|
11262
12266
|
/* The order-independent replay owns its pass and issues directly into it; a recorded command
|
|
11263
12267
|
would be replayed into the frame's passes instead, which is not where these belong. */
|
|
11264
12268
|
if (this.oitEncoder !== null)
|
|
@@ -11306,10 +12310,26 @@ export class WebGPURenderer {
|
|
|
11306
12310
|
}
|
|
11307
12311
|
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
11308
12312
|
issueCommand(pass, command) {
|
|
12313
|
+
/* A static list, recorded once: one call, and the pass's bindings are its to set again. */
|
|
12314
|
+
if (command.bundles !== null) {
|
|
12315
|
+
pass.executeBundles(command.bundles);
|
|
12316
|
+
return;
|
|
12317
|
+
}
|
|
11309
12318
|
if (command.pipeline === null || command.bindGroup === null)
|
|
11310
12319
|
return;
|
|
11311
12320
|
pass.setPipeline(command.pipeline);
|
|
11312
|
-
if (command.offsetCount ===
|
|
12321
|
+
if (command.offsetCount === 4) {
|
|
12322
|
+
pass.setBindGroup(0, command.bindGroup, [
|
|
12323
|
+
command.offsetA,
|
|
12324
|
+
command.offsetB,
|
|
12325
|
+
command.offsetC,
|
|
12326
|
+
command.offsetD,
|
|
12327
|
+
]);
|
|
12328
|
+
}
|
|
12329
|
+
else if (command.offsetCount === 3) {
|
|
12330
|
+
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB, command.offsetC]);
|
|
12331
|
+
}
|
|
12332
|
+
else if (command.offsetCount === 2) {
|
|
11313
12333
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
11314
12334
|
}
|
|
11315
12335
|
else if (command.offsetCount === 1) {
|
|
@@ -11575,7 +12595,7 @@ export class WebGPURenderer {
|
|
|
11575
12595
|
this.pass?.end();
|
|
11576
12596
|
this.pass = null;
|
|
11577
12597
|
if (!this.surface.lost)
|
|
11578
|
-
this.
|
|
12598
|
+
this.submit([this.encoder.finish()]);
|
|
11579
12599
|
this.encoder = null;
|
|
11580
12600
|
}
|
|
11581
12601
|
endFrame() {
|
|
@@ -11639,7 +12659,7 @@ export class WebGPURenderer {
|
|
|
11639
12659
|
* there would have nothing to ride on. See `GpuTimestamps.resolve`.
|
|
11640
12660
|
*/
|
|
11641
12661
|
this.gpuTimer.resolve(this.encoder);
|
|
11642
|
-
this.
|
|
12662
|
+
this.submit([this.encoder.finish()]);
|
|
11643
12663
|
this.encoder = null;
|
|
11644
12664
|
this.framePresented = true;
|
|
11645
12665
|
/* Latched rather than assigned: `framePresented` goes back to false every `beginFrame`. */
|
|
@@ -12042,6 +13062,9 @@ export class WebGPURenderer {
|
|
|
12042
13062
|
const reads = maskOf(...(definition.reads ?? []));
|
|
12043
13063
|
this.passReads.set(handle, reads);
|
|
12044
13064
|
this.passReadsUnion |= reads;
|
|
13065
|
+
/* The colour copy a reader needs, now rather than at its first draw: see `passColour`. */
|
|
13066
|
+
if ((reads & COLOR_SNAPSHOT) !== 0 && this.passColour === null)
|
|
13067
|
+
this.allocatePassColour();
|
|
12045
13068
|
if (definition.prepare !== undefined)
|
|
12046
13069
|
this.preparingPasses++;
|
|
12047
13070
|
definition.init?.(this.passDevice());
|
|
@@ -12061,6 +13084,8 @@ export class WebGPURenderer {
|
|
|
12061
13084
|
environment: null,
|
|
12062
13085
|
distanceField: null,
|
|
12063
13086
|
jitter: this.passJitter,
|
|
13087
|
+
sceneDepth: null,
|
|
13088
|
+
sceneColor: null,
|
|
12064
13089
|
};
|
|
12065
13090
|
/**
|
|
12066
13091
|
* The object handed to a pass as `PrepareContext.environment`, rebuilt only when it changes.
|
|
@@ -12146,6 +13171,15 @@ export class WebGPURenderer {
|
|
|
12146
13171
|
* than merely what happens.
|
|
12147
13172
|
*/
|
|
12148
13173
|
this.prepareContext.distanceField = this.fieldComposer?.field ?? null;
|
|
13174
|
+
/* Settled for the frame: `ensureComposite` has run, so the view stays until the pass draws. */
|
|
13175
|
+
this.prepareContext.sceneDepth = this.resolvedDepthView;
|
|
13176
|
+
/*
|
|
13177
|
+
* Under a reconstruction a colour reader draws after the upscale (see `drawPass`), so it reads
|
|
13178
|
+
* the output-size copy the late refraction keeps rather than the render's.
|
|
13179
|
+
*/
|
|
13180
|
+
this.prepareContext.sceneColor = this.reconstructionWanted
|
|
13181
|
+
? this.refractSnapshotView
|
|
13182
|
+
: this.passColourView;
|
|
12149
13183
|
for (const definition of this.passes.definitions) {
|
|
12150
13184
|
definition?.prepare?.(this.prepareContext);
|
|
12151
13185
|
}
|
|
@@ -12163,6 +13197,35 @@ export class WebGPURenderer {
|
|
|
12163
13197
|
const definition = passAt(this.passes, handle);
|
|
12164
13198
|
if (definition === undefined)
|
|
12165
13199
|
return;
|
|
13200
|
+
const reads = this.passReads.get(handle) ?? 0;
|
|
13201
|
+
/*
|
|
13202
|
+
* **Under a reconstruction, a pass that reads the frame's colour draws after the blended draws
|
|
13203
|
+
* do.** Those land after the upscale, at the output's size, so a pass drawn among them at the
|
|
13204
|
+
* render's would read a frame without them and then be covered by them — a full-screen effect
|
|
13205
|
+
* with the hit sparks drawn sharp over it. Queued instead, and drawn by `runLate` over the
|
|
13206
|
+
* reconstructed picture, after every late draw, whatever order it was asked in.
|
|
13207
|
+
*/
|
|
13208
|
+
if ((reads & COLOR_SNAPSHOT) !== 0 && this.drawsLate()) {
|
|
13209
|
+
this.latePasses.push(handle);
|
|
13210
|
+
return;
|
|
13211
|
+
}
|
|
13212
|
+
/*
|
|
13213
|
+
* A pass that reads the depth copy gets it with everything drawn so far: the copy is taken
|
|
13214
|
+
* here, once a frame, as a soft-edged sprite takes it. Before `openPass` below, because taking
|
|
13215
|
+
* it ends the frame's pass and opens another.
|
|
13216
|
+
*/
|
|
13217
|
+
if ((reads & DEPTH_SNAPSHOT) !== 0 && !this.framePresented && this.openPass() !== null) {
|
|
13218
|
+
this.takeVolumeDepth();
|
|
13219
|
+
}
|
|
13220
|
+
/*
|
|
13221
|
+
* And one that reads the colour gets it afresh, with everything drawn before it — a full-screen
|
|
13222
|
+
* pass drawn last reads the whole frame and an effect drawn mid-frame reads the frame so far.
|
|
13223
|
+
* A copy a reading pass, where the depth above is one a frame: what a colour reader draws is
|
|
13224
|
+
* itself part of what the next one should see.
|
|
13225
|
+
*/
|
|
13226
|
+
if ((reads & COLOR_SNAPSHOT) !== 0 && !this.framePresented && this.openPass() !== null) {
|
|
13227
|
+
this.takePassColour();
|
|
13228
|
+
}
|
|
12166
13229
|
if (!this.quality.frameGraph) {
|
|
12167
13230
|
const pass = this.openPass();
|
|
12168
13231
|
if (pass === null)
|
|
@@ -12170,7 +13233,7 @@ export class WebGPURenderer {
|
|
|
12170
13233
|
definition.draw(this.fillPassContext(pass));
|
|
12171
13234
|
return;
|
|
12172
13235
|
}
|
|
12173
|
-
recordNode(this.arena, VERB_PASS,
|
|
13236
|
+
recordNode(this.arena, VERB_PASS, reads, this.currentTarget(), handle, 0);
|
|
12174
13237
|
if (this.framePresented)
|
|
12175
13238
|
this.queueOverlayFlush();
|
|
12176
13239
|
}
|
|
@@ -12257,7 +13320,7 @@ export class WebGPURenderer {
|
|
|
12257
13320
|
definition.dispatch(this.computeContext);
|
|
12258
13321
|
pass.end();
|
|
12259
13322
|
this.flushRings();
|
|
12260
|
-
|
|
13323
|
+
this.submit([encoder.finish()]);
|
|
12261
13324
|
}
|
|
12262
13325
|
/** Let go of a definition. A handle kept past this dispatches nothing; see `ComputeHandle`. */
|
|
12263
13326
|
unregisterCompute(handle) {
|
|
@@ -12279,8 +13342,8 @@ export class WebGPURenderer {
|
|
|
12279
13342
|
bindClusters(camera, env, f, i, at) {
|
|
12280
13343
|
const binner = this.clusterBinner;
|
|
12281
13344
|
i[at('uClustered')] = binner === null ? 0 : 1;
|
|
12282
|
-
|
|
12283
|
-
|
|
13345
|
+
/* Written whether or not the lights are clustered: a surface overlay's rim finds its place on
|
|
13346
|
+
the screen through the same two (\`overlayScreen\`). */
|
|
12284
13347
|
const p = camera.projection;
|
|
12285
13348
|
const tanHalfFovY = 1 / (p[5] ?? 1);
|
|
12286
13349
|
const aspect = (p[5] ?? 1) / (p[0] ?? 1);
|
|
@@ -12296,6 +13359,8 @@ export class WebGPURenderer {
|
|
|
12296
13359
|
f[frustumAt + 2] = tanHalfFovY;
|
|
12297
13360
|
f[frustumAt + 3] = aspect;
|
|
12298
13361
|
f.set(camera.view, at('uView'));
|
|
13362
|
+
if (binner === null)
|
|
13363
|
+
return;
|
|
12299
13364
|
/* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
|
|
12300
13365
|
const set = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
|
|
12301
13366
|
/* A slot is read only under `POINT_SHADOWS`; see `lightHasFixture`. */
|
|
@@ -12596,6 +13661,43 @@ export class WebGPURenderer {
|
|
|
12596
13661
|
}
|
|
12597
13662
|
/** This frame's exposure into the tone curve. Starts at the profile's own. */
|
|
12598
13663
|
exposure;
|
|
13664
|
+
/**
|
|
13665
|
+
* The `filmic` curve every later frame is graded through, until it is set again. See
|
|
13666
|
+
* `RendererApi.setFilmicCurve`; the constants are solved here once, not per frame or per pixel.
|
|
13667
|
+
*/
|
|
13668
|
+
setFilmicCurve(curve) {
|
|
13669
|
+
this.filmCurve = curve;
|
|
13670
|
+
if (resolveFilmicCurve(curve, this.filmConstants, this.headroom) && !this.warnedFilmClamped) {
|
|
13671
|
+
this.warnedFilmClamped = true;
|
|
13672
|
+
console.warn(FILMIC_CLAMPED);
|
|
13673
|
+
}
|
|
13674
|
+
}
|
|
13675
|
+
warnedFilmClamped = false;
|
|
13676
|
+
/** The curve last set, so a new headroom can solve it again. */
|
|
13677
|
+
filmCurve = DEFAULT_FILMIC_CURVE;
|
|
13678
|
+
/** The display's peak over paper white; 1 on a standard range. See `setDisplayLuminance`. */
|
|
13679
|
+
headroom = 1;
|
|
13680
|
+
/** The range the frame goes out in. See `RendererApi.displayRange`. */
|
|
13681
|
+
get displayRange() {
|
|
13682
|
+
return this.surface.displayRange;
|
|
13683
|
+
}
|
|
13684
|
+
/** Why it is that range, in words. */
|
|
13685
|
+
get displayRangeReason() {
|
|
13686
|
+
return this.surface.displayRangeReason;
|
|
13687
|
+
}
|
|
13688
|
+
/**
|
|
13689
|
+
* How bright paper white and the display's peak are, in any one unit (nits, usually), held until
|
|
13690
|
+
* changed: their ratio is how far above white the highlights may run on a high range display,
|
|
13691
|
+
* and nothing changes on a standard one. See `RendererApi.setDisplayLuminance`.
|
|
13692
|
+
*/
|
|
13693
|
+
setDisplayLuminance(paperWhite, peak) {
|
|
13694
|
+
this.headroom = displayHeadroom(this.surface.displayRange, paperWhite, peak);
|
|
13695
|
+
resolveFilmicCurve(this.filmCurve, this.filmConstants, this.headroom);
|
|
13696
|
+
}
|
|
13697
|
+
/** The curve's eight constants, and the two views the composite uploads them through. */
|
|
13698
|
+
filmConstants = new Float32Array(FILMIC_CONSTANTS);
|
|
13699
|
+
filmA = this.filmConstants.subarray(0, 4);
|
|
13700
|
+
filmB = this.filmConstants.subarray(4, 8);
|
|
12599
13701
|
/*
|
|
12600
13702
|
* -----------------------------------------------------------------------------------------
|
|
12601
13703
|
* The three screen effects, which this backend cannot yet honour — and says so.
|
|
@@ -12639,6 +13741,8 @@ export class WebGPURenderer {
|
|
|
12639
13741
|
veilColor = [0, 0, 0];
|
|
12640
13742
|
veilAlpha = 0;
|
|
12641
13743
|
vignette = 0;
|
|
13744
|
+
/** The lens's colour fringe: red's and green's pull and where it begins. See `fringe.ts`. */
|
|
13745
|
+
fringe = new Float32Array(FRINGE_FLOATS);
|
|
12642
13746
|
grain = 0;
|
|
12643
13747
|
grainSeed = 0;
|
|
12644
13748
|
warnedLookWithoutComposite = false;
|
|
@@ -12660,6 +13764,20 @@ export class WebGPURenderer {
|
|
|
12660
13764
|
}
|
|
12661
13765
|
this.vignette = clampVignette(strength);
|
|
12662
13766
|
}
|
|
13767
|
+
/**
|
|
13768
|
+
* The lens's colour fringe, held until changed: `intensity` is a percentage, 0 is none, and red
|
|
13769
|
+
* and green are pulled toward the centre by their wavelength's distance from blue, growing from
|
|
13770
|
+
* `start` — a share of the half-frame, 0 the centre — to the whole of it at the edge. Applied to
|
|
13771
|
+
* scene light at the composite's first read. Needs `screenEffects`, and says so once rather than
|
|
13772
|
+
* doing nothing. See `shaders/fringe.ts`.
|
|
13773
|
+
*/
|
|
13774
|
+
setChromaticAberration(intensity, start = 0) {
|
|
13775
|
+
if (!this.hasComposite) {
|
|
13776
|
+
this.warnLookWithoutComposite(intensity);
|
|
13777
|
+
return;
|
|
13778
|
+
}
|
|
13779
|
+
resolveFringe(intensity, start, this.fringe);
|
|
13780
|
+
}
|
|
12663
13781
|
/**
|
|
12664
13782
|
* Film grain, held until changed: an amplitude in display values (0 is none, 0.03 is a fine
|
|
12665
13783
|
* grain) and this frame's seed. **The seed is the caller's**, because the engine takes time from
|
|
@@ -12793,9 +13911,26 @@ export class WebGPURenderer {
|
|
|
12793
13911
|
setSpeedRush(strength) {
|
|
12794
13912
|
this.rushStrength = Math.min(Math.max(strength, 0), 1);
|
|
12795
13913
|
}
|
|
12796
|
-
/**
|
|
12797
|
-
|
|
13914
|
+
/**
|
|
13915
|
+
* How much of the frame's motion blur to apply, 0 to 1, and the longest smear as a share of the
|
|
13916
|
+
* frame, both held until changed. See `RendererApi.setCameraMotionBlur`.
|
|
13917
|
+
*/
|
|
13918
|
+
setCameraMotionBlur(scale, maxShare = MOTION_BLUR_MAX_UV) {
|
|
12798
13919
|
this.motionBlurScale = Math.min(Math.max(scale, 0), 1);
|
|
13920
|
+
this.motionBlurMax = clampMotionShare(maxShare);
|
|
13921
|
+
}
|
|
13922
|
+
/** The longest smear, as a share of the frame. See `setCameraMotionBlur`. */
|
|
13923
|
+
motionBlurMax = MOTION_BLUR_MAX_UV;
|
|
13924
|
+
warnedObjectBlur = false;
|
|
13925
|
+
/**
|
|
13926
|
+
* Said once: a draw named where it was last frame while the frame blurs, and this frame keeps no
|
|
13927
|
+
* motion target to blur it by — the camera's motion alone smears it. See `OBJECT_BLUR_UNKEPT`.
|
|
13928
|
+
*/
|
|
13929
|
+
warnObjectBlur() {
|
|
13930
|
+
if (this.warnedObjectBlur)
|
|
13931
|
+
return;
|
|
13932
|
+
this.warnedObjectBlur = true;
|
|
13933
|
+
console.warn(OBJECT_BLUR_UNKEPT);
|
|
12799
13934
|
}
|
|
12800
13935
|
/**
|
|
12801
13936
|
* The camera has cut: this frame is a new shot, not the next moment of the last one.
|
|
@@ -13239,7 +14374,7 @@ export class WebGPURenderer {
|
|
|
13239
14374
|
.end();
|
|
13240
14375
|
}
|
|
13241
14376
|
}
|
|
13242
|
-
|
|
14377
|
+
this.submit([clear.finish()]);
|
|
13243
14378
|
}
|
|
13244
14379
|
/**
|
|
13245
14380
|
* Declare where a grid's probes stand, and allocate the layers for them.
|
|
@@ -13342,18 +14477,19 @@ export class WebGPURenderer {
|
|
|
13342
14477
|
* `bindMeshPass` and `drawMesh`, which record into whatever `this.pass` is. That is the same
|
|
13343
14478
|
* arrangement `bakeFace` uses for a point light's cube, and it is why a bake must not be
|
|
13344
14479
|
* opened inside a frame's pass by a caller who then expects the frame to continue.
|
|
13345
|
-
|
|
13346
|
-
const savedPass = this.pass;
|
|
13347
|
-
/*
|
|
14480
|
+
*
|
|
13348
14481
|
* **A bake is a boundary, and boundaries flush.** The mirror says why in its own words: what
|
|
13349
14482
|
* has been recorded and not yet replayed belongs to the pass that is about to end, and leaving
|
|
13350
14483
|
* it replays it into whichever pass opens next — a draw in the wrong target rather than a
|
|
13351
14484
|
* missing one. This is the fourth such boundary in the backend and the only one that was never
|
|
13352
14485
|
* given the treatment, which is what let a bake carry the frame's pending draws into a cube
|
|
13353
|
-
* face.
|
|
14486
|
+
* face. **The pass is saved after the flush**, which opens it where none was: saved before,
|
|
14487
|
+
* the null given back left the flushed pass open and the frame's encoder locked. See
|
|
14488
|
+
* `captureScene`.
|
|
13354
14489
|
*/
|
|
13355
14490
|
if (this.quality.frameGraph)
|
|
13356
14491
|
this.flushGraph();
|
|
14492
|
+
const savedPass = this.pass;
|
|
13357
14493
|
const savedEncoder = this.encoder;
|
|
13358
14494
|
/*
|
|
13359
14495
|
* **The rings are marked here and given back after the submit below**, because this bake is its
|
|
@@ -13449,7 +14585,7 @@ export class WebGPURenderer {
|
|
|
13449
14585
|
if (whole)
|
|
13450
14586
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
13451
14587
|
this.flushRings();
|
|
13452
|
-
|
|
14588
|
+
this.submit([encoder.finish()]);
|
|
13453
14589
|
this.rewindRings(skinMark);
|
|
13454
14590
|
this.encoderEpoch = frameEpoch;
|
|
13455
14591
|
this.encoder = savedEncoder;
|
|
@@ -13459,6 +14595,7 @@ export class WebGPURenderer {
|
|
|
13459
14595
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
13460
14596
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
13461
14597
|
this.materials.dirty();
|
|
14598
|
+
this.reopenPass();
|
|
13462
14599
|
this.probePassActive = false;
|
|
13463
14600
|
this.probeBounce = false;
|
|
13464
14601
|
this.rebindEnvironment();
|
|
@@ -13506,7 +14643,7 @@ export class WebGPURenderer {
|
|
|
13506
14643
|
this.uploadProbeFaces(probe, image);
|
|
13507
14644
|
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
13508
14645
|
this.buildProbeChain(encoder, probe, 0);
|
|
13509
|
-
|
|
14646
|
+
this.submit([encoder.finish()]);
|
|
13510
14647
|
this.markProbeFilled(0);
|
|
13511
14648
|
this.probeAmbient = options?.irradiance ?? true;
|
|
13512
14649
|
this.rebuildFlatBindGroup();
|
|
@@ -13531,7 +14668,7 @@ export class WebGPURenderer {
|
|
|
13531
14668
|
const { device } = this.surface;
|
|
13532
14669
|
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
13533
14670
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
13534
|
-
|
|
14671
|
+
this.submit([encoder.finish()]);
|
|
13535
14672
|
this.markProbeFilled(layer);
|
|
13536
14673
|
this.probeAmbient = options?.irradiance ?? true;
|
|
13537
14674
|
this.rebuildFlatBindGroup();
|
|
@@ -13938,6 +15075,7 @@ export class WebGPURenderer {
|
|
|
13938
15075
|
this.sceneColor,
|
|
13939
15076
|
this.sceneColorMsaa,
|
|
13940
15077
|
this.refractSnapshot,
|
|
15078
|
+
this.passColour,
|
|
13941
15079
|
this.resolvedDepth,
|
|
13942
15080
|
this.aoTarget,
|
|
13943
15081
|
this.aoScratch,
|
|
@@ -14003,6 +15141,10 @@ export class WebGPURenderer {
|
|
|
14003
15141
|
usage: 0x4 | 0x2 | 0x10, // TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT
|
|
14004
15142
|
});
|
|
14005
15143
|
this.refractSnapshotView = this.refractSnapshot.createView();
|
|
15144
|
+
this.passColour = null;
|
|
15145
|
+
this.passColourView = null;
|
|
15146
|
+
if ((this.passReadsUnion & COLOR_SNAPSHOT) !== 0)
|
|
15147
|
+
this.allocatePassColour();
|
|
14006
15148
|
this.refractMips = new SnapshotMips(device, this.pipelines, this.refractSnapshot, reconstructed ? RECON_HISTORY_FORMAT : format);
|
|
14007
15149
|
/*
|
|
14008
15150
|
* **Every cached flat bind group is stale the moment this view changes**, and the failure is
|
|
@@ -14218,6 +15360,10 @@ export class WebGPURenderer {
|
|
|
14218
15360
|
usage: USAGE,
|
|
14219
15361
|
});
|
|
14220
15362
|
this.ssrReflectionView = this.ssrReflection.createView();
|
|
15363
|
+
/* The frame's materials, at the scene's size; a stand-in where nothing traces them. */
|
|
15364
|
+
this.reflectionSurface?.size(width, height);
|
|
15365
|
+
const probeMap = this.reflectionSurface?.probeView ?? this.blankView;
|
|
15366
|
+
const tintMap = this.reflectionSurface?.tintView ?? this.blankView;
|
|
14221
15367
|
this.ssrBindGroup =
|
|
14222
15368
|
this.resolvedDepthView === null || this.sceneColorView === null
|
|
14223
15369
|
? null
|
|
@@ -14231,8 +15377,23 @@ export class WebGPURenderer {
|
|
|
14231
15377
|
},
|
|
14232
15378
|
...entry(SSR_TEXTURES.uSsrDepth, this.resolvedDepthView, true),
|
|
14233
15379
|
...entry(SSR_TEXTURES.uSsrScene, this.sceneColorView),
|
|
15380
|
+
...entry(SSR_TEXTURES.uSsrProbeMap, probeMap),
|
|
15381
|
+
...entry(SSR_TEXTURES.uSsrTintMap, tintMap),
|
|
14234
15382
|
],
|
|
14235
15383
|
});
|
|
15384
|
+
this.ssrMaterialGroup = device.createBindGroup({
|
|
15385
|
+
label: 'post.ssrMaterialGroup',
|
|
15386
|
+
layout: this.ssrMaterialLayout,
|
|
15387
|
+
entries: [
|
|
15388
|
+
{
|
|
15389
|
+
binding: SSR_MATERIAL_UNIFORMS,
|
|
15390
|
+
resource: { buffer: this.ssrMaterialUniforms, size: SSR_MATERIAL_SIZE },
|
|
15391
|
+
},
|
|
15392
|
+
...entry(SSR_MATERIAL_TEXTURES.uSsrReflection, this.ssrReflectionView),
|
|
15393
|
+
...entry(SSR_MATERIAL_TEXTURES.uSsrProbeMap, probeMap),
|
|
15394
|
+
...entry(SSR_MATERIAL_TEXTURES.uSsrTintMap, tintMap),
|
|
15395
|
+
],
|
|
15396
|
+
});
|
|
14236
15397
|
this.ssrResolveGroup = device.createBindGroup({
|
|
14237
15398
|
label: 'post.ssrResolveGroup',
|
|
14238
15399
|
layout: this.ssrResolveLayout,
|