@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
|
@@ -187,10 +187,26 @@ import {
|
|
|
187
187
|
packModel,
|
|
188
188
|
skinProfileDistance,
|
|
189
189
|
} from '../../surfaceModel.ts';
|
|
190
|
-
import type { SurfaceModel } from '../../surfaceModel.ts';
|
|
190
|
+
import type { SurfaceModel, SurfaceModelKind } from '../../surfaceModel.ts';
|
|
191
|
+
import {
|
|
192
|
+
OVERLAY_FLOATS,
|
|
193
|
+
overlayLays,
|
|
194
|
+
packSurfaceOverlay,
|
|
195
|
+
type SurfaceOverlay,
|
|
196
|
+
} from '../../surfaceOverlay.ts';
|
|
197
|
+
import {
|
|
198
|
+
frameReflectionsRefused,
|
|
199
|
+
resolveFrameReflections,
|
|
200
|
+
type ResolvedFrameReflections,
|
|
201
|
+
} from '../../frameReflections.ts';
|
|
202
|
+
import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.ts';
|
|
191
203
|
import { validateClothBinding } from '../../clothBindingData.ts';
|
|
192
204
|
import type { ClothBindingData } from '../../clothBindingData.ts';
|
|
193
|
-
import type {
|
|
205
|
+
import type {
|
|
206
|
+
CompressedTextureFormat,
|
|
207
|
+
CompressedTextureSource,
|
|
208
|
+
SurfaceSource,
|
|
209
|
+
} from '../../compressedSource.ts';
|
|
194
210
|
import {
|
|
195
211
|
createSdfTextBindGroup,
|
|
196
212
|
createSdfTextBindGroupLayout,
|
|
@@ -216,7 +232,24 @@ import type { SurfaceMaterial, SurfaceTextureOptions } from '../../surfaceTextur
|
|
|
216
232
|
import type { RenderBackend } from '../api.ts';
|
|
217
233
|
import { reflectionTargetSize, type ReflectionSize } from '../../planarReflectionDraw.ts';
|
|
218
234
|
import { drawingBufferSize } from '../../drawingBuffer.ts';
|
|
219
|
-
import {
|
|
235
|
+
import {
|
|
236
|
+
MOTION_BLUR_MAX_UV,
|
|
237
|
+
OBJECT_BLUR_UNKEPT,
|
|
238
|
+
OUTPUT_TRANSFORM_CODE,
|
|
239
|
+
RUSH_REACH_UV,
|
|
240
|
+
clampMotionShare,
|
|
241
|
+
forwardTransformCode,
|
|
242
|
+
} from '../../vertexDefaults.ts';
|
|
243
|
+
import { FRINGE_FLOATS, resolveFringe } from '../../shaders/fringe.ts';
|
|
244
|
+
import { displayHeadroom, type DisplayRange } from '../../displayRange.ts';
|
|
245
|
+
import {
|
|
246
|
+
DEFAULT_FILMIC_CURVE,
|
|
247
|
+
FILMIC_CLAMPED,
|
|
248
|
+
FILMIC_CONSTANTS,
|
|
249
|
+
FILMIC_WITHOUT_COMPOSITE,
|
|
250
|
+
resolveFilmicCurve,
|
|
251
|
+
type FilmicCurve,
|
|
252
|
+
} from '../../filmicCurve.ts';
|
|
220
253
|
import { ExposurePass } from './exposurePass.ts';
|
|
221
254
|
import { AO_STORE } from '../../shaders/ambientOcclusion.ts';
|
|
222
255
|
import { clampAutoExposure } from '../../shaders/exposure.ts';
|
|
@@ -281,9 +314,15 @@ import {
|
|
|
281
314
|
DECAL_TEXTURES,
|
|
282
315
|
DECAL_UNIFORMS,
|
|
283
316
|
PREMULTIPLIED_OVER_BLEND,
|
|
317
|
+
FRAME_REFLECTION_BLEND,
|
|
284
318
|
REFLECTION_OVER_BLEND,
|
|
285
319
|
SSR_FRAG_FIELDS,
|
|
286
320
|
SSR_FRAG_SIZE,
|
|
321
|
+
SSR_MATERIAL_FIELDS,
|
|
322
|
+
SSR_MATERIAL_RESOLVE_FRAG_WGSL,
|
|
323
|
+
SSR_MATERIAL_SIZE,
|
|
324
|
+
SSR_MATERIAL_TEXTURES,
|
|
325
|
+
SSR_MATERIAL_UNIFORMS,
|
|
287
326
|
SSR_RESOLVE_FRAG_WGSL,
|
|
288
327
|
SSR_RESOLVE_TEXTURES,
|
|
289
328
|
SSR_TEXTURES,
|
|
@@ -377,23 +416,41 @@ import {
|
|
|
377
416
|
SUN_STATIC_LAYER,
|
|
378
417
|
} from '../../shadowMap.ts';
|
|
379
418
|
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
380
|
-
import {
|
|
419
|
+
import {
|
|
420
|
+
VERTEX_LAYOUT,
|
|
421
|
+
createGpuMesh,
|
|
422
|
+
createGpuMeshIncremental,
|
|
423
|
+
type GpuMesh,
|
|
424
|
+
type StagedRewrite,
|
|
425
|
+
} from './buffers.ts';
|
|
381
426
|
import type { GpuSurface } from './device.ts';
|
|
382
427
|
import { SkinPaletteRing } from './skinPaletteRing.ts';
|
|
383
428
|
import {
|
|
384
429
|
DEPTH_FORMAT,
|
|
430
|
+
FLAT_ANIMATED_VERT_FIELDS,
|
|
385
431
|
FLAT_VERT_FIELDS,
|
|
386
432
|
FLAT_VERT_SIZE,
|
|
433
|
+
FLAT_VIEW_FIELDS,
|
|
434
|
+
FLAT_VIEW_SIZE,
|
|
387
435
|
createFlatBindGroup,
|
|
388
436
|
createFlatBindGroupLayout,
|
|
389
|
-
|
|
437
|
+
flatFragmentScratch,
|
|
390
438
|
flatVariant,
|
|
391
439
|
flatVertexBindings,
|
|
392
440
|
type FlatVariant,
|
|
393
441
|
flatPipeline,
|
|
394
442
|
flatPipelineAsync,
|
|
443
|
+
probeStageCeilings,
|
|
395
444
|
} from './flatPass.ts';
|
|
396
|
-
import type { OitTarget } from './flatPass.ts';
|
|
445
|
+
import type { AnimationViews, FlatFragmentScratch, OitTarget } from './flatPass.ts';
|
|
446
|
+
import { GpuBoneAnimation } from './boneAnimations.ts';
|
|
447
|
+
import { packBoneAnimation } from '../../boneAnimation.ts';
|
|
448
|
+
import type { BoneAnimationClip, BoneAnimationHandle } from '../../boneAnimation.ts';
|
|
449
|
+
import { MaterialStore, hashWords } from './materialStore.ts';
|
|
450
|
+
import { GpuStaticDraws, encodeBundle, takeRecorded } from './staticBundles.ts';
|
|
451
|
+
import type { StaticRecording } from './staticBundles.ts';
|
|
452
|
+
import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.ts';
|
|
453
|
+
import type { StaticDrawsHandle } from '../../staticDraws.ts';
|
|
397
454
|
import {
|
|
398
455
|
DEPTH_FRAG_BINDING,
|
|
399
456
|
DEPTH_FRAG_SIZE,
|
|
@@ -401,6 +458,8 @@ import {
|
|
|
401
458
|
DEPTH_PREVIOUS_BINDING,
|
|
402
459
|
DEPTH_CUTOUT_VERT_FIELDS,
|
|
403
460
|
DEPTH_CUTOUT_VERT_SIZE,
|
|
461
|
+
DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS,
|
|
462
|
+
DEPTH_INSTANCED_ANIMATED_VERT_FIELDS,
|
|
404
463
|
DEPTH_INSTANCED_CUTOUT_VERT_FIELDS,
|
|
405
464
|
DEPTH_VERT_FIELDS,
|
|
406
465
|
DEPTH_VERT_SIZE,
|
|
@@ -694,34 +753,20 @@ const MAX_SKY_DRAWS = 64;
|
|
|
694
753
|
/**
|
|
695
754
|
* Whether the environment probe's permutation fits this device's per-stage binding limits.
|
|
696
755
|
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
756
|
+
* **Two ceilings, counted separately**: sampled textures and samplers, since textures that share a
|
|
757
|
+
* sampler (`// wgsl:share` in `flat/preamble.ts`) count once against the second. The widest variant
|
|
758
|
+
* binds sixteen textures and twelve samplers, which is exactly WebGPU's default texture ceiling, and
|
|
759
|
+
* `select.ts` asks for the adapter's own — 48 here — so the probe does not depend on that request.
|
|
701
760
|
*/
|
|
702
761
|
function probeFits(device: GPUDevice): boolean {
|
|
703
762
|
/*
|
|
704
|
-
* **
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
* it
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
*
|
|
712
|
-
* It used to declare seventeen *samplers* too, and that was the binding constraint: this
|
|
713
|
-
* adapter offers exactly sixteen and no request can raise it, so the probe stood down and
|
|
714
|
-
* reflective surfaces kept a gradient. The shadow bindings share one declaration — see
|
|
715
|
-
* `// wgsl:share shadow` in `flat/preamble.ts`, which costs nothing because they were already
|
|
716
|
-
* one object — so the count is four, and the fourth is the normal map's: it is a colour image
|
|
717
|
-
* and wants filtering, which a non-filtering shadow sampler cannot give it.
|
|
718
|
-
*
|
|
719
|
-
* **Counted from the generated bindings rather than typed**: `FLAT_BINDINGS.flatFrag`'s
|
|
720
|
-
* widest variant is `directionalShadows+environmentProbe+pointShadows`, seven textures and
|
|
721
|
-
* four samplers. A stale number here reads as a device declining a feature it can run.
|
|
722
|
-
*/
|
|
723
|
-
const TEXTURES = 7;
|
|
724
|
-
const SAMPLERS = 4;
|
|
763
|
+
* **Counted from the generated bindings rather than typed** (`probeStageCeilings`). It was typed
|
|
764
|
+
* here as seven textures and four samplers while the widest variant grew to sixteen and twelve
|
|
765
|
+
* with the material maps, glass, the fixture atlas and DriftLight: a stale number reads as a
|
|
766
|
+
* device declining a feature it can run, or, the way this one had drifted, as one admitted to a
|
|
767
|
+
* permutation it cannot bind.
|
|
768
|
+
*/
|
|
769
|
+
const { textures: TEXTURES, samplers: SAMPLERS } = probeStageCeilings();
|
|
725
770
|
const fits =
|
|
726
771
|
device.limits.maxSampledTexturesPerShaderStage >= TEXTURES &&
|
|
727
772
|
device.limits.maxSamplersPerShaderStage >= SAMPLERS;
|
|
@@ -789,6 +834,7 @@ const VERB_PASS = 3;
|
|
|
789
834
|
/** What the depth snapshot copies from, and what it copies into. */
|
|
790
835
|
const DEPTH_SOURCE = maskOf('sceneDepth');
|
|
791
836
|
const DEPTH_SNAPSHOT = maskOf('depthSnapshot');
|
|
837
|
+
const COLOR_SNAPSHOT = maskOf('colorSnapshot');
|
|
792
838
|
|
|
793
839
|
/** The mirror's own colour and depth. */
|
|
794
840
|
const MIRROR_TARGET = maskOf('mirrorColor', 'mirrorDepth');
|
|
@@ -886,6 +932,14 @@ const MAX_WATER_BODIES_PER_FRAME = 16;
|
|
|
886
932
|
*/
|
|
887
933
|
const MAX_BATCHES_PER_FRAME = 8;
|
|
888
934
|
|
|
935
|
+
/**
|
|
936
|
+
* Mesh passes a frame may open before the ring grows: the frame's own, a mirror's, a capture's, and
|
|
937
|
+
* a probe face's, which gives its slot back once submitted. A frame past it loses the draws of the
|
|
938
|
+
* passes that found no slot, counted on the `materials` line, and the ring is grown for the next.
|
|
939
|
+
* Each slot is the pass's block, 3,328 to 6,656 bytes by variant, so the ring is 213 to 426 KB.
|
|
940
|
+
*/
|
|
941
|
+
const PASSES_PER_FRAME = 64;
|
|
942
|
+
|
|
889
943
|
/** Buffer usage flags, from the specification. See `buffers.ts` for why they are not read off the global. */
|
|
890
944
|
const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
|
|
891
945
|
|
|
@@ -1400,6 +1454,15 @@ interface SunTintTarget {
|
|
|
1400
1454
|
readonly mips: readonly [SnapshotMips, SnapshotMips];
|
|
1401
1455
|
}
|
|
1402
1456
|
|
|
1457
|
+
/** A skinned or morphed flat group, and what it was built with, compared by identity. */
|
|
1458
|
+
interface SkinnedTwin {
|
|
1459
|
+
readonly group: GPUBindGroup;
|
|
1460
|
+
readonly palette: GPUTextureView | null;
|
|
1461
|
+
readonly deltas: GPUTextureView | null;
|
|
1462
|
+
readonly cloth: GPUTextureView | null;
|
|
1463
|
+
readonly clothBinding: GPUTextureView | null;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1403
1466
|
export class WebGPURenderer implements RendererApi {
|
|
1404
1467
|
/**
|
|
1405
1468
|
* Real timestamp queries, where the adapter offers them.
|
|
@@ -1483,6 +1546,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1483
1546
|
private readonly skinnedBindGroupLayout: GPUBindGroupLayout;
|
|
1484
1547
|
private readonly morphedBindGroupLayout: GPUBindGroupLayout;
|
|
1485
1548
|
private readonly bothBindGroupLayout: GPUBindGroupLayout;
|
|
1549
|
+
/** The instanced layout of a batch playing a bone animation. See `animatedGroup`. */
|
|
1550
|
+
private readonly animatedBindGroupLayout: GPUBindGroupLayout;
|
|
1486
1551
|
/** The morph weights the following draws use, or null for none. */
|
|
1487
1552
|
private morphWeights: Float32Array | null = null;
|
|
1488
1553
|
/**
|
|
@@ -1495,14 +1560,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1495
1560
|
* group per character per frame — an allocation in the frame loop, which `AGENTS.md` forbids.
|
|
1496
1561
|
* Keyed by slot instead, the groups are built once and reused for the life of the renderer.
|
|
1497
1562
|
*/
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1563
|
+
/*
|
|
1564
|
+
* **And by material within a slot**, keyed by the unskinned group each was built beside. One
|
|
1565
|
+
* group a slot was rebuilt whenever the material beside it changed, so a character drawn in
|
|
1566
|
+
* thirty-odd sections, each its own material, built one a section a frame — about ninety a frame
|
|
1567
|
+
* for two characters, reported by a consumer. A slot's map holds one entry a material its rigs
|
|
1568
|
+
* are drawn in, and is emptied wherever the unskinned groups are, whose identities it is keyed
|
|
1569
|
+
* by.
|
|
1570
|
+
*/
|
|
1571
|
+
private readonly skinnedGroups: (Map<GPUBindGroup, SkinnedTwin> | undefined)[] = [];
|
|
1506
1572
|
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
1507
1573
|
private cloth: { binding: GpuClothBinding; particles: GpuClothParticles } | null = null;
|
|
1508
1574
|
private clothStandCache: { texture: GPUTexture; views: ClothViews } | null = null;
|
|
@@ -1572,22 +1638,51 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1572
1638
|
/** Every distinct cache above, so a mesh's pipelines are built for each. One entry when aliased. */
|
|
1573
1639
|
private readonly pipelineTargets: readonly PipelineCache[];
|
|
1574
1640
|
private readonly bindGroupLayout: GPUBindGroupLayout;
|
|
1575
|
-
/** One slot per draw, uploaded once a frame. See `UniformRing`. */
|
|
1576
|
-
private
|
|
1577
|
-
/**
|
|
1578
|
-
|
|
1641
|
+
/** One slot per draw, uploaded once a frame. See `UniformRing`. Swapped while a static list records. */
|
|
1642
|
+
private perDraw: UniformRing;
|
|
1643
|
+
/**
|
|
1644
|
+
* The lights, fog and camera, a slot per mesh pass a frame opens: `bindMeshPass` writes them, and
|
|
1645
|
+
* the first draw after it takes a slot. A ring rather than one buffer because a frame opens more
|
|
1646
|
+
* than one pass — a mirror's, a capture's, a probe face's — and a queue write does not interleave
|
|
1647
|
+
* with the draws recorded between them. See `UniformRing`.
|
|
1648
|
+
*/
|
|
1649
|
+
private passRing: UniformRing;
|
|
1650
|
+
/** Each material's block, kept across frames for as long as it does not change. */
|
|
1651
|
+
private readonly materialStore: MaterialStore;
|
|
1579
1652
|
/**
|
|
1580
|
-
*
|
|
1653
|
+
* Both blocks as the CPU holds them, the pass's then the material's, patched by name and copied
|
|
1654
|
+
* into a slot by the next draw after something changed them. See `FlatFragmentScratch`.
|
|
1581
1655
|
*
|
|
1582
1656
|
* Authoritative between draws: a setter changes one field here and marks the block dirty, and
|
|
1583
|
-
* the next `drawMesh`
|
|
1584
|
-
*
|
|
1657
|
+
* the next `drawMesh` places it. That keeps a setter free of any knowledge of slots, which is
|
|
1658
|
+
* what lets it be called anywhere a scene wants.
|
|
1585
1659
|
*/
|
|
1586
1660
|
private readonly perFrameStaging: ArrayBuffer;
|
|
1587
1661
|
private readonly perFrameFloats: Float32Array;
|
|
1588
1662
|
private readonly perFrameInts: Int32Array;
|
|
1663
|
+
/** The two halves of the scratch, as the words a slot is written from. */
|
|
1664
|
+
private readonly passInts: Int32Array;
|
|
1665
|
+
private readonly materialInts: Int32Array;
|
|
1589
1666
|
/** The slot the open material occupies, and whether the next draw must take a new one. */
|
|
1590
1667
|
private readonly materials = new MaterialChanges();
|
|
1668
|
+
/** The same for the pass's block: reopened by `bindMeshPass` and by nothing a material sets. */
|
|
1669
|
+
private readonly passBlock = new MaterialChanges();
|
|
1670
|
+
/**
|
|
1671
|
+
* The vertex stage's view block — the camera, the light's matrix, the wind — a slot a pass, and a
|
|
1672
|
+
* second, unjittered, for a blended draw landing after the upscale. Reopened with the pass, and by
|
|
1673
|
+
* `setWind` and `beginShadowPass`, which change what it holds.
|
|
1674
|
+
*/
|
|
1675
|
+
private viewRing: UniformRing;
|
|
1676
|
+
private readonly viewBlock = new MaterialChanges();
|
|
1677
|
+
private readonly lateViewBlock = new MaterialChanges();
|
|
1678
|
+
/** The view slot the draw being submitted bound, read by the halves it keeps for later passes. */
|
|
1679
|
+
private drawViewOffset = 0;
|
|
1680
|
+
/** Pass slots asked for and refused this frame, and materials refused: what grows the two. */
|
|
1681
|
+
private passesAsked = 0;
|
|
1682
|
+
private passesRefused = 0;
|
|
1683
|
+
private viewsAsked = 0;
|
|
1684
|
+
private viewsRefused = 0;
|
|
1685
|
+
private materialsRefused = 0;
|
|
1591
1686
|
|
|
1592
1687
|
/*
|
|
1593
1688
|
* The frame graph's recording, and the machinery that replays it.
|
|
@@ -1856,8 +1951,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1856
1951
|
private readonly variant: FlatVariant;
|
|
1857
1952
|
/** Whether this profile compiled the point-shadow path, and so has sentinels to write. */
|
|
1858
1953
|
private readonly pointShadowsCompiled: boolean;
|
|
1859
|
-
/** What the generator recorded about that permutation
|
|
1860
|
-
private readonly fragment:
|
|
1954
|
+
/** What the generator recorded about that permutation, as the renderer's scratch lays it out. */
|
|
1955
|
+
private readonly fragment: FlatFragmentScratch;
|
|
1861
1956
|
|
|
1862
1957
|
private depth: GPUTexture | null = null;
|
|
1863
1958
|
private depthView: GPUTextureView | null = null;
|
|
@@ -1919,6 +2014,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1919
2014
|
*/
|
|
1920
2015
|
private refractSnapshot: GPUTexture | null = null;
|
|
1921
2016
|
private refractSnapshotView: GPUTextureView | null = null;
|
|
2017
|
+
/**
|
|
2018
|
+
* The frame's colour as a registered pass reads it (`reads: ['colorSnapshot']`): the scene's own
|
|
2019
|
+
* size and format, refilled at each such pass's draw. Allocated only once a pass declares the
|
|
2020
|
+
* read, beside the frame's targets, and never in the frame loop. See `takePassColour`.
|
|
2021
|
+
*/
|
|
2022
|
+
private passColour: GPUTexture | null = null;
|
|
2023
|
+
private passColourView: GPUTextureView | null = null;
|
|
2024
|
+
/** Registered passes that read the colour, queued this frame for after the upscale. */
|
|
2025
|
+
private readonly latePasses: PassHandle[] = [];
|
|
2026
|
+
/** A late pass draws unjittered, as every late draw does. */
|
|
2027
|
+
private readonly latePassJitter = new Float32Array(2);
|
|
1922
2028
|
/** Whether this frame has taken it. One snapshot serves every refracting draw. */
|
|
1923
2029
|
private refractSnapshotTaken = false;
|
|
1924
2030
|
/**
|
|
@@ -2429,8 +2535,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2429
2535
|
private readonly reflectionQueue = new ReflectionQueue();
|
|
2430
2536
|
private ssrLayout!: GPUBindGroupLayout;
|
|
2431
2537
|
private ssrResolveLayout!: GPUBindGroupLayout;
|
|
2538
|
+
/** The frame's materials' reflections, resolved; null where off. See `frameReflections.ts`. */
|
|
2539
|
+
private readonly frameReflections: ResolvedFrameReflections | null;
|
|
2540
|
+
/** Their surface half, null where they are off or cannot be drawn: `reflectionSurfacePass.ts`. */
|
|
2541
|
+
private readonly reflectionSurface: ReflectionSurfacePass | null;
|
|
2542
|
+
/** The material resolve: its layout, its one block (the blur) and its group over the three maps. */
|
|
2543
|
+
private ssrMaterialLayout!: GPUBindGroupLayout;
|
|
2544
|
+
private ssrMaterialUniforms!: GPUBuffer;
|
|
2545
|
+
private readonly ssrMaterialFloats = new Float32Array(SSR_MATERIAL_SIZE / 4);
|
|
2546
|
+
private ssrMaterialGroup: GPUBindGroup | null = null;
|
|
2432
2547
|
private ssrUniforms!: GPUBuffer;
|
|
2433
|
-
|
|
2548
|
+
/* One slot a surface, and one more for the frame's materials' trace. */
|
|
2549
|
+
private readonly ssrStaging = new ArrayBuffer(SSR_SLOT * (MAX_REFLECTIVE_SURFACES + 1));
|
|
2434
2550
|
private readonly ssrFloats = new Float32Array(this.ssrStaging);
|
|
2435
2551
|
private ssrReflection: GPUTexture | null = null;
|
|
2436
2552
|
private ssrReflectionView: GPUTextureView | null = null;
|
|
@@ -2580,7 +2696,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2580
2696
|
if (this.quality.frameGraph) recordNode(this.arena, VERB_SCOPE, 0, MIRROR_TARGET, 0, 0);
|
|
2581
2697
|
this.pass?.end();
|
|
2582
2698
|
this.flushRings();
|
|
2583
|
-
this.
|
|
2699
|
+
this.submit([this.encoder.finish()]);
|
|
2584
2700
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'reflection' });
|
|
2585
2701
|
/*
|
|
2586
2702
|
* **Opened on demand, like the frame's own.** Nothing has been recorded into the mirror yet,
|
|
@@ -2640,7 +2756,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2640
2756
|
this.windStreakFrags,
|
|
2641
2757
|
this.scatterVerts,
|
|
2642
2758
|
this.scatterFrags,
|
|
2643
|
-
this.
|
|
2759
|
+
this.passRing,
|
|
2760
|
+
this.viewRing,
|
|
2644
2761
|
this.filmVerts,
|
|
2645
2762
|
this.filmFrags,
|
|
2646
2763
|
this.insetUniforms,
|
|
@@ -2661,6 +2778,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2661
2778
|
/* Culls first: a submit that follows may carry a draw reading what they write. */
|
|
2662
2779
|
this.instanceCulls?.flush();
|
|
2663
2780
|
for (const ring of this.rings()) ring.flush();
|
|
2781
|
+
this.materialStore.flush();
|
|
2664
2782
|
}
|
|
2665
2783
|
|
|
2666
2784
|
/** Where every ring stands before work that submits an encoder of its own. */
|
|
@@ -2680,6 +2798,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2680
2798
|
private rewindRings(skinMark: number): void {
|
|
2681
2799
|
const rings = this.rings();
|
|
2682
2800
|
for (let i = 0; i < rings.length; i++) (rings[i] as UniformRing).rewind(this.ringMarks[i] ?? 0);
|
|
2801
|
+
/* The open pass may sit in a slot just given back, which the next pass would overwrite. */
|
|
2802
|
+
this.reopenPass();
|
|
2683
2803
|
this.skinPalettes.rewind(skinMark);
|
|
2684
2804
|
if (this.skinPaletteSlot >= skinMark) this.skinPaletteSlot = -1;
|
|
2685
2805
|
}
|
|
@@ -2882,7 +3002,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2882
3002
|
* ones that move something *every* group holds: the shadow cubemaps, the probe fence, the cookie
|
|
2883
3003
|
* atlas and the IES rows. None of those is a frame path.
|
|
2884
3004
|
*/
|
|
2885
|
-
private
|
|
3005
|
+
private flatBindGroups = new Map<
|
|
2886
3006
|
GpuSurfaceTexture | null,
|
|
2887
3007
|
Map<
|
|
2888
3008
|
GpuSurfaceTexture | null,
|
|
@@ -3000,8 +3120,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3000
3120
|
const { device } = this.surface;
|
|
3001
3121
|
const { depth, colorMsaa } = target.attachments(this.samples, this.pipelines.format);
|
|
3002
3122
|
|
|
3003
|
-
|
|
3123
|
+
/*
|
|
3124
|
+
* **Flushed, and only then set aside.** The flush replays what the frame has recorded, and
|
|
3125
|
+
* replaying opens the frame's pass where none is open yet; saved before it, the pass given back
|
|
3126
|
+
* afterwards was the null that stood there, while the one the flush opened was left open on the
|
|
3127
|
+
* frame's encoder and every later pass on it was refused — a black frame for any capture taken
|
|
3128
|
+
* after the frame had drawn something. `scripts/capture-check.mjs` takes one at three points.
|
|
3129
|
+
*/
|
|
3004
3130
|
if (this.quality.frameGraph) this.flushGraph();
|
|
3131
|
+
const savedPass = this.pass;
|
|
3005
3132
|
const savedEncoder = this.encoder;
|
|
3006
3133
|
this.markRings();
|
|
3007
3134
|
const skinMark = this.skinPalettes.mark();
|
|
@@ -3046,7 +3173,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3046
3173
|
pass.end();
|
|
3047
3174
|
this.pass = null;
|
|
3048
3175
|
this.flushRings();
|
|
3049
|
-
|
|
3176
|
+
this.submit([encoder.finish()]);
|
|
3050
3177
|
this.rewindRings(skinMark);
|
|
3051
3178
|
this.encoderEpoch = frameEpoch;
|
|
3052
3179
|
this.encoder = savedEncoder;
|
|
@@ -3054,6 +3181,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3054
3181
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
3055
3182
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
3056
3183
|
this.materials.dirty();
|
|
3184
|
+
this.reopenPass();
|
|
3057
3185
|
this.capturePassActive = false;
|
|
3058
3186
|
}
|
|
3059
3187
|
return true;
|
|
@@ -3068,11 +3196,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3068
3196
|
*/
|
|
3069
3197
|
private readonly retiredTextures: GPUTexture[] = [];
|
|
3070
3198
|
|
|
3071
|
-
updateSurfaceTexture(
|
|
3199
|
+
updateSurfaceTexture(
|
|
3200
|
+
texture: GpuSurfaceTexture,
|
|
3201
|
+
source: TexImageSource | CompressedTextureSource,
|
|
3202
|
+
): void {
|
|
3072
3203
|
if (this.surface.lost) return;
|
|
3073
|
-
const replaced = texture.update(source);
|
|
3204
|
+
const replaced = texture.update(source, this.compressedFormats);
|
|
3074
3205
|
if (replaced === null) return;
|
|
3075
|
-
/* A new size is a new view, so every group holding the old one is dropped — see
|
|
3206
|
+
/* A new size or format is a new view, so every group holding the old one is dropped — see
|
|
3207
|
+
below. */
|
|
3076
3208
|
this.retiredTextures.push(replaced);
|
|
3077
3209
|
this.forgetBindingsOf(texture);
|
|
3078
3210
|
}
|
|
@@ -3105,6 +3237,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3105
3237
|
if (this.ormMap === texture) this.ormMap = null;
|
|
3106
3238
|
if (this.emissiveMap === texture) this.emissiveMap = null;
|
|
3107
3239
|
if (this.modelMap === texture) this.modelMap = null;
|
|
3240
|
+
if (this.overlayMaps === texture) this.overlayMaps = null;
|
|
3108
3241
|
this.forgetBindingsOf(texture);
|
|
3109
3242
|
texture.dispose();
|
|
3110
3243
|
this.captureTargets.get(texture)?.dispose();
|
|
@@ -3123,6 +3256,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3123
3256
|
/* Same reasoning, for whichever SDF labels were bound against this atlas. */
|
|
3124
3257
|
this.sdfTextBindGroups.delete(texture);
|
|
3125
3258
|
this.flatBindGroups.clear();
|
|
3259
|
+
this.flatGroupEpoch += 1;
|
|
3260
|
+
this.overlayGroups.clear();
|
|
3261
|
+
this.skinnedGroups.length = 0;
|
|
3126
3262
|
/*
|
|
3127
3263
|
* The blank one is built with the albedo held aside, because `buildFlatBindGroup` reads
|
|
3128
3264
|
* `this.albedo` for that slot — leaving it in place would put a real colour map into the
|
|
@@ -3314,6 +3450,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3314
3450
|
const emissiveMap = (material?.emissive ?? null) as GpuSurfaceTexture | null;
|
|
3315
3451
|
const modelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
|
|
3316
3452
|
this.materialModel = material?.model ?? null;
|
|
3453
|
+
const reflectivity = material?.reflectivity;
|
|
3454
|
+
const environmentGain = material?.environmentGain;
|
|
3455
|
+
this.materialReflectivity =
|
|
3456
|
+
reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
|
|
3457
|
+
this.materialEnvironmentGain =
|
|
3458
|
+
environmentGain === undefined ? null : Math.max(0, environmentGain);
|
|
3459
|
+
this.writeEnvironmentDials();
|
|
3317
3460
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
3318
3461
|
const model = material?.model;
|
|
3319
3462
|
warnUnpagedLightmap(model, modelMap);
|
|
@@ -3342,7 +3485,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3342
3485
|
const cut = this.materialField('uCutout');
|
|
3343
3486
|
f[cut] = cutoff;
|
|
3344
3487
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
3345
|
-
|
|
3488
|
+
/* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
|
|
3489
|
+
f[cut + 2] = 0;
|
|
3346
3490
|
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
3347
3491
|
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
3348
3492
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
@@ -3516,17 +3660,47 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3516
3660
|
this.material((f) => f.set(this.ambientSH, this.materialField('uAmbientSH')));
|
|
3517
3661
|
}
|
|
3518
3662
|
|
|
3663
|
+
/**
|
|
3664
|
+
* The overlay the following draws wear, or null for none: pass state, put back to none by
|
|
3665
|
+
* `bindMeshPass`. See `renderer.ts` and `surfaceOverlay.ts`. The numbers go in the material block,
|
|
3666
|
+
* which every variant declares; the atlas goes in a twin of each flat group (`overlayGroup`).
|
|
3667
|
+
*/
|
|
3668
|
+
setSurfaceOverlay(overlay: SurfaceOverlay | null): void {
|
|
3669
|
+
if (this.surface.lost) return;
|
|
3670
|
+
packSurfaceOverlay(overlay, this.overlayFloats);
|
|
3671
|
+
/* The first overlay laid is when the lit pipelines start carrying one. */
|
|
3672
|
+
if (overlayLays(overlay)) this.enableLit('SURFACE_OVERLAY');
|
|
3673
|
+
this.overlayMaps = (overlay?.maps ?? null) as GpuSurfaceTexture | null;
|
|
3674
|
+
this.material((f) => f.set(this.overlayFloats, this.materialField('uOverlay')));
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3519
3677
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
3520
3678
|
setSurfaceReflectivity(amount: number): void {
|
|
3521
|
-
this.
|
|
3522
|
-
|
|
3523
|
-
});
|
|
3679
|
+
this.passReflectivity = Math.min(1, Math.max(0, amount));
|
|
3680
|
+
this.writeEnvironmentDials();
|
|
3524
3681
|
}
|
|
3525
3682
|
|
|
3526
3683
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
3527
3684
|
setEnvironmentGain(gain: number): void {
|
|
3685
|
+
this.passEnvironmentGain = Math.max(0, gain);
|
|
3686
|
+
this.writeEnvironmentDials();
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
/**
|
|
3690
|
+
* The reflectivity and environment gain the setters gave the pass, and the ones the material
|
|
3691
|
+
* set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
|
|
3692
|
+
* material's where it has one and the pass's otherwise, the rule `renderer.ts` keeps too.
|
|
3693
|
+
*/
|
|
3694
|
+
private passReflectivity = 0;
|
|
3695
|
+
private passEnvironmentGain = 1;
|
|
3696
|
+
private materialReflectivity: number | null = null;
|
|
3697
|
+
private materialEnvironmentGain: number | null = null;
|
|
3698
|
+
|
|
3699
|
+
private writeEnvironmentDials(): void {
|
|
3528
3700
|
this.material((f) => {
|
|
3529
|
-
|
|
3701
|
+
const dials = this.materialField('uEnvironmentDials');
|
|
3702
|
+
f[dials + 1] = this.materialReflectivity ?? this.passReflectivity;
|
|
3703
|
+
f[dials + 2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
|
|
3530
3704
|
});
|
|
3531
3705
|
}
|
|
3532
3706
|
|
|
@@ -4045,7 +4219,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4045
4219
|
}
|
|
4046
4220
|
this.pass?.end();
|
|
4047
4221
|
this.flushRings();
|
|
4048
|
-
this.
|
|
4222
|
+
this.submit([this.encoder.finish()]);
|
|
4049
4223
|
this.encoder = this.surface.device.createCommandEncoder({ label: 'frame.resumed' });
|
|
4050
4224
|
this.reflectionPassActive = false;
|
|
4051
4225
|
this.reflectionReady = true;
|
|
@@ -4260,7 +4434,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4260
4434
|
*/
|
|
4261
4435
|
private gradeCode(): number {
|
|
4262
4436
|
return !this.hasComposite || !this.quality.hdrScene
|
|
4263
|
-
? (
|
|
4437
|
+
? forwardTransformCode(this.quality.outputTransform)
|
|
4264
4438
|
: 0;
|
|
4265
4439
|
}
|
|
4266
4440
|
|
|
@@ -5728,6 +5902,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5728
5902
|
*/
|
|
5729
5903
|
setWind(windX: number, windZ: number, windGust: number, timeSeconds: number): void {
|
|
5730
5904
|
resolveScatterDeform(windX, windZ, windGust, timeSeconds, null, this.frameWind);
|
|
5905
|
+
this.reopenViews();
|
|
5731
5906
|
}
|
|
5732
5907
|
|
|
5733
5908
|
/**
|
|
@@ -5747,22 +5922,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5747
5922
|
this.shadowDraws.writeFloats(slot, DEPTH_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
5748
5923
|
}
|
|
5749
5924
|
|
|
5750
|
-
/**
|
|
5751
|
-
* The five wind fields of one per-draw slot.
|
|
5752
|
-
*
|
|
5753
|
-
* **Never called for an instanced draw.** That variant declares none of these, and
|
|
5754
|
-
* `flatPass.ts` records what writing a field a variant does not declare costs: the block is
|
|
5755
|
-
* shorter, so the write lands on whatever occupies that offset instead.
|
|
5756
|
-
*/
|
|
5757
|
-
private writeWind(slot: number): void {
|
|
5758
|
-
const d = this.frameWind;
|
|
5759
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindDirection.offset, d.direction);
|
|
5760
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindSpeed.offset, d.bend);
|
|
5761
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindGust.offset, d.gust);
|
|
5762
|
-
this.perDraw.writeFloat(slot, FLAT_VERT_FIELDS.uWindTime.offset, d.time);
|
|
5763
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
5764
|
-
}
|
|
5765
|
-
|
|
5766
5925
|
/** Refilled per frame rather than allocated; see `resolvePointLights`. */
|
|
5767
5926
|
private readonly lights: ResolvedPointLights = {
|
|
5768
5927
|
count: 0,
|
|
@@ -5917,6 +6076,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5917
6076
|
* at submit, and a texture's view is new when an update changes its size.
|
|
5918
6077
|
*/
|
|
5919
6078
|
private readonly shadowCutoutLayout: GPUBindGroupLayout;
|
|
6079
|
+
/** A crowd's casters: the plain and cutout layouts with a bone animation's three textures. */
|
|
6080
|
+
private readonly shadowAnimatedLayout: GPUBindGroupLayout;
|
|
6081
|
+
private readonly shadowAnimatedCutoutLayout: GPUBindGroupLayout;
|
|
5920
6082
|
private readonly cutoutGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5921
6083
|
private readonly cutoutPeelGroups = new Map<GpuSurfaceTexture, GPUBindGroup>();
|
|
5922
6084
|
private shadowPeelPassUniforms: GPUBuffer | null = null;
|
|
@@ -6172,8 +6334,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6172
6334
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
6173
6335
|
if (count === 0) return;
|
|
6174
6336
|
const cutout = cutoutOf(material);
|
|
6337
|
+
/* A crowd casts where its clip has moved it, through the animated variant. */
|
|
6338
|
+
const animation = gpuBatch.animation === null ? null : this.animationViews(gpuBatch);
|
|
6339
|
+
if (gpuBatch.animation !== null && animation === null) return;
|
|
6340
|
+
const cutoutTexture =
|
|
6341
|
+
cutout === null ? null : (cutout.albedo as unknown as GpuSurfaceTexture);
|
|
6175
6342
|
const cutoutGroup =
|
|
6176
|
-
|
|
6343
|
+
animation !== null
|
|
6344
|
+
? this.animatedDepthGroup(gpuBatch, animation, cutoutTexture)
|
|
6345
|
+
: cutoutTexture === null
|
|
6346
|
+
? null
|
|
6347
|
+
: this.cutoutGroup(cutoutTexture);
|
|
6348
|
+
if (animation !== null && cutoutGroup === null) return;
|
|
6177
6349
|
|
|
6178
6350
|
const slot = this.shadowDraws.allocate();
|
|
6179
6351
|
if (slot === null) return;
|
|
@@ -6184,23 +6356,43 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6184
6356
|
DEPTH_VERT_FIELDS.uLightViewProj.offset,
|
|
6185
6357
|
this.correctedLightViewProj,
|
|
6186
6358
|
);
|
|
6187
|
-
|
|
6359
|
+
const cuts = cutoutTexture !== null && cutoutGroup !== null;
|
|
6360
|
+
if (cutout !== null && cuts) {
|
|
6188
6361
|
this.writeCutout(slot, cutout, DEPTH_INSTANCED_CUTOUT_VERT_FIELDS);
|
|
6189
6362
|
}
|
|
6363
|
+
const clip = gpuBatch.animation;
|
|
6364
|
+
if (animation !== null && clip !== null) {
|
|
6365
|
+
const fields = cuts
|
|
6366
|
+
? DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT_FIELDS
|
|
6367
|
+
: DEPTH_INSTANCED_ANIMATED_VERT_FIELDS;
|
|
6368
|
+
this.shadowDraws.writeFloat(slot, fields.uSceneTime.offset, this.animationTime);
|
|
6369
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset, clip.framesPerSecond);
|
|
6370
|
+
this.shadowDraws.writeFloat(slot, fields.uBoneClip.offset + 4, clip.boneScale);
|
|
6371
|
+
}
|
|
6190
6372
|
|
|
6191
|
-
const cuts = cutoutGroup !== null;
|
|
6192
6373
|
const cull = material?.doubleSided === true ? 'none' : this.depthCullMode;
|
|
6374
|
+
const animated = animation !== null;
|
|
6193
6375
|
pass.setPipeline(
|
|
6194
6376
|
depthPipeline(
|
|
6195
6377
|
this.pipelines,
|
|
6196
6378
|
this.surface.device,
|
|
6197
|
-
|
|
6198
|
-
|
|
6379
|
+
animated
|
|
6380
|
+
? cuts
|
|
6381
|
+
? this.shadowAnimatedCutoutLayout
|
|
6382
|
+
: this.shadowAnimatedLayout
|
|
6383
|
+
: cuts
|
|
6384
|
+
? this.shadowCutoutLayout
|
|
6385
|
+
: this.shadowLayout,
|
|
6386
|
+
`${cuts ? 'depth-instanced-cutout' : 'depth-instanced'}${animated ? '-animated' : ''}` +
|
|
6387
|
+
`|${cull}|${geometry.key ?? ''}`,
|
|
6199
6388
|
this.presentOf(geometry),
|
|
6200
6389
|
cull,
|
|
6201
6390
|
false,
|
|
6202
6391
|
true,
|
|
6203
6392
|
cuts,
|
|
6393
|
+
false,
|
|
6394
|
+
false,
|
|
6395
|
+
animated,
|
|
6204
6396
|
),
|
|
6205
6397
|
);
|
|
6206
6398
|
pass.setBindGroup(0, cutoutGroup ?? this.shadowGroup(), [slot]);
|
|
@@ -6579,6 +6771,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6579
6771
|
this.skinPalettes = new SkinPaletteRing(quality.drawsPerFrame);
|
|
6580
6772
|
this.maxDrawingBufferPixels = quality.maxDrawingBufferPixels;
|
|
6581
6773
|
this.exposure = quality.outputExposure;
|
|
6774
|
+
resolveFilmicCurve(DEFAULT_FILMIC_CURVE, this.filmConstants);
|
|
6775
|
+
if (quality.outputTransform === 'filmic' && !(quality.screenEffects && quality.hdrScene)) {
|
|
6776
|
+
console.warn(FILMIC_WITHOUT_COMPOSITE);
|
|
6777
|
+
}
|
|
6582
6778
|
this.flushSchedule =
|
|
6583
6779
|
quality.frameGraph && quality.identifierGraph
|
|
6584
6780
|
? createFlushSchedule(MAX_DRAWS_PER_FRAME)
|
|
@@ -6656,6 +6852,26 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6656
6852
|
);
|
|
6657
6853
|
}
|
|
6658
6854
|
this.skinScatter = skinScreen ? new SkinScatterPass(device) : null;
|
|
6855
|
+
/*
|
|
6856
|
+
* The frame's materials' reflections, where the frame can carry them: scene light to swap the
|
|
6857
|
+
* probe's share in, and one sample, which the trace's depth copy and both maps are. Said once
|
|
6858
|
+
* where it cannot. See `frameReflections.ts`.
|
|
6859
|
+
*/
|
|
6860
|
+
this.frameReflections = resolveFrameReflections(quality.screenSpaceReflections);
|
|
6861
|
+
const reflectionsRefusal =
|
|
6862
|
+
this.frameReflections === null
|
|
6863
|
+
? null
|
|
6864
|
+
: !quality.screenEffects || !quality.hdrScene
|
|
6865
|
+
? 'the frame keeps no scene light to swap the probe in (`screenEffects` and `hdrScene`)'
|
|
6866
|
+
: this.samples > 1
|
|
6867
|
+
? 'a multisampled frame has no single depth or colour to march through'
|
|
6868
|
+
: null;
|
|
6869
|
+
if (reflectionsRefusal !== null)
|
|
6870
|
+
console.warn(`WebGPU: ${frameReflectionsRefused(reflectionsRefusal)}`);
|
|
6871
|
+
this.reflectionSurface =
|
|
6872
|
+
this.frameReflections !== null && reflectionsRefusal === null
|
|
6873
|
+
? new ReflectionSurfacePass(device)
|
|
6874
|
+
: null;
|
|
6659
6875
|
this.skinPipelines = skinScreen
|
|
6660
6876
|
? new PipelineCache(
|
|
6661
6877
|
device,
|
|
@@ -6690,7 +6906,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6690
6906
|
pointShadows: quality.pointShadows,
|
|
6691
6907
|
});
|
|
6692
6908
|
this.pointShadowsCompiled = quality.pointShadows;
|
|
6693
|
-
this.fragment =
|
|
6909
|
+
this.fragment = flatFragmentScratch(this.variant);
|
|
6694
6910
|
this.bindGroupLayout = createFlatBindGroupLayout(device, this.variant);
|
|
6695
6911
|
/*
|
|
6696
6912
|
* The skinned twin, built eagerly because it is a descriptor rather than a compile — WebGPU
|
|
@@ -6700,6 +6916,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6700
6916
|
this.skinnedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true);
|
|
6701
6917
|
this.morphedBindGroupLayout = createFlatBindGroupLayout(device, this.variant, false, true);
|
|
6702
6918
|
this.bothBindGroupLayout = createFlatBindGroupLayout(device, this.variant, true, true);
|
|
6919
|
+
/* And the instanced twin that plays a bone animation: its clip's and clocks' textures. */
|
|
6920
|
+
this.animatedBindGroupLayout = createFlatBindGroupLayout(
|
|
6921
|
+
device,
|
|
6922
|
+
this.variant,
|
|
6923
|
+
false,
|
|
6924
|
+
false,
|
|
6925
|
+
true,
|
|
6926
|
+
);
|
|
6703
6927
|
this.perDraw = new UniformRing(
|
|
6704
6928
|
device,
|
|
6705
6929
|
FLAT_VERT_SIZE,
|
|
@@ -6708,30 +6932,48 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6708
6932
|
'flat.vertRing',
|
|
6709
6933
|
this.drawBudget,
|
|
6710
6934
|
);
|
|
6711
|
-
|
|
6935
|
+
const { passSize, materialSize } = this.fragment;
|
|
6936
|
+
this.perFrameStaging = new ArrayBuffer(passSize + materialSize);
|
|
6712
6937
|
this.perFrameFloats = new Float32Array(this.perFrameStaging);
|
|
6713
6938
|
this.perFrameInts = new Int32Array(this.perFrameStaging);
|
|
6939
|
+
this.passInts = new Int32Array(this.perFrameStaging, 0, passSize / 4);
|
|
6940
|
+
this.materialInts = new Int32Array(this.perFrameStaging, passSize, materialSize / 4);
|
|
6714
6941
|
/*
|
|
6715
|
-
* **
|
|
6716
|
-
*
|
|
6717
|
-
*
|
|
6718
|
-
*
|
|
6719
|
-
*
|
|
6720
|
-
*
|
|
6721
|
-
* commands, so one buffer rewritten between two draws gives *both* the second value. Every
|
|
6722
|
-
* surface in a scene would wear the last material set, which is bug 7's shape at pass scale.
|
|
6942
|
+
* **Two blocks, because they change at two rates.** `setSurfaceTexture`, `setSurfaceGrain` and
|
|
6943
|
+
* their siblings are pass state on both backends — a material covers many draws — and on WebGL2
|
|
6944
|
+
* each is a `uniform1f` that lands immediately. Here they live in a block, and
|
|
6945
|
+
* `queue.writeBuffer` does not interleave with recorded commands, so one buffer rewritten between
|
|
6946
|
+
* two draws gives *both* the second value: every surface would wear the last material set, which
|
|
6947
|
+
* is bug 7's shape at pass scale. So each draw binds a slot.
|
|
6723
6948
|
*
|
|
6724
|
-
*
|
|
6725
|
-
*
|
|
6949
|
+
* It was one block until 4.10.0, lights and all, copied whole into a ring slot per material
|
|
6950
|
+
* change and uploaded every frame: a stage of four hundred materials sent 4.8 MB a frame to say
|
|
6951
|
+
* what it had said the frame before. Now the pass's block takes a ring slot per pass, and a
|
|
6952
|
+
* material's block a slot of the store, where an unchanged material stays from frame to frame.
|
|
6726
6953
|
*/
|
|
6727
|
-
this.
|
|
6954
|
+
this.passRing = new UniformRing(
|
|
6728
6955
|
device,
|
|
6729
|
-
|
|
6956
|
+
passSize,
|
|
6957
|
+
PASSES_PER_FRAME,
|
|
6958
|
+
USAGE_UNIFORM_DST,
|
|
6959
|
+
'flat.passRing',
|
|
6960
|
+
);
|
|
6961
|
+
this.materialStore = new MaterialStore(
|
|
6962
|
+
device,
|
|
6963
|
+
materialSize,
|
|
6730
6964
|
this.quality.materialChangesPerFrame,
|
|
6731
6965
|
USAGE_UNIFORM_DST,
|
|
6732
|
-
'flat.
|
|
6966
|
+
'flat.materials',
|
|
6733
6967
|
this.materialBudget,
|
|
6734
6968
|
);
|
|
6969
|
+
/* Two a pass at most: the jittered view and the unjittered one. */
|
|
6970
|
+
this.viewRing = new UniformRing(
|
|
6971
|
+
device,
|
|
6972
|
+
FLAT_VIEW_SIZE,
|
|
6973
|
+
2 * PASSES_PER_FRAME,
|
|
6974
|
+
USAGE_UNIFORM_DST,
|
|
6975
|
+
'flat.viewRing',
|
|
6976
|
+
);
|
|
6735
6977
|
|
|
6736
6978
|
/*
|
|
6737
6979
|
* A one-pixel stand-in for the albedo binding. The albedo's flag is zero for untextured
|
|
@@ -6880,7 +7122,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6880
7122
|
})
|
|
6881
7123
|
.end();
|
|
6882
7124
|
}
|
|
6883
|
-
|
|
7125
|
+
this.submit([clear.finish()]);
|
|
6884
7126
|
/*
|
|
6885
7127
|
* `nearest` on a depth texture sampled as an ordinary float, matching what
|
|
6886
7128
|
* `shadowMap.ts` asks WebGL2 for. A filtering sampler cannot be used on a depth format
|
|
@@ -7046,6 +7288,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7046
7288
|
addressModeU: 'clamp-to-edge',
|
|
7047
7289
|
addressModeV: 'clamp-to-edge',
|
|
7048
7290
|
});
|
|
7291
|
+
/* Or a surface overlay's atlas, in the twin a draw wearing one binds: `overlayGroup`. */
|
|
7292
|
+
const maps = this.overlayBinding ? this.overlayMaps : null;
|
|
7293
|
+
if (maps !== null) {
|
|
7294
|
+
return { view: maps.layerView() ?? blankView, sampler: this.refractSampler };
|
|
7295
|
+
}
|
|
7049
7296
|
return { view: this.refractSnapshotView ?? blankView, sampler: this.refractSampler };
|
|
7050
7297
|
}
|
|
7051
7298
|
/*
|
|
@@ -7222,6 +7469,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7222
7469
|
{ binding: RUSH_TEXTURES.uExposureHeld },
|
|
7223
7470
|
/* Local exposure's grid, read filtered between tiles. See `localExposure.ts`. */
|
|
7224
7471
|
{ binding: RUSH_TEXTURES.uExposureLocal },
|
|
7472
|
+
/* Each mover's own motion: the reconstruction's target, or the scene where there is none. */
|
|
7473
|
+
{ binding: RUSH_TEXTURES.uMotion },
|
|
7225
7474
|
]);
|
|
7226
7475
|
this.rushUniforms = device.createBuffer({
|
|
7227
7476
|
label: 'post.rushUniforms',
|
|
@@ -7239,11 +7488,29 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7239
7488
|
this.ssrLayout = createPostStageLayout(device, SSR_UNIFORMS, SSR_FRAG_SIZE, [
|
|
7240
7489
|
{ binding: SSR_TEXTURES.uSsrDepth, filterable: false },
|
|
7241
7490
|
{ binding: SSR_TEXTURES.uSsrScene },
|
|
7491
|
+
/* The frame's materials, for the trace that reads them: `frameReflections.ts`. */
|
|
7492
|
+
{ binding: SSR_TEXTURES.uSsrProbeMap },
|
|
7493
|
+
{ binding: SSR_TEXTURES.uSsrTintMap },
|
|
7242
7494
|
]);
|
|
7243
7495
|
this.ssrResolveLayout = createSsrResolveLayout(device);
|
|
7496
|
+
this.ssrMaterialLayout = createPostStageLayout(
|
|
7497
|
+
device,
|
|
7498
|
+
SSR_MATERIAL_UNIFORMS,
|
|
7499
|
+
SSR_MATERIAL_SIZE,
|
|
7500
|
+
[
|
|
7501
|
+
{ binding: SSR_MATERIAL_TEXTURES.uSsrReflection },
|
|
7502
|
+
{ binding: SSR_MATERIAL_TEXTURES.uSsrProbeMap },
|
|
7503
|
+
{ binding: SSR_MATERIAL_TEXTURES.uSsrTintMap },
|
|
7504
|
+
],
|
|
7505
|
+
);
|
|
7506
|
+
this.ssrMaterialUniforms = device.createBuffer({
|
|
7507
|
+
label: 'post.ssrMaterialUniforms',
|
|
7508
|
+
size: SSR_MATERIAL_SIZE,
|
|
7509
|
+
usage: USAGE_UNIFORM_DST,
|
|
7510
|
+
});
|
|
7244
7511
|
this.ssrUniforms = device.createBuffer({
|
|
7245
7512
|
label: 'post.ssrUniforms',
|
|
7246
|
-
size: SSR_SLOT * MAX_REFLECTIVE_SURFACES,
|
|
7513
|
+
size: SSR_SLOT * (MAX_REFLECTIVE_SURFACES + 1),
|
|
7247
7514
|
usage: USAGE_UNIFORM_DST,
|
|
7248
7515
|
});
|
|
7249
7516
|
this.decalUniforms = device.createBuffer({
|
|
@@ -7468,6 +7735,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7468
7735
|
this.shadowLayout = createDepthBindGroupLayout(device);
|
|
7469
7736
|
this.shadowSkinnedLayout = createDepthBindGroupLayout(device, true);
|
|
7470
7737
|
this.shadowCutoutLayout = createDepthBindGroupLayout(device, false, true);
|
|
7738
|
+
this.shadowAnimatedLayout = createDepthBindGroupLayout(device, false, false, true);
|
|
7739
|
+
this.shadowAnimatedCutoutLayout = createDepthBindGroupLayout(device, false, true, true);
|
|
7471
7740
|
this.shadowDraws = new UniformRing(
|
|
7472
7741
|
device,
|
|
7473
7742
|
/* The cutout block is the plain one and two fields more, and a slot must hold either. */
|
|
@@ -7947,7 +8216,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7947
8216
|
/* Last frame's snapshot is last frame's scene. */
|
|
7948
8217
|
this.refractSnapshotTaken = false;
|
|
7949
8218
|
this.skinScattered = false;
|
|
8219
|
+
/* One frame of the jitter's sequence however many times the frame binds its camera. */
|
|
8220
|
+
this.temporalHistory.nextFrame();
|
|
8221
|
+
this.latePasses.length = 0;
|
|
7950
8222
|
this.skinScatter?.reset();
|
|
8223
|
+
this.reflectionSurface?.reset();
|
|
7951
8224
|
this.plumeVerts.reset();
|
|
7952
8225
|
this.plumeFrags.reset();
|
|
7953
8226
|
this.waterVerts.reset();
|
|
@@ -7975,9 +8248,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7975
8248
|
this.lineVerts.reset();
|
|
7976
8249
|
this.lineFrags.reset();
|
|
7977
8250
|
this.skyUniforms.reset();
|
|
7978
|
-
/* The
|
|
7979
|
-
|
|
8251
|
+
/* The pass ring, and the open slots with it: last frame's pass slots are gone. Its materials
|
|
8252
|
+
stay in the store, which a draw finds them in again. */
|
|
8253
|
+
this.passRing.reset();
|
|
8254
|
+
this.viewRing.reset();
|
|
8255
|
+
this.materialStore.beginFrame();
|
|
8256
|
+
this.passesAsked = 0;
|
|
8257
|
+
this.passesRefused = 0;
|
|
8258
|
+
this.viewsAsked = 0;
|
|
8259
|
+
this.viewsRefused = 0;
|
|
8260
|
+
this.materialsRefused = 0;
|
|
7980
8261
|
this.materials.dirty();
|
|
8262
|
+
this.reopenPass();
|
|
7981
8263
|
/* Spent by `runMotion` at the end of the last frame; a frame states its movers afresh. */
|
|
7982
8264
|
this.motionPass?.reset();
|
|
7983
8265
|
this.latePass?.reset();
|
|
@@ -8312,6 +8594,40 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8312
8594
|
};
|
|
8313
8595
|
}
|
|
8314
8596
|
|
|
8597
|
+
/**
|
|
8598
|
+
* The copies this frame's mesh rewrites owe (`StagedRewrite`), recorded since the last submit and
|
|
8599
|
+
* sent at the head of the next one: one encoder however many meshes are rewritten. Null while
|
|
8600
|
+
* nothing is owed.
|
|
8601
|
+
*/
|
|
8602
|
+
private pendingRewrites: GPUCommandEncoder | null = null;
|
|
8603
|
+
/** One record handed to every staged rewrite, refilled each call, so a rewrite allocates nothing. */
|
|
8604
|
+
private readonly stagedRewrite: StagedRewrite = {
|
|
8605
|
+
encoder: null as unknown as GPUCommandEncoder,
|
|
8606
|
+
staging: null as unknown as GPUBuffer,
|
|
8607
|
+
previous: null,
|
|
8608
|
+
};
|
|
8609
|
+
|
|
8610
|
+
private rewriteCopies(): GPUCommandEncoder {
|
|
8611
|
+
this.pendingRewrites ??= this.surface.device.createCommandEncoder({
|
|
8612
|
+
label: 'mesh.rewriteCopies',
|
|
8613
|
+
});
|
|
8614
|
+
return this.pendingRewrites;
|
|
8615
|
+
}
|
|
8616
|
+
|
|
8617
|
+
/**
|
|
8618
|
+
* Submit `buffers`, led by any copies the frame's mesh rewrites owe. **Every submit of this
|
|
8619
|
+
* renderer goes through here**, because any of them may draw a rewritten mesh — the frame's own,
|
|
8620
|
+
* a mirror's, a shadow's, a probe's — and the copies must be ahead of the first that does.
|
|
8621
|
+
*/
|
|
8622
|
+
private submit(buffers: GPUCommandBuffer[]): void {
|
|
8623
|
+
const rewrites = this.pendingRewrites;
|
|
8624
|
+
if (rewrites !== null) {
|
|
8625
|
+
this.pendingRewrites = null;
|
|
8626
|
+
buffers.unshift(rewrites.finish());
|
|
8627
|
+
}
|
|
8628
|
+
this.surface.device.queue.submit(buffers);
|
|
8629
|
+
}
|
|
8630
|
+
|
|
8315
8631
|
/**
|
|
8316
8632
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
8317
8633
|
*
|
|
@@ -8334,20 +8650,35 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8334
8650
|
* `changeFrames.ts`.
|
|
8335
8651
|
*/
|
|
8336
8652
|
const motion = mesh.motion;
|
|
8337
|
-
let
|
|
8653
|
+
let staged: StagedRewrite | null = null;
|
|
8338
8654
|
if (motion !== null && this.reconstructionWanted) {
|
|
8339
8655
|
const frame = frameOfChange(this.moverFrame, this.inFrame);
|
|
8656
|
+
const size = mesh.vertexBuffers[0]?.size ?? 0;
|
|
8657
|
+
let previous: GPUBuffer | null = null;
|
|
8340
8658
|
if (keepsPrevious(motion.changed, frame)) {
|
|
8341
8659
|
motion.previous ??= this.surface.device.createBuffer({
|
|
8342
8660
|
label: 'recon.dynamicPrevious',
|
|
8343
|
-
size
|
|
8661
|
+
size,
|
|
8344
8662
|
usage: 0x0020 | 0x0008, // VERTEX | COPY_DST
|
|
8345
8663
|
});
|
|
8346
8664
|
previous = motion.previous;
|
|
8347
8665
|
}
|
|
8348
8666
|
motion.changed = frame;
|
|
8667
|
+
/*
|
|
8668
|
+
* **Staged, and copied in behind the copy aside, in an encoder the frame's next submit leads
|
|
8669
|
+
* with**: no submit of the rewrite's own. See `StagedRewrite` and `submit`.
|
|
8670
|
+
*/
|
|
8671
|
+
motion.staging ??= this.surface.device.createBuffer({
|
|
8672
|
+
label: 'mesh.stagedPositions',
|
|
8673
|
+
size,
|
|
8674
|
+
usage: 0x0004 | 0x0008, // COPY_SRC | COPY_DST
|
|
8675
|
+
});
|
|
8676
|
+
staged = this.stagedRewrite;
|
|
8677
|
+
staged.encoder = this.rewriteCopies();
|
|
8678
|
+
staged.staging = motion.staging;
|
|
8679
|
+
staged.previous = previous;
|
|
8349
8680
|
}
|
|
8350
|
-
mesh.update(this.surface.device, positions, normals,
|
|
8681
|
+
mesh.update(this.surface.device, positions, normals, staged);
|
|
8351
8682
|
}
|
|
8352
8683
|
|
|
8353
8684
|
/**
|
|
@@ -8551,12 +8882,20 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8551
8882
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
8552
8883
|
*/
|
|
8553
8884
|
f[at('uGrain')] = 1;
|
|
8885
|
+
/* No reflection and an environment at its own brightness, from the pass and from no material. */
|
|
8886
|
+
this.passReflectivity = 0;
|
|
8887
|
+
this.passEnvironmentGain = 1;
|
|
8888
|
+
this.materialReflectivity = null;
|
|
8889
|
+
this.materialEnvironmentGain = null;
|
|
8554
8890
|
f[at('uEnvironmentDials') + 1] = 0;
|
|
8555
8891
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
8556
8892
|
f[at('uWriteMode')] = 0;
|
|
8557
8893
|
f[at('uWriteMode') + 1] = 0;
|
|
8558
8894
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
8559
8895
|
f[at('uEnvironmentDials') + 2] = 1;
|
|
8896
|
+
/* No overlay until a draw sets one. See `setSurfaceOverlay`. */
|
|
8897
|
+
this.overlayFloats.fill(0);
|
|
8898
|
+
f.fill(0, at('uOverlay'), at('uOverlay') + OVERLAY_FLOATS);
|
|
8560
8899
|
/* The frame's own ambient until a draw asks for its own. See `setAmbientSH`. */
|
|
8561
8900
|
f.fill(0, at('uAmbientSH'), at('uAmbientSH') + AMBIENT_SH_FLOATS);
|
|
8562
8901
|
/*
|
|
@@ -8662,6 +9001,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8662
9001
|
*/
|
|
8663
9002
|
if (this.quality.nightEmissive) f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
8664
9003
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
9004
|
+
/* The frame the cutout dither moves with, in the near corner's w. See `uHighlightMin`. */
|
|
9005
|
+
f[at('uHighlightMin') + 3] = this.cutoutFrame;
|
|
8665
9006
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
8666
9007
|
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
8667
9008
|
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
@@ -8934,6 +9275,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8934
9275
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
8935
9276
|
*/
|
|
8936
9277
|
this.materials.dirty();
|
|
9278
|
+
this.reopenPass();
|
|
8937
9279
|
/*
|
|
8938
9280
|
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
8939
9281
|
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
@@ -8949,6 +9291,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8949
9291
|
this.emissiveMap = null;
|
|
8950
9292
|
this.modelMap = null;
|
|
8951
9293
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
9294
|
+
/* Nor an overlay; its numbers are zeroed with the block's other defaults. */
|
|
9295
|
+
this.overlayMaps = null;
|
|
8952
9296
|
this.uvScale[0] = 1;
|
|
8953
9297
|
this.uvScale[1] = 1;
|
|
8954
9298
|
this.uvOffset[0] = 0;
|
|
@@ -8956,31 +9300,114 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8956
9300
|
}
|
|
8957
9301
|
|
|
8958
9302
|
/**
|
|
8959
|
-
* The slot the next draw's material lives in,
|
|
9303
|
+
* The slot the next draw's material lives in, placing it when something dirtied it, with the
|
|
9304
|
+
* pass's slot taken first where `bindMeshPass` reopened it: `passOffset` is that one's offset.
|
|
8960
9305
|
*
|
|
8961
|
-
* **A
|
|
8962
|
-
* material
|
|
8963
|
-
*
|
|
9306
|
+
* **A placement per material change, not per draw.** A scene drawing a hundred meshes of one
|
|
9307
|
+
* material places it once; a scene switching material every draw places one each, and a material
|
|
9308
|
+
* the store already holds — the same numbers as a draw before it, this frame or an earlier one —
|
|
9309
|
+
* is bound where it is and uploads nothing.
|
|
8964
9310
|
*/
|
|
8965
9311
|
private materialSlotForDraw(): number | null {
|
|
9312
|
+
if (!this.passBlock.open) {
|
|
9313
|
+
this.passesAsked += 1;
|
|
9314
|
+
const pass = this.passRing.allocate();
|
|
9315
|
+
if (pass === null) {
|
|
9316
|
+
this.passesRefused += 1;
|
|
9317
|
+
this.materialBudget.ask();
|
|
9318
|
+
this.materialBudget.drop();
|
|
9319
|
+
if (!this.warnedPassesFull) {
|
|
9320
|
+
this.warnedPassesFull = true;
|
|
9321
|
+
console.warn(
|
|
9322
|
+
`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.`,
|
|
9323
|
+
);
|
|
9324
|
+
}
|
|
9325
|
+
return null;
|
|
9326
|
+
}
|
|
9327
|
+
this.passRing.writeBlock(pass, this.passInts);
|
|
9328
|
+
this.passBlock.slot = pass;
|
|
9329
|
+
}
|
|
8966
9330
|
if (this.materials.open) return this.materials.slot;
|
|
8967
|
-
const slot = this.
|
|
9331
|
+
const slot = this.materialStore.place(this.materialInts);
|
|
8968
9332
|
if (slot === null) {
|
|
8969
|
-
|
|
8970
|
-
|
|
9333
|
+
this.materialsRefused += 1;
|
|
9334
|
+
this.warnMaterialsFull();
|
|
9335
|
+
return null;
|
|
9336
|
+
}
|
|
9337
|
+
this.materials.slot = slot;
|
|
9338
|
+
return slot;
|
|
9339
|
+
}
|
|
9340
|
+
|
|
9341
|
+
/** The pass slot `materialSlotForDraw` left open, read beside the material's by every lit draw. */
|
|
9342
|
+
private get passOffset(): number {
|
|
9343
|
+
return this.passBlock.slot;
|
|
9344
|
+
}
|
|
9345
|
+
|
|
9346
|
+
/** A new pass: its block, and both its views, are taken afresh by the next draw. */
|
|
9347
|
+
private reopenPass(): void {
|
|
9348
|
+
this.passBlock.dirty();
|
|
9349
|
+
this.reopenViews();
|
|
9350
|
+
}
|
|
9351
|
+
|
|
9352
|
+
/** What a view holds changed — the wind, the light's matrix — so the next draw takes a new one. */
|
|
9353
|
+
private reopenViews(): void {
|
|
9354
|
+
this.viewBlock.dirty();
|
|
9355
|
+
this.lateViewBlock.dirty();
|
|
9356
|
+
}
|
|
9357
|
+
|
|
9358
|
+
/**
|
|
9359
|
+
* The view slot a lit draw reads its camera from: the pass's, or its unjittered twin for a blended
|
|
9360
|
+
* draw landing after the upscale. Taken by the first draw that needs it after the pass, the wind or
|
|
9361
|
+
* the light's matrix changed, and shared by every draw until then. Null when the ring is full, which
|
|
9362
|
+
* skips the draw and grows the ring for the next frame, as the pass ring does.
|
|
9363
|
+
*/
|
|
9364
|
+
private viewSlotFor(late: boolean, viewProj: Float32Array): number | null {
|
|
9365
|
+
const block = late ? this.lateViewBlock : this.viewBlock;
|
|
9366
|
+
if (block.open) return block.slot;
|
|
9367
|
+
this.viewsAsked += 1;
|
|
9368
|
+
const slot = this.viewRing.allocate();
|
|
9369
|
+
if (slot === null) {
|
|
9370
|
+
this.viewsRefused += 1;
|
|
9371
|
+
this.materialBudget.ask();
|
|
9372
|
+
this.materialBudget.drop();
|
|
9373
|
+
if (!this.warnedPassesFull) {
|
|
9374
|
+
this.warnedPassesFull = true;
|
|
8971
9375
|
console.warn(
|
|
8972
|
-
`WebGPU: more than ${this.
|
|
9376
|
+
`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.`,
|
|
8973
9377
|
);
|
|
8974
9378
|
}
|
|
8975
9379
|
return null;
|
|
8976
9380
|
}
|
|
8977
|
-
this.
|
|
8978
|
-
|
|
9381
|
+
this.writeView(this.viewRing, slot, viewProj);
|
|
9382
|
+
block.slot = slot;
|
|
8979
9383
|
return slot;
|
|
8980
9384
|
}
|
|
8981
9385
|
|
|
9386
|
+
/** A view block: the camera, the light's matrix and the frame's wind, into `ring` at `slot`. */
|
|
9387
|
+
private writeView(ring: UniformRing, slot: number, viewProj: Float32Array): void {
|
|
9388
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uViewProj.offset, viewProj);
|
|
9389
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
9390
|
+
/* The scene's clock, which a bone animation's instances read their moment from. */
|
|
9391
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uSceneTime.offset, this.animationTime);
|
|
9392
|
+
const d = this.frameWind;
|
|
9393
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindDirection.offset, d.direction);
|
|
9394
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindSpeed.offset, d.bend);
|
|
9395
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindGust.offset, d.gust);
|
|
9396
|
+
ring.writeFloat(slot, FLAT_VIEW_FIELDS.uWindTime.offset, d.time);
|
|
9397
|
+
ring.writeFloats(slot, FLAT_VIEW_FIELDS.uWindSpatialPhase.offset, d.spatialPhase);
|
|
9398
|
+
}
|
|
9399
|
+
|
|
9400
|
+
private warnMaterialsFull(): void {
|
|
9401
|
+
if (this.warnedMaterialsFull) return;
|
|
9402
|
+
this.warnedMaterialsFull = true;
|
|
9403
|
+
console.warn(
|
|
9404
|
+
`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.`,
|
|
9405
|
+
);
|
|
9406
|
+
}
|
|
9407
|
+
|
|
8982
9408
|
/** Said once rather than every frame, for the reason `warnedFull` gives. */
|
|
8983
9409
|
private warnedMaterialsFull = false;
|
|
9410
|
+
private warnedPassesFull = false;
|
|
8984
9411
|
private warnedSkyFull = false;
|
|
8985
9412
|
private warnedParticlesTwice = false;
|
|
8986
9413
|
/**
|
|
@@ -9021,7 +9448,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9021
9448
|
* What this saves is the GPU's share, which for a heavy mesh is most of the cost and for a
|
|
9022
9449
|
* trivial one is very little.
|
|
9023
9450
|
*/
|
|
9024
|
-
|
|
9451
|
+
/* A static list draws every entry in every view, since a bundle cannot leave one out. */
|
|
9452
|
+
if (
|
|
9453
|
+
this.quality.cullDraws &&
|
|
9454
|
+
this.staticRecording === null &&
|
|
9455
|
+
!this.visible(mesh.bounds, model as ReadonlyMat4)
|
|
9456
|
+
) {
|
|
9457
|
+
return;
|
|
9458
|
+
}
|
|
9025
9459
|
/*
|
|
9026
9460
|
* And the other half of the same question, where the profile asked for one. Gated on
|
|
9027
9461
|
* `cullDraws` alongside the frustum test, because both are the renderer doing a consumer's
|
|
@@ -9036,6 +9470,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9036
9470
|
* where it happened to pass, write motion for a surface no pixel of the frame belongs to.
|
|
9037
9471
|
*/
|
|
9038
9472
|
if (this.tracksMotionNow()) this.recordDrawMotion(mesh, model, previousModel);
|
|
9473
|
+
else if (previousModel !== null && this.quality.cameraMotionBlur * this.motionBlurScale > 0) {
|
|
9474
|
+
this.warnObjectBlur();
|
|
9475
|
+
}
|
|
9039
9476
|
noteMapsWithoutUvs(mesh, mesh.hasUvs, this.materialHasMaps);
|
|
9040
9477
|
/* An opaque draw meets the medium as the dial says: `submitMesh` reads an absent `fog` as
|
|
9041
9478
|
true, which is a translucent draw's default and would put every opaque draw back in the fog
|
|
@@ -9209,21 +9646,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9209
9646
|
`TranslucentMeshOptions.reconstructed`. */
|
|
9210
9647
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
9211
9648
|
const late = lands || (blend && this.oitReplaying && this.reconstructing);
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
);
|
|
9649
|
+
/* The camera, the light's matrix and the wind are the pass's, in its view block. */
|
|
9650
|
+
const view = this.viewSlotFor(late, late ? this.correctedViewProj : this.viewProj);
|
|
9651
|
+
if (view === null) return;
|
|
9652
|
+
this.drawViewOffset = view;
|
|
9217
9653
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uModel.offset, model);
|
|
9218
9654
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uTint.offset, tint ?? WHITE);
|
|
9219
|
-
/* The frame's wind, for a mesh carrying a sway lane. Written on this path and not the
|
|
9220
|
-
instanced one, which declares none of these fields. */
|
|
9221
|
-
this.writeWind(slot);
|
|
9222
9655
|
/* The material's repeats, not a constant: geometry authored in metres is textured at
|
|
9223
9656
|
whatever density the material asks for without rebuilding it. */
|
|
9224
9657
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
9225
9658
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
9226
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
9227
9659
|
/*
|
|
9228
9660
|
* Whether this mesh's tangent frame is real. WebGPU has no disabled attribute, so location 10
|
|
9229
9661
|
* is always backed — by the caller's data or by the constant buffer — which makes the binding
|
|
@@ -9420,8 +9852,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9420
9852
|
command.pipeline = pipeline;
|
|
9421
9853
|
command.bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
9422
9854
|
command.offsetA = slot;
|
|
9423
|
-
command.offsetB =
|
|
9424
|
-
command.
|
|
9855
|
+
command.offsetB = this.passOffset;
|
|
9856
|
+
command.offsetC = material;
|
|
9857
|
+
command.offsetD = this.drawViewOffset;
|
|
9858
|
+
command.offsetCount = 4;
|
|
9425
9859
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
9426
9860
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
9427
9861
|
}
|
|
@@ -9433,7 +9867,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9433
9867
|
const pass = this.openPass();
|
|
9434
9868
|
if (pass === null) return;
|
|
9435
9869
|
pass.setPipeline(pipeline);
|
|
9436
|
-
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
9870
|
+
pass.setBindGroup(0, this.flatBindGroupFor(skinned, morphed, deltas), [
|
|
9871
|
+
slot,
|
|
9872
|
+
this.passOffset,
|
|
9873
|
+
material,
|
|
9874
|
+
this.drawViewOffset,
|
|
9875
|
+
]);
|
|
9437
9876
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
9438
9877
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
|
|
9439
9878
|
}
|
|
@@ -9455,6 +9894,26 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9455
9894
|
material,
|
|
9456
9895
|
);
|
|
9457
9896
|
}
|
|
9897
|
+
/* And its surface half, for the frame's materials' reflections. See `keepReflectionSurface`. */
|
|
9898
|
+
if (!blend && lit && depthWrite && this.reflectionSurfaceKeeps()) {
|
|
9899
|
+
this.keepReflectionSurface(
|
|
9900
|
+
mesh,
|
|
9901
|
+
key,
|
|
9902
|
+
base,
|
|
9903
|
+
skinned,
|
|
9904
|
+
morphed,
|
|
9905
|
+
deltas,
|
|
9906
|
+
layer,
|
|
9907
|
+
eight,
|
|
9908
|
+
clothed,
|
|
9909
|
+
modelKind,
|
|
9910
|
+
slot,
|
|
9911
|
+
material,
|
|
9912
|
+
null,
|
|
9913
|
+
1,
|
|
9914
|
+
null,
|
|
9915
|
+
);
|
|
9916
|
+
}
|
|
9458
9917
|
}
|
|
9459
9918
|
/* Back to the defaults for whatever is drawn next, exactly as `renderer.ts` restores them —
|
|
9460
9919
|
unconditionally, because the scratch state above is shared by every draw after this one
|
|
@@ -9529,16 +9988,127 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9529
9988
|
'instanced pipeline.',
|
|
9530
9989
|
);
|
|
9531
9990
|
}
|
|
9991
|
+
const animation = (options.animation ?? null) as GpuBoneAnimation | null;
|
|
9992
|
+
/* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
|
|
9993
|
+
lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
|
|
9994
|
+
if (animation !== null && !this.presentOf(mesh as GpuMesh & { key?: string })['grain']) {
|
|
9995
|
+
throw new Error(
|
|
9996
|
+
'WebGPU: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
|
|
9997
|
+
'(`MeshData.lightmapUvs`), and this mesh has none.',
|
|
9998
|
+
);
|
|
9999
|
+
}
|
|
9532
10000
|
const batch = new GpuInstancedBatch(
|
|
9533
10001
|
this.surface.device,
|
|
9534
10002
|
mesh,
|
|
9535
10003
|
capacity,
|
|
9536
10004
|
`flat.instances:${(mesh as GpuMesh & { key?: string }).key ?? 'mesh'}`,
|
|
9537
10005
|
options.cull ?? false,
|
|
10006
|
+
animation,
|
|
9538
10007
|
);
|
|
9539
10008
|
return batch;
|
|
9540
10009
|
}
|
|
9541
10010
|
|
|
10011
|
+
/** See `Renderer.setAnimationTime`. Every view taken after it reads the new clock. */
|
|
10012
|
+
setAnimationTime(seconds: number): void {
|
|
10013
|
+
if (!Number.isFinite(seconds) || seconds === this.animationTime) return;
|
|
10014
|
+
this.animationTime = seconds;
|
|
10015
|
+
this.reopenViews();
|
|
10016
|
+
}
|
|
10017
|
+
|
|
10018
|
+
/** The clock `setAnimationTime` set, 0 until it is. */
|
|
10019
|
+
private animationTime = 0;
|
|
10020
|
+
|
|
10021
|
+
/** See `Renderer.createBoneAnimation`. */
|
|
10022
|
+
createBoneAnimation(clip: BoneAnimationClip): BoneAnimationHandle {
|
|
10023
|
+
return new GpuBoneAnimation(this.surface.device, packBoneAnimation(clip));
|
|
10024
|
+
}
|
|
10025
|
+
|
|
10026
|
+
/** See `Renderer.disposeBoneAnimation`. A batch still playing it draws nothing after. */
|
|
10027
|
+
disposeBoneAnimation(animation: BoneAnimationHandle): void {
|
|
10028
|
+
const clip = animation as GpuBoneAnimation;
|
|
10029
|
+
if (!clip.disposed) clip.dispose();
|
|
10030
|
+
}
|
|
10031
|
+
|
|
10032
|
+
/** An animated batch's clip and clocks, as its groups bind them. */
|
|
10033
|
+
private animationViews(batch: GpuInstancedBatch): AnimationViews | null {
|
|
10034
|
+
const animation = batch.animation;
|
|
10035
|
+
const clocks = batch.clocks;
|
|
10036
|
+
if (animation === null || clocks === null || animation.disposed) return null;
|
|
10037
|
+
return { places: animation.placesView, turns: animation.turnsView, clocks: clocks.view };
|
|
10038
|
+
}
|
|
10039
|
+
|
|
10040
|
+
/** An animated batch's groups, dropped wherever the renderer drops its own. */
|
|
10041
|
+
private freshAnimatedGroups(batch: GpuInstancedBatch): void {
|
|
10042
|
+
if (batch.groupEpoch === this.flatGroupEpoch) return;
|
|
10043
|
+
batch.litGroups.clear();
|
|
10044
|
+
batch.depthGroups.clear();
|
|
10045
|
+
batch.depthPeelGroups.clear();
|
|
10046
|
+
batch.groupEpoch = this.flatGroupEpoch;
|
|
10047
|
+
}
|
|
10048
|
+
|
|
10049
|
+
/**
|
|
10050
|
+
* The lit group an animated batch draws with: the material's group, or its overlay twin, again
|
|
10051
|
+
* with the clip's and the clocks' textures — kept on the batch by the group it was built beside,
|
|
10052
|
+
* as a skinned twin is kept by its palette slot.
|
|
10053
|
+
*/
|
|
10054
|
+
private animatedGroup(batch: GpuInstancedBatch, views: AnimationViews): GPUBindGroup {
|
|
10055
|
+
this.freshAnimatedGroups(batch);
|
|
10056
|
+
const base = this.overlayGroup();
|
|
10057
|
+
let group = batch.litGroups.get(base);
|
|
10058
|
+
if (group === undefined) {
|
|
10059
|
+
this.overlayBinding = base !== this.bindGroup;
|
|
10060
|
+
group = createFlatBindGroup(
|
|
10061
|
+
this.surface.device,
|
|
10062
|
+
this.animatedBindGroupLayout,
|
|
10063
|
+
this.variant,
|
|
10064
|
+
this.perDraw.buffer,
|
|
10065
|
+
this.passRing.buffer,
|
|
10066
|
+
this.materialStore.buffer,
|
|
10067
|
+
this.viewRing.buffer,
|
|
10068
|
+
this.flatTextures,
|
|
10069
|
+
null,
|
|
10070
|
+
null,
|
|
10071
|
+
null,
|
|
10072
|
+
views,
|
|
10073
|
+
);
|
|
10074
|
+
this.overlayBinding = false;
|
|
10075
|
+
this.bindGroupBudget.ask();
|
|
10076
|
+
batch.litGroups.set(base, group);
|
|
10077
|
+
}
|
|
10078
|
+
return group;
|
|
10079
|
+
}
|
|
10080
|
+
|
|
10081
|
+
/** An animated caster's shadow group, by what it cuts with and whether this is the peel. */
|
|
10082
|
+
private animatedDepthGroup(
|
|
10083
|
+
batch: GpuInstancedBatch,
|
|
10084
|
+
views: AnimationViews,
|
|
10085
|
+
cutout: GpuSurfaceTexture | null,
|
|
10086
|
+
): GPUBindGroup | null {
|
|
10087
|
+
this.freshAnimatedGroups(batch);
|
|
10088
|
+
const peel = this.shadowLayerIsPeel && this.shadowPeelPassUniforms !== null;
|
|
10089
|
+
const groups = peel ? batch.depthPeelGroups : batch.depthGroups;
|
|
10090
|
+
const held = groups.get(cutout);
|
|
10091
|
+
if (held !== undefined) return held;
|
|
10092
|
+
const passUniforms = peel ? this.shadowPeelPassUniforms : this.shadowPassUniforms;
|
|
10093
|
+
const previousView = peel ? this.sunStaticArrayView : this.shadowBlankView;
|
|
10094
|
+
const previousSampler = peel ? this.shadowPeelSampler : this.shadowBlankSampler;
|
|
10095
|
+
if (passUniforms === null || previousView === null || previousSampler === null) return null;
|
|
10096
|
+
const group = createDepthBindGroup(
|
|
10097
|
+
this.surface.device,
|
|
10098
|
+
cutout === null ? this.shadowAnimatedLayout : this.shadowAnimatedCutoutLayout,
|
|
10099
|
+
this.shadowDraws.buffer,
|
|
10100
|
+
passUniforms,
|
|
10101
|
+
previousView,
|
|
10102
|
+
previousSampler,
|
|
10103
|
+
null,
|
|
10104
|
+
cutout === null ? null : { view: cutout.view, sampler: cutout.sampler },
|
|
10105
|
+
null,
|
|
10106
|
+
views,
|
|
10107
|
+
);
|
|
10108
|
+
groups.set(cutout, group);
|
|
10109
|
+
return group;
|
|
10110
|
+
}
|
|
10111
|
+
|
|
9542
10112
|
/** The device instance culls, built on the first culling draw. See `InstanceCullPass`. */
|
|
9543
10113
|
private instanceCulls: InstanceCullPass | null = null;
|
|
9544
10114
|
/** Counts frames, so a batch knows when its cull slots were last taken. */
|
|
@@ -9572,7 +10142,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9572
10142
|
this.submitInstanced(batch, data, 1, false, {});
|
|
9573
10143
|
/* A batch that moved this frame pairs each slot with last frame's; the batch is the identity. */
|
|
9574
10144
|
const previous = batch.previousBuffer;
|
|
9575
|
-
if (
|
|
10145
|
+
if (
|
|
10146
|
+
previous !== null &&
|
|
10147
|
+
this.staticRecording === null &&
|
|
10148
|
+
batch.changed === this.moverFrame &&
|
|
10149
|
+
this.tracksMotionNow()
|
|
10150
|
+
) {
|
|
9576
10151
|
const paired = pairedInstances(batch.previousCount, Math.min(data.count, batch.capacity));
|
|
9577
10152
|
if (paired > 0) this.motionPass?.recordInstanced(batch.mesh, batch.buffer, previous, paired);
|
|
9578
10153
|
}
|
|
@@ -9606,6 +10181,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9606
10181
|
const refraction = options.refraction ?? 0;
|
|
9607
10182
|
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
9608
10183
|
if ((refraction <= 0 && !glassy) || !this.takeRefractSnapshot()) return false;
|
|
10184
|
+
this.seeThroughBound = true;
|
|
9609
10185
|
const glass = this.glassScratch;
|
|
9610
10186
|
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
9611
10187
|
const seeAt = this.materialField('uSeeThrough');
|
|
@@ -9618,6 +10194,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9618
10194
|
this.perFrameFloats[tintAt] = refractTint?.[0] ?? 1;
|
|
9619
10195
|
this.perFrameFloats[tintAt + 1] = refractTint?.[1] ?? 1;
|
|
9620
10196
|
this.perFrameFloats[tintAt + 2] = refractTint?.[2] ?? 1;
|
|
10197
|
+
/* w: this framebuffer's first row is the top, so up the screen is down the rows. */
|
|
10198
|
+
this.perFrameFloats[tintAt + 3] = -1;
|
|
9621
10199
|
const glassTintAt = this.materialField('uGlassTint');
|
|
9622
10200
|
this.perFrameFloats[glassTintAt] = glass.tint[0];
|
|
9623
10201
|
this.perFrameFloats[glassTintAt + 1] = glass.tint[1];
|
|
@@ -9627,6 +10205,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9627
10205
|
|
|
9628
10206
|
/** Put the scratch back after `bindSeeThrough`: refraction and glass off. */
|
|
9629
10207
|
private releaseSeeThrough(): void {
|
|
10208
|
+
this.seeThroughBound = false;
|
|
9630
10209
|
const seeAt = this.materialField('uSeeThrough');
|
|
9631
10210
|
this.perFrameFloats[seeAt] = 0;
|
|
9632
10211
|
this.perFrameFloats[seeAt + 2] = 0;
|
|
@@ -9692,10 +10271,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9692
10271
|
/* A culling batch's camera draw: the whole batch against the view and the occluders on the CPU,
|
|
9693
10272
|
then instance by instance on the device into an indirect draw. Blended draws keep every
|
|
9694
10273
|
instance and their order. See `InstancedOptions.cull`. */
|
|
9695
|
-
const culling = !blend && batch.cull;
|
|
10274
|
+
const culling = !blend && batch.cull && this.staticRecording === null;
|
|
9696
10275
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
10276
|
+
/* A batch playing a bone animation reads its clocks by instance index, which a compaction
|
|
10277
|
+
would reorder: it is culled whole and never instance by instance. */
|
|
10278
|
+
const animation = batch.animation === null ? null : this.animationViews(batch);
|
|
10279
|
+
if (batch.animation !== null && animation === null) return;
|
|
9697
10280
|
const cut =
|
|
9698
|
-
culling && cullsInstances(count, mesh.indexCount)
|
|
10281
|
+
culling && animation === null && cullsInstances(count, mesh.indexCount)
|
|
10282
|
+
? this.takeCullSlot(batch, count)
|
|
10283
|
+
: null;
|
|
9699
10284
|
const instanceBuffer = cut === null ? batch.buffer : cut.instances;
|
|
9700
10285
|
|
|
9701
10286
|
const slot = this.perDraw.allocate();
|
|
@@ -9711,15 +10296,22 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9711
10296
|
|
|
9712
10297
|
/* Late unless it moves with what it lies on. See `TranslucentMeshOptions.reconstructed`. */
|
|
9713
10298
|
const lands = blend && options.reconstructed !== true && this.drawsLate();
|
|
9714
|
-
this.
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
lands ? this.correctedViewProj : this.viewProj,
|
|
9718
|
-
);
|
|
10299
|
+
const view = this.viewSlotFor(lands, lands ? this.correctedViewProj : this.viewProj);
|
|
10300
|
+
if (view === null) return;
|
|
10301
|
+
this.drawViewOffset = view;
|
|
9719
10302
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvScale.offset, this.uvScale);
|
|
9720
10303
|
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uUvOffset.offset, this.uvOffset);
|
|
9721
|
-
this.perDraw.writeFloats(slot, FLAT_VERT_FIELDS.uLightViewProj.offset, this.lightViewProj);
|
|
9722
10304
|
this.perDraw.writeInt(slot, FLAT_VERT_FIELDS.uHasTangents.offset, mesh.hasTangents ? 1 : 0);
|
|
10305
|
+
/* The clip's speed and bone scale, read by the animated variant's own field. */
|
|
10306
|
+
const clip = batch.animation;
|
|
10307
|
+
if (animation !== null && clip !== null) {
|
|
10308
|
+
this.perDraw.writeFloat(
|
|
10309
|
+
slot,
|
|
10310
|
+
FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset,
|
|
10311
|
+
clip.framesPerSecond,
|
|
10312
|
+
);
|
|
10313
|
+
this.perDraw.writeFloat(slot, FLAT_ANIMATED_VERT_FIELDS.uBoneClip.offset + 4, clip.boneScale);
|
|
10314
|
+
}
|
|
9723
10315
|
|
|
9724
10316
|
const dimmed = opacity < 1;
|
|
9725
10317
|
if (dimmed) {
|
|
@@ -9757,7 +10349,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9757
10349
|
plain one would hand a draw the wrong layout. */
|
|
9758
10350
|
const adds = blend && options.additive === true;
|
|
9759
10351
|
const key =
|
|
9760
|
-
`${base}|inst${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
10352
|
+
`${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
9761
10353
|
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
9762
10354
|
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
9763
10355
|
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
@@ -9772,7 +10364,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9772
10364
|
pipeline = flatPipeline(
|
|
9773
10365
|
pipelines,
|
|
9774
10366
|
this.surface.device,
|
|
9775
|
-
this.flatLayoutFor(false, false),
|
|
10367
|
+
animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout,
|
|
9776
10368
|
this.variant,
|
|
9777
10369
|
key,
|
|
9778
10370
|
present,
|
|
@@ -9788,6 +10380,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9788
10380
|
false,
|
|
9789
10381
|
false,
|
|
9790
10382
|
modelKind,
|
|
10383
|
+
'whole',
|
|
10384
|
+
false,
|
|
10385
|
+
animation !== null,
|
|
9791
10386
|
);
|
|
9792
10387
|
}
|
|
9793
10388
|
|
|
@@ -9798,10 +10393,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9798
10393
|
: this.recordDraw(0, this.currentTarget());
|
|
9799
10394
|
if (command !== null) {
|
|
9800
10395
|
command.pipeline = pipeline;
|
|
9801
|
-
command.bindGroup =
|
|
10396
|
+
command.bindGroup =
|
|
10397
|
+
animation === null
|
|
10398
|
+
? this.flatBindGroupFor(false, false, null)
|
|
10399
|
+
: this.animatedGroup(batch, animation);
|
|
9802
10400
|
command.offsetA = slot;
|
|
9803
|
-
command.offsetB =
|
|
9804
|
-
command.
|
|
10401
|
+
command.offsetB = this.passOffset;
|
|
10402
|
+
command.offsetC = material;
|
|
10403
|
+
command.offsetD = this.drawViewOffset;
|
|
10404
|
+
command.offsetCount = 4;
|
|
9805
10405
|
let index = 0;
|
|
9806
10406
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
9807
10407
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
@@ -9817,7 +10417,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9817
10417
|
const pass = this.openPass();
|
|
9818
10418
|
if (pass === null) return;
|
|
9819
10419
|
pass.setPipeline(pipeline);
|
|
9820
|
-
pass.setBindGroup(
|
|
10420
|
+
pass.setBindGroup(
|
|
10421
|
+
0,
|
|
10422
|
+
animation === null
|
|
10423
|
+
? this.flatBindGroupFor(false, false, null)
|
|
10424
|
+
: this.animatedGroup(batch, animation),
|
|
10425
|
+
[slot, this.passOffset, material, this.drawViewOffset],
|
|
10426
|
+
);
|
|
9821
10427
|
let index = 0;
|
|
9822
10428
|
for (; index < mesh.vertexBuffers.length; index++) {
|
|
9823
10429
|
pass.setVertexBuffer(index, mesh.vertexBuffers[index] as GPUBuffer);
|
|
@@ -9827,6 +10433,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9827
10433
|
if (cut === null) pass.drawIndexed(mesh.indexCount, count);
|
|
9828
10434
|
else pass.drawIndexedIndirect(cut.args, 0);
|
|
9829
10435
|
}
|
|
10436
|
+
/* And its surface half, for the frame's materials' reflections, with the same survivors. */
|
|
10437
|
+
if (!blend && lit && depthWrite && this.reflectionSurfaceKeeps()) {
|
|
10438
|
+
this.keepReflectionSurface(
|
|
10439
|
+
mesh,
|
|
10440
|
+
key,
|
|
10441
|
+
base,
|
|
10442
|
+
false,
|
|
10443
|
+
false,
|
|
10444
|
+
null,
|
|
10445
|
+
layer,
|
|
10446
|
+
false,
|
|
10447
|
+
false,
|
|
10448
|
+
modelKind,
|
|
10449
|
+
slot,
|
|
10450
|
+
material,
|
|
10451
|
+
instanceBuffer,
|
|
10452
|
+
count,
|
|
10453
|
+
cut === null ? null : cut.args,
|
|
10454
|
+
animation === null ? null : batch,
|
|
10455
|
+
);
|
|
10456
|
+
}
|
|
9830
10457
|
}
|
|
9831
10458
|
/* Back to the defaults, unconditionally and for the reason `drawMesh` gives at length: this
|
|
9832
10459
|
scratch block is cumulative state shared by every draw after this one, and an early return
|
|
@@ -10037,6 +10664,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10037
10664
|
if (view === null) return false;
|
|
10038
10665
|
|
|
10039
10666
|
this.lightViewProj.set(lightViewProj as Float32Array);
|
|
10667
|
+
this.reopenViews();
|
|
10040
10668
|
mat4.multiply(this.correctedLightViewProj, SHADOW_CLIP_CORRECTION, lightViewProj);
|
|
10041
10669
|
this.shadowLayerIsPeel = peel;
|
|
10042
10670
|
this.shadowLayerIsDynamic = dynamic;
|
|
@@ -10149,6 +10777,264 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10149
10777
|
casters(this.sceneCasterSink);
|
|
10150
10778
|
}
|
|
10151
10779
|
|
|
10780
|
+
/* -- Static draws ---------------------------------------------------------------- */
|
|
10781
|
+
|
|
10782
|
+
/** See `Renderer.createStaticDraws`. Recorded on its first draw, into each view it is drawn in. */
|
|
10783
|
+
createStaticDraws(casters: ShadowCasters): StaticDrawsHandle {
|
|
10784
|
+
return new GpuStaticDraws(
|
|
10785
|
+
this.surface.device,
|
|
10786
|
+
captureStaticDraws(casters),
|
|
10787
|
+
this.fragment.passSize,
|
|
10788
|
+
FLAT_VIEW_SIZE,
|
|
10789
|
+
FLAT_VERT_SIZE,
|
|
10790
|
+
);
|
|
10791
|
+
}
|
|
10792
|
+
|
|
10793
|
+
/**
|
|
10794
|
+
* See `Renderer.drawStaticDraws`. One bundle executed, after a pass block and a view block are
|
|
10795
|
+
* written for this view; the list is recorded first where it has no recording that is current for
|
|
10796
|
+
* this target and starting state. See `staticBundles.ts`.
|
|
10797
|
+
*/
|
|
10798
|
+
drawStaticDraws(draws: StaticDrawsHandle): void {
|
|
10799
|
+
if (this.surface.lost) return;
|
|
10800
|
+
const list = draws as GpuStaticDraws;
|
|
10801
|
+
if (list.disposed) {
|
|
10802
|
+
this.warnStaticDisposed();
|
|
10803
|
+
return;
|
|
10804
|
+
}
|
|
10805
|
+
if (!this.canDraw() || this.viewProj === null) return;
|
|
10806
|
+
/* A list's draws are rigid and wear no overlay: put back to none, as `bindMeshPass` does. */
|
|
10807
|
+
this.setSkinPalette(null);
|
|
10808
|
+
this.setMorphWeights(null);
|
|
10809
|
+
this.setCloth(null);
|
|
10810
|
+
this.setSurfaceOverlay(null);
|
|
10811
|
+
this.setMaterial(null);
|
|
10812
|
+
this.sceneCasterMaterial = undefined;
|
|
10813
|
+
/* A probe face gives its slots back after every face; a list replays there draw by draw. */
|
|
10814
|
+
if (this.probePassActive) {
|
|
10815
|
+
this.replayStaticImmediately(list);
|
|
10816
|
+
return;
|
|
10817
|
+
}
|
|
10818
|
+
/* The copy first: taking one may grow the list's rings, which no recording before can bind. */
|
|
10819
|
+
const copy = list.takeCopy(this.moverFrame);
|
|
10820
|
+
const current = this.staticRecordingFor(list);
|
|
10821
|
+
if (current === null || current.immediate) {
|
|
10822
|
+
this.replayStaticImmediately(list);
|
|
10823
|
+
return;
|
|
10824
|
+
}
|
|
10825
|
+
current.usedIn = this.moverFrame;
|
|
10826
|
+
list.writePass(copy, this.passInts);
|
|
10827
|
+
this.writeView(list.views, list.viewOffset(copy), this.viewProj);
|
|
10828
|
+
list.uploadCopy(copy);
|
|
10829
|
+
|
|
10830
|
+
const cache = current.cache;
|
|
10831
|
+
let frame = current.frameBundles[copy];
|
|
10832
|
+
if (frame === undefined || frame === null) {
|
|
10833
|
+
frame = encodeBundle(
|
|
10834
|
+
this.surface.device,
|
|
10835
|
+
{
|
|
10836
|
+
colorFormats: [cache.format],
|
|
10837
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
10838
|
+
sampleCount: cache.sampleCount,
|
|
10839
|
+
depthReadOnly: false,
|
|
10840
|
+
},
|
|
10841
|
+
current.frame,
|
|
10842
|
+
list.passOffset(copy),
|
|
10843
|
+
list.viewOffset(copy),
|
|
10844
|
+
'static.frame',
|
|
10845
|
+
);
|
|
10846
|
+
current.frameBundles[copy] = frame;
|
|
10847
|
+
}
|
|
10848
|
+
const command = this.recordDraw(0, this.currentTarget());
|
|
10849
|
+
if (command !== null) {
|
|
10850
|
+
command.bundles = frame;
|
|
10851
|
+
command.pipeline = null;
|
|
10852
|
+
command.bindGroup = null;
|
|
10853
|
+
} else {
|
|
10854
|
+
this.openPass()?.executeBundles(frame);
|
|
10855
|
+
}
|
|
10856
|
+
const surface = this.reflectionSurface;
|
|
10857
|
+
if (current.surfaceKept && surface !== null && current.surface.taken > 0) {
|
|
10858
|
+
let halves = current.surfaceBundles[copy];
|
|
10859
|
+
if (halves === undefined || halves === null) {
|
|
10860
|
+
halves = encodeBundle(
|
|
10861
|
+
this.surface.device,
|
|
10862
|
+
{
|
|
10863
|
+
colorFormats: [null, REFLECTION_SURFACE_FORMAT, REFLECTION_SURFACE_FORMAT],
|
|
10864
|
+
depthStencilFormat: DEPTH_FORMAT,
|
|
10865
|
+
sampleCount: 1,
|
|
10866
|
+
depthReadOnly: true,
|
|
10867
|
+
},
|
|
10868
|
+
current.surface,
|
|
10869
|
+
list.passOffset(copy),
|
|
10870
|
+
list.viewOffset(copy),
|
|
10871
|
+
'static.surface',
|
|
10872
|
+
);
|
|
10873
|
+
current.surfaceBundles[copy] = halves;
|
|
10874
|
+
}
|
|
10875
|
+
const kept = surface.take();
|
|
10876
|
+
kept.bundles = halves;
|
|
10877
|
+
}
|
|
10878
|
+
/* Counted as the draws would have been, so a frame reports the same on both backends. */
|
|
10879
|
+
for (let draw = 0; draw < current.frame.taken; draw++) this.drawBudget.ask();
|
|
10880
|
+
if (!this.staticJustRecorded) {
|
|
10881
|
+
for (let ask = 0; ask < current.materialAsks; ask++) this.materialBudget.ask();
|
|
10882
|
+
}
|
|
10883
|
+
this.staticJustRecorded = false;
|
|
10884
|
+
}
|
|
10885
|
+
|
|
10886
|
+
/** A list drawn the way `drawSceneCasters` draws, a draw at a time, for where a bundle cannot be. */
|
|
10887
|
+
private replayStaticImmediately(list: GpuStaticDraws): void {
|
|
10888
|
+
this.staticJustRecorded = false;
|
|
10889
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
10890
|
+
this.setMaterial(null);
|
|
10891
|
+
this.sceneCasterMaterial = undefined;
|
|
10892
|
+
}
|
|
10893
|
+
|
|
10894
|
+
/** See `Renderer.disposeStaticDraws`. Its pins let go; its meshes and materials are the caller's. */
|
|
10895
|
+
disposeStaticDraws(draws: StaticDrawsHandle): void {
|
|
10896
|
+
const list = draws as GpuStaticDraws;
|
|
10897
|
+
if (list.disposed) return;
|
|
10898
|
+
list.dispose(this.unpinFor(list));
|
|
10899
|
+
}
|
|
10900
|
+
|
|
10901
|
+
/** The recording to replay `list` from here, made now if none is current; null if it cannot be. */
|
|
10902
|
+
private staticRecordingFor(list: GpuStaticDraws): StaticRecording | null {
|
|
10903
|
+
/* Geometry still arriving is not drawn yet, so a recording now would leave it out for good. */
|
|
10904
|
+
for (const entry of list.entries) {
|
|
10905
|
+
const mesh = entry.kind === 'mesh' ? entry.mesh : (entry.batch as GpuInstancedBatch).mesh;
|
|
10906
|
+
if (!mesh.complete) return null;
|
|
10907
|
+
}
|
|
10908
|
+
const cache = this.targetPipelines();
|
|
10909
|
+
const start = this.materialInts;
|
|
10910
|
+
const startHash = hashWords(start, start.length);
|
|
10911
|
+
const surfaceKept = this.reflectionSurfaceKeeps();
|
|
10912
|
+
let recording = list.find(cache, start, startHash, this.cutoutResolveStaged, surfaceKept);
|
|
10913
|
+
if (
|
|
10914
|
+
recording !== null &&
|
|
10915
|
+
recording.cacheVersion === cache.version &&
|
|
10916
|
+
recording.groupEpoch === this.flatGroupEpoch &&
|
|
10917
|
+
recording.storeEpoch === this.materialStore.epoch &&
|
|
10918
|
+
recording.ringEpoch === list.ringEpoch
|
|
10919
|
+
) {
|
|
10920
|
+
return recording;
|
|
10921
|
+
}
|
|
10922
|
+
const unpin = this.unpinFor(list);
|
|
10923
|
+
if (recording === null) {
|
|
10924
|
+
recording = list.open(cache, start, startHash, this.cutoutResolveStaged, surfaceKept, unpin);
|
|
10925
|
+
} else {
|
|
10926
|
+
list.clear(recording, recording.storeEpoch === this.materialStore.epoch ? unpin : () => {});
|
|
10927
|
+
}
|
|
10928
|
+
this.recordStatic(list, recording);
|
|
10929
|
+
return recording;
|
|
10930
|
+
}
|
|
10931
|
+
|
|
10932
|
+
/** How a list's pins are let go: through the store, unless it has forgotten them since. */
|
|
10933
|
+
private unpinFor(list: GpuStaticDraws): (offset: number) => void {
|
|
10934
|
+
const epoch = this.materialStore.epoch;
|
|
10935
|
+
return (offset) => {
|
|
10936
|
+
if (this.materialStore.epoch === epoch && list.pinEpoch === epoch) {
|
|
10937
|
+
this.materialStore.unpin(offset);
|
|
10938
|
+
}
|
|
10939
|
+
};
|
|
10940
|
+
}
|
|
10941
|
+
|
|
10942
|
+
/**
|
|
10943
|
+
* Record `list` into `recording`: its entries through the scene-caster path, with the list's
|
|
10944
|
+
* rings and group cache swapped in for the frame's and the pass and view slots held at the list's
|
|
10945
|
+
* first copy, so every command the path records binds the list's buffers and offsets the bundle
|
|
10946
|
+
* can rewrite per copy. Everything swapped is put back, whatever the path throws.
|
|
10947
|
+
*/
|
|
10948
|
+
private recordStatic(list: GpuStaticDraws, recording: StaticRecording): void {
|
|
10949
|
+
if (list.groupEpoch !== this.flatGroupEpoch) {
|
|
10950
|
+
list.groups = new Map();
|
|
10951
|
+
list.blank = null;
|
|
10952
|
+
list.groupEpoch = this.flatGroupEpoch;
|
|
10953
|
+
}
|
|
10954
|
+
list.drawRing.reset();
|
|
10955
|
+
const perDraw = this.perDraw;
|
|
10956
|
+
const passRing = this.passRing;
|
|
10957
|
+
const viewRing = this.viewRing;
|
|
10958
|
+
const groups = this.flatBindGroups;
|
|
10959
|
+
const blank = this.blankAlbedoBindGroup;
|
|
10960
|
+
const bindGroup = this.bindGroup;
|
|
10961
|
+
const passSlot = this.passBlock.slot;
|
|
10962
|
+
const viewSlot = this.viewBlock.slot;
|
|
10963
|
+
const lateSlot = this.lateViewBlock.slot;
|
|
10964
|
+
const asked = this.materialBudget.used;
|
|
10965
|
+
this.perDraw = list.drawRing;
|
|
10966
|
+
this.passRing = list.passes;
|
|
10967
|
+
this.viewRing = list.views;
|
|
10968
|
+
this.flatBindGroups = list.groups as typeof groups;
|
|
10969
|
+
/* The blank group over the list's rings, built as the frame's is: with no maps held. */
|
|
10970
|
+
list.blank ??= this.buildFlatBindGroup();
|
|
10971
|
+
this.blankAlbedoBindGroup = list.blank;
|
|
10972
|
+
this.bindGroup = this.flatGroupForMaps(null, null, null, null, null);
|
|
10973
|
+
this.passBlock.slot = list.passOffset(0);
|
|
10974
|
+
this.viewBlock.slot = list.viewOffset(0);
|
|
10975
|
+
this.lateViewBlock.slot = list.viewOffset(0);
|
|
10976
|
+
this.materials.dirty();
|
|
10977
|
+
this.staticRecording = recording;
|
|
10978
|
+
try {
|
|
10979
|
+
this.sceneCasterMaterial = undefined;
|
|
10980
|
+
replayStaticDraws(list, this.sceneCasterSink);
|
|
10981
|
+
} finally {
|
|
10982
|
+
this.staticRecording = null;
|
|
10983
|
+
this.perDraw = perDraw;
|
|
10984
|
+
this.passRing = passRing;
|
|
10985
|
+
this.viewRing = viewRing;
|
|
10986
|
+
this.flatBindGroups = groups;
|
|
10987
|
+
this.blankAlbedoBindGroup = blank;
|
|
10988
|
+
this.bindGroup = bindGroup;
|
|
10989
|
+
this.passBlock.slot = passSlot;
|
|
10990
|
+
this.viewBlock.slot = viewSlot;
|
|
10991
|
+
this.lateViewBlock.slot = lateSlot;
|
|
10992
|
+
this.materials.dirty();
|
|
10993
|
+
this.sceneCasterMaterial = undefined;
|
|
10994
|
+
this.setMaterial(null);
|
|
10995
|
+
}
|
|
10996
|
+
recording.materialAsks = this.materialBudget.used - asked;
|
|
10997
|
+
const store = this.materialStore;
|
|
10998
|
+
recording.cacheVersion = recording.cache.version;
|
|
10999
|
+
recording.groupEpoch = this.flatGroupEpoch;
|
|
11000
|
+
recording.storeEpoch = store.epoch;
|
|
11001
|
+
recording.ringEpoch = list.ringEpoch;
|
|
11002
|
+
/* A skin split into halves is replayed draw by draw, so nothing recorded is kept or pinned. */
|
|
11003
|
+
if (recording.immediate) {
|
|
11004
|
+
resetPool(recording.frame);
|
|
11005
|
+
resetPool(recording.surface);
|
|
11006
|
+
return;
|
|
11007
|
+
}
|
|
11008
|
+
/* Every material the commands bind, pinned once, so nothing displaces what a bundle bakes. */
|
|
11009
|
+
for (const pool of [recording.frame, recording.surface]) {
|
|
11010
|
+
for (let i = 0; i < pool.taken; i++) {
|
|
11011
|
+
const offset = (pool.commands[i] as DrawCommand).offsetC;
|
|
11012
|
+
if (!recording.pins.includes(offset)) {
|
|
11013
|
+
store.pin(offset);
|
|
11014
|
+
recording.pins.push(offset);
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
list.pinEpoch = store.epoch;
|
|
11019
|
+
list.uploadDraws();
|
|
11020
|
+
this.staticJustRecorded = true;
|
|
11021
|
+
}
|
|
11022
|
+
|
|
11023
|
+
/** The recording under way, which `recordDraw` and the side passes take their commands into. */
|
|
11024
|
+
private staticRecording: StaticRecording | null = null;
|
|
11025
|
+
/** Whether the replay about to count its material changes recorded them, and so counted them. */
|
|
11026
|
+
private staticJustRecorded = false;
|
|
11027
|
+
|
|
11028
|
+
/** A disposed list drawn, said once: the frame loop does not throw. */
|
|
11029
|
+
private warnStaticDisposed(): void {
|
|
11030
|
+
if (this.warnedStaticDisposed) return;
|
|
11031
|
+
this.warnedStaticDisposed = true;
|
|
11032
|
+
console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
|
|
11033
|
+
}
|
|
11034
|
+
private warnedStaticDisposed = false;
|
|
11035
|
+
/** Bumped wherever the lit groups are dropped, which a static list's own groups follow. */
|
|
11036
|
+
private flatGroupEpoch = 0;
|
|
11037
|
+
|
|
10152
11038
|
drawShadowCasters(casters: ShadowCasters): void {
|
|
10153
11039
|
if (this.surface.lost || this.shadowPass === null) return;
|
|
10154
11040
|
casters(this.casterSink);
|
|
@@ -10494,7 +11380,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10494
11380
|
}
|
|
10495
11381
|
this.shadowDraws.flush();
|
|
10496
11382
|
this.scatterDepthDraws.flush();
|
|
10497
|
-
this.
|
|
11383
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
10498
11384
|
/* A growth's old texture, copied out of by what was just submitted and needed no longer. */
|
|
10499
11385
|
this.retiredSunTexture?.destroy();
|
|
10500
11386
|
this.retiredSunTexture = null;
|
|
@@ -10534,6 +11420,20 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10534
11420
|
private warnedNoPointShadowArray = false;
|
|
10535
11421
|
/** Said once, for the retired ambient dial. */
|
|
10536
11422
|
private warnedEnvironmentAmbient = false;
|
|
11423
|
+
/** The overlay the draws wear, `uOverlay`: see `setSurfaceOverlay`. */
|
|
11424
|
+
private readonly overlayFloats = new Float32Array(OVERLAY_FLOATS);
|
|
11425
|
+
/** Its atlas, bound where the refraction copy goes in a twin of each flat group. */
|
|
11426
|
+
private overlayMaps: GpuSurfaceTexture | null = null;
|
|
11427
|
+
/**
|
|
11428
|
+
* Each flat group's twin with the atlas in the refraction slot, by the group and the atlas, built
|
|
11429
|
+
* on the first draw that wears it and dropped wherever `flatBindGroups` is.
|
|
11430
|
+
*/
|
|
11431
|
+
private readonly overlayGroups = new Map<GPUBindGroup, Map<GpuSurfaceTexture, GPUBindGroup>>();
|
|
11432
|
+
/** True while a twin is built: what `flatTextures` hands the refraction slot then. */
|
|
11433
|
+
private overlayBinding = false;
|
|
11434
|
+
/** True between `bindSeeThrough` and `releaseSeeThrough`: that draw reads the copy, not the atlas. */
|
|
11435
|
+
private seeThroughBound = false;
|
|
11436
|
+
private warnedOverlayRefraction = false;
|
|
10537
11437
|
private readonly castingWorldIndices = new Int32Array(POINT_SHADOW_POOL).fill(-1);
|
|
10538
11438
|
private readonly resolvedPointShadows = createResolvedPointShadows(MAX_POINT_LIGHTS);
|
|
10539
11439
|
/** One face's view-projection, rebuilt per face rather than allocated. */
|
|
@@ -10770,7 +11670,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10770
11670
|
* its own slot.
|
|
10771
11671
|
*/
|
|
10772
11672
|
this.pointShadowArray?.flush();
|
|
10773
|
-
this.
|
|
11673
|
+
this.submit([this.shadowEncoder.finish()]);
|
|
10774
11674
|
this.shadowEncoder = null;
|
|
10775
11675
|
this.shadowPass = null;
|
|
10776
11676
|
this.gpuTimer.end();
|
|
@@ -10977,19 +11877,58 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10977
11877
|
this.bindGroupLayout,
|
|
10978
11878
|
this.variant,
|
|
10979
11879
|
this.perDraw.buffer,
|
|
10980
|
-
this.
|
|
11880
|
+
this.passRing.buffer,
|
|
11881
|
+
this.materialStore.buffer,
|
|
11882
|
+
this.viewRing.buffer,
|
|
10981
11883
|
this.flatTextures,
|
|
10982
11884
|
);
|
|
10983
11885
|
}
|
|
10984
11886
|
|
|
11887
|
+
/**
|
|
11888
|
+
* The open material's flat group, or its twin with the surface overlay's atlas in the refraction
|
|
11889
|
+
* slot where the draw wears one and does not refract. A twin is built from the live material,
|
|
11890
|
+
* which `bindGroup` always is, and kept by the group and the atlas.
|
|
11891
|
+
*
|
|
11892
|
+
* **A draw that refracts, or is glass, keeps the frame's copy** and its overlay reads no image —
|
|
11893
|
+
* the shader asks the same of `uSeeThrough` (`overlayImagesBound`) — said once.
|
|
11894
|
+
*/
|
|
11895
|
+
private overlayGroup(): GPUBindGroup {
|
|
11896
|
+
const maps = this.overlayMaps;
|
|
11897
|
+
if (maps === null) return this.bindGroup;
|
|
11898
|
+
if (this.seeThroughBound) {
|
|
11899
|
+
if (!this.warnedOverlayRefraction) {
|
|
11900
|
+
this.warnedOverlayRefraction = true;
|
|
11901
|
+
console.warn(
|
|
11902
|
+
'WebGPU: a draw that refracts or is glass wears a surface overlay with maps. The frame ' +
|
|
11903
|
+
"copy it reads takes the overlay's slot, so its rim, dissolve and wrinkles read no image.",
|
|
11904
|
+
);
|
|
11905
|
+
}
|
|
11906
|
+
return this.bindGroup;
|
|
11907
|
+
}
|
|
11908
|
+
let byMaps = this.overlayGroups.get(this.bindGroup);
|
|
11909
|
+
if (byMaps === undefined) {
|
|
11910
|
+
byMaps = new Map();
|
|
11911
|
+
this.overlayGroups.set(this.bindGroup, byMaps);
|
|
11912
|
+
}
|
|
11913
|
+
let twin = byMaps.get(maps);
|
|
11914
|
+
if (twin === undefined) {
|
|
11915
|
+
this.overlayBinding = true;
|
|
11916
|
+
twin = this.buildFlatBindGroup();
|
|
11917
|
+
this.overlayBinding = false;
|
|
11918
|
+
this.bindGroupBudget.ask();
|
|
11919
|
+
byMaps.set(maps, twin);
|
|
11920
|
+
}
|
|
11921
|
+
return twin;
|
|
11922
|
+
}
|
|
11923
|
+
|
|
10985
11924
|
/**
|
|
10986
11925
|
* The bind group a draw uses, skinned or not.
|
|
10987
11926
|
*
|
|
10988
|
-
* **The skinned twin is
|
|
10989
|
-
*
|
|
10990
|
-
*
|
|
10991
|
-
*
|
|
10992
|
-
* missed invalidation is not possible rather than merely unlikely, and it is why
|
|
11927
|
+
* **The skinned twin is kept by palette slot and by the unskinned group it was built beside**,
|
|
11928
|
+
* which is one a material, and compared against the views it holds — the palette's, which
|
|
11929
|
+
* changes only when the slot's texture grows, the morph deltas and the cloth's — rather than
|
|
11930
|
+
* against a flag somebody has to remember to clear at six call sites. Comparing object identity
|
|
11931
|
+
* means a missed invalidation is not possible rather than merely unlikely, and it is why
|
|
10993
11932
|
* `SkinPaletteTexture.view()` caches: a fresh view every call never compares equal and would
|
|
10994
11933
|
* rebuild this every draw.
|
|
10995
11934
|
*/
|
|
@@ -10998,39 +11937,53 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10998
11937
|
morphed: boolean,
|
|
10999
11938
|
deltas: GPUTextureView | null,
|
|
11000
11939
|
): GPUBindGroup {
|
|
11001
|
-
|
|
11940
|
+
const base = this.overlayGroup();
|
|
11941
|
+
if (!skinned && !morphed) return base;
|
|
11002
11942
|
const palette = skinned ? this.skinPalettes.view(this.skinPaletteSlot) : null;
|
|
11003
11943
|
/* Slot `k` caches at `k + 1`; index 0 is the morphed draw that is not skinned and has no
|
|
11004
11944
|
slot of its own. See `skinnedGroups`. */
|
|
11005
11945
|
const at = skinned ? this.skinPaletteSlot + 1 : 0;
|
|
11006
11946
|
/* A skinned group carries the cloth's views, or the stand-in's: see `clothViews`. */
|
|
11007
11947
|
const cloth = skinned ? this.clothViews() : null;
|
|
11948
|
+
let byMaterial = this.skinnedGroups[at];
|
|
11949
|
+
if (byMaterial === undefined) {
|
|
11950
|
+
byMaterial = new Map();
|
|
11951
|
+
this.skinnedGroups[at] = byMaterial;
|
|
11952
|
+
}
|
|
11953
|
+
let twin = byMaterial.get(base);
|
|
11008
11954
|
if (
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
this.skinnedGroupsClothBinding[at] !== (cloth?.binding ?? null)
|
|
11955
|
+
twin === undefined ||
|
|
11956
|
+
twin.palette !== palette ||
|
|
11957
|
+
twin.deltas !== deltas ||
|
|
11958
|
+
twin.cloth !== (cloth?.particles ?? null) ||
|
|
11959
|
+
twin.clothBinding !== (cloth?.binding ?? null)
|
|
11015
11960
|
) {
|
|
11016
|
-
|
|
11961
|
+
/* Built from the live material, with the overlay's atlas where `base` is its twin. */
|
|
11962
|
+
this.overlayBinding = base !== this.bindGroup;
|
|
11963
|
+
const group = createFlatBindGroup(
|
|
11017
11964
|
this.surface.device,
|
|
11018
11965
|
this.flatLayoutFor(skinned, morphed),
|
|
11019
11966
|
this.variant,
|
|
11020
11967
|
this.perDraw.buffer,
|
|
11021
|
-
this.
|
|
11968
|
+
this.passRing.buffer,
|
|
11969
|
+
this.materialStore.buffer,
|
|
11970
|
+
this.viewRing.buffer,
|
|
11022
11971
|
this.flatTextures,
|
|
11023
11972
|
palette,
|
|
11024
11973
|
deltas,
|
|
11025
11974
|
cloth,
|
|
11026
11975
|
);
|
|
11027
|
-
this.
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11976
|
+
this.overlayBinding = false;
|
|
11977
|
+
twin = {
|
|
11978
|
+
group,
|
|
11979
|
+
palette,
|
|
11980
|
+
deltas,
|
|
11981
|
+
cloth: cloth?.particles ?? null,
|
|
11982
|
+
clothBinding: cloth?.binding ?? null,
|
|
11983
|
+
};
|
|
11984
|
+
byMaterial.set(base, twin);
|
|
11032
11985
|
}
|
|
11033
|
-
return
|
|
11986
|
+
return twin.group;
|
|
11034
11987
|
}
|
|
11035
11988
|
|
|
11036
11989
|
/** The layout matching a pair of vertex flags. A pipeline and its bind group must agree. */
|
|
@@ -11068,8 +12021,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11068
12021
|
if (this.drawBudget.dropped > 0) {
|
|
11069
12022
|
grew = this.perDraw.growTo(roomFor(this.drawBudget.used)) || grew;
|
|
11070
12023
|
}
|
|
11071
|
-
if (this.
|
|
11072
|
-
grew = this.
|
|
12024
|
+
if (this.materialsRefused > 0) {
|
|
12025
|
+
grew = this.materialStore.growTo(roomFor(this.materialBudget.used)) || grew;
|
|
12026
|
+
}
|
|
12027
|
+
if (this.passesRefused > 0) {
|
|
12028
|
+
grew = this.passRing.growTo(roomFor(this.passesAsked)) || grew;
|
|
12029
|
+
}
|
|
12030
|
+
if (this.viewsRefused > 0) {
|
|
12031
|
+
grew = this.viewRing.growTo(roomFor(this.viewsAsked)) || grew;
|
|
11073
12032
|
}
|
|
11074
12033
|
if (grew) this.rebuildFlatGroupsForNewRings();
|
|
11075
12034
|
}
|
|
@@ -11090,8 +12049,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11090
12049
|
*/
|
|
11091
12050
|
private rebuildFlatGroupsForNewRings(): void {
|
|
11092
12051
|
this.flatBindGroups.clear();
|
|
11093
|
-
this.
|
|
12052
|
+
this.flatGroupEpoch += 1;
|
|
12053
|
+
this.overlayGroups.clear();
|
|
11094
12054
|
this.skinnedGroups.length = 0;
|
|
12055
|
+
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
11095
12056
|
|
|
11096
12057
|
const albedo = this.albedo;
|
|
11097
12058
|
const normalMap = this.normalMap;
|
|
@@ -11119,6 +12080,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11119
12080
|
);
|
|
11120
12081
|
/* The open material lives in a slot of a buffer that no longer exists. */
|
|
11121
12082
|
this.materials.dirty();
|
|
12083
|
+
this.reopenPass();
|
|
11122
12084
|
}
|
|
11123
12085
|
|
|
11124
12086
|
/**
|
|
@@ -11153,6 +12115,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11153
12115
|
*/
|
|
11154
12116
|
private rebuildFlatBindGroup(): void {
|
|
11155
12117
|
this.flatBindGroups.clear();
|
|
12118
|
+
this.flatGroupEpoch += 1;
|
|
12119
|
+
this.overlayGroups.clear();
|
|
12120
|
+
this.skinnedGroups.length = 0;
|
|
11156
12121
|
this.flatEnvironment = this.flatTextures('uEnvironment').view;
|
|
11157
12122
|
if (
|
|
11158
12123
|
this.albedo === null &&
|
|
@@ -11394,9 +12359,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11394
12359
|
? (OUTPUT_TRANSFORM_CODE[this.quality.outputTransform] ?? 0)
|
|
11395
12360
|
: 0;
|
|
11396
12361
|
f[at('uOutputExposure')] = graded ? this.exposure : 1;
|
|
12362
|
+
f.set(this.filmA, at('uFilmA'));
|
|
12363
|
+
f.set(this.filmB, at('uFilmB'));
|
|
12364
|
+
f[at('uDisplayHeadroom')] = this.headroom;
|
|
11397
12365
|
f.set(this.reprojection, at('uReprojection'));
|
|
11398
12366
|
f[at('uMotionStrength')] = motionStrength;
|
|
11399
|
-
f[at('uMotionMax')] =
|
|
12367
|
+
f[at('uMotionMax')] = this.motionBlurMax;
|
|
12368
|
+
/* Each mover's own motion, where the reconstruction drew its target this frame. */
|
|
12369
|
+
f[at('uObjectMotion')] = this.reconstructing && this.reconMotionView !== null ? 1 : 0;
|
|
11400
12370
|
f[at('uAoStrength')] = this.frameProjection === null ? 0 : aoStrength;
|
|
11401
12371
|
/*
|
|
11402
12372
|
* The occlusion read where this frame's jitter put it (`uAoOffset`): measured from the jittered
|
|
@@ -11440,6 +12410,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11440
12410
|
f[at('uGradeSize')] = this.gradeSize;
|
|
11441
12411
|
/* The lens and the print. See `setVignette` and `setFilmGrain`. */
|
|
11442
12412
|
f[at('uVignette')] = this.vignette;
|
|
12413
|
+
f.set(this.fringe, at('uFringe'));
|
|
11443
12414
|
f[at('uAutoExposure')] = adapting ? this.autoExposure : 0;
|
|
11444
12415
|
f[at('uLocalExposure')] = measuring && localising ? this.localExposure : 0;
|
|
11445
12416
|
f[at('uGrain')] = this.grain;
|
|
@@ -11485,7 +12456,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11485
12456
|
private rebuildRushBindGroup(): void {
|
|
11486
12457
|
if (this.sceneColorView === null || this.resolvedDepthView === null) return;
|
|
11487
12458
|
if (this.aoTargetView === null) return;
|
|
11488
|
-
this.rushBindGroup = this.buildRushBindGroup(
|
|
12459
|
+
this.rushBindGroup = this.buildRushBindGroup(
|
|
12460
|
+
'post.rushBindGroup',
|
|
12461
|
+
this.sceneColorView,
|
|
12462
|
+
this.sceneColorView,
|
|
12463
|
+
);
|
|
11489
12464
|
/*
|
|
11490
12465
|
* **And the reconstruction's twin, from the same three call sites.** A colour grade replaces
|
|
11491
12466
|
* the lookup table and a resize replaces the depth and the occlusion target, and both groups
|
|
@@ -11493,12 +12468,21 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11493
12468
|
* come to read a stale view on exactly the frames a consumer changed something.
|
|
11494
12469
|
*/
|
|
11495
12470
|
if (this.reconShownView !== null) {
|
|
11496
|
-
this.reconRushGroup = this.buildRushBindGroup(
|
|
12471
|
+
this.reconRushGroup = this.buildRushBindGroup(
|
|
12472
|
+
'recon.rushBindGroup',
|
|
12473
|
+
this.reconShownView,
|
|
12474
|
+
this.reconMotionView ?? this.reconShownView,
|
|
12475
|
+
);
|
|
11497
12476
|
}
|
|
11498
12477
|
}
|
|
11499
12478
|
|
|
11500
12479
|
/** The composite's group over one scene source. See `rebuildRushBindGroup`. */
|
|
11501
|
-
private buildRushBindGroup(
|
|
12480
|
+
private buildRushBindGroup(
|
|
12481
|
+
label: string,
|
|
12482
|
+
sceneView: GPUTextureView,
|
|
12483
|
+
/** Each mover's own motion, or a stand-in the shader is told not to read. */
|
|
12484
|
+
motionView: GPUTextureView,
|
|
12485
|
+
): GPUBindGroup | null {
|
|
11502
12486
|
const { device } = this.surface;
|
|
11503
12487
|
if (this.resolvedDepthView === null || this.aoTargetView === null) return null;
|
|
11504
12488
|
const entry = (
|
|
@@ -11530,6 +12514,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11530
12514
|
and a declared binding still needs a complete texture, as the bloom fallback says. */
|
|
11531
12515
|
...entry(RUSH_TEXTURES.uExposureHeld, this.exposurePass?.heldView ?? sceneView),
|
|
11532
12516
|
...entry(RUSH_TEXTURES.uExposureLocal, this.exposurePass?.localView ?? sceneView),
|
|
12517
|
+
...entry(RUSH_TEXTURES.uMotion, motionView),
|
|
11533
12518
|
],
|
|
11534
12519
|
layout: this.rushLayout,
|
|
11535
12520
|
});
|
|
@@ -11813,7 +12798,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11813
12798
|
);
|
|
11814
12799
|
}
|
|
11815
12800
|
/* The composite's own group, over the resolved picture instead of the scene target. */
|
|
11816
|
-
this.reconRushGroup = this.buildRushBindGroup(
|
|
12801
|
+
this.reconRushGroup = this.buildRushBindGroup(
|
|
12802
|
+
'recon.rushBindGroup',
|
|
12803
|
+
this.reconShownView,
|
|
12804
|
+
this.reconMotionView ?? this.reconShownView,
|
|
12805
|
+
);
|
|
11817
12806
|
/* And the scene's, because `ensureComposite` built it before this one existed. */
|
|
11818
12807
|
this.rebuildRushBindGroup();
|
|
11819
12808
|
/* And the bloom's, which blooms the resolved picture: see `rebuildBloomGroups`. */
|
|
@@ -11927,7 +12916,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11927
12916
|
const depth = this.depthView;
|
|
11928
12917
|
if (!this.reconstructing || late === null || shown === null || depth === null) return;
|
|
11929
12918
|
const oitSet = this.oitActive && this.translucentQueue.length > 0;
|
|
11930
|
-
if (late.pending === 0 && !oitSet) return;
|
|
12919
|
+
if (late.pending === 0 && !oitSet && this.latePasses.length === 0) return;
|
|
11931
12920
|
const width = Math.max(1, this.surface.canvas.width);
|
|
11932
12921
|
const height = Math.max(1, this.surface.canvas.height);
|
|
11933
12922
|
/* The order-independent set tests against this depth too, so it is made for either. */
|
|
@@ -11951,20 +12940,64 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11951
12940
|
this.refractSnapshotTaken = true;
|
|
11952
12941
|
}
|
|
11953
12942
|
this.lateRefractWanted = false;
|
|
11954
|
-
if (late.pending
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
12943
|
+
if (late.pending > 0) {
|
|
12944
|
+
const pass = encoder.beginRenderPass({
|
|
12945
|
+
label: 'recon.late',
|
|
12946
|
+
timestampWrites: this.gpuTimer.writesFor(),
|
|
12947
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
12948
|
+
depthStencilAttachment: {
|
|
12949
|
+
view: late.depthView(width, height),
|
|
12950
|
+
depthLoadOp: 'load',
|
|
12951
|
+
/* Kept: the order-independent set replays against it after this pass. */
|
|
12952
|
+
depthStoreOp: 'store',
|
|
12953
|
+
},
|
|
12954
|
+
});
|
|
12955
|
+
late.replay(pass, this.issueBound);
|
|
12956
|
+
pass.end();
|
|
12957
|
+
}
|
|
12958
|
+
this.runLatePasses(encoder, shown, late.depthView(width, height));
|
|
12959
|
+
}
|
|
12960
|
+
|
|
12961
|
+
/**
|
|
12962
|
+
* The registered passes `drawPass` queued for after the upscale: each handed a fresh copy of the
|
|
12963
|
+
* reconstructed picture with every late draw in it, in the output-size texture the late
|
|
12964
|
+
* refraction keeps (its own reads are done by now: they were recorded before this copy), then
|
|
12965
|
+
* drawn over the picture in a pass of its own.
|
|
12966
|
+
*/
|
|
12967
|
+
private runLatePasses(
|
|
12968
|
+
encoder: GPUCommandEncoder,
|
|
12969
|
+
shown: GPUTextureView,
|
|
12970
|
+
depth: GPUTextureView,
|
|
12971
|
+
): void {
|
|
12972
|
+
const copy = this.refractSnapshot;
|
|
12973
|
+
const source = this.reconShown;
|
|
12974
|
+
for (const handle of this.latePasses) {
|
|
12975
|
+
const definition = passAt(this.passes, handle);
|
|
12976
|
+
if (definition === undefined) continue;
|
|
12977
|
+
if (copy !== null && source !== null) {
|
|
12978
|
+
encoder.copyTextureToTexture({ texture: source }, { texture: copy }, [
|
|
12979
|
+
copy.width,
|
|
12980
|
+
copy.height,
|
|
12981
|
+
]);
|
|
12982
|
+
}
|
|
12983
|
+
const pass = encoder.beginRenderPass({
|
|
12984
|
+
label: 'recon.latePass',
|
|
12985
|
+
timestampWrites: this.gpuTimer.writesFor(),
|
|
12986
|
+
colorAttachments: [{ view: shown, loadOp: 'load', storeOp: 'store' }],
|
|
12987
|
+
depthStencilAttachment: { view: depth, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
12988
|
+
});
|
|
12989
|
+
const context = this.passContext;
|
|
12990
|
+
context.pass = pass;
|
|
12991
|
+
context.outputTransform = this.gradeCode();
|
|
12992
|
+
context.outputExposure = this.gradeExposure();
|
|
12993
|
+
context.jitter = this.latePassJitter;
|
|
12994
|
+
context.format = RECON_HISTORY_FORMAT;
|
|
12995
|
+
context.depthFormat = DEPTH_FORMAT;
|
|
12996
|
+
context.samples = 1;
|
|
12997
|
+
definition.draw(context as PassContext);
|
|
12998
|
+
context.jitter = this.passJitter;
|
|
12999
|
+
pass.end();
|
|
13000
|
+
}
|
|
11968
13001
|
}
|
|
11969
13002
|
|
|
11970
13003
|
/** Whether this draw lands in the reconstructed picture. See `lateRouting.ts`. */
|
|
@@ -12391,7 +13424,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12391
13424
|
* other is the disagreement the parity rule exists to prevent. Said once rather than dropped.
|
|
12392
13425
|
*/
|
|
12393
13426
|
private runReflections(encoder: GPUCommandEncoder): void {
|
|
12394
|
-
|
|
13427
|
+
const frameReflections = this.frameReflections;
|
|
13428
|
+
const surfaceHalf = this.reflectionSurface;
|
|
13429
|
+
const materials = frameReflections !== null && surfaceHalf !== null;
|
|
13430
|
+
if (this.reflectionQueue.length === 0 && !materials) return;
|
|
12395
13431
|
const scene = this.sceneColorView;
|
|
12396
13432
|
const reflection = this.ssrReflectionView;
|
|
12397
13433
|
const group = this.ssrBindGroup;
|
|
@@ -12436,9 +13472,81 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12436
13472
|
f[base + at('uSsrThickness')] = surface.thicknessM;
|
|
12437
13473
|
f[base + at('uSsrSteps')] = surface.steps;
|
|
12438
13474
|
f[base + at('uSsrEdgeFade')] = REFLECTION_EDGE_FADE;
|
|
13475
|
+
f[base + at('uSsrMaterial')] = 0;
|
|
12439
13476
|
slot++;
|
|
12440
13477
|
});
|
|
13478
|
+
const regions = slot;
|
|
13479
|
+
/* The frame's materials' trace takes the slot after the surfaces': the box, its axis and its
|
|
13480
|
+
strength unread, the march and the ceiling its own. See `frameReflections.ts`. */
|
|
13481
|
+
if (materials) {
|
|
13482
|
+
const base = (slot * SSR_SLOT) / 4;
|
|
13483
|
+
f.set(this.ssrDepthToWorld, base + at('uSsrDepthToWorld'));
|
|
13484
|
+
f.set(this.ssrViewProj, base + at('uSsrViewProj'));
|
|
13485
|
+
f.set(this.frameEye, base + at('uSsrEye'));
|
|
13486
|
+
f.fill(0, base + at('uSsrAxis'), base + at('uSsrAxis') + 3);
|
|
13487
|
+
f.fill(1, base + at('uSsrTint'), base + at('uSsrTint') + 3);
|
|
13488
|
+
f[base + at('uSsrStrength')] = 1;
|
|
13489
|
+
f[base + at('uSsrFresnel')] = 0;
|
|
13490
|
+
f[base + at('uSsrFacingCos')] = -1;
|
|
13491
|
+
f[base + at('uSsrReach')] = frameReflections.reachM;
|
|
13492
|
+
f[base + at('uSsrThickness')] = frameReflections.thicknessM;
|
|
13493
|
+
f[base + at('uSsrSteps')] = frameReflections.steps;
|
|
13494
|
+
f[base + at('uSsrEdgeFade')] = REFLECTION_EDGE_FADE;
|
|
13495
|
+
f[base + at('uSsrMaterial')] = 1;
|
|
13496
|
+
f[base + at('uSsrMaxRoughness')] = frameReflections.maxRoughness;
|
|
13497
|
+
slot++;
|
|
13498
|
+
}
|
|
12441
13499
|
device.queue.writeBuffer(this.ssrUniforms, 0, this.ssrStaging, 0, slot * SSR_SLOT);
|
|
13500
|
+
const tracePipeline = postPipeline(
|
|
13501
|
+
this.pipelines,
|
|
13502
|
+
device,
|
|
13503
|
+
this.ssrLayout,
|
|
13504
|
+
'post.ssrTrace',
|
|
13505
|
+
SSR_TRACE_FRAG_WGSL,
|
|
13506
|
+
this.pipelines.format,
|
|
13507
|
+
PREMULTIPLIED_OVER_BLEND,
|
|
13508
|
+
);
|
|
13509
|
+
|
|
13510
|
+
if (materials && this.depthView !== null && this.ssrMaterialGroup !== null) {
|
|
13511
|
+
/* The frame's opaque lit draws again, into what the trace and the resolve read. */
|
|
13512
|
+
surfaceHalf.draw(encoder, this.depthView, this.issueBound);
|
|
13513
|
+
const trace = encoder.beginRenderPass({
|
|
13514
|
+
label: 'post.ssr.materialTrace',
|
|
13515
|
+
colorAttachments: [
|
|
13516
|
+
{ view: reflection, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
13517
|
+
],
|
|
13518
|
+
});
|
|
13519
|
+
trace.setPipeline(tracePipeline);
|
|
13520
|
+
trace.setBindGroup(0, group, [regions * SSR_SLOT]);
|
|
13521
|
+
trace.draw(3);
|
|
13522
|
+
trace.end();
|
|
13523
|
+
/* The blur's texel and its radius at roughness 1, as a share of the frame's height. */
|
|
13524
|
+
const m = this.ssrMaterialFloats;
|
|
13525
|
+
const blurAt = this.postField(SSR_MATERIAL_FIELDS, 'uSsrBlur');
|
|
13526
|
+
m[blurAt] = 1 / Math.max(width, 1);
|
|
13527
|
+
m[blurAt + 1] = 1 / Math.max(height, 1);
|
|
13528
|
+
m[blurAt + 2] = frameReflections.blur * height;
|
|
13529
|
+
device.queue.writeBuffer(this.ssrMaterialUniforms, 0, m);
|
|
13530
|
+
const resolve = encoder.beginRenderPass({
|
|
13531
|
+
label: 'post.ssr.materialResolve',
|
|
13532
|
+
colorAttachments: [{ view: scene, loadOp: 'load', storeOp: 'store' }],
|
|
13533
|
+
});
|
|
13534
|
+
resolve.setPipeline(
|
|
13535
|
+
postPipeline(
|
|
13536
|
+
this.pipelines,
|
|
13537
|
+
device,
|
|
13538
|
+
this.ssrMaterialLayout,
|
|
13539
|
+
'post.ssrMaterialResolve',
|
|
13540
|
+
SSR_MATERIAL_RESOLVE_FRAG_WGSL,
|
|
13541
|
+
this.pipelines.format,
|
|
13542
|
+
FRAME_REFLECTION_BLEND,
|
|
13543
|
+
),
|
|
13544
|
+
);
|
|
13545
|
+
resolve.setBindGroup(0, this.ssrMaterialGroup, [0]);
|
|
13546
|
+
resolve.draw(3);
|
|
13547
|
+
resolve.end();
|
|
13548
|
+
}
|
|
13549
|
+
if (regions === 0) return;
|
|
12442
13550
|
|
|
12443
13551
|
/* Cleared whole and then scissored per surface: the resolve reads every pixel, and one left
|
|
12444
13552
|
from the last frame would composite that reflection over this frame's scene. */
|
|
@@ -12448,17 +13556,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12448
13556
|
{ view: reflection, loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 0] },
|
|
12449
13557
|
],
|
|
12450
13558
|
});
|
|
12451
|
-
trace.setPipeline(
|
|
12452
|
-
postPipeline(
|
|
12453
|
-
this.pipelines,
|
|
12454
|
-
device,
|
|
12455
|
-
this.ssrLayout,
|
|
12456
|
-
'post.ssrTrace',
|
|
12457
|
-
SSR_TRACE_FRAG_WGSL,
|
|
12458
|
-
this.pipelines.format,
|
|
12459
|
-
PREMULTIPLIED_OVER_BLEND,
|
|
12460
|
-
),
|
|
12461
|
-
);
|
|
13559
|
+
trace.setPipeline(tracePipeline);
|
|
12462
13560
|
slot = 0;
|
|
12463
13561
|
this.reflectionQueue.replay((surface) => {
|
|
12464
13562
|
const index = slot++;
|
|
@@ -13222,6 +14320,101 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13222
14320
|
);
|
|
13223
14321
|
}
|
|
13224
14322
|
|
|
14323
|
+
/** Whether a draw now is the frame's own, and so has a surface half to keep. */
|
|
14324
|
+
private reflectionSurfaceKeeps(): boolean {
|
|
14325
|
+
return (
|
|
14326
|
+
this.reflectionSurface !== null &&
|
|
14327
|
+
!this.reflectionPassActive &&
|
|
14328
|
+
!this.probePassActive &&
|
|
14329
|
+
!this.capturePassActive &&
|
|
14330
|
+
!this.framePresented &&
|
|
14331
|
+
!this.overlayActive &&
|
|
14332
|
+
this.oitMode === 'none'
|
|
14333
|
+
);
|
|
14334
|
+
}
|
|
14335
|
+
|
|
14336
|
+
/**
|
|
14337
|
+
* An opaque lit draw's surface half, kept for the frame's materials' reflections: the draw again,
|
|
14338
|
+
* with the same slots, buffers and bind group, through the pipeline with `REFLECTION_SURFACE` on —
|
|
14339
|
+
* which writes what the pixel reflects of its environment into the two targets
|
|
14340
|
+
* `reflectionSurfacePass.ts` holds and tests for the depth its frame draw wrote. Replayed when the
|
|
14341
|
+
* frame's pass has ended, before the trace reads them. See `shaders/flat/reflectionSurface.ts`.
|
|
14342
|
+
*/
|
|
14343
|
+
private keepReflectionSurface(
|
|
14344
|
+
mesh: GpuMesh,
|
|
14345
|
+
frameKey: string,
|
|
14346
|
+
base: string,
|
|
14347
|
+
skinned: boolean,
|
|
14348
|
+
morphed: boolean,
|
|
14349
|
+
deltas: GPUTextureView | null,
|
|
14350
|
+
layer: number,
|
|
14351
|
+
eight: boolean,
|
|
14352
|
+
clothed: boolean,
|
|
14353
|
+
modelKind: SurfaceModelKind | null,
|
|
14354
|
+
slot: number,
|
|
14355
|
+
material: number,
|
|
14356
|
+
instanceBuffer: GPUBuffer | null,
|
|
14357
|
+
instances: number,
|
|
14358
|
+
indirect: GPUBuffer | null,
|
|
14359
|
+
/** A batch playing a bone animation, whose surface half moves as its draw does. */
|
|
14360
|
+
animated: GpuInstancedBatch | null = null,
|
|
14361
|
+
): void {
|
|
14362
|
+
const surface = this.reflectionSurface;
|
|
14363
|
+
const present = this.meshPresent.get(base);
|
|
14364
|
+
if (surface === null || present === undefined) return;
|
|
14365
|
+
const animation = animated === null ? null : this.animationViews(animated);
|
|
14366
|
+
const key = `${frameKey}|rs`;
|
|
14367
|
+
const pipeline =
|
|
14368
|
+
this.pipelines.peek(key) ??
|
|
14369
|
+
flatPipeline(
|
|
14370
|
+
this.pipelines,
|
|
14371
|
+
this.surface.device,
|
|
14372
|
+
animation === null ? this.flatLayoutFor(skinned, morphed) : this.animatedBindGroupLayout,
|
|
14373
|
+
this.variant,
|
|
14374
|
+
key,
|
|
14375
|
+
present,
|
|
14376
|
+
false,
|
|
14377
|
+
skinned,
|
|
14378
|
+
morphed,
|
|
14379
|
+
true,
|
|
14380
|
+
layer,
|
|
14381
|
+
instanceBuffer !== null,
|
|
14382
|
+
'none',
|
|
14383
|
+
this.materialDoubleSided,
|
|
14384
|
+
false,
|
|
14385
|
+
eight,
|
|
14386
|
+
clothed,
|
|
14387
|
+
modelKind,
|
|
14388
|
+
'whole',
|
|
14389
|
+
true,
|
|
14390
|
+
animation !== null,
|
|
14391
|
+
);
|
|
14392
|
+
/* A static list's surface halves go into its own pool, beside its draws. */
|
|
14393
|
+
const command =
|
|
14394
|
+
this.staticRecording === null ? surface.take() : takeRecorded(this.staticRecording.surface);
|
|
14395
|
+
command.pipeline = pipeline;
|
|
14396
|
+
command.bindGroup =
|
|
14397
|
+
animated === null || animation === null
|
|
14398
|
+
? this.flatBindGroupFor(skinned, morphed, deltas)
|
|
14399
|
+
: this.animatedGroup(animated, animation);
|
|
14400
|
+
command.offsetA = slot;
|
|
14401
|
+
command.offsetB = this.passOffset;
|
|
14402
|
+
command.offsetC = material;
|
|
14403
|
+
command.offsetD = this.drawViewOffset;
|
|
14404
|
+
command.offsetCount = 4;
|
|
14405
|
+
let index = 0;
|
|
14406
|
+
for (; index < mesh.vertexBuffers.length; index++) {
|
|
14407
|
+
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
14408
|
+
}
|
|
14409
|
+
if (instanceBuffer !== null) command.vertexBuffers[index++] = instanceBuffer;
|
|
14410
|
+
command.vertexCount = index;
|
|
14411
|
+
command.indexBuffer = mesh.indexBuffer;
|
|
14412
|
+
command.indexed = true;
|
|
14413
|
+
command.count = mesh.indexCount;
|
|
14414
|
+
command.instances = instances;
|
|
14415
|
+
command.indirect = indirect;
|
|
14416
|
+
}
|
|
14417
|
+
|
|
13225
14418
|
/**
|
|
13226
14419
|
* A skin draw's diffuse and colour halves, kept for the spread: the frame's draw again, twice,
|
|
13227
14420
|
* with the same slots, buffers and bind group, through the pipelines that write the diffuse's
|
|
@@ -13241,6 +14434,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13241
14434
|
slot: number,
|
|
13242
14435
|
material: number,
|
|
13243
14436
|
): void {
|
|
14437
|
+
/* A skin's halves are not bundled: a static list holding one replays draw by draw. */
|
|
14438
|
+
if (this.staticRecording !== null) {
|
|
14439
|
+
this.staticRecording.immediate = true;
|
|
14440
|
+
return;
|
|
14441
|
+
}
|
|
13244
14442
|
const skin = this.skinScatter;
|
|
13245
14443
|
const pipelines = this.skinPipelines;
|
|
13246
14444
|
const present = this.meshPresent.get(base);
|
|
@@ -13276,8 +14474,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13276
14474
|
command.pipeline = pipeline;
|
|
13277
14475
|
command.bindGroup = bindGroup;
|
|
13278
14476
|
command.offsetA = slot;
|
|
13279
|
-
command.offsetB =
|
|
13280
|
-
command.
|
|
14477
|
+
command.offsetB = this.passOffset;
|
|
14478
|
+
command.offsetC = material;
|
|
14479
|
+
command.offsetD = this.drawViewOffset;
|
|
14480
|
+
command.offsetCount = 4;
|
|
13281
14481
|
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
13282
14482
|
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
13283
14483
|
}
|
|
@@ -13301,11 +14501,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13301
14501
|
private runSkinScatter(): void {
|
|
13302
14502
|
const skin = this.skinScatter;
|
|
13303
14503
|
if (skin === null || this.skinScattered) return;
|
|
14504
|
+
/*
|
|
14505
|
+
* **Not the frame's picture, so not the frame's moment**: a blended draw in a mirror, a probe
|
|
14506
|
+
* bake or a scene capture lands over none of the frame's skin. Latching there switched the
|
|
14507
|
+
* spread off for the frame, so a capture with a glowing sign in it left every face drawn after
|
|
14508
|
+
* it flat. `scripts/capture-check.mjs`.
|
|
14509
|
+
*/
|
|
14510
|
+
if (this.reflectionPassActive || this.probePassActive || this.capturePassActive) return;
|
|
13304
14511
|
/* Latched whether or not skin was kept: a skin drawn after this point is the whole surface. */
|
|
13305
14512
|
this.skinScattered = true;
|
|
13306
14513
|
if (!skin.pending) return;
|
|
13307
|
-
if (this.
|
|
13308
|
-
if (this.capturePassActive) return;
|
|
14514
|
+
if (this.oitReplaying) return;
|
|
13309
14515
|
const encoder = this.encoder;
|
|
13310
14516
|
const depth = this.depthView;
|
|
13311
14517
|
const resolved = this.resolvedDepthView;
|
|
@@ -13430,6 +14636,65 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13430
14636
|
return true;
|
|
13431
14637
|
}
|
|
13432
14638
|
|
|
14639
|
+
/** The colour copy a registered pass reads, at the scene's size and format. See `passColour`. */
|
|
14640
|
+
private allocatePassColour(): void {
|
|
14641
|
+
const source = this.sceneColor;
|
|
14642
|
+
if (source === null || this.passColour !== null) return;
|
|
14643
|
+
this.passColour = this.surface.device.createTexture({
|
|
14644
|
+
label: 'pass.sceneColor',
|
|
14645
|
+
size: [source.width, source.height],
|
|
14646
|
+
format: this.pipelines.format,
|
|
14647
|
+
usage: 0x4 | 0x2, // TEXTURE_BINDING | COPY_DST
|
|
14648
|
+
});
|
|
14649
|
+
this.passColourView = this.passColour.createView();
|
|
14650
|
+
}
|
|
14651
|
+
|
|
14652
|
+
/**
|
|
14653
|
+
* The frame's colour so far, into `passColour`, for a registered pass that reads it: the
|
|
14654
|
+
* boundary `takeRefractSnapshot` describes — flush, end the pass, copy what its end resolved,
|
|
14655
|
+
* reopen with `load` — and refused where that one is, in a mirror, a probe bake or a capture,
|
|
14656
|
+
* whose pictures are not the frame's. **Not latched**, unlike refraction's: each reading pass
|
|
14657
|
+
* sees the passes drawn before it, at a full-frame copy each.
|
|
14658
|
+
*/
|
|
14659
|
+
private takePassColour(): void {
|
|
14660
|
+
if (this.reflectionPassActive || this.probePassActive || this.capturePassActive) return;
|
|
14661
|
+
const copy = this.passColour;
|
|
14662
|
+
const source = this.sceneColor;
|
|
14663
|
+
const encoder = this.encoder;
|
|
14664
|
+
if (copy === null || source === null || encoder === null || this.pass === null) return;
|
|
14665
|
+
if (this.quality.frameGraph) this.flushGraph();
|
|
14666
|
+
this.pass.end();
|
|
14667
|
+
encoder.copyTextureToTexture({ texture: source }, { texture: copy }, [copy.width, copy.height]);
|
|
14668
|
+
this.pass = this.reopenFramePass('pass.colourTaken');
|
|
14669
|
+
}
|
|
14670
|
+
|
|
14671
|
+
/**
|
|
14672
|
+
* The frame's pass again after a copy between passes, loading everything already drawn: the same
|
|
14673
|
+
* attachments the copy's caller ended, with nothing cleared.
|
|
14674
|
+
*/
|
|
14675
|
+
private reopenFramePass(label: string): GPURenderPassEncoder | null {
|
|
14676
|
+
const encoder = this.encoder;
|
|
14677
|
+
const target = this.compositeTarget();
|
|
14678
|
+
if (encoder === null || target === null) return null;
|
|
14679
|
+
const multisampled = this.compositeMsaa() !== null;
|
|
14680
|
+
return encoder.beginRenderPass({
|
|
14681
|
+
label,
|
|
14682
|
+
timestampWrites: this.gpuTimer.writesFor(),
|
|
14683
|
+
colorAttachments: [
|
|
14684
|
+
{
|
|
14685
|
+
view: this.compositeMsaa() ?? target,
|
|
14686
|
+
resolveTarget: multisampled ? target : undefined,
|
|
14687
|
+
loadOp: 'load',
|
|
14688
|
+
storeOp: resolvedStoreOp(multisampled, false, this.quality.discardResolvedAttachments),
|
|
14689
|
+
},
|
|
14690
|
+
],
|
|
14691
|
+
depthStencilAttachment:
|
|
14692
|
+
this.depthView === null
|
|
14693
|
+
? undefined
|
|
14694
|
+
: { view: this.depthView, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
14695
|
+
});
|
|
14696
|
+
}
|
|
14697
|
+
|
|
13433
14698
|
/**
|
|
13434
14699
|
* The pass to record into, reopening one on the canvas if the frame has already gone.
|
|
13435
14700
|
*
|
|
@@ -13621,6 +14886,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13621
14886
|
/* And a march reads the same copy, for the same reason and with the same failure if it is
|
|
13622
14887
|
discarded: a depth of zero is the far plane reversed, so every ray finds nothing. */
|
|
13623
14888
|
this.reflectionQueue.length > 0 ||
|
|
14889
|
+
/* And the frame's materials' reflections, whose surface half tests against the depth too. */
|
|
14890
|
+
this.reflectionSurface !== null ||
|
|
13624
14891
|
/* And the medium's own march, which stops where the frame's depth says a surface is. Left
|
|
13625
14892
|
out of this, every ray runs to `maxDistance` through the walls and the fog is drawn on
|
|
13626
14893
|
top of the room rather than in it. */
|
|
@@ -13656,6 +14923,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13656
14923
|
* site reads as one `if` either way.
|
|
13657
14924
|
*/
|
|
13658
14925
|
private recordDraw(reads: number, writes: number): DrawCommand | null {
|
|
14926
|
+
/* A static list recording takes its draws into its own pool, never the frame graph's. */
|
|
14927
|
+
if (this.staticRecording !== null) return takeRecorded(this.staticRecording.frame);
|
|
13659
14928
|
/* The order-independent replay owns its pass and issues directly into it; a recorded command
|
|
13660
14929
|
would be replayed into the frame's passes instead, which is not where these belong. */
|
|
13661
14930
|
if (this.oitEncoder !== null) return null;
|
|
@@ -13698,9 +14967,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13698
14967
|
|
|
13699
14968
|
/** One recorded draw, issued into an open pass. Shared by the graph's replay and `runLate`. */
|
|
13700
14969
|
private issueCommand(pass: GPURenderPassEncoder, command: DrawCommand): void {
|
|
14970
|
+
/* A static list, recorded once: one call, and the pass's bindings are its to set again. */
|
|
14971
|
+
if (command.bundles !== null) {
|
|
14972
|
+
pass.executeBundles(command.bundles);
|
|
14973
|
+
return;
|
|
14974
|
+
}
|
|
13701
14975
|
if (command.pipeline === null || command.bindGroup === null) return;
|
|
13702
14976
|
pass.setPipeline(command.pipeline);
|
|
13703
|
-
if (command.offsetCount ===
|
|
14977
|
+
if (command.offsetCount === 4) {
|
|
14978
|
+
pass.setBindGroup(0, command.bindGroup, [
|
|
14979
|
+
command.offsetA,
|
|
14980
|
+
command.offsetB,
|
|
14981
|
+
command.offsetC,
|
|
14982
|
+
command.offsetD,
|
|
14983
|
+
]);
|
|
14984
|
+
} else if (command.offsetCount === 3) {
|
|
14985
|
+
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB, command.offsetC]);
|
|
14986
|
+
} else if (command.offsetCount === 2) {
|
|
13704
14987
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA, command.offsetB]);
|
|
13705
14988
|
} else if (command.offsetCount === 1) {
|
|
13706
14989
|
pass.setBindGroup(0, command.bindGroup, [command.offsetA]);
|
|
@@ -13962,7 +15245,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13962
15245
|
this.flushRings();
|
|
13963
15246
|
this.pass?.end();
|
|
13964
15247
|
this.pass = null;
|
|
13965
|
-
if (!this.surface.lost) this.
|
|
15248
|
+
if (!this.surface.lost) this.submit([this.encoder.finish()]);
|
|
13966
15249
|
this.encoder = null;
|
|
13967
15250
|
}
|
|
13968
15251
|
|
|
@@ -14022,7 +15305,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14022
15305
|
* there would have nothing to ride on. See `GpuTimestamps.resolve`.
|
|
14023
15306
|
*/
|
|
14024
15307
|
this.gpuTimer.resolve(this.encoder);
|
|
14025
|
-
this.
|
|
15308
|
+
this.submit([this.encoder.finish()]);
|
|
14026
15309
|
this.encoder = null;
|
|
14027
15310
|
this.framePresented = true;
|
|
14028
15311
|
/* Latched rather than assigned: `framePresented` goes back to false every `beginFrame`. */
|
|
@@ -14485,6 +15768,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14485
15768
|
const reads = maskOf(...(definition.reads ?? []));
|
|
14486
15769
|
this.passReads.set(handle, reads);
|
|
14487
15770
|
this.passReadsUnion |= reads;
|
|
15771
|
+
/* The colour copy a reader needs, now rather than at its first draw: see `passColour`. */
|
|
15772
|
+
if ((reads & COLOR_SNAPSHOT) !== 0 && this.passColour === null) this.allocatePassColour();
|
|
14488
15773
|
if (definition.prepare !== undefined) this.preparingPasses++;
|
|
14489
15774
|
definition.init?.(this.passDevice());
|
|
14490
15775
|
return handle;
|
|
@@ -14504,12 +15789,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14504
15789
|
environment: PassEnvironment | null;
|
|
14505
15790
|
distanceField: ComposedField | null;
|
|
14506
15791
|
jitter: Float32Array;
|
|
15792
|
+
sceneDepth: GPUTextureView | null;
|
|
15793
|
+
sceneColor: GPUTextureView | null;
|
|
14507
15794
|
} = {
|
|
14508
15795
|
backend: 'webgpu',
|
|
14509
15796
|
encoder: null,
|
|
14510
15797
|
environment: null,
|
|
14511
15798
|
distanceField: null,
|
|
14512
15799
|
jitter: this.passJitter,
|
|
15800
|
+
sceneDepth: null,
|
|
15801
|
+
sceneColor: null,
|
|
14513
15802
|
};
|
|
14514
15803
|
/**
|
|
14515
15804
|
* The object handed to a pass as `PrepareContext.environment`, rebuilt only when it changes.
|
|
@@ -14607,6 +15896,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14607
15896
|
* than merely what happens.
|
|
14608
15897
|
*/
|
|
14609
15898
|
this.prepareContext.distanceField = this.fieldComposer?.field ?? null;
|
|
15899
|
+
/* Settled for the frame: `ensureComposite` has run, so the view stays until the pass draws. */
|
|
15900
|
+
this.prepareContext.sceneDepth = this.resolvedDepthView;
|
|
15901
|
+
/*
|
|
15902
|
+
* Under a reconstruction a colour reader draws after the upscale (see `drawPass`), so it reads
|
|
15903
|
+
* the output-size copy the late refraction keeps rather than the render's.
|
|
15904
|
+
*/
|
|
15905
|
+
this.prepareContext.sceneColor = this.reconstructionWanted
|
|
15906
|
+
? this.refractSnapshotView
|
|
15907
|
+
: this.passColourView;
|
|
14610
15908
|
for (const definition of this.passes.definitions) {
|
|
14611
15909
|
definition?.prepare?.(this.prepareContext as PrepareContext);
|
|
14612
15910
|
}
|
|
@@ -14623,20 +15921,42 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14623
15921
|
if (!this.canDraw()) return;
|
|
14624
15922
|
const definition = passAt(this.passes, handle);
|
|
14625
15923
|
if (definition === undefined) return;
|
|
15924
|
+
const reads = this.passReads.get(handle) ?? 0;
|
|
15925
|
+
/*
|
|
15926
|
+
* **Under a reconstruction, a pass that reads the frame's colour draws after the blended draws
|
|
15927
|
+
* do.** Those land after the upscale, at the output's size, so a pass drawn among them at the
|
|
15928
|
+
* render's would read a frame without them and then be covered by them — a full-screen effect
|
|
15929
|
+
* with the hit sparks drawn sharp over it. Queued instead, and drawn by `runLate` over the
|
|
15930
|
+
* reconstructed picture, after every late draw, whatever order it was asked in.
|
|
15931
|
+
*/
|
|
15932
|
+
if ((reads & COLOR_SNAPSHOT) !== 0 && this.drawsLate()) {
|
|
15933
|
+
this.latePasses.push(handle);
|
|
15934
|
+
return;
|
|
15935
|
+
}
|
|
15936
|
+
/*
|
|
15937
|
+
* A pass that reads the depth copy gets it with everything drawn so far: the copy is taken
|
|
15938
|
+
* here, once a frame, as a soft-edged sprite takes it. Before `openPass` below, because taking
|
|
15939
|
+
* it ends the frame's pass and opens another.
|
|
15940
|
+
*/
|
|
15941
|
+
if ((reads & DEPTH_SNAPSHOT) !== 0 && !this.framePresented && this.openPass() !== null) {
|
|
15942
|
+
this.takeVolumeDepth();
|
|
15943
|
+
}
|
|
15944
|
+
/*
|
|
15945
|
+
* And one that reads the colour gets it afresh, with everything drawn before it — a full-screen
|
|
15946
|
+
* pass drawn last reads the whole frame and an effect drawn mid-frame reads the frame so far.
|
|
15947
|
+
* A copy a reading pass, where the depth above is one a frame: what a colour reader draws is
|
|
15948
|
+
* itself part of what the next one should see.
|
|
15949
|
+
*/
|
|
15950
|
+
if ((reads & COLOR_SNAPSHOT) !== 0 && !this.framePresented && this.openPass() !== null) {
|
|
15951
|
+
this.takePassColour();
|
|
15952
|
+
}
|
|
14626
15953
|
if (!this.quality.frameGraph) {
|
|
14627
15954
|
const pass = this.openPass();
|
|
14628
15955
|
if (pass === null) return;
|
|
14629
15956
|
definition.draw(this.fillPassContext(pass));
|
|
14630
15957
|
return;
|
|
14631
15958
|
}
|
|
14632
|
-
recordNode(
|
|
14633
|
-
this.arena,
|
|
14634
|
-
VERB_PASS,
|
|
14635
|
-
this.passReads.get(handle) ?? 0,
|
|
14636
|
-
this.currentTarget(),
|
|
14637
|
-
handle,
|
|
14638
|
-
0,
|
|
14639
|
-
);
|
|
15959
|
+
recordNode(this.arena, VERB_PASS, reads, this.currentTarget(), handle, 0);
|
|
14640
15960
|
if (this.framePresented) this.queueOverlayFlush();
|
|
14641
15961
|
}
|
|
14642
15962
|
|
|
@@ -14727,7 +16047,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14727
16047
|
definition.dispatch(this.computeContext as ComputeContext);
|
|
14728
16048
|
pass.end();
|
|
14729
16049
|
this.flushRings();
|
|
14730
|
-
|
|
16050
|
+
this.submit([encoder.finish()]);
|
|
14731
16051
|
}
|
|
14732
16052
|
|
|
14733
16053
|
/** Let go of a definition. A handle kept past this dispatches nothing; see `ComputeHandle`. */
|
|
@@ -14757,8 +16077,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14757
16077
|
): void {
|
|
14758
16078
|
const binner = this.clusterBinner;
|
|
14759
16079
|
i[at('uClustered')] = binner === null ? 0 : 1;
|
|
14760
|
-
if (binner === null) return;
|
|
14761
16080
|
|
|
16081
|
+
/* Written whether or not the lights are clustered: a surface overlay's rim finds its place on
|
|
16082
|
+
the screen through the same two (\`overlayScreen\`). */
|
|
14762
16083
|
const p = camera.projection;
|
|
14763
16084
|
const tanHalfFovY = 1 / (p[5] ?? 1);
|
|
14764
16085
|
const aspect = (p[5] ?? 1) / (p[0] ?? 1);
|
|
@@ -14774,6 +16095,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14774
16095
|
f[frustumAt + 2] = tanHalfFovY;
|
|
14775
16096
|
f[frustumAt + 3] = aspect;
|
|
14776
16097
|
f.set(camera.view as Float32Array, at('uView'));
|
|
16098
|
+
if (binner === null) return;
|
|
14777
16099
|
|
|
14778
16100
|
/* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
|
|
14779
16101
|
const set = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
|
|
@@ -15098,6 +16420,49 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15098
16420
|
/** This frame's exposure into the tone curve. Starts at the profile's own. */
|
|
15099
16421
|
private exposure: number;
|
|
15100
16422
|
|
|
16423
|
+
/**
|
|
16424
|
+
* The `filmic` curve every later frame is graded through, until it is set again. See
|
|
16425
|
+
* `RendererApi.setFilmicCurve`; the constants are solved here once, not per frame or per pixel.
|
|
16426
|
+
*/
|
|
16427
|
+
setFilmicCurve(curve: FilmicCurve): void {
|
|
16428
|
+
this.filmCurve = curve;
|
|
16429
|
+
if (resolveFilmicCurve(curve, this.filmConstants, this.headroom) && !this.warnedFilmClamped) {
|
|
16430
|
+
this.warnedFilmClamped = true;
|
|
16431
|
+
console.warn(FILMIC_CLAMPED);
|
|
16432
|
+
}
|
|
16433
|
+
}
|
|
16434
|
+
|
|
16435
|
+
private warnedFilmClamped = false;
|
|
16436
|
+
/** The curve last set, so a new headroom can solve it again. */
|
|
16437
|
+
private filmCurve: FilmicCurve = DEFAULT_FILMIC_CURVE;
|
|
16438
|
+
/** The display's peak over paper white; 1 on a standard range. See `setDisplayLuminance`. */
|
|
16439
|
+
private headroom = 1;
|
|
16440
|
+
|
|
16441
|
+
/** The range the frame goes out in. See `RendererApi.displayRange`. */
|
|
16442
|
+
get displayRange(): DisplayRange {
|
|
16443
|
+
return this.surface.displayRange;
|
|
16444
|
+
}
|
|
16445
|
+
|
|
16446
|
+
/** Why it is that range, in words. */
|
|
16447
|
+
get displayRangeReason(): string {
|
|
16448
|
+
return this.surface.displayRangeReason;
|
|
16449
|
+
}
|
|
16450
|
+
|
|
16451
|
+
/**
|
|
16452
|
+
* How bright paper white and the display's peak are, in any one unit (nits, usually), held until
|
|
16453
|
+
* changed: their ratio is how far above white the highlights may run on a high range display,
|
|
16454
|
+
* and nothing changes on a standard one. See `RendererApi.setDisplayLuminance`.
|
|
16455
|
+
*/
|
|
16456
|
+
setDisplayLuminance(paperWhite: number, peak: number): void {
|
|
16457
|
+
this.headroom = displayHeadroom(this.surface.displayRange, paperWhite, peak);
|
|
16458
|
+
resolveFilmicCurve(this.filmCurve, this.filmConstants, this.headroom);
|
|
16459
|
+
}
|
|
16460
|
+
|
|
16461
|
+
/** The curve's eight constants, and the two views the composite uploads them through. */
|
|
16462
|
+
private readonly filmConstants = new Float32Array(FILMIC_CONSTANTS);
|
|
16463
|
+
private readonly filmA = this.filmConstants.subarray(0, 4);
|
|
16464
|
+
private readonly filmB = this.filmConstants.subarray(4, 8);
|
|
16465
|
+
|
|
15101
16466
|
/*
|
|
15102
16467
|
* -----------------------------------------------------------------------------------------
|
|
15103
16468
|
* The three screen effects, which this backend cannot yet honour — and says so.
|
|
@@ -15142,6 +16507,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15142
16507
|
private readonly veilColor: Vec3 = [0, 0, 0];
|
|
15143
16508
|
private veilAlpha = 0;
|
|
15144
16509
|
private vignette = 0;
|
|
16510
|
+
/** The lens's colour fringe: red's and green's pull and where it begins. See `fringe.ts`. */
|
|
16511
|
+
private readonly fringe = new Float32Array(FRINGE_FLOATS);
|
|
15145
16512
|
private grain = 0;
|
|
15146
16513
|
private grainSeed = 0;
|
|
15147
16514
|
private warnedLookWithoutComposite = false;
|
|
@@ -15165,6 +16532,21 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15165
16532
|
this.vignette = clampVignette(strength);
|
|
15166
16533
|
}
|
|
15167
16534
|
|
|
16535
|
+
/**
|
|
16536
|
+
* The lens's colour fringe, held until changed: `intensity` is a percentage, 0 is none, and red
|
|
16537
|
+
* and green are pulled toward the centre by their wavelength's distance from blue, growing from
|
|
16538
|
+
* `start` — a share of the half-frame, 0 the centre — to the whole of it at the edge. Applied to
|
|
16539
|
+
* scene light at the composite's first read. Needs `screenEffects`, and says so once rather than
|
|
16540
|
+
* doing nothing. See `shaders/fringe.ts`.
|
|
16541
|
+
*/
|
|
16542
|
+
setChromaticAberration(intensity: number, start = 0): void {
|
|
16543
|
+
if (!this.hasComposite) {
|
|
16544
|
+
this.warnLookWithoutComposite(intensity);
|
|
16545
|
+
return;
|
|
16546
|
+
}
|
|
16547
|
+
resolveFringe(intensity, start, this.fringe);
|
|
16548
|
+
}
|
|
16549
|
+
|
|
15168
16550
|
/**
|
|
15169
16551
|
* Film grain, held until changed: an amplitude in display values (0 is none, 0.03 is a fine
|
|
15170
16552
|
* grain) and this frame's seed. **The seed is the caller's**, because the engine takes time from
|
|
@@ -15316,9 +16698,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15316
16698
|
this.rushStrength = Math.min(Math.max(strength, 0), 1);
|
|
15317
16699
|
}
|
|
15318
16700
|
|
|
15319
|
-
/**
|
|
15320
|
-
|
|
16701
|
+
/**
|
|
16702
|
+
* How much of the frame's motion blur to apply, 0 to 1, and the longest smear as a share of the
|
|
16703
|
+
* frame, both held until changed. See `RendererApi.setCameraMotionBlur`.
|
|
16704
|
+
*/
|
|
16705
|
+
setCameraMotionBlur(scale: number, maxShare = MOTION_BLUR_MAX_UV): void {
|
|
15321
16706
|
this.motionBlurScale = Math.min(Math.max(scale, 0), 1);
|
|
16707
|
+
this.motionBlurMax = clampMotionShare(maxShare);
|
|
16708
|
+
}
|
|
16709
|
+
|
|
16710
|
+
/** The longest smear, as a share of the frame. See `setCameraMotionBlur`. */
|
|
16711
|
+
private motionBlurMax = MOTION_BLUR_MAX_UV;
|
|
16712
|
+
private warnedObjectBlur = false;
|
|
16713
|
+
|
|
16714
|
+
/**
|
|
16715
|
+
* Said once: a draw named where it was last frame while the frame blurs, and this frame keeps no
|
|
16716
|
+
* motion target to blur it by — the camera's motion alone smears it. See `OBJECT_BLUR_UNKEPT`.
|
|
16717
|
+
*/
|
|
16718
|
+
private warnObjectBlur(): void {
|
|
16719
|
+
if (this.warnedObjectBlur) return;
|
|
16720
|
+
this.warnedObjectBlur = true;
|
|
16721
|
+
console.warn(OBJECT_BLUR_UNKEPT);
|
|
15322
16722
|
}
|
|
15323
16723
|
/**
|
|
15324
16724
|
* The camera has cut: this frame is a new shot, not the next moment of the last one.
|
|
@@ -15806,7 +17206,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15806
17206
|
.end();
|
|
15807
17207
|
}
|
|
15808
17208
|
}
|
|
15809
|
-
|
|
17209
|
+
this.submit([clear.finish()]);
|
|
15810
17210
|
}
|
|
15811
17211
|
|
|
15812
17212
|
/**
|
|
@@ -15927,17 +17327,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15927
17327
|
* `bindMeshPass` and `drawMesh`, which record into whatever `this.pass` is. That is the same
|
|
15928
17328
|
* arrangement `bakeFace` uses for a point light's cube, and it is why a bake must not be
|
|
15929
17329
|
* opened inside a frame's pass by a caller who then expects the frame to continue.
|
|
15930
|
-
|
|
15931
|
-
const savedPass = this.pass;
|
|
15932
|
-
/*
|
|
17330
|
+
*
|
|
15933
17331
|
* **A bake is a boundary, and boundaries flush.** The mirror says why in its own words: what
|
|
15934
17332
|
* has been recorded and not yet replayed belongs to the pass that is about to end, and leaving
|
|
15935
17333
|
* it replays it into whichever pass opens next — a draw in the wrong target rather than a
|
|
15936
17334
|
* missing one. This is the fourth such boundary in the backend and the only one that was never
|
|
15937
17335
|
* given the treatment, which is what let a bake carry the frame's pending draws into a cube
|
|
15938
|
-
* face.
|
|
17336
|
+
* face. **The pass is saved after the flush**, which opens it where none was: saved before,
|
|
17337
|
+
* the null given back left the flushed pass open and the frame's encoder locked. See
|
|
17338
|
+
* `captureScene`.
|
|
15939
17339
|
*/
|
|
15940
17340
|
if (this.quality.frameGraph) this.flushGraph();
|
|
17341
|
+
const savedPass = this.pass;
|
|
15941
17342
|
const savedEncoder = this.encoder;
|
|
15942
17343
|
/*
|
|
15943
17344
|
* **The rings are marked here and given back after the submit below**, because this bake is its
|
|
@@ -16036,7 +17437,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16036
17437
|
const whole = endFace === PROBE_FACES.length;
|
|
16037
17438
|
if (whole) this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
16038
17439
|
this.flushRings();
|
|
16039
|
-
|
|
17440
|
+
this.submit([encoder.finish()]);
|
|
16040
17441
|
this.rewindRings(skinMark);
|
|
16041
17442
|
this.encoderEpoch = frameEpoch;
|
|
16042
17443
|
|
|
@@ -16046,6 +17447,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16046
17447
|
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
16047
17448
|
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
16048
17449
|
this.materials.dirty();
|
|
17450
|
+
this.reopenPass();
|
|
16049
17451
|
this.probePassActive = false;
|
|
16050
17452
|
this.probeBounce = false;
|
|
16051
17453
|
this.rebindEnvironment();
|
|
@@ -16103,7 +17505,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16103
17505
|
this.uploadProbeFaces(probe, image);
|
|
16104
17506
|
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
16105
17507
|
this.buildProbeChain(encoder, probe, 0);
|
|
16106
|
-
|
|
17508
|
+
this.submit([encoder.finish()]);
|
|
16107
17509
|
|
|
16108
17510
|
this.markProbeFilled(0);
|
|
16109
17511
|
this.probeAmbient = options?.irradiance ?? true;
|
|
@@ -16141,7 +17543,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16141
17543
|
const { device } = this.surface;
|
|
16142
17544
|
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
16143
17545
|
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
16144
|
-
|
|
17546
|
+
this.submit([encoder.finish()]);
|
|
16145
17547
|
this.markProbeFilled(layer);
|
|
16146
17548
|
this.probeAmbient = options?.irradiance ?? true;
|
|
16147
17549
|
this.rebuildFlatBindGroup();
|
|
@@ -16629,6 +18031,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16629
18031
|
this.sceneColor,
|
|
16630
18032
|
this.sceneColorMsaa,
|
|
16631
18033
|
this.refractSnapshot,
|
|
18034
|
+
this.passColour,
|
|
16632
18035
|
this.resolvedDepth,
|
|
16633
18036
|
this.aoTarget,
|
|
16634
18037
|
this.aoScratch,
|
|
@@ -16698,6 +18101,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16698
18101
|
usage: 0x4 | 0x2 | 0x10, // TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT
|
|
16699
18102
|
});
|
|
16700
18103
|
this.refractSnapshotView = this.refractSnapshot.createView();
|
|
18104
|
+
this.passColour = null;
|
|
18105
|
+
this.passColourView = null;
|
|
18106
|
+
if ((this.passReadsUnion & COLOR_SNAPSHOT) !== 0) this.allocatePassColour();
|
|
16701
18107
|
this.refractMips = new SnapshotMips(
|
|
16702
18108
|
device,
|
|
16703
18109
|
this.pipelines,
|
|
@@ -16926,6 +18332,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16926
18332
|
usage: USAGE,
|
|
16927
18333
|
});
|
|
16928
18334
|
this.ssrReflectionView = this.ssrReflection.createView();
|
|
18335
|
+
/* The frame's materials, at the scene's size; a stand-in where nothing traces them. */
|
|
18336
|
+
this.reflectionSurface?.size(width, height);
|
|
18337
|
+
const probeMap = this.reflectionSurface?.probeView ?? this.blankView;
|
|
18338
|
+
const tintMap = this.reflectionSurface?.tintView ?? this.blankView;
|
|
16929
18339
|
this.ssrBindGroup =
|
|
16930
18340
|
this.resolvedDepthView === null || this.sceneColorView === null
|
|
16931
18341
|
? null
|
|
@@ -16939,8 +18349,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16939
18349
|
},
|
|
16940
18350
|
...entry(SSR_TEXTURES.uSsrDepth, this.resolvedDepthView, true),
|
|
16941
18351
|
...entry(SSR_TEXTURES.uSsrScene, this.sceneColorView),
|
|
18352
|
+
...entry(SSR_TEXTURES.uSsrProbeMap, probeMap),
|
|
18353
|
+
...entry(SSR_TEXTURES.uSsrTintMap, tintMap),
|
|
16942
18354
|
],
|
|
16943
18355
|
});
|
|
18356
|
+
this.ssrMaterialGroup = device.createBindGroup({
|
|
18357
|
+
label: 'post.ssrMaterialGroup',
|
|
18358
|
+
layout: this.ssrMaterialLayout,
|
|
18359
|
+
entries: [
|
|
18360
|
+
{
|
|
18361
|
+
binding: SSR_MATERIAL_UNIFORMS,
|
|
18362
|
+
resource: { buffer: this.ssrMaterialUniforms, size: SSR_MATERIAL_SIZE },
|
|
18363
|
+
},
|
|
18364
|
+
...entry(SSR_MATERIAL_TEXTURES.uSsrReflection, this.ssrReflectionView),
|
|
18365
|
+
...entry(SSR_MATERIAL_TEXTURES.uSsrProbeMap, probeMap),
|
|
18366
|
+
...entry(SSR_MATERIAL_TEXTURES.uSsrTintMap, tintMap),
|
|
18367
|
+
],
|
|
18368
|
+
});
|
|
16944
18369
|
this.ssrResolveGroup = device.createBindGroup({
|
|
16945
18370
|
label: 'post.ssrResolveGroup',
|
|
16946
18371
|
layout: this.ssrResolveLayout,
|