@forgeax/engine-render 0.1.23 → 0.1.24
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/README.md +131 -9
- package/dist/assembly/bundler-contract.d.ts +9 -0
- package/dist/assembly/bundler-contract.d.ts.map +1 -0
- package/dist/assembly/device-feature-admission.d.ts +8 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
- package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +7 -243
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +3 -2
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
- package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +168 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -0
- package/dist/assembly/material-shader-prewarm.d.ts +21 -0
- package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
- package/dist/assembly/recovery/generation.d.ts +46 -0
- package/dist/assembly/recovery/generation.d.ts.map +1 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts +10 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +19 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -0
- package/dist/assembly/renderer-lifecycle.d.ts +34 -0
- package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +55 -0
- package/dist/assembly/webgpu-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +152 -0
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
- package/dist/chunk-GE3SDD72.mjs.map +1 -0
- package/dist/{chunk-HZUPSYW7.mjs → chunk-KZFTK6DO.mjs} +102 -15
- package/dist/chunk-KZFTK6DO.mjs.map +1 -0
- package/dist/{chunk-FF7K3FSO.mjs → chunk-Q752SHI5.mjs} +89 -89
- package/dist/{chunk-FF7K3FSO.mjs.map → chunk-Q752SHI5.mjs.map} +1 -1
- package/dist/{chunk-QODA5UKU.mjs → chunk-RR5L4SRS.mjs} +2 -2
- package/dist/chunk-RR5L4SRS.mjs.map +1 -0
- package/dist/{chunk-YALPS44A.mjs → chunk-TFH5I7JJ.mjs} +47 -47
- package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
- package/dist/{chunk-FBC4R44J.mjs → chunk-UQYS7FRM.mjs} +57 -57
- package/dist/chunk-UQYS7FRM.mjs.map +1 -0
- package/dist/{chunk-4NPUASZR.mjs → chunk-YKB5DQV7.mjs} +161 -19
- package/dist/chunk-YKB5DQV7.mjs.map +1 -0
- package/dist/{chunk-ZUWKAZ2I.mjs → chunk-YRUYGA4A.mjs} +615 -237
- package/dist/chunk-YRUYGA4A.mjs.map +1 -0
- package/dist/{chunk-DIYW3M5D.mjs → chunk-Z3YYH2MB.mjs} +2210 -2213
- package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
- package/dist/construct-renderer.mjs +19841 -16190
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +2 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +24 -4
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/frame.d.ts +5 -0
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/inspection.d.ts +7 -0
- package/dist/environment/inspection.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +20 -2
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +58 -2
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +13 -0
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +899 -0
- package/dist/extract/contracts.d.ts.map +1 -0
- package/dist/features/host.d.ts +2 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +3 -0
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts +2 -0
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +28 -0
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +1 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +7 -6
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +7 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +6 -6
- package/dist/internal.mjs +6 -6
- package/dist/light-buffer-layout.d.ts +3 -0
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts +34 -10
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +56 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/occlusion-proxy.d.ts +4 -0
- package/dist/occlusion-proxy.d.ts.map +1 -0
- package/dist/pbr-pipeline.d.ts +32 -9
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/points-lines/prepare.d.ts +3 -0
- package/dist/points-lines/prepare.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +33 -0
- package/dist/points-lines/standard-owner.d.ts.map +1 -0
- package/dist/record/frame-snapshot.d.ts +5 -0
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +14 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +14 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -17
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +11 -3
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/recovery-pipeline.d.ts +30 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +26 -4
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +1 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +65 -0
- package/dist/recovery/render-system-candidate.d.ts.map +1 -0
- package/dist/render-contract.d.ts +64 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +12 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +156 -22
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +128 -4
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +19 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/taa-history-store.d.ts +15 -1
- package/dist/temporal/taa-history-store.d.ts.map +1 -1
- package/dist/temporal/transaction.d.ts +5 -0
- package/dist/temporal/transaction.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
- package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
- package/src/__tests__/environment-generation.integration.test.ts +23 -0
- package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
- package/src/__tests__/factory-contract.integration.test.ts +122 -7
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
- package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
- package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +24 -9
- package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
- package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
- package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
- package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
- package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +85 -18
- package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
- package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
- package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
- package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
- package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
- package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
- package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
- package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
- package/src/__tests__/shader-manifest-fixture.ts +13 -16
- package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
- package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
- package/src/assembly/bundler-contract.ts +9 -0
- package/src/assembly/device-feature-admission.ts +15 -5
- package/src/assembly/extended-lighting-fallback.ts +216 -0
- package/src/assembly/factory.ts +81 -10088
- package/src/assembly/material/assembly.ts +18 -2
- package/src/assembly/material/standard-layer-projection.ts +16 -0
- package/src/assembly/material-shader-policy.ts +854 -0
- package/src/assembly/material-shader-prewarm.ts +88 -0
- package/src/assembly/mesh-ssbo-grow.ts +270 -0
- package/src/assembly/recovery/device-loss-fanout.ts +89 -0
- package/src/assembly/recovery/generation.ts +141 -0
- package/src/assembly/recovery/recovery-attempt.ts +800 -0
- package/src/assembly/recovery/renderer-recover.ts +906 -0
- package/src/assembly/renderer-facade.ts +342 -0
- package/src/assembly/renderer-frame-transaction.ts +45 -0
- package/src/assembly/renderer-helpers.ts +99 -0
- package/src/assembly/renderer-lifecycle.ts +159 -0
- package/src/assembly/shader-prewarm-policy.ts +157 -0
- package/src/assembly/webgpu-ready-contract.ts +14 -0
- package/src/assembly/webgpu-ready.ts +4079 -0
- package/src/assembly/webgpu-renderer.ts +4095 -0
- package/src/components/camera.ts +1 -1
- package/src/debug-draw-glue.ts +1 -1
- package/src/device/device-scope.ts +35 -6
- package/src/device/gpu-residency.ts +194 -26
- package/src/environment/frame.ts +6 -0
- package/src/environment/inspection.ts +10 -0
- package/src/environment/lifecycle.ts +114 -8
- package/src/errors/recover.ts +76 -5
- package/src/errors/render.ts +17 -0
- package/src/extract/contracts.ts +974 -0
- package/src/features/host.ts +16 -0
- package/src/features/prepared-gpu-work.ts +20 -2
- package/src/features/prepared-graphics-store.ts +17 -0
- package/src/gpu-driven/production-raster.ts +97 -2
- package/src/gpu-scene.ts +3 -2
- package/src/ibl/IblPipelineCache.ts +134 -50
- package/src/ibl/skylight-bind-group.ts +26 -0
- package/src/index.ts +9 -1
- package/src/light-buffer-layout.ts +3 -0
- package/src/materials.ts +262 -45
- package/src/mesh-material-bindings.ts +59 -0
- package/src/occlusion-proxy.ts +64 -0
- package/src/pbr-pipeline.ts +139 -15
- package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
- package/src/points-lines/prepare.ts +9 -0
- package/src/points-lines/standard-owner.ts +403 -0
- package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
- package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
- package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
- package/src/record/frame-snapshot.ts +15 -0
- package/src/record/frame-targets.ts +11 -1
- package/src/record/frame.ts +6 -2
- package/src/record/helpers.ts +1 -1
- package/src/record/main-pass-geometry.ts +230 -5
- package/src/record/main-pass-material.ts +316 -209
- package/src/record/main-pass-sprite-draws.ts +6 -7
- package/src/record/main-pass.ts +94 -6
- package/src/record/mesh-ssbo.ts +32 -10
- package/src/record/recovery-pipeline.ts +695 -0
- package/src/record/render-context.ts +35 -4
- package/src/record/typed-frame-graph.ts +45 -1
- package/src/record/view-ubo.ts +3 -3
- package/src/recovery/render-system-candidate.ts +839 -0
- package/src/render-contract.ts +65 -7
- package/src/render-system-extract-tail.ts +2983 -0
- package/src/render-system-extract.ts +124 -2977
- package/src/render-system.ts +868 -659
- package/src/scene/render-scene.ts +124 -1
- package/src/temporal/frame-coordinator.ts +4 -1
- package/src/temporal/taa-history-store.ts +46 -2
- package/src/temporal/temporal-view.ts +1 -1
- package/src/temporal/transaction.ts +9 -0
- package/src/tilemap-chunk-extract-system.ts +1 -1
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
- package/dist/chunk-4L3P6DT5.mjs.map +0 -1
- package/dist/chunk-4NPUASZR.mjs.map +0 -1
- package/dist/chunk-DIYW3M5D.mjs.map +0 -1
- package/dist/chunk-FBC4R44J.mjs.map +0 -1
- package/dist/chunk-HZUPSYW7.mjs.map +0 -1
- package/dist/chunk-QODA5UKU.mjs.map +0 -1
- package/dist/chunk-YALPS44A.mjs.map +0 -1
- package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
package/src/render-system.ts
CHANGED
|
@@ -16,13 +16,22 @@
|
|
|
16
16
|
// .invert` (charter proposition 5: no math reinvention; render-system.test.ts
|
|
17
17
|
// asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type MipmapEncoderWork,
|
|
21
|
+
numMipLevels,
|
|
22
|
+
resolveAssetHandle,
|
|
23
|
+
} from '@forgeax/engine-assets-runtime';
|
|
20
24
|
import type { World } from '@forgeax/engine-ecs';
|
|
21
25
|
import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
22
|
-
import { mat4 } from '@forgeax/engine-math';
|
|
23
26
|
import type { RecorderSession } from '@forgeax/engine-profiler';
|
|
24
27
|
import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
|
|
25
|
-
import type {
|
|
28
|
+
import type {
|
|
29
|
+
BindGroupEntry,
|
|
30
|
+
BindGroupLayout,
|
|
31
|
+
Buffer,
|
|
32
|
+
RenderPipeline,
|
|
33
|
+
RhiDevice,
|
|
34
|
+
} from '@forgeax/engine-rhi';
|
|
26
35
|
import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
|
|
27
36
|
import {
|
|
28
37
|
derive,
|
|
@@ -30,7 +39,6 @@ import {
|
|
|
30
39
|
type Handle,
|
|
31
40
|
type MaterialAsset,
|
|
32
41
|
type MaterialTextureValue,
|
|
33
|
-
type MeshAsset,
|
|
34
42
|
type RenderPipelineAsset,
|
|
35
43
|
RenderQueue,
|
|
36
44
|
type SamplerAsset,
|
|
@@ -43,14 +51,16 @@ import {
|
|
|
43
51
|
projectDirectionalShadowInspectionSource,
|
|
44
52
|
} from './assembly/directional-shadow-inspection';
|
|
45
53
|
import { createClusterBinScratch } from './cluster-binner';
|
|
54
|
+
import type { DeviceScope, LifecycleResourceSpec } from './device/device-scope';
|
|
55
|
+
import type { EnvironmentGeneration } from './environment/generation';
|
|
46
56
|
import { EnvironmentLifecycle } from './environment/lifecycle';
|
|
47
57
|
import {
|
|
48
58
|
type ObservationUnavailableError,
|
|
49
|
-
PointsLinesMaterialUnsupportedError,
|
|
50
59
|
type RenderError,
|
|
51
60
|
TransmissionCapabilityMissingError,
|
|
52
61
|
} from './errors/render';
|
|
53
62
|
import {
|
|
63
|
+
type RenderFeatureHost,
|
|
54
64
|
type RenderFeaturePreparedGraphicsResolverInput,
|
|
55
65
|
runRenderFeatureFrame,
|
|
56
66
|
settlePreparedGraphicsCompletion,
|
|
@@ -84,20 +94,14 @@ import type {
|
|
|
84
94
|
TemporalTargetInspection,
|
|
85
95
|
TransmissionInspection,
|
|
86
96
|
} from './inspection-types';
|
|
87
|
-
import {
|
|
88
|
-
import { PointsLinesExpansionCache } from './points-lines/expansion-cache';
|
|
89
|
-
import {
|
|
90
|
-
inspectPointsLines,
|
|
91
|
-
type PointsLinesInspection,
|
|
92
|
-
type PointsLinesSourceError,
|
|
93
|
-
} from './points-lines/inspection';
|
|
94
|
-
import {
|
|
95
|
-
createPointsLinesLanePreparationAdapter,
|
|
96
|
-
type PointsLinesLanePreparationAdapter,
|
|
97
|
-
} from './points-lines/prepare';
|
|
98
|
-
import { createPointsLinesLaneAdapter, type PointsLinesBackend } from './points-lines/record';
|
|
97
|
+
import { buildOcclusionProxyVertices } from './occlusion-proxy';
|
|
99
98
|
import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
|
|
100
|
-
import
|
|
99
|
+
import { StandardPointsLinesOwner } from './points-lines/standard-owner';
|
|
100
|
+
import {
|
|
101
|
+
createRenderSystemRecovery,
|
|
102
|
+
materialSamplerHandles,
|
|
103
|
+
materialTextureHandles,
|
|
104
|
+
} from './recovery/render-system-candidate';
|
|
101
105
|
import { ReflectionProbeRecordOwner } from './reflection/record-owner';
|
|
102
106
|
import {
|
|
103
107
|
OcclusionRenderRuntime,
|
|
@@ -108,15 +112,10 @@ import { inspectVolumetricFog } from './volume/inspection';
|
|
|
108
112
|
|
|
109
113
|
export type { RenderSceneInspection } from './inspection-types';
|
|
110
114
|
|
|
111
|
-
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
112
115
|
import { createCubeCaptureScheduler } from './capture/scheduler';
|
|
116
|
+
import type { GpuResidencyCache } from './device/gpu-residency';
|
|
113
117
|
import type { EnvironmentInspection } from './environment/inspection';
|
|
114
|
-
import {
|
|
115
|
-
GPU_BUFFER_USAGE_COPY_DST,
|
|
116
|
-
GPU_BUFFER_USAGE_INDEX,
|
|
117
|
-
GPU_BUFFER_USAGE_UNIFORM,
|
|
118
|
-
GPU_BUFFER_USAGE_VERTEX,
|
|
119
|
-
} from './gpu-usage';
|
|
118
|
+
import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from './gpu-usage';
|
|
120
119
|
import { resetHdrpBuffers } from './hdrp-buffers';
|
|
121
120
|
import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
|
|
122
121
|
import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
|
|
@@ -158,9 +157,9 @@ import type {
|
|
|
158
157
|
GpuPassTimingSession,
|
|
159
158
|
} from './record/gpu-pass-timing/session';
|
|
160
159
|
import type { GpuTimingCapture } from './record/gpu-timing';
|
|
161
|
-
import { computeProjectionMatrix, computeViewMatrix } from './record/helpers';
|
|
162
160
|
import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
|
|
163
|
-
import type {
|
|
161
|
+
import type { RecoveryPipelineReadiness } from './record/recovery-pipeline';
|
|
162
|
+
import type { PipelineState, RenderSystemInternals } from './record/render-context';
|
|
164
163
|
import type { CubeCaptureGraphState } from './record/typed-frame-graph';
|
|
165
164
|
import {
|
|
166
165
|
type RenderFeatureGraphCandidate,
|
|
@@ -183,7 +182,7 @@ import type {
|
|
|
183
182
|
RenderableSnapshot,
|
|
184
183
|
} from './render-system-extract';
|
|
185
184
|
import { extractFrames } from './render-system-extract';
|
|
186
|
-
import { PersistentRenderScene } from './scene/render-scene';
|
|
185
|
+
import { type PersistentGpuDrivenCandidate, PersistentRenderScene } from './scene/render-scene';
|
|
187
186
|
import { inspectLodOcclusion } from './scene/visibility/inspection';
|
|
188
187
|
import { resolveSsaoParameters } from './ssao-config';
|
|
189
188
|
import { projectSsrDependencies, resolveSsrAdmissionGeneration } from './ssr/admission';
|
|
@@ -210,6 +209,34 @@ import {
|
|
|
210
209
|
inspectTransmissionFromAdmission,
|
|
211
210
|
} from './transmission/inspection';
|
|
212
211
|
|
|
212
|
+
function canonicalizeWorldComposition(
|
|
213
|
+
worlds: readonly World[],
|
|
214
|
+
owners: Pick<DrawOwnerOptions, 'cameraOwner' | 'resourceOwner'>,
|
|
215
|
+
leases: readonly RenderReadLease[] | undefined,
|
|
216
|
+
): {
|
|
217
|
+
readonly worlds: readonly World[];
|
|
218
|
+
readonly owners: Pick<DrawOwnerOptions, 'cameraOwner' | 'resourceOwner'>;
|
|
219
|
+
readonly leases: readonly RenderReadLease[] | undefined;
|
|
220
|
+
} {
|
|
221
|
+
const order = worlds
|
|
222
|
+
.map((world, index) => ({ world, index }))
|
|
223
|
+
.sort((left, right) => left.world.identity.localeCompare(right.world.identity));
|
|
224
|
+
const indexByOriginal = new Map(order.map((entry, index) => [entry.index, index]));
|
|
225
|
+
return {
|
|
226
|
+
worlds: Object.freeze(order.map((entry) => entry.world)),
|
|
227
|
+
owners: {
|
|
228
|
+
cameraOwner: indexByOriginal.get(owners.cameraOwner) ?? owners.cameraOwner,
|
|
229
|
+
resourceOwner: indexByOriginal.get(owners.resourceOwner) ?? owners.resourceOwner,
|
|
230
|
+
},
|
|
231
|
+
leases:
|
|
232
|
+
leases === undefined
|
|
233
|
+
? undefined
|
|
234
|
+
: Object.freeze(
|
|
235
|
+
order.map((entry) => leases[entry.index]).filter((lease) => lease !== undefined),
|
|
236
|
+
),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
213
240
|
interface LodInspectionQuerySummary {
|
|
214
241
|
readonly used: number;
|
|
215
242
|
readonly capacity: number;
|
|
@@ -361,6 +388,128 @@ export {
|
|
|
361
388
|
selectSwapChainFormat,
|
|
362
389
|
} from './record/render-context';
|
|
363
390
|
|
|
391
|
+
export type RendererProducerRootKind =
|
|
392
|
+
| 'backend-surface'
|
|
393
|
+
| 'shader-material-pipeline'
|
|
394
|
+
| 'mesh-texture-sampler'
|
|
395
|
+
| 'render-scene'
|
|
396
|
+
| 'gpu-driven'
|
|
397
|
+
| 'feature'
|
|
398
|
+
| 'external-source'
|
|
399
|
+
| 'render-graph'
|
|
400
|
+
| 'target-history'
|
|
401
|
+
| 'observation-lease';
|
|
402
|
+
|
|
403
|
+
export interface RendererProducerRoot {
|
|
404
|
+
readonly kind: RendererProducerRootKind;
|
|
405
|
+
readonly owner: string;
|
|
406
|
+
readonly candidateScope: 'device-scope';
|
|
407
|
+
readonly visibility: 'visible-workset' | 'non-visible-lazy';
|
|
408
|
+
readonly disabledWork: 'zero' | 'lazy';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Device-bound post-process resources prepared off to the side of the active renderer. */
|
|
412
|
+
export interface RecoveryPostProcessResources {
|
|
413
|
+
readonly device: RhiDevice;
|
|
414
|
+
readonly paramsBuffers: Map<string, Buffer>;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const PRODUCER_ROOT_MATRIX: readonly RendererProducerRoot[] = Object.freeze([
|
|
418
|
+
{
|
|
419
|
+
kind: 'backend-surface',
|
|
420
|
+
owner: 'backend-surface-owner',
|
|
421
|
+
candidateScope: 'device-scope',
|
|
422
|
+
visibility: 'visible-workset',
|
|
423
|
+
disabledWork: 'zero',
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
kind: 'shader-material-pipeline',
|
|
427
|
+
owner: 'shader-material-pipeline-owner',
|
|
428
|
+
candidateScope: 'device-scope',
|
|
429
|
+
visibility: 'visible-workset',
|
|
430
|
+
disabledWork: 'zero',
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
kind: 'mesh-texture-sampler',
|
|
434
|
+
owner: 'gpu-residency',
|
|
435
|
+
candidateScope: 'device-scope',
|
|
436
|
+
visibility: 'visible-workset',
|
|
437
|
+
disabledWork: 'lazy',
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
kind: 'render-scene',
|
|
441
|
+
owner: 'persistent-render-scene',
|
|
442
|
+
candidateScope: 'device-scope',
|
|
443
|
+
visibility: 'visible-workset',
|
|
444
|
+
disabledWork: 'zero',
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
kind: 'gpu-driven',
|
|
448
|
+
owner: 'gpu-driven-production',
|
|
449
|
+
candidateScope: 'device-scope',
|
|
450
|
+
visibility: 'visible-workset',
|
|
451
|
+
disabledWork: 'zero',
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
kind: 'feature',
|
|
455
|
+
owner: 'render-feature-host',
|
|
456
|
+
candidateScope: 'device-scope',
|
|
457
|
+
visibility: 'visible-workset',
|
|
458
|
+
disabledWork: 'lazy',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
kind: 'external-source',
|
|
462
|
+
owner: 'external-source-owner',
|
|
463
|
+
candidateScope: 'device-scope',
|
|
464
|
+
visibility: 'non-visible-lazy',
|
|
465
|
+
disabledWork: 'zero',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
kind: 'render-graph',
|
|
469
|
+
owner: 'typed-frame-graph',
|
|
470
|
+
candidateScope: 'device-scope',
|
|
471
|
+
visibility: 'visible-workset',
|
|
472
|
+
disabledWork: 'zero',
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
kind: 'target-history',
|
|
476
|
+
owner: 'temporal-target-owner',
|
|
477
|
+
candidateScope: 'device-scope',
|
|
478
|
+
visibility: 'visible-workset',
|
|
479
|
+
disabledWork: 'zero',
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
kind: 'observation-lease',
|
|
483
|
+
owner: 'render-observation-owner',
|
|
484
|
+
candidateScope: 'device-scope',
|
|
485
|
+
visibility: 'non-visible-lazy',
|
|
486
|
+
disabledWork: 'zero',
|
|
487
|
+
},
|
|
488
|
+
]);
|
|
489
|
+
|
|
490
|
+
export function createRendererProducerRootMatrix(): readonly RendererProducerRoot[] {
|
|
491
|
+
return PRODUCER_ROOT_MATRIX;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Production-owned recovery facts. These values are projections of the live
|
|
496
|
+
* RenderSystem owners; they are not a test ledger or a second graph model.
|
|
497
|
+
*/
|
|
498
|
+
export interface RecoveryProductionEvidence {
|
|
499
|
+
readonly producerRoots: readonly RendererProducerRoot[];
|
|
500
|
+
readonly graph: {
|
|
501
|
+
readonly ready: boolean;
|
|
502
|
+
readonly generation: number;
|
|
503
|
+
readonly passCount: number;
|
|
504
|
+
readonly resourceCount: number;
|
|
505
|
+
};
|
|
506
|
+
readonly residency: { readonly meshResidencyEpoch: number };
|
|
507
|
+
readonly submissions: {
|
|
508
|
+
readonly count: number;
|
|
509
|
+
readonly lastGeneration: number | undefined;
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
364
513
|
/**
|
|
365
514
|
* Unified transparent-queue sub-sort covering all four
|
|
366
515
|
* {@link TransparentSortConfig} modes. Reorders the
|
|
@@ -508,6 +657,19 @@ export interface RenderSystem {
|
|
|
508
657
|
renderReadLeases?: readonly RenderReadLease[],
|
|
509
658
|
timingCapture?: GpuTimingCapture,
|
|
510
659
|
): boolean;
|
|
660
|
+
/** Prepare a detached graph candidate without entering the frame record path. */
|
|
661
|
+
prepareRecoveryGraphCandidate(
|
|
662
|
+
runtime: RecoveryGraphCandidateRuntime,
|
|
663
|
+
): RecoveryGraphCandidatePreparation;
|
|
664
|
+
/** Finish and submit detached candidate setup, never a frame receipt. */
|
|
665
|
+
submitCandidateSetup(
|
|
666
|
+
candidate: RecoveryGraphCandidate,
|
|
667
|
+
isValid: () => boolean,
|
|
668
|
+
): Result<RecoveryGraphSetupSubmission, RhiError>;
|
|
669
|
+
/** Publish a detached graph candidate at the generation publication boundary. */
|
|
670
|
+
publishRecoveryGraphCandidate(candidate: RecoveryGraphCandidate): void;
|
|
671
|
+
/** Discard a detached graph candidate that did not reach publication. */
|
|
672
|
+
discardRecoveryGraphCandidate(candidate: RecoveryGraphCandidate): void;
|
|
511
673
|
/** Release renderer-owned persistent state for one detached World. */
|
|
512
674
|
detachScene(world: World): void;
|
|
513
675
|
/** Release the profiler catalog contribution owned by this RenderSystem. */
|
|
@@ -560,6 +722,8 @@ export interface RenderSystem {
|
|
|
560
722
|
readonly pointsLinesSnapshots: readonly PointsLinesRetainedSnapshot[];
|
|
561
723
|
/** Current mesh-slot provenance and active diagnostics from the last frame. */
|
|
562
724
|
readonly meshMaterialBindings: readonly MeshMaterialBindingObservation[];
|
|
725
|
+
/** Final-submit IBL binding-chain receipt for the last diagnostic frame. */
|
|
726
|
+
readonly iblBinding: import('./mesh-material-bindings').IblBindingInspection | undefined;
|
|
563
727
|
/**
|
|
564
728
|
* feat-20260531-bloom-first-declarative-render-graph-pass M4 fix-up w19:
|
|
565
729
|
* per-frame render-graph pass names in declaration order. Empty array
|
|
@@ -573,6 +737,8 @@ export interface RenderSystem {
|
|
|
573
737
|
readonly lastSuccessfulCameraAntialias: CameraSnapshot['antialias'] | undefined;
|
|
574
738
|
/** Detached compiled-graph facts used by the renderer inspection owner. */
|
|
575
739
|
readonly perFrameGraphInfo: CompiledRenderGraphInfo | undefined;
|
|
740
|
+
/** Production-owner recovery facts used by Renderer.inspect(). */
|
|
741
|
+
readonly recoveryEvidence: RecoveryProductionEvidence;
|
|
576
742
|
/**
|
|
577
743
|
* feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
|
|
578
744
|
* createBindGroup counter. Reset to 0 on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry,
|
|
@@ -600,6 +766,8 @@ export interface RenderSystem {
|
|
|
600
766
|
configureStandard(config: RenderPipelineAsset['config']): void;
|
|
601
767
|
/** Register one engine-owned post-process shader used by the Standard lane. */
|
|
602
768
|
registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void;
|
|
769
|
+
/** Resolve one active built-in post-process declaration for a detached candidate. */
|
|
770
|
+
readonly lookupPostProcess: (id: string) => PostProcessShaderEntry | undefined;
|
|
603
771
|
/**
|
|
604
772
|
* feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M5 / w21:
|
|
605
773
|
* release the per-RenderSystem frame-state GPU bookkeeping during
|
|
@@ -623,9 +791,19 @@ export interface RenderSystem {
|
|
|
623
791
|
* handles and are rebuilt after the replacement device is live.
|
|
624
792
|
* Idempotent and graph-optional (no-op when nothing has compiled yet).
|
|
625
793
|
*/
|
|
626
|
-
resetForRecover(): void;
|
|
794
|
+
resetForRecover(retiringPipelineState?: PipelineState, replacementDevice?: RhiDevice): void;
|
|
795
|
+
/** Build post-process parameter buffers without mutating the active bundle. */
|
|
796
|
+
prepareRecoveryPostProcessResources(
|
|
797
|
+
device: RhiDevice,
|
|
798
|
+
): Result<RecoveryPostProcessResources, RhiError>;
|
|
799
|
+
/** Publish a previously prepared post-process bundle at the recovery boundary. */
|
|
800
|
+
publishRecoveryPostProcessResources(candidate: RecoveryPostProcessResources): void;
|
|
801
|
+
/** Release a candidate bundle that was never published. */
|
|
802
|
+
discardRecoveryPostProcessResources(candidate: RecoveryPostProcessResources): void;
|
|
627
803
|
/** Recreate post-process GPU parameter resources after a device rebuild. */
|
|
628
804
|
restorePostProcessResources(): void;
|
|
805
|
+
/** Build candidate roots from explicit candidate-owned device state. */
|
|
806
|
+
prepareRecoveryRoots(runtime: RecoveryRootRuntime): RecoveryRootBundle;
|
|
629
807
|
}
|
|
630
808
|
|
|
631
809
|
function isStructuredRendererError(error: unknown): error is RhiError | RenderError {
|
|
@@ -681,11 +859,12 @@ function makePreparedPipelinePendingError(): RhiError {
|
|
|
681
859
|
});
|
|
682
860
|
}
|
|
683
861
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
862
|
+
/** The single Points/Lines owner’s staged device-generation handoff. */
|
|
863
|
+
export interface RecoveryPointsLinesCandidate {
|
|
864
|
+
createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
|
|
865
|
+
publish(): void;
|
|
866
|
+
release(): void;
|
|
867
|
+
}
|
|
689
868
|
|
|
690
869
|
function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationFailure | undefined {
|
|
691
870
|
if (typeof error !== 'object' || error === null) return undefined;
|
|
@@ -714,30 +893,56 @@ function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationF
|
|
|
714
893
|
};
|
|
715
894
|
}
|
|
716
895
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
const add = (handle: Handle<'TextureAsset', 'shared'> | undefined): void => {
|
|
722
|
-
if (handle !== undefined) handles.set(Number(handle), handle);
|
|
723
|
-
};
|
|
724
|
-
for (const handle of material.textureHandles?.values() ?? []) add(handle);
|
|
725
|
-
add(material.baseColorTexture);
|
|
726
|
-
add(material.metallicRoughnessTexture);
|
|
727
|
-
add(material.normalTexture);
|
|
728
|
-
add(material.emissiveTexture);
|
|
729
|
-
add(material.occlusionTexture);
|
|
730
|
-
return [...handles.values()];
|
|
896
|
+
/** Candidate-only device/runtime inputs used by the recovery prepare seam. */
|
|
897
|
+
export interface RecoveryGraphCandidateRuntime {
|
|
898
|
+
readonly internals: RenderSystemInternals;
|
|
899
|
+
readonly pipelineState: PipelineState;
|
|
731
900
|
}
|
|
732
901
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
902
|
+
/** Detached graph state prepared on a replacement device, before publication. */
|
|
903
|
+
export interface RecoveryGraphCandidate {
|
|
904
|
+
readonly frameState: RenderFrameState;
|
|
905
|
+
readonly setupWorks: readonly MipmapEncoderWork[];
|
|
906
|
+
readonly device: RhiDevice;
|
|
907
|
+
readonly generation: number;
|
|
908
|
+
readonly gpuDrivenProduction?: GpuDrivenProduction;
|
|
909
|
+
readonly gpuDrivenScene?: PersistentGpuDrivenCandidate;
|
|
910
|
+
/** Staged CPU-expanded Points/Lines buffers owned by the same RenderSystem. */
|
|
911
|
+
readonly pointsLines?: RecoveryPointsLinesCandidate;
|
|
912
|
+
readonly featureHost?: RenderFeatureHost;
|
|
913
|
+
readonly featureGpuWork?: RenderFeatureGpuWorkOwner;
|
|
914
|
+
readonly featureGraphCandidate?: RenderFeatureGraphCandidate;
|
|
915
|
+
readonly postProcessPipelines?: ReadonlyMap<string, RenderPipeline>;
|
|
916
|
+
readonly recoveryReadiness: RecoveryPipelineReadiness;
|
|
917
|
+
/** Release all candidate roots before publication; safe to call repeatedly. */
|
|
918
|
+
readonly release: () => void;
|
|
919
|
+
/** Transfer candidate-root ownership to the published RenderSystem. */
|
|
920
|
+
readonly markPublished: () => void;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/** Candidate setup completion; completion is bounded by the recovery owner. */
|
|
924
|
+
export interface RecoveryGraphSetupSubmission {
|
|
925
|
+
readonly completion?: PromiseLike<unknown>;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
export type RecoveryGraphCandidatePreparation =
|
|
929
|
+
| { readonly kind: 'no-seed' }
|
|
930
|
+
| { readonly kind: 'ready'; readonly candidate: RecoveryGraphCandidate }
|
|
931
|
+
| { readonly kind: 'failed'; readonly reason: string; readonly cause?: unknown };
|
|
932
|
+
|
|
933
|
+
/** Explicit candidate-owned inputs for lifecycle-root preparation. */
|
|
934
|
+
export interface RecoveryRootRuntime {
|
|
935
|
+
readonly scope: DeviceScope;
|
|
936
|
+
readonly device: RhiDevice;
|
|
937
|
+
readonly gpuStore: GpuResidencyCache;
|
|
938
|
+
readonly graphCandidate?: RecoveryGraphCandidate | undefined;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/** Candidate roots plus the one synchronous owner switch at publication. */
|
|
942
|
+
export interface RecoveryRootBundle {
|
|
943
|
+
readonly roots: readonly LifecycleResourceSpec<unknown>[];
|
|
944
|
+
publish(): void;
|
|
945
|
+
discard(): void;
|
|
741
946
|
}
|
|
742
947
|
|
|
743
948
|
/**
|
|
@@ -856,267 +1061,6 @@ function observeMaterialResidency(
|
|
|
856
1061
|
};
|
|
857
1062
|
}
|
|
858
1063
|
|
|
859
|
-
/**
|
|
860
|
-
* The Standard renderer's single Points/Lines prepare owner.
|
|
861
|
-
*
|
|
862
|
-
* The retained snapshot is the identity boundary; this owner only resolves
|
|
863
|
-
* the source assets, runs admission, and publishes the existing preparation
|
|
864
|
-
* and record contracts to the main geometry loop. The expansion cache is
|
|
865
|
-
* shared across lanes and this owner keeps the one prepared vertex/index
|
|
866
|
-
* resource pair for each retained identity.
|
|
867
|
-
*/
|
|
868
|
-
class StandardPointsLinesOwner implements PointsLinesRecordOwner {
|
|
869
|
-
private readonly cache = new PointsLinesExpansionCache();
|
|
870
|
-
private readonly preparations = new Map<string, PointsLinesPreparationAdapter>();
|
|
871
|
-
private readonly active = new Map<string, PointsLinesInspection>();
|
|
872
|
-
private readonly layoutProjection = deriveVertexLayoutProjection({
|
|
873
|
-
position: new Float32Array(0),
|
|
874
|
-
normal: new Float32Array(0),
|
|
875
|
-
uv: new Float32Array(0),
|
|
876
|
-
tangent: new Float32Array(0),
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
constructor(private readonly internals: RenderSystemInternals) {}
|
|
880
|
-
|
|
881
|
-
beginFrame(): void {
|
|
882
|
-
this.active.clear();
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
prepare(
|
|
886
|
-
entry: import('./record/frame-snapshot').ValidatedRenderable,
|
|
887
|
-
clustered: boolean,
|
|
888
|
-
): PointsLinesRecordSubmission | undefined {
|
|
889
|
-
const snapshot = entry.source.pointsLines;
|
|
890
|
-
if (snapshot === undefined || snapshot.component === undefined || entry.world === undefined) {
|
|
891
|
-
return undefined;
|
|
892
|
-
}
|
|
893
|
-
const key = `${snapshot.worldId}:${snapshot.entityKey}`;
|
|
894
|
-
const topology = snapshot.component === 'Points' ? 'point-list' : 'line-list';
|
|
895
|
-
const backend = this.backend();
|
|
896
|
-
const lane = backend === 'wgpu-webgl2' ? 'cpu-webgl2' : clustered ? 'clustered' : 'direct';
|
|
897
|
-
const meshResult = resolveAssetHandle<MeshAsset>(
|
|
898
|
-
entry.world,
|
|
899
|
-
toShared<'MeshAsset'>(entry.source.assetHandle),
|
|
900
|
-
);
|
|
901
|
-
const materialResult = resolveAssetHandle<MaterialAsset>(
|
|
902
|
-
entry.world,
|
|
903
|
-
toShared<'MaterialAsset'>(snapshot.materialHandle),
|
|
904
|
-
);
|
|
905
|
-
if (!meshResult.ok || !materialResult.ok) {
|
|
906
|
-
this.publishRefusal(
|
|
907
|
-
snapshot,
|
|
908
|
-
new PointsLinesMaterialUnsupportedError({
|
|
909
|
-
entity: snapshot.entityKey,
|
|
910
|
-
material: 'unresolved',
|
|
911
|
-
pass: 'forward',
|
|
912
|
-
module: 'asset-resolution',
|
|
913
|
-
reason: 'source MeshAsset or MaterialAsset could not be resolved',
|
|
914
|
-
}),
|
|
915
|
-
);
|
|
916
|
-
return undefined;
|
|
917
|
-
}
|
|
918
|
-
const admission = admitPointsLines({
|
|
919
|
-
entity: snapshot.entityKey,
|
|
920
|
-
mesh: meshResult.value,
|
|
921
|
-
material: materialResult.value,
|
|
922
|
-
...(snapshot.style?.kind === 'points'
|
|
923
|
-
? {
|
|
924
|
-
points: {
|
|
925
|
-
sizePx: snapshot.style.sizePx,
|
|
926
|
-
shape: snapshot.style.shape === 'circle' ? 1 : 0,
|
|
927
|
-
},
|
|
928
|
-
}
|
|
929
|
-
: { lines: { widthPx: snapshot.style?.widthPx ?? 1 } }),
|
|
930
|
-
});
|
|
931
|
-
if (!admission.ok) {
|
|
932
|
-
this.publishRefusal(snapshot, admission.error);
|
|
933
|
-
return undefined;
|
|
934
|
-
}
|
|
935
|
-
const geometryKey = this.cache.getOrCreate(snapshot, meshResult.value).key;
|
|
936
|
-
const prepKey = `${geometryKey}:${lane}:${backend}`;
|
|
937
|
-
let preparation = this.preparations.get(prepKey);
|
|
938
|
-
if (preparation === undefined) {
|
|
939
|
-
preparation = createPointsLinesLanePreparationAdapter(lane, backend, {
|
|
940
|
-
cache: this.cache,
|
|
941
|
-
adapter: {
|
|
942
|
-
create: (geometry) => {
|
|
943
|
-
const vertex = this.internals.device.createBuffer({
|
|
944
|
-
label: `points-lines-vertices:${geometryKey}`,
|
|
945
|
-
size: Math.max(4, geometry.vertices.byteLength),
|
|
946
|
-
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
947
|
-
mappedAtCreation: false,
|
|
948
|
-
});
|
|
949
|
-
if (!vertex.ok) return err(vertex.error);
|
|
950
|
-
const index = this.internals.device.createBuffer({
|
|
951
|
-
label: `points-lines-indices:${geometryKey}`,
|
|
952
|
-
size: Math.max(4, geometry.indices.byteLength),
|
|
953
|
-
usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
954
|
-
mappedAtCreation: false,
|
|
955
|
-
});
|
|
956
|
-
if (!index.ok) {
|
|
957
|
-
this.internals.device.destroyBuffer(vertex.value);
|
|
958
|
-
return err(index.error);
|
|
959
|
-
}
|
|
960
|
-
return ok({ vertexBuffer: vertex.value, indexBuffer: index.value });
|
|
961
|
-
},
|
|
962
|
-
upload: (resource, geometry) => {
|
|
963
|
-
if (geometry.vertices.byteLength > 0) {
|
|
964
|
-
const vertexWrite = this.internals.device.queue.writeBuffer(
|
|
965
|
-
resource.vertexBuffer,
|
|
966
|
-
0,
|
|
967
|
-
geometry.vertices,
|
|
968
|
-
);
|
|
969
|
-
if (!vertexWrite.ok) return err(vertexWrite.error);
|
|
970
|
-
}
|
|
971
|
-
if (geometry.indices.byteLength > 0) {
|
|
972
|
-
const indexWrite = this.internals.device.queue.writeBuffer(
|
|
973
|
-
resource.indexBuffer,
|
|
974
|
-
0,
|
|
975
|
-
geometry.indices,
|
|
976
|
-
);
|
|
977
|
-
if (!indexWrite.ok) return err(indexWrite.error);
|
|
978
|
-
}
|
|
979
|
-
return ok(geometry.derivedBytes);
|
|
980
|
-
},
|
|
981
|
-
validate: (_resource, geometry) =>
|
|
982
|
-
geometry.expandedVertexCount > 0 && geometry.expandedIndexCount > 0
|
|
983
|
-
? ok(undefined)
|
|
984
|
-
: err(new Error('Points/Lines expansion contains no drawable triangles')),
|
|
985
|
-
destroy: (resource) => {
|
|
986
|
-
this.internals.device.destroyBuffer(resource.vertexBuffer);
|
|
987
|
-
this.internals.device.destroyBuffer(resource.indexBuffer);
|
|
988
|
-
},
|
|
989
|
-
},
|
|
990
|
-
});
|
|
991
|
-
this.preparations.set(prepKey, preparation);
|
|
992
|
-
}
|
|
993
|
-
const prepared = preparation.prepare(snapshot, meshResult.value);
|
|
994
|
-
if (!prepared.ok) {
|
|
995
|
-
const lkg = preparation.lastKnownGood();
|
|
996
|
-
if (lkg !== undefined) {
|
|
997
|
-
const lkgPlan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
|
|
998
|
-
lkg.snapshot,
|
|
999
|
-
lkg.geometry,
|
|
1000
|
-
);
|
|
1001
|
-
const state = preparation.inspect();
|
|
1002
|
-
this.active.set(
|
|
1003
|
-
key,
|
|
1004
|
-
inspectPointsLines({
|
|
1005
|
-
snapshot,
|
|
1006
|
-
topology,
|
|
1007
|
-
lane,
|
|
1008
|
-
pointCount: lkg.geometry.pointCount,
|
|
1009
|
-
segmentCount: lkg.geometry.segmentCount,
|
|
1010
|
-
sourceBytes: lkg.geometry.sourceBytes,
|
|
1011
|
-
derivedBytes: lkg.geometry.derivedBytes,
|
|
1012
|
-
cache: { hit: true, rebuilds: state.rebuilds, evictions: 0 },
|
|
1013
|
-
drawCount: lkgPlan.drawCount,
|
|
1014
|
-
uploadBytes: 0,
|
|
1015
|
-
lastKnownGood: true,
|
|
1016
|
-
refusal: {
|
|
1017
|
-
code: prepared.error.code,
|
|
1018
|
-
expected: prepared.error.expected,
|
|
1019
|
-
hint: prepared.error.hint,
|
|
1020
|
-
detail: prepared.error.detail,
|
|
1021
|
-
generation: prepared.error.detail.generation,
|
|
1022
|
-
lastKnownGood: true,
|
|
1023
|
-
},
|
|
1024
|
-
}),
|
|
1025
|
-
);
|
|
1026
|
-
return {
|
|
1027
|
-
plan: lkgPlan,
|
|
1028
|
-
vertexBuffer: lkg.resource.vertexBuffer,
|
|
1029
|
-
indexBuffer: lkg.resource.indexBuffer,
|
|
1030
|
-
layoutProjection: this.layoutProjection,
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
this.publishRefusal(snapshot, prepared.error);
|
|
1034
|
-
return undefined;
|
|
1035
|
-
}
|
|
1036
|
-
const plan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
|
|
1037
|
-
snapshot,
|
|
1038
|
-
prepared.value.geometry,
|
|
1039
|
-
);
|
|
1040
|
-
const state = preparation.inspect();
|
|
1041
|
-
this.active.set(
|
|
1042
|
-
key,
|
|
1043
|
-
inspectPointsLines({
|
|
1044
|
-
snapshot,
|
|
1045
|
-
topology,
|
|
1046
|
-
lane,
|
|
1047
|
-
pointCount: prepared.value.geometry.pointCount,
|
|
1048
|
-
segmentCount: prepared.value.geometry.segmentCount,
|
|
1049
|
-
sourceBytes: prepared.value.geometry.sourceBytes,
|
|
1050
|
-
derivedBytes: prepared.value.geometry.derivedBytes,
|
|
1051
|
-
cache: {
|
|
1052
|
-
hit: prepared.value.uploadedBytes === 0,
|
|
1053
|
-
rebuilds: state.rebuilds,
|
|
1054
|
-
evictions: 0,
|
|
1055
|
-
},
|
|
1056
|
-
drawCount: plan.drawCount,
|
|
1057
|
-
uploadBytes: prepared.value.uploadedBytes,
|
|
1058
|
-
lastKnownGood: prepared.value.lastKnownGood,
|
|
1059
|
-
}),
|
|
1060
|
-
);
|
|
1061
|
-
return {
|
|
1062
|
-
plan,
|
|
1063
|
-
vertexBuffer: prepared.value.resource.vertexBuffer,
|
|
1064
|
-
indexBuffer: prepared.value.resource.indexBuffer,
|
|
1065
|
-
layoutProjection: this.layoutProjection,
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
resetForDeviceLoss(): void {
|
|
1070
|
-
for (const preparation of this.preparations.values()) preparation.resetForDeviceLoss();
|
|
1071
|
-
this.active.clear();
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
inspections(): readonly PointsLinesInspection[] {
|
|
1075
|
-
return [...this.active.values()];
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
private backend(): PointsLinesBackend {
|
|
1079
|
-
switch (this.internals.device.caps.backendKind) {
|
|
1080
|
-
case 'wgpu-webgl2':
|
|
1081
|
-
return 'wgpu-webgl2';
|
|
1082
|
-
case 'null':
|
|
1083
|
-
return 'null';
|
|
1084
|
-
default:
|
|
1085
|
-
return 'webgpu';
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
private publishRefusal(
|
|
1090
|
-
snapshot: PointsLinesRetainedSnapshot,
|
|
1091
|
-
error: PointsLinesSourceError,
|
|
1092
|
-
): void {
|
|
1093
|
-
this.active.set(
|
|
1094
|
-
`${snapshot.worldId}:${snapshot.entityKey}`,
|
|
1095
|
-
inspectPointsLines({
|
|
1096
|
-
snapshot,
|
|
1097
|
-
topology: snapshot.component === 'Points' ? 'point-list' : 'line-list',
|
|
1098
|
-
lane: 'refused',
|
|
1099
|
-
pointCount: 0,
|
|
1100
|
-
segmentCount: 0,
|
|
1101
|
-
sourceBytes: 0,
|
|
1102
|
-
derivedBytes: 0,
|
|
1103
|
-
cache: { hit: false, rebuilds: 0, evictions: 0 },
|
|
1104
|
-
drawCount: 0,
|
|
1105
|
-
uploadBytes: 0,
|
|
1106
|
-
lastKnownGood: false,
|
|
1107
|
-
refusal: {
|
|
1108
|
-
code: error.code,
|
|
1109
|
-
expected: error.expected,
|
|
1110
|
-
hint: error.hint,
|
|
1111
|
-
detail: error.detail,
|
|
1112
|
-
generation: snapshot.meshGeneration,
|
|
1113
|
-
lastKnownGood: false,
|
|
1114
|
-
},
|
|
1115
|
-
}),
|
|
1116
|
-
);
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
1064
|
export function createRenderSystem(internals: RenderSystemInternals): RenderSystem {
|
|
1121
1065
|
const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
|
|
1122
1066
|
'render',
|
|
@@ -1169,7 +1113,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1169
1113
|
internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
|
|
1170
1114
|
},
|
|
1171
1115
|
});
|
|
1172
|
-
|
|
1116
|
+
let environmentLifecycle = new EnvironmentLifecycle(internals.deviceScope);
|
|
1173
1117
|
const transmissionAdmission = new TransmissionCandidateAdmission();
|
|
1174
1118
|
let lastTransmissionInspection: TransmissionInspection | undefined;
|
|
1175
1119
|
let lastTransmissionKey = '';
|
|
@@ -1294,8 +1238,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1294
1238
|
});
|
|
1295
1239
|
lastTransmissionKey = key;
|
|
1296
1240
|
};
|
|
1241
|
+
// GPU-driven production uses the validated adapter for normal frames. The
|
|
1242
|
+
// assembly-supplied closure resolves its device-bound adapter at call time,
|
|
1243
|
+
// so recovery candidate preparation still follows candidateShaderState
|
|
1244
|
+
// rather than retaining a lost-generation module.
|
|
1297
1245
|
const gpuDrivenShaderFactory =
|
|
1298
1246
|
internals.shaderModuleFactory ??
|
|
1247
|
+
internals.immediateShaderModuleFactory ??
|
|
1299
1248
|
({
|
|
1300
1249
|
createShaderModule: () =>
|
|
1301
1250
|
err(
|
|
@@ -1306,7 +1255,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1306
1255
|
}),
|
|
1307
1256
|
),
|
|
1308
1257
|
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
|
|
1309
|
-
let gpuDrivenProduction =
|
|
1258
|
+
let gpuDrivenProduction = GpuDrivenProduction.forDevice(internals.device, gpuDrivenShaderFactory);
|
|
1310
1259
|
let occlusionRuntime: OcclusionRenderRuntime | undefined = new OcclusionRenderRuntime(
|
|
1311
1260
|
internals.device,
|
|
1312
1261
|
persistentRenderScene.visibilityFacetStore(),
|
|
@@ -1324,34 +1273,36 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1324
1273
|
}
|
|
1325
1274
|
return occlusionRuntime;
|
|
1326
1275
|
};
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1276
|
+
const createFeatureGpuWorkOwner = (runtime: RenderSystemInternals): RenderFeatureGpuWorkOwner =>
|
|
1277
|
+
createRenderFeatureGpuWorkOwner({
|
|
1278
|
+
getDevice: () => runtime.device,
|
|
1279
|
+
getShaderModuleFactory: () =>
|
|
1280
|
+
runtime.shaderModuleFactory ??
|
|
1281
|
+
({
|
|
1282
|
+
createShaderModule: () =>
|
|
1283
|
+
err(
|
|
1284
|
+
new RhiError({
|
|
1285
|
+
code: 'rhi-not-available',
|
|
1286
|
+
expected: 'the renderer backend exposes shader module creation',
|
|
1287
|
+
hint: 'construct the renderer through the backend pack before activating GPU features',
|
|
1288
|
+
}),
|
|
1289
|
+
),
|
|
1290
|
+
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory),
|
|
1291
|
+
getImmediateShaderModuleFactory: () =>
|
|
1292
|
+
runtime.immediateShaderModuleFactory ??
|
|
1293
|
+
runtime.shaderModuleFactory ??
|
|
1294
|
+
({
|
|
1295
|
+
createShaderModule: () =>
|
|
1296
|
+
err(
|
|
1297
|
+
new RhiError({
|
|
1298
|
+
code: 'rhi-not-available',
|
|
1299
|
+
expected: 'the renderer backend exposes shader module creation',
|
|
1300
|
+
hint: 'construct the renderer through the backend pack before activating GPU features',
|
|
1301
|
+
}),
|
|
1302
|
+
),
|
|
1303
|
+
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory),
|
|
1304
|
+
});
|
|
1305
|
+
let featureGpuWork = createFeatureGpuWorkOwner(internals);
|
|
1355
1306
|
const disposeFeatureGpuWork = (): void => {
|
|
1356
1307
|
const disposed = featureGpuWork.dispose();
|
|
1357
1308
|
if (!disposed.ok) internals.errorRegistry.fire(disposed.error);
|
|
@@ -1493,6 +1444,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1493
1444
|
spotShadowSnapshots: [],
|
|
1494
1445
|
};
|
|
1495
1446
|
let directFrameId = 0;
|
|
1447
|
+
let submittedFrameCount = 0;
|
|
1448
|
+
let lastSubmittedGeneration: number | undefined;
|
|
1496
1449
|
let lastDirectionalShadowLights: ExtractedLights | undefined;
|
|
1497
1450
|
let lastDirectionalShadowCandidate: 'accepted' | 'failed' = 'failed';
|
|
1498
1451
|
let lastDirectionalShadowError: DirectionalShadowInspectionSource['error'];
|
|
@@ -1551,7 +1504,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1551
1504
|
// D-3 / D-8: per-shader params UBO resource table (id -> GPU Buffer).
|
|
1552
1505
|
// Eager-created at register time when entry.params is present (byteSize >= 16,
|
|
1553
1506
|
// defaultValue.length === byteSize); reused frame-to-frame via queue.writeBuffer.
|
|
1554
|
-
|
|
1507
|
+
let postProcessParamsBuffers = new Map<string, Buffer>();
|
|
1508
|
+
let postProcessParamsDevice = internals.device;
|
|
1555
1509
|
const builtinPostProcessEntries = new Map<string, PostProcessShaderEntry>();
|
|
1556
1510
|
let activeFeaturePostProcessEntries: ReadonlyMap<string, PostProcessShaderEntry> = new Map();
|
|
1557
1511
|
const lookupPostProcess = (id: string): PostProcessShaderEntry | undefined =>
|
|
@@ -1567,6 +1521,76 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1567
1521
|
// per-frame snapshot + bind it at group(1) binding(2).
|
|
1568
1522
|
const getPostProcessParamsBuffer = (id: string): Buffer | undefined =>
|
|
1569
1523
|
postProcessParamsBuffers.get(id);
|
|
1524
|
+
const postProcessDeclarations = (): readonly (readonly [string, PostProcessShaderEntry])[] => [
|
|
1525
|
+
...builtinPostProcessEntries.entries(),
|
|
1526
|
+
...activeFeaturePostProcessEntries.entries(),
|
|
1527
|
+
];
|
|
1528
|
+
const disposePostProcessResources = (resources: RecoveryPostProcessResources): void => {
|
|
1529
|
+
for (const buffer of resources.paramsBuffers.values()) {
|
|
1530
|
+
const destroyed = resources.device.destroyBuffer(buffer);
|
|
1531
|
+
if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
const prepareRecoveryPostProcessResources = (
|
|
1535
|
+
device: RhiDevice,
|
|
1536
|
+
): Result<RecoveryPostProcessResources, RhiError> => {
|
|
1537
|
+
const paramsBuffers = new Map<string, Buffer>();
|
|
1538
|
+
try {
|
|
1539
|
+
for (const [id, entry] of postProcessDeclarations()) {
|
|
1540
|
+
if (id === 'forgeax.taa-resolve') continue;
|
|
1541
|
+
let buffer: Buffer | undefined;
|
|
1542
|
+
if (entry.params !== undefined) {
|
|
1543
|
+
const created = device.createBuffer({
|
|
1544
|
+
label: `post-process-params-${id}`,
|
|
1545
|
+
size: entry.params.byteSize,
|
|
1546
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1547
|
+
mappedAtCreation: false,
|
|
1548
|
+
});
|
|
1549
|
+
if (!created.ok) throw created.error;
|
|
1550
|
+
buffer = created.value;
|
|
1551
|
+
paramsBuffers.set(id, buffer);
|
|
1552
|
+
const written = device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
|
|
1553
|
+
if (!written.ok) throw written.error;
|
|
1554
|
+
} else if (entryHasDepthRead(entry)) {
|
|
1555
|
+
const created = device.createBuffer({
|
|
1556
|
+
label: `post-process-params-${id}`,
|
|
1557
|
+
size: DEPTH_MIN_PARAMS_BYTE_SIZE,
|
|
1558
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1559
|
+
mappedAtCreation: false,
|
|
1560
|
+
});
|
|
1561
|
+
if (!created.ok) throw created.error;
|
|
1562
|
+
buffer = created.value;
|
|
1563
|
+
}
|
|
1564
|
+
if (buffer !== undefined) paramsBuffers.set(id, buffer);
|
|
1565
|
+
}
|
|
1566
|
+
} catch (cause) {
|
|
1567
|
+
disposePostProcessResources({ device, paramsBuffers });
|
|
1568
|
+
if (cause instanceof RhiError) return err(cause);
|
|
1569
|
+
return err(
|
|
1570
|
+
new RhiError({
|
|
1571
|
+
code: 'webgpu-runtime-error',
|
|
1572
|
+
expected: 'post-process recovery resources are created on the candidate device',
|
|
1573
|
+
hint: cause instanceof Error ? cause.message : String(cause),
|
|
1574
|
+
}),
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
return ok({ device, paramsBuffers });
|
|
1578
|
+
};
|
|
1579
|
+
const publishRecoveryPostProcessResources = (candidate: RecoveryPostProcessResources): void => {
|
|
1580
|
+
if (candidate.paramsBuffers === postProcessParamsBuffers) return;
|
|
1581
|
+
const previous = { device: postProcessParamsDevice, paramsBuffers: postProcessParamsBuffers };
|
|
1582
|
+
postProcessParamsDevice = candidate.device;
|
|
1583
|
+
postProcessParamsBuffers = candidate.paramsBuffers;
|
|
1584
|
+
disposePostProcessResources(previous);
|
|
1585
|
+
};
|
|
1586
|
+
const discardRecoveryPostProcessResources = (candidate: RecoveryPostProcessResources): void => {
|
|
1587
|
+
if (candidate.paramsBuffers === postProcessParamsBuffers) {
|
|
1588
|
+
postProcessParamsBuffers = new Map();
|
|
1589
|
+
disposePostProcessResources(candidate);
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
disposePostProcessResources(candidate);
|
|
1593
|
+
};
|
|
1570
1594
|
interface PostProcessParamsCandidate {
|
|
1571
1595
|
readonly buffers: ReadonlyMap<string, Buffer>;
|
|
1572
1596
|
readonly created: readonly Buffer[];
|
|
@@ -1666,6 +1690,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1666
1690
|
const key = `${id}|${colorFormat}|${signature}`;
|
|
1667
1691
|
const cached = postProcessPipelineCache.get(key);
|
|
1668
1692
|
if (cached !== undefined) return cached;
|
|
1693
|
+
internals.recoveryColdWorkGuard?.notePipelineColdWork();
|
|
1669
1694
|
const factory = internals.buildPostProcessPipeline;
|
|
1670
1695
|
if (factory === undefined) return null;
|
|
1671
1696
|
const built = factory(
|
|
@@ -1842,44 +1867,60 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1842
1867
|
createBindGroup: 0,
|
|
1843
1868
|
keys: [],
|
|
1844
1869
|
};
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1870
|
+
type PreparedResolverCaches = {
|
|
1871
|
+
readonly preparedPipelineIds: WeakMap<object, string>;
|
|
1872
|
+
readonly preparedMaterialPipelineShaders: WeakMap<object, string>;
|
|
1873
|
+
readonly preparedGroup0Pipelines: WeakSet<object>;
|
|
1874
|
+
readonly preparedViewOnlyPipelines: WeakSet<object>;
|
|
1875
|
+
readonly preparedRenderMaterialPipelines: WeakSet<object>;
|
|
1876
|
+
readonly preparedAssetHandles: WeakMap<World, Map<string, number>>;
|
|
1877
|
+
};
|
|
1878
|
+
const createPreparedResolverCaches = (): PreparedResolverCaches => ({
|
|
1879
|
+
preparedPipelineIds: new WeakMap(),
|
|
1880
|
+
preparedMaterialPipelineShaders: new WeakMap(),
|
|
1881
|
+
preparedGroup0Pipelines: new WeakSet(),
|
|
1882
|
+
preparedViewOnlyPipelines: new WeakSet(),
|
|
1883
|
+
preparedRenderMaterialPipelines: new WeakSet(),
|
|
1884
|
+
preparedAssetHandles: new WeakMap(),
|
|
1885
|
+
});
|
|
1886
|
+
const activePreparedResolverCaches = createPreparedResolverCaches();
|
|
1851
1887
|
const preparedHandle = <Brand extends string>(
|
|
1888
|
+
runtime: RenderSystemInternals,
|
|
1889
|
+
caches: PreparedResolverCaches,
|
|
1852
1890
|
world: World,
|
|
1853
1891
|
guid: string,
|
|
1854
1892
|
brand: Brand,
|
|
1855
1893
|
): Handle<Brand, 'shared'> | undefined => {
|
|
1856
|
-
let handles = preparedAssetHandles.get(world);
|
|
1894
|
+
let handles = caches.preparedAssetHandles.get(world);
|
|
1857
1895
|
if (handles === undefined) {
|
|
1858
1896
|
handles = new Map();
|
|
1859
|
-
preparedAssetHandles.set(world, handles);
|
|
1897
|
+
caches.preparedAssetHandles.set(world, handles);
|
|
1860
1898
|
}
|
|
1861
1899
|
const key = `${brand}:${guid.toLowerCase()}`;
|
|
1862
1900
|
const cached = handles.get(key);
|
|
1863
1901
|
if (cached !== undefined) return cached as Handle<Brand, 'shared'>;
|
|
1864
|
-
const asset =
|
|
1902
|
+
const asset = runtime.assets.lookup(guid);
|
|
1865
1903
|
if (asset === undefined) return undefined;
|
|
1866
1904
|
const handle = world.allocSharedRef(brand, asset);
|
|
1867
1905
|
handles.set(key, handle as number);
|
|
1868
1906
|
return handle;
|
|
1869
1907
|
};
|
|
1870
1908
|
const preparedMaterialBindings = (
|
|
1909
|
+
runtime: RenderSystemInternals,
|
|
1910
|
+
caches: PreparedResolverCaches,
|
|
1911
|
+
worlds: readonly World[],
|
|
1871
1912
|
materialShaderId: string,
|
|
1872
1913
|
worldIndex: number,
|
|
1873
1914
|
materialGuid: string,
|
|
1874
1915
|
layout: BindGroupLayout,
|
|
1875
1916
|
) => {
|
|
1876
|
-
const world =
|
|
1877
|
-
const material =
|
|
1878
|
-
const pipelineState =
|
|
1917
|
+
const world = worlds[worldIndex];
|
|
1918
|
+
const material = runtime.assets.lookup(materialGuid) as MaterialAsset | undefined;
|
|
1919
|
+
const pipelineState = runtime.getPipelineState();
|
|
1879
1920
|
if (world === undefined || material?.kind !== 'material' || pipelineState === null) {
|
|
1880
1921
|
return err(new Error('prepared material asset is unavailable'));
|
|
1881
1922
|
}
|
|
1882
|
-
const schema =
|
|
1923
|
+
const schema = runtime.getParamSchema?.(materialShaderId) ?? [];
|
|
1883
1924
|
const values = material.values ?? {};
|
|
1884
1925
|
const derived = derive(schema);
|
|
1885
1926
|
const fields = [...derived.textureFieldNames];
|
|
@@ -1892,7 +1933,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1892
1933
|
schema,
|
|
1893
1934
|
values as Readonly<Record<string, number | readonly number[]>>,
|
|
1894
1935
|
);
|
|
1895
|
-
const created =
|
|
1936
|
+
const created = runtime.device.createBuffer({
|
|
1896
1937
|
label: `prepared-material:${materialGuid}`,
|
|
1897
1938
|
size: payload.byteLength,
|
|
1898
1939
|
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
@@ -1900,9 +1941,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1900
1941
|
if (!created.ok) return created;
|
|
1901
1942
|
buffer = created.value;
|
|
1902
1943
|
payloadByteLength = payload.byteLength;
|
|
1903
|
-
const written =
|
|
1944
|
+
const written = runtime.device.queue.writeBuffer(buffer, 0, payload);
|
|
1904
1945
|
if (!written.ok) {
|
|
1905
|
-
|
|
1946
|
+
runtime.device.destroyBuffer(buffer);
|
|
1906
1947
|
return written;
|
|
1907
1948
|
}
|
|
1908
1949
|
}
|
|
@@ -1919,20 +1960,32 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1919
1960
|
: undefined;
|
|
1920
1961
|
let sampler = pipelineState.defaultSampler;
|
|
1921
1962
|
if (textureValue?.sampler !== undefined) {
|
|
1922
|
-
const handle = preparedHandle(
|
|
1963
|
+
const handle = preparedHandle(
|
|
1964
|
+
runtime,
|
|
1965
|
+
caches,
|
|
1966
|
+
world,
|
|
1967
|
+
String(textureValue.sampler),
|
|
1968
|
+
'SamplerAsset',
|
|
1969
|
+
);
|
|
1923
1970
|
if (handle !== undefined) {
|
|
1924
1971
|
const asset = resolveAssetHandle<SamplerAsset>(world, handle);
|
|
1925
1972
|
if (asset.ok) {
|
|
1926
|
-
const resident =
|
|
1973
|
+
const resident = runtime.gpuStore.ensureSamplerResident(handle, asset.value, world);
|
|
1927
1974
|
if (resident.ok) sampler = resident.value;
|
|
1928
1975
|
}
|
|
1929
1976
|
}
|
|
1930
1977
|
}
|
|
1931
1978
|
let view = pipelineState.defaultWhiteTextureView;
|
|
1932
1979
|
if (textureValue !== undefined) {
|
|
1933
|
-
const handle = preparedHandle(
|
|
1980
|
+
const handle = preparedHandle(
|
|
1981
|
+
runtime,
|
|
1982
|
+
caches,
|
|
1983
|
+
world,
|
|
1984
|
+
String(textureValue.texture),
|
|
1985
|
+
'TextureAsset',
|
|
1986
|
+
);
|
|
1934
1987
|
if (handle !== undefined) {
|
|
1935
|
-
view = residentTextureView(world,
|
|
1988
|
+
view = residentTextureView(world, runtime.gpuStore, runtime, handle) ?? view;
|
|
1936
1989
|
}
|
|
1937
1990
|
}
|
|
1938
1991
|
textureResources.push({ sampler, view });
|
|
@@ -1968,12 +2021,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1968
2021
|
textureIndex += 1;
|
|
1969
2022
|
continue;
|
|
1970
2023
|
}
|
|
1971
|
-
if (buffer !== undefined)
|
|
2024
|
+
if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
|
|
1972
2025
|
return err(new Error('prepared material schema contains unsupported binding kinds'));
|
|
1973
2026
|
}
|
|
1974
2027
|
const fallback = pipelineState.skylightFallback;
|
|
1975
2028
|
if (fallback === null) {
|
|
1976
|
-
if (buffer !== undefined)
|
|
2029
|
+
if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
|
|
1977
2030
|
return err(new Error('prepared material skylight fallback is unavailable'));
|
|
1978
2031
|
}
|
|
1979
2032
|
const completeEntries = assembleMaterialWithSkylightEntries(entries, {
|
|
@@ -1985,206 +2038,261 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1985
2038
|
brdfLutSampler: fallback.sampler,
|
|
1986
2039
|
intensityBuffer: fallback.intensityBuffer,
|
|
1987
2040
|
});
|
|
1988
|
-
const group =
|
|
2041
|
+
const group = runtime.device.createBindGroup({ layout, entries: completeEntries });
|
|
1989
2042
|
if (!group.ok) {
|
|
1990
|
-
if (buffer !== undefined)
|
|
2043
|
+
if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
|
|
1991
2044
|
return group;
|
|
1992
2045
|
}
|
|
1993
2046
|
if (buffer === undefined) return group;
|
|
1994
2047
|
return ok({
|
|
1995
2048
|
handle: group.value,
|
|
1996
2049
|
dynamicOffsets: [0, 0],
|
|
1997
|
-
release: () =>
|
|
2050
|
+
release: () => runtime.device.destroyBuffer(buffer),
|
|
1998
2051
|
});
|
|
1999
2052
|
};
|
|
2000
2053
|
const lastFrustumStats: { culled: number; total: number } = { culled: 0, total: 0 };
|
|
2001
2054
|
const lastVisibilityStats: { explicitlyHidden: number } = { explicitlyHidden: 0 };
|
|
2002
2055
|
let lastMeshMaterialBindings: readonly MeshMaterialBindingObservation[] = [];
|
|
2003
2056
|
let lastMeshMaterialBindingFrame: ExtractedFrame | undefined;
|
|
2004
|
-
const
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2057
|
+
const createPreparedResolverFactory =
|
|
2058
|
+
(
|
|
2059
|
+
runtime: RenderSystemInternals,
|
|
2060
|
+
gpuWork: RenderFeatureGpuWorkOwner,
|
|
2061
|
+
worlds: readonly World[],
|
|
2062
|
+
caches: PreparedResolverCaches,
|
|
2063
|
+
) =>
|
|
2064
|
+
(input: RenderFeaturePreparedGraphicsResolverInput): PreparedGraphicsResolver => {
|
|
2065
|
+
const {
|
|
2066
|
+
preparedPipelineIds,
|
|
2067
|
+
preparedMaterialPipelineShaders,
|
|
2068
|
+
preparedGroup0Pipelines,
|
|
2069
|
+
preparedViewOnlyPipelines,
|
|
2070
|
+
preparedRenderMaterialPipelines,
|
|
2071
|
+
} = caches;
|
|
2072
|
+
return createPreparedGraphicsResolver({
|
|
2073
|
+
device: runtime.device,
|
|
2074
|
+
featureIdentity: input.featureIdentity,
|
|
2075
|
+
generation: input.generation,
|
|
2076
|
+
capabilityAvailable: true,
|
|
2077
|
+
featureOrder: input.order,
|
|
2078
|
+
lookup: input.lookup,
|
|
2079
|
+
resolveGpuBuffer: (reference) => gpuWork.resolveBuffer(input.featureIdentity, reference),
|
|
2080
|
+
resolvePipeline: (descriptor) => {
|
|
2081
|
+
const postProcessEntry =
|
|
2082
|
+
input.fullscreenEffects.get(descriptor.shader) ??
|
|
2083
|
+
builtinPostProcessEntries.get(descriptor.shader);
|
|
2084
|
+
if (postProcessEntry !== undefined) {
|
|
2085
|
+
const fullscreen = buildFullscreenPostProcessPass(
|
|
2086
|
+
{ device: runtime.device, errorRegistry: runtime.errorRegistry },
|
|
2087
|
+
postProcessEntry,
|
|
2088
|
+
);
|
|
2089
|
+
if (fullscreen === null) return err(new Error('prepared post-process layout failed'));
|
|
2090
|
+
const pipeline = runtime.getPostProcessPipeline?.(
|
|
2091
|
+
descriptor.shader,
|
|
2092
|
+
fullscreen.bindGroupLayout,
|
|
2093
|
+
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
2094
|
+
postProcessEntry,
|
|
2095
|
+
);
|
|
2096
|
+
if (pipeline !== null && pipeline !== undefined) {
|
|
2097
|
+
preparedPipelineIds.set(pipeline as object, descriptor.shader);
|
|
2098
|
+
if (runtime.getMaterialShaderBindingContract?.(descriptor.shader) === 'group-0') {
|
|
2099
|
+
preparedGroup0Pipelines.add(pipeline as object);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
return pipeline === null || pipeline === undefined
|
|
2103
|
+
? err(makePreparedPipelinePendingError())
|
|
2104
|
+
: ok(pipeline);
|
|
2105
|
+
}
|
|
2106
|
+
const preparedPipeline = runtime.getMaterialShaderPipeline?.(
|
|
2027
2107
|
descriptor.shader,
|
|
2028
|
-
|
|
2108
|
+
descriptor.colorFormats[0] === 'rgba16float',
|
|
2109
|
+
descriptor.renderState,
|
|
2110
|
+
descriptor.topology,
|
|
2111
|
+
descriptor.indexFormat,
|
|
2112
|
+
undefined,
|
|
2113
|
+
'forward',
|
|
2114
|
+
undefined,
|
|
2115
|
+
descriptor.sampleCount ?? 1,
|
|
2116
|
+
// Prepared graphics own their declared attachment format. Passing
|
|
2117
|
+
// it through keeps the PSO compatible with feature-target views
|
|
2118
|
+
// (the ordinary material path intentionally defaults to the
|
|
2119
|
+
// swap-chain view format).
|
|
2029
2120
|
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
2030
|
-
|
|
2121
|
+
undefined,
|
|
2122
|
+
descriptor.depthFormat === undefined
|
|
2123
|
+
? null
|
|
2124
|
+
: (descriptor.depthFormat as GPUTextureFormat),
|
|
2125
|
+
descriptor.vertexLayout,
|
|
2126
|
+
undefined,
|
|
2127
|
+
input.shaderModuleMode,
|
|
2031
2128
|
);
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2129
|
+
// A requested material shader is an exact pipeline contract. During
|
|
2130
|
+
// async shader warmup, substituting the generic unlit pipeline can
|
|
2131
|
+
// mismatch vertex layouts and HDR attachment formats, turning a
|
|
2132
|
+
// retryable next-frame prepare into an invalid GPU command buffer.
|
|
2133
|
+
const pipeline = preparedPipeline ?? null;
|
|
2134
|
+
if (pipeline !== null) {
|
|
2135
|
+
preparedMaterialPipelineShaders.set(pipeline as object, descriptor.shader);
|
|
2136
|
+
const bindingContract = runtime.getMaterialShaderBindingContract?.(descriptor.shader);
|
|
2137
|
+
if (bindingContract === 'group-0') {
|
|
2035
2138
|
preparedGroup0Pipelines.add(pipeline as object);
|
|
2139
|
+
} else if (bindingContract === 'view-only') {
|
|
2140
|
+
preparedViewOnlyPipelines.add(pipeline as object);
|
|
2141
|
+
} else {
|
|
2142
|
+
preparedRenderMaterialPipelines.add(pipeline as object);
|
|
2036
2143
|
}
|
|
2037
2144
|
}
|
|
2038
|
-
return pipeline === null
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
undefined,
|
|
2051
|
-
descriptor.sampleCount ?? 1,
|
|
2052
|
-
// Prepared graphics own their declared attachment format. Passing
|
|
2053
|
-
// it through keeps the PSO compatible with feature-target views
|
|
2054
|
-
// (the ordinary material path intentionally defaults to the
|
|
2055
|
-
// swap-chain view format).
|
|
2056
|
-
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
2057
|
-
undefined,
|
|
2058
|
-
descriptor.depthFormat === undefined
|
|
2059
|
-
? null
|
|
2060
|
-
: (descriptor.depthFormat as GPUTextureFormat),
|
|
2061
|
-
descriptor.vertexLayout,
|
|
2062
|
-
undefined,
|
|
2063
|
-
input.shaderModuleMode,
|
|
2064
|
-
);
|
|
2065
|
-
// A requested material shader is an exact pipeline contract. During
|
|
2066
|
-
// async shader warmup, substituting the generic unlit pipeline can
|
|
2067
|
-
// mismatch vertex layouts and HDR attachment formats, turning a
|
|
2068
|
-
// retryable next-frame prepare into an invalid GPU command buffer.
|
|
2069
|
-
const pipeline = preparedPipeline ?? null;
|
|
2070
|
-
if (pipeline !== null) {
|
|
2071
|
-
preparedMaterialPipelineShaders.set(pipeline as object, descriptor.shader);
|
|
2072
|
-
const bindingContract = internals.getMaterialShaderBindingContract?.(descriptor.shader);
|
|
2073
|
-
if (bindingContract === 'group-0') {
|
|
2074
|
-
preparedGroup0Pipelines.add(pipeline as object);
|
|
2075
|
-
} else if (bindingContract === 'view-only') {
|
|
2076
|
-
preparedViewOnlyPipelines.add(pipeline as object);
|
|
2077
|
-
} else {
|
|
2078
|
-
preparedRenderMaterialPipelines.add(pipeline as object);
|
|
2145
|
+
return pipeline === null ? err(makePreparedPipelinePendingError()) : ok(pipeline);
|
|
2146
|
+
},
|
|
2147
|
+
resolveBindings: (descriptor, pipeline) => {
|
|
2148
|
+
const materialShaderId = preparedMaterialPipelineShaders.get(pipeline as object);
|
|
2149
|
+
const bindingContract =
|
|
2150
|
+
materialShaderId === undefined
|
|
2151
|
+
? undefined
|
|
2152
|
+
: runtime.getMaterialShaderBindingContract?.(materialShaderId);
|
|
2153
|
+
if (bindingContract === 'group-0-resource') {
|
|
2154
|
+
return descriptor.values.sceneDepth === undefined
|
|
2155
|
+
? err(new Error('prepared group-0 resource pipeline requires a scene target'))
|
|
2156
|
+
: ok(undefined);
|
|
2079
2157
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2158
|
+
if (bindingContract === 'view-and-scene-depth') {
|
|
2159
|
+
return ok(undefined);
|
|
2160
|
+
}
|
|
2161
|
+
if (preparedGroup0Pipelines.has(pipeline as object)) {
|
|
2162
|
+
const layout =
|
|
2163
|
+
(materialShaderId === undefined
|
|
2164
|
+
? undefined
|
|
2165
|
+
: runtime.getMaterialBindGroupLayout?.(materialShaderId)) ??
|
|
2166
|
+
(
|
|
2167
|
+
pipeline as RenderPipeline & {
|
|
2168
|
+
getBindGroupLayout?: (index: number) => BindGroupLayout;
|
|
2169
|
+
}
|
|
2170
|
+
).getBindGroupLayout?.(0);
|
|
2171
|
+
return layout === undefined
|
|
2172
|
+
? err(new Error('prepared group-0 pipeline bind group layout is unavailable'))
|
|
2173
|
+
: runtime.device.createBindGroup({ layout, entries: [] });
|
|
2174
|
+
}
|
|
2175
|
+
if (
|
|
2176
|
+
preparedViewOnlyPipelines.has(pipeline as object) ||
|
|
2177
|
+
(preparedRenderMaterialPipelines.has(pipeline as object) &&
|
|
2178
|
+
descriptor.values.group === 0) ||
|
|
2179
|
+
pipeline === runtime.getPipelineState()?.unlitPipeline
|
|
2180
|
+
) {
|
|
2181
|
+
return ok(undefined);
|
|
2182
|
+
}
|
|
2183
|
+
const group = descriptor.values.group;
|
|
2098
2184
|
const layout =
|
|
2099
|
-
(materialShaderId
|
|
2100
|
-
?
|
|
2101
|
-
|
|
2185
|
+
(materialShaderId !== undefined
|
|
2186
|
+
? (runtime.getMaterialBindGroupLayout?.(materialShaderId) ??
|
|
2187
|
+
(group === 0 ? runtime.getPipelineState()?.materialBindGroupLayout : undefined))
|
|
2188
|
+
: undefined) ??
|
|
2102
2189
|
(
|
|
2103
2190
|
pipeline as RenderPipeline & {
|
|
2104
2191
|
getBindGroupLayout?: (index: number) => BindGroupLayout;
|
|
2105
2192
|
}
|
|
2106
|
-
).getBindGroupLayout?.(0);
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
pipeline as RenderPipeline & {
|
|
2127
|
-
getBindGroupLayout?: (index: number) => BindGroupLayout;
|
|
2128
|
-
}
|
|
2129
|
-
).getBindGroupLayout?.(group === 1 ? 1 : 0);
|
|
2130
|
-
const material = descriptor.values.material;
|
|
2131
|
-
if (
|
|
2132
|
-
group === 1 &&
|
|
2133
|
-
layout !== undefined &&
|
|
2134
|
-
materialShaderId !== undefined &&
|
|
2135
|
-
typeof material === 'object' &&
|
|
2136
|
-
material !== null &&
|
|
2137
|
-
'world' in material &&
|
|
2138
|
-
'guid' in material &&
|
|
2139
|
-
typeof material.world === 'number' &&
|
|
2140
|
-
typeof material.guid === 'string'
|
|
2141
|
-
) {
|
|
2142
|
-
return preparedMaterialBindings(materialShaderId, material.world, material.guid, layout);
|
|
2143
|
-
}
|
|
2144
|
-
return layout === undefined
|
|
2145
|
-
? err(new Error('prepared pipeline bind group layout is unavailable'))
|
|
2146
|
-
: internals.device.createBindGroup({
|
|
2193
|
+
).getBindGroupLayout?.(group === 1 ? 1 : 0);
|
|
2194
|
+
const material = descriptor.values.material;
|
|
2195
|
+
if (
|
|
2196
|
+
group === 1 &&
|
|
2197
|
+
layout !== undefined &&
|
|
2198
|
+
materialShaderId !== undefined &&
|
|
2199
|
+
typeof material === 'object' &&
|
|
2200
|
+
material !== null &&
|
|
2201
|
+
'world' in material &&
|
|
2202
|
+
'guid' in material &&
|
|
2203
|
+
typeof material.world === 'number' &&
|
|
2204
|
+
typeof material.guid === 'string'
|
|
2205
|
+
) {
|
|
2206
|
+
return preparedMaterialBindings(
|
|
2207
|
+
runtime,
|
|
2208
|
+
caches,
|
|
2209
|
+
worlds,
|
|
2210
|
+
materialShaderId,
|
|
2211
|
+
material.world,
|
|
2212
|
+
material.guid,
|
|
2147
2213
|
layout,
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
return layout === undefined
|
|
2217
|
+
? err(new Error('prepared pipeline bind group layout is unavailable'))
|
|
2218
|
+
: runtime.device.createBindGroup({
|
|
2219
|
+
layout,
|
|
2220
|
+
entries:
|
|
2221
|
+
group === 1 && preparedPipelineIds.has(pipeline as object)
|
|
2222
|
+
? [
|
|
2223
|
+
{
|
|
2224
|
+
binding: 0,
|
|
2225
|
+
resource: {
|
|
2226
|
+
kind: 'textureView',
|
|
2227
|
+
value: runtime.getPipelineState()?.fallbackTextureView as never,
|
|
2228
|
+
},
|
|
2156
2229
|
},
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2230
|
+
{
|
|
2231
|
+
binding: 1,
|
|
2232
|
+
resource: {
|
|
2233
|
+
kind: 'sampler',
|
|
2234
|
+
value: runtime.getPipelineState()?.defaultSampler as never,
|
|
2235
|
+
},
|
|
2163
2236
|
},
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2237
|
+
...(runtime.getPostProcessParamsBuffer?.(
|
|
2238
|
+
preparedPipelineIds.get(pipeline as object) ?? '',
|
|
2239
|
+
) === undefined
|
|
2240
|
+
? []
|
|
2241
|
+
: [
|
|
2242
|
+
{
|
|
2243
|
+
binding: 2,
|
|
2244
|
+
resource: {
|
|
2245
|
+
kind: 'buffer' as const,
|
|
2246
|
+
value: {
|
|
2247
|
+
buffer: runtime.getPostProcessParamsBuffer?.(
|
|
2248
|
+
preparedPipelineIds.get(pipeline as object) ?? '',
|
|
2249
|
+
) as never,
|
|
2250
|
+
},
|
|
2178
2251
|
},
|
|
2179
2252
|
},
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
]
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2253
|
+
]),
|
|
2254
|
+
]
|
|
2255
|
+
: [],
|
|
2256
|
+
});
|
|
2257
|
+
},
|
|
2258
|
+
});
|
|
2259
|
+
};
|
|
2260
|
+
const preparedResolverFactory = (
|
|
2261
|
+
input: RenderFeaturePreparedGraphicsResolverInput,
|
|
2262
|
+
): PreparedGraphicsResolver =>
|
|
2263
|
+
createPreparedResolverFactory(
|
|
2264
|
+
internals,
|
|
2265
|
+
featureGpuWork,
|
|
2266
|
+
preparedWorlds,
|
|
2267
|
+
activePreparedResolverCaches,
|
|
2268
|
+
)(input);
|
|
2269
|
+
const recoveryOwner = createRenderSystemRecovery({
|
|
2270
|
+
frameState,
|
|
2271
|
+
internals,
|
|
2272
|
+
getEnvironmentLifecycle: () => environmentLifecycle,
|
|
2273
|
+
getGpuDrivenProduction: () => gpuDrivenProduction,
|
|
2274
|
+
setGpuDrivenProduction: (production) => {
|
|
2275
|
+
gpuDrivenProduction = production;
|
|
2276
|
+
},
|
|
2277
|
+
disposeFeatureGpuWork,
|
|
2278
|
+
setFeatureGpuWork: (owner) => {
|
|
2279
|
+
featureGpuWork = owner;
|
|
2280
|
+
},
|
|
2281
|
+
getActiveFeaturePostProcessEntries: () => activeFeaturePostProcessEntries,
|
|
2282
|
+
setActiveFeaturePostProcessEntries: (entries) => {
|
|
2283
|
+
activeFeaturePostProcessEntries = entries;
|
|
2284
|
+
},
|
|
2285
|
+
postProcessPipelineCache,
|
|
2286
|
+
clearPostProcessPipelineCache,
|
|
2287
|
+
clearPostProcessPipelineEntry,
|
|
2288
|
+
invalidatePostProcessModule,
|
|
2289
|
+
persistentRenderScene,
|
|
2290
|
+
gpuDrivenShaderFactory,
|
|
2291
|
+
createFeatureGpuWorkOwner,
|
|
2292
|
+
pointsLinesOwner,
|
|
2293
|
+
createPreparedResolverFactory,
|
|
2294
|
+
createPreparedResolverCaches,
|
|
2295
|
+
});
|
|
2188
2296
|
return {
|
|
2189
2297
|
releaseProfilerCatalog(): void {
|
|
2190
2298
|
releaseProfilerCatalog?.();
|
|
@@ -2387,7 +2495,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2387
2495
|
renderReadLeases?: readonly RenderReadLease[],
|
|
2388
2496
|
timingCapture?: GpuTimingCapture,
|
|
2389
2497
|
): boolean {
|
|
2390
|
-
|
|
2498
|
+
const composition = canonicalizeWorldComposition(worlds, opts, renderReadLeases);
|
|
2499
|
+
const compositionWorlds = composition.worlds;
|
|
2500
|
+
const compositionLeases = composition.leases;
|
|
2501
|
+
preparedWorlds = compositionWorlds;
|
|
2391
2502
|
const profileSession = internals.profiler?.activeSession();
|
|
2392
2503
|
let ownsProfileFrame = false;
|
|
2393
2504
|
let submitted = false;
|
|
@@ -2403,7 +2514,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2403
2514
|
// cameraOwner drives the surfaced cameras + frustum
|
|
2404
2515
|
// cull; resourceOwner drives skylight/skybox/postProcess + per-world
|
|
2405
2516
|
// record config.
|
|
2406
|
-
const { cameraOwner, resourceOwner } =
|
|
2517
|
+
const { cameraOwner, resourceOwner } = composition.owners;
|
|
2407
2518
|
// Keep the compiled graph as last-known-good until the candidate topology
|
|
2408
2519
|
// compiles. The failed candidate frame does not execute it; successful
|
|
2409
2520
|
// replacement retires it atomically in ensureCompiledFrameGraph.
|
|
@@ -2438,15 +2549,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2438
2549
|
// the dedicated resourceOwner index. The variable is named
|
|
2439
2550
|
// `resourceWorld` so the record-stage reads are self-describing (they
|
|
2440
2551
|
// are resource-owner reads, not camera reads).
|
|
2441
|
-
const resourceWorld =
|
|
2552
|
+
const resourceWorld = compositionWorlds[resourceOwner] as World;
|
|
2442
2553
|
const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
|
|
2443
2554
|
return persistentRenderScene.extractComposition(
|
|
2444
|
-
|
|
2555
|
+
compositionWorlds,
|
|
2445
2556
|
{ cameraOwner, resourceOwner },
|
|
2446
2557
|
internals.assets.catalogEpoch,
|
|
2447
2558
|
(renderables) =>
|
|
2448
2559
|
extractFrames(
|
|
2449
|
-
|
|
2560
|
+
compositionWorlds,
|
|
2450
2561
|
{ cameraOwner, resourceOwner },
|
|
2451
2562
|
internals.assets,
|
|
2452
2563
|
internals.getPipelineState(),
|
|
@@ -2459,7 +2570,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2459
2570
|
: { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
|
|
2460
2571
|
},
|
|
2461
2572
|
),
|
|
2462
|
-
|
|
2573
|
+
compositionLeases,
|
|
2463
2574
|
);
|
|
2464
2575
|
});
|
|
2465
2576
|
const {
|
|
@@ -2491,16 +2602,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2491
2602
|
skylight,
|
|
2492
2603
|
);
|
|
2493
2604
|
latestCamera = cameras[0];
|
|
2494
|
-
persistentRenderScene.updateVisibilityFacet(
|
|
2605
|
+
persistentRenderScene.updateVisibilityFacet(compositionWorlds, latestCamera, renderables);
|
|
2495
2606
|
let occlusionProjection:
|
|
2496
2607
|
| import('./scene/visibility/occlusion-runtime').OcclusionFrameProjection
|
|
2497
2608
|
| undefined;
|
|
2498
2609
|
let occlusionFallback: LodOcclusionInspection['fallback'] = { active: false };
|
|
2499
|
-
const worldKeys = persistentRenderScene.visibilityWorldKeysFor(
|
|
2610
|
+
const worldKeys = persistentRenderScene.visibilityWorldKeysFor(compositionWorlds);
|
|
2500
2611
|
const slotByEntity = persistentRenderScene.compositionSlotByStableEntity();
|
|
2501
2612
|
if (latestCamera !== undefined) {
|
|
2502
2613
|
const camera = latestCamera;
|
|
2503
|
-
const cameraWorld =
|
|
2614
|
+
const cameraWorld = compositionWorlds[camera.worldId ?? cameraOwner];
|
|
2504
2615
|
const view = viewKey({
|
|
2505
2616
|
attachmentId: cameraWorld?.identity ?? 'missing-camera-world',
|
|
2506
2617
|
cameraEntity: camera.entityKey ?? 0,
|
|
@@ -2514,7 +2625,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2514
2625
|
cachedCandidates.cameraWorldIdentity === (cameraWorld?.identity ?? '')
|
|
2515
2626
|
? cachedCandidates.candidates
|
|
2516
2627
|
: renderables.flatMap((renderable) => {
|
|
2517
|
-
const world =
|
|
2628
|
+
const world = compositionWorlds[renderable.worldId];
|
|
2518
2629
|
const aabb = renderable.localAabb;
|
|
2519
2630
|
// Occlusion is a LOD transport concern. A plain mesh has no
|
|
2520
2631
|
// lower-detail range to select, so keep it on the ordinary CPU
|
|
@@ -2599,7 +2710,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2599
2710
|
latestCamera !== undefined &&
|
|
2600
2711
|
ensureOcclusionRuntime().prepareUnavailable
|
|
2601
2712
|
) {
|
|
2602
|
-
const cameraWorld =
|
|
2713
|
+
const cameraWorld = compositionWorlds[latestCamera.worldId ?? cameraOwner];
|
|
2603
2714
|
if (cameraWorld !== undefined) {
|
|
2604
2715
|
const fallbackView = viewKey({
|
|
2605
2716
|
attachmentId: cameraWorld.identity,
|
|
@@ -2609,7 +2720,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2609
2720
|
});
|
|
2610
2721
|
for (const renderable of renderables) {
|
|
2611
2722
|
if ((renderable.lods?.length ?? 0) === 0) continue;
|
|
2612
|
-
const world =
|
|
2723
|
+
const world = compositionWorlds[renderable.worldId];
|
|
2613
2724
|
if (world === undefined) continue;
|
|
2614
2725
|
const slot = slotByEntity.get(
|
|
2615
2726
|
worldEntityKey(
|
|
@@ -2632,7 +2743,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2632
2743
|
}
|
|
2633
2744
|
}
|
|
2634
2745
|
const visibilityProjection = persistentRenderScene.projectVisibility(
|
|
2635
|
-
|
|
2746
|
+
compositionWorlds,
|
|
2636
2747
|
latestCamera,
|
|
2637
2748
|
renderables,
|
|
2638
2749
|
dispatch,
|
|
@@ -2678,7 +2789,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2678
2789
|
const inspectionQuery = ensureOcclusionRuntime().inspect();
|
|
2679
2790
|
const inspectionBudget = persistentRenderScene.visibilityBudgetValue();
|
|
2680
2791
|
const inspectionWorlds = createLodWorldInspections(
|
|
2681
|
-
|
|
2792
|
+
compositionWorlds,
|
|
2682
2793
|
renderables,
|
|
2683
2794
|
submissionRenderables,
|
|
2684
2795
|
inspectionCamera,
|
|
@@ -2697,8 +2808,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2697
2808
|
: { active: false },
|
|
2698
2809
|
);
|
|
2699
2810
|
const attachmentByWorldKey = new Map<number, string>();
|
|
2700
|
-
for (let worldId = 0; worldId <
|
|
2701
|
-
const world =
|
|
2811
|
+
for (let worldId = 0; worldId < compositionWorlds.length; worldId += 1) {
|
|
2812
|
+
const world = compositionWorlds[worldId];
|
|
2702
2813
|
if (world === undefined) continue;
|
|
2703
2814
|
const worldKey = worldKeys[worldId] ?? worldId;
|
|
2704
2815
|
if (attachmentByWorldKey.has(worldKey)) {
|
|
@@ -2717,7 +2828,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2717
2828
|
sourceKey: `render-frame:${frameState.frameNumber}`,
|
|
2718
2829
|
},
|
|
2719
2830
|
view: {
|
|
2720
|
-
attachmentId:
|
|
2831
|
+
attachmentId:
|
|
2832
|
+
compositionWorlds[inspectionCamera.worldId ?? cameraOwner]?.identity ?? 'none',
|
|
2721
2833
|
cameraEntity: inspectionCamera.entityKey ?? 0,
|
|
2722
2834
|
viewRole: 'main',
|
|
2723
2835
|
viewGeneration: inspectionCamera.historyVersion ?? 0,
|
|
@@ -2859,7 +2971,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2859
2971
|
const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
|
|
2860
2972
|
if (internals.featureHost === undefined) return [];
|
|
2861
2973
|
const featureFrame = runRenderFeatureFrame(internals.featureHost, {
|
|
2862
|
-
worlds,
|
|
2974
|
+
worlds: compositionWorlds,
|
|
2863
2975
|
owner: resourceOwner,
|
|
2864
2976
|
frameNumber: frameState.frameNumber,
|
|
2865
2977
|
visibilitySnapshots: frame.featureVisibilitySnapshots,
|
|
@@ -3021,7 +3133,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3021
3133
|
skybox,
|
|
3022
3134
|
skyboxCount,
|
|
3023
3135
|
postProcessParams,
|
|
3024
|
-
|
|
3136
|
+
compositionWorlds,
|
|
3025
3137
|
recordProfilePhase,
|
|
3026
3138
|
{
|
|
3027
3139
|
owner: gpuDrivenProduction,
|
|
@@ -3034,7 +3146,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3034
3146
|
telemetryCandidateCount: lodCandidateCountForFrame,
|
|
3035
3147
|
...(occlusionProjection === undefined ? {} : { occlusion: occlusionProjection }),
|
|
3036
3148
|
},
|
|
3037
|
-
|
|
3149
|
+
compositionLeases,
|
|
3038
3150
|
featureGraphCandidate,
|
|
3039
3151
|
pointsLinesOwner,
|
|
3040
3152
|
{
|
|
@@ -3051,6 +3163,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3051
3163
|
timingCapture,
|
|
3052
3164
|
),
|
|
3053
3165
|
);
|
|
3166
|
+
if (submitted) {
|
|
3167
|
+
try {
|
|
3168
|
+
internals.recoveryColdWorkGuard?.finish();
|
|
3169
|
+
} catch (cause) {
|
|
3170
|
+
submitted = false;
|
|
3171
|
+
throw cause;
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3054
3174
|
if (submitted && lastLodOcclusionInspection !== undefined) {
|
|
3055
3175
|
const query = ensureOcclusionRuntime().inspect();
|
|
3056
3176
|
lastLodOcclusionInspection = {
|
|
@@ -3090,6 +3210,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3090
3210
|
};
|
|
3091
3211
|
}
|
|
3092
3212
|
if (submitted) {
|
|
3213
|
+
recoveryOwner.setLastSuccessfulFrameSeed({
|
|
3214
|
+
frame,
|
|
3215
|
+
worlds: compositionWorlds,
|
|
3216
|
+
cameraOwner: composition.owners.cameraOwner,
|
|
3217
|
+
resourceOwner: composition.owners.resourceOwner,
|
|
3218
|
+
width: Math.max(1, internals.canvas.width),
|
|
3219
|
+
height: Math.max(1, internals.canvas.height),
|
|
3220
|
+
});
|
|
3221
|
+
submittedFrameCount += 1;
|
|
3222
|
+
lastSubmittedGeneration = internals.deviceScope?.generation;
|
|
3093
3223
|
if (occlusionProjection === undefined) {
|
|
3094
3224
|
runProfiledRenderPhase(profileSession, 'record/occlusion-global-advance', () =>
|
|
3095
3225
|
ensureOcclusionRuntime().advanceSuccessfulSubmit(frameState.frameNumber),
|
|
@@ -3226,6 +3356,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3226
3356
|
get meshMaterialBindings(): readonly MeshMaterialBindingObservation[] {
|
|
3227
3357
|
return lastMeshMaterialBindings;
|
|
3228
3358
|
},
|
|
3359
|
+
get iblBinding(): import('./mesh-material-bindings').IblBindingInspection | undefined {
|
|
3360
|
+
return frameState.iblBindingInspection;
|
|
3361
|
+
},
|
|
3229
3362
|
get perFramePassNames(): readonly string[] {
|
|
3230
3363
|
return frameState.compiledFrameGraph?.inspect().passes.map((pass) => pass.name) ?? [];
|
|
3231
3364
|
},
|
|
@@ -3235,6 +3368,25 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3235
3368
|
get perFrameGraphInfo(): CompiledRenderGraphInfo | undefined {
|
|
3236
3369
|
return frameState.compiledFrameGraph?.inspect();
|
|
3237
3370
|
},
|
|
3371
|
+
get recoveryEvidence(): RecoveryProductionEvidence {
|
|
3372
|
+
const graph = frameState.compiledFrameGraph?.inspect();
|
|
3373
|
+
return Object.freeze({
|
|
3374
|
+
producerRoots: createRendererProducerRootMatrix(),
|
|
3375
|
+
graph: Object.freeze({
|
|
3376
|
+
ready: graph !== undefined,
|
|
3377
|
+
generation: frameState.graphGeneration,
|
|
3378
|
+
passCount: graph?.passes.length ?? 0,
|
|
3379
|
+
resourceCount: graph?.resources.length ?? 0,
|
|
3380
|
+
}),
|
|
3381
|
+
residency: Object.freeze({
|
|
3382
|
+
meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
|
|
3383
|
+
}),
|
|
3384
|
+
submissions: Object.freeze({
|
|
3385
|
+
count: submittedFrameCount,
|
|
3386
|
+
lastGeneration: lastSubmittedGeneration,
|
|
3387
|
+
}),
|
|
3388
|
+
});
|
|
3389
|
+
},
|
|
3238
3390
|
get volumetricFog(): import('./volume/inspection').VolumetricFogInspection {
|
|
3239
3391
|
return frameState.volumetricFogInspection;
|
|
3240
3392
|
},
|
|
@@ -3330,6 +3482,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3330
3482
|
throw cause;
|
|
3331
3483
|
}
|
|
3332
3484
|
},
|
|
3485
|
+
lookupPostProcess,
|
|
3486
|
+
prepareRecoveryGraphCandidate: recoveryOwner.prepareRecoveryGraphCandidate,
|
|
3487
|
+
submitCandidateSetup: recoveryOwner.submitCandidateSetup,
|
|
3488
|
+
publishRecoveryGraphCandidate: recoveryOwner.publishRecoveryGraphCandidate,
|
|
3489
|
+
discardRecoveryGraphCandidate: recoveryOwner.discardRecoveryGraphCandidate,
|
|
3333
3490
|
disposeFrameState(): void {
|
|
3334
3491
|
(internals as TimingSessionHost).gpuPassTimingSession?.dispose();
|
|
3335
3492
|
(internals as TimingSessionHost).gpuPassTimingSession = undefined;
|
|
@@ -3337,6 +3494,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3337
3494
|
(internals as TimingSessionHost).gpuPassTimingSubmittedWork = undefined;
|
|
3338
3495
|
(internals as TimingSessionHost).gpuPassTimingFrameIdentity = undefined;
|
|
3339
3496
|
(internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
|
|
3497
|
+
pointsLinesOwner.dispose();
|
|
3340
3498
|
persistentRenderScene.dispose();
|
|
3341
3499
|
gpuDrivenProduction.dispose();
|
|
3342
3500
|
occlusionRuntime?.dispose();
|
|
@@ -3436,7 +3594,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3436
3594
|
frameState.pointShadowAtlas = null;
|
|
3437
3595
|
}
|
|
3438
3596
|
},
|
|
3439
|
-
resetForRecover(): void {
|
|
3597
|
+
resetForRecover(retiringPipelineState?: PipelineState, replacementDevice?: RhiDevice): void {
|
|
3598
|
+
frameState.lastSuccessfulTemporalView = undefined;
|
|
3599
|
+
frameState.successfulTemporalFrameIndex = 0;
|
|
3600
|
+
frameState.pendingTemporalCommit = { kind: 'none' };
|
|
3601
|
+
// The active expansion buffers belong to the lost generation. Abandon
|
|
3602
|
+
// their handles without invoking destroy on a device that can no longer
|
|
3603
|
+
// service cleanup; the staged candidate owns the replacement buffers.
|
|
3604
|
+
pointsLinesOwner.abandonForDeviceLoss();
|
|
3440
3605
|
ssrFormatReceipt = undefined;
|
|
3441
3606
|
ssrFormatProbeGeneration = -1;
|
|
3442
3607
|
ssrFormatProbe = undefined;
|
|
@@ -3457,18 +3622,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3457
3622
|
updateTransmissionInspection(lastTransmissionAntialias, false);
|
|
3458
3623
|
persistentRenderScene.resetGpuForRecover();
|
|
3459
3624
|
gpuDrivenProduction.dispose();
|
|
3460
|
-
|
|
3625
|
+
// The active renderer still points at the lost device until the caller
|
|
3626
|
+
// completes the synchronous publication boundary. Bind the replacement
|
|
3627
|
+
// explicitly so the first published draw never routes through an owner
|
|
3628
|
+
// recreated against the old physical device.
|
|
3629
|
+
gpuDrivenProduction = GpuDrivenProduction.forDevice(
|
|
3630
|
+
replacementDevice ?? internals.device,
|
|
3631
|
+
gpuDrivenShaderFactory,
|
|
3632
|
+
);
|
|
3461
3633
|
occlusionRuntime?.dispose();
|
|
3462
3634
|
// The replacement device is installed by the host after this hook. Do
|
|
3463
3635
|
// not recreate device-bound query resources against the lost device;
|
|
3464
3636
|
// the first post-recovery frame lazily binds the current device.
|
|
3465
3637
|
occlusionRuntime = undefined;
|
|
3466
3638
|
disposeFeatureGpuWork();
|
|
3639
|
+
featureGpuWork = createFeatureGpuWorkOwner(internals);
|
|
3467
3640
|
reflectionProbeOwner.dispose();
|
|
3468
3641
|
// A recovered device cannot retain handles from the lost device. The
|
|
3469
3642
|
// Bloom owner abandons candidates and retires every committed generation
|
|
3470
3643
|
// before the new PipelineState is rebuilt.
|
|
3471
|
-
|
|
3644
|
+
(
|
|
3645
|
+
retiringPipelineState ?? internals.getPipelineState()
|
|
3646
|
+
)?.perPassResources.drainBloomResources?.();
|
|
3472
3647
|
// feat-20260622-s5 M3 / B-2 / w18: recover() rebuild drops device-bound
|
|
3473
3648
|
// state minted by the lost device. The active graph and per-entity caches
|
|
3474
3649
|
// must be discarded, not merely marked for destruction: their opaque
|
|
@@ -3554,113 +3729,147 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3554
3729
|
// the lost device, so recovery must invalidate this cache alongside the
|
|
3555
3730
|
// feature-host declarations and UBOs below.
|
|
3556
3731
|
postProcessPipelineCache.clear();
|
|
3557
|
-
// Logical declarations
|
|
3558
|
-
//
|
|
3559
|
-
postProcessParamsBuffers.clear();
|
|
3732
|
+
// Logical declarations and the active params bundle stay live until the
|
|
3733
|
+
// candidate post-process bundle crosses the publication boundary.
|
|
3560
3734
|
resetRenderFeatureGraphState(internals);
|
|
3561
3735
|
},
|
|
3736
|
+
prepareRecoveryPostProcessResources(device: RhiDevice) {
|
|
3737
|
+
return prepareRecoveryPostProcessResources(device);
|
|
3738
|
+
},
|
|
3739
|
+
publishRecoveryPostProcessResources(candidate: RecoveryPostProcessResources): void {
|
|
3740
|
+
publishRecoveryPostProcessResources(candidate);
|
|
3741
|
+
},
|
|
3742
|
+
discardRecoveryPostProcessResources(candidate: RecoveryPostProcessResources): void {
|
|
3743
|
+
discardRecoveryPostProcessResources(candidate);
|
|
3744
|
+
},
|
|
3562
3745
|
restorePostProcessResources(): void {
|
|
3563
|
-
const
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3746
|
+
const prepared = prepareRecoveryPostProcessResources(internals.device);
|
|
3747
|
+
if (!prepared.ok) throw prepared.error;
|
|
3748
|
+
publishRecoveryPostProcessResources(prepared.value);
|
|
3749
|
+
},
|
|
3750
|
+
prepareRecoveryRoots(runtime: RecoveryRootRuntime): RecoveryRootBundle {
|
|
3751
|
+
// Snapshot only CPU projections/declarations here. The root create
|
|
3752
|
+
// callbacks below must never close over a mutable active owner while
|
|
3753
|
+
// LifecycleTransaction is awaiting candidate construction.
|
|
3754
|
+
const candidateEnvironmentLifecycle = environmentLifecycle.createRecoveryCandidate(
|
|
3755
|
+
runtime.scope,
|
|
3756
|
+
);
|
|
3757
|
+
const roots: LifecycleResourceSpec<unknown>[] = [];
|
|
3758
|
+
let candidateEnvironmentGeneration: EnvironmentGeneration | undefined;
|
|
3759
|
+
let environmentRootCleaned = false;
|
|
3760
|
+
let published = false;
|
|
3761
|
+
if (candidateEnvironmentLifecycle.hasRecoveryFrame()) {
|
|
3762
|
+
const environmentRoot = candidateEnvironmentLifecycle.createRecoveryRoot(runtime.scope);
|
|
3763
|
+
roots.push({
|
|
3764
|
+
...environmentRoot,
|
|
3765
|
+
create: async () => {
|
|
3766
|
+
const generation = (await environmentRoot.create()) as EnvironmentGeneration;
|
|
3767
|
+
candidateEnvironmentGeneration = generation;
|
|
3768
|
+
return generation;
|
|
3769
|
+
},
|
|
3770
|
+
cleanup: (value) => {
|
|
3771
|
+
if (environmentRootCleaned) return;
|
|
3772
|
+
environmentRootCleaned = true;
|
|
3773
|
+
if (candidateEnvironmentGeneration === value) {
|
|
3774
|
+
candidateEnvironmentGeneration = undefined;
|
|
3775
|
+
}
|
|
3776
|
+
if (published) {
|
|
3777
|
+
candidateEnvironmentLifecycle.retirePublishedGeneration(
|
|
3778
|
+
value as EnvironmentGeneration,
|
|
3779
|
+
);
|
|
3780
|
+
} else {
|
|
3781
|
+
environmentRoot.cleanup(value);
|
|
3782
|
+
}
|
|
3783
|
+
},
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
const graphCandidate = runtime.graphCandidate;
|
|
3787
|
+
if (graphCandidate !== undefined) {
|
|
3788
|
+
const releaseGraphCandidate = (): void => graphCandidate.release();
|
|
3789
|
+
const compiledGraph = graphCandidate.frameState.compiledFrameGraph;
|
|
3790
|
+
if (compiledGraph !== null) {
|
|
3791
|
+
roots.push({
|
|
3792
|
+
kind: 'pipeline',
|
|
3793
|
+
create: () => {
|
|
3794
|
+
if (!runtime.scope.isAlive()) {
|
|
3795
|
+
throw new Error('Recovery graph candidate scope is not active.');
|
|
3796
|
+
}
|
|
3797
|
+
if (graphCandidate.frameState.compiledFrameGraph !== compiledGraph) {
|
|
3798
|
+
throw new Error('Recovery graph candidate was changed before aggregate assembly.');
|
|
3799
|
+
}
|
|
3800
|
+
return compiledGraph;
|
|
3801
|
+
},
|
|
3802
|
+
// All graph/feature/GPU-driven candidate resources share one
|
|
3803
|
+
// owner. The root is a real release seam, not a marker; the
|
|
3804
|
+
// candidate latch makes every root cleanup call harmless after
|
|
3805
|
+
// the first one.
|
|
3806
|
+
cleanup: releaseGraphCandidate,
|
|
3590
3807
|
});
|
|
3591
|
-
if (!created.ok) throw created.error;
|
|
3592
|
-
buffer = created.value;
|
|
3593
3808
|
}
|
|
3594
|
-
if (
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3809
|
+
if (graphCandidate.featureHost !== undefined) {
|
|
3810
|
+
const featureRoot = graphCandidate.featureHost.createRecoveryRoot(runtime.scope);
|
|
3811
|
+
roots.push({ ...featureRoot, cleanup: releaseGraphCandidate });
|
|
3812
|
+
}
|
|
3813
|
+
if (graphCandidate.featureGpuWork !== undefined) {
|
|
3814
|
+
const featureWorkRoot = graphCandidate.featureGpuWork.createRecoveryRoot(runtime.scope);
|
|
3815
|
+
roots.push({ ...featureWorkRoot, cleanup: releaseGraphCandidate });
|
|
3816
|
+
}
|
|
3817
|
+
if (graphCandidate.gpuDrivenProduction !== undefined) {
|
|
3818
|
+
const productionRoot = graphCandidate.gpuDrivenProduction.createRecoveryRoot(
|
|
3819
|
+
runtime.scope,
|
|
3820
|
+
);
|
|
3821
|
+
roots.push({ ...productionRoot, cleanup: releaseGraphCandidate });
|
|
3822
|
+
}
|
|
3823
|
+
if (graphCandidate.gpuDrivenScene?.state !== undefined) {
|
|
3824
|
+
const sceneRoot = graphCandidate.gpuDrivenScene.createRecoveryRoot(runtime.scope);
|
|
3825
|
+
roots.push({ ...sceneRoot, cleanup: releaseGraphCandidate });
|
|
3826
|
+
}
|
|
3827
|
+
if (graphCandidate.pointsLines !== undefined) {
|
|
3828
|
+
const pointsLinesRoot = graphCandidate.pointsLines.createRecoveryRoot(runtime.scope);
|
|
3829
|
+
roots.push({ ...pointsLinesRoot, cleanup: releaseGraphCandidate });
|
|
3830
|
+
}
|
|
3831
|
+
if (runtime.gpuStore.recoveryResourceCount() > 0) {
|
|
3832
|
+
const gpuStoreRoot = runtime.gpuStore.createRecoveryRoot(runtime.scope);
|
|
3833
|
+
roots.push({ ...gpuStoreRoot, cleanup: releaseGraphCandidate });
|
|
3598
3834
|
}
|
|
3599
3835
|
}
|
|
3836
|
+
let discarded = false;
|
|
3837
|
+
return {
|
|
3838
|
+
roots: Object.freeze(roots),
|
|
3839
|
+
publish: () => {
|
|
3840
|
+
if (published || discarded) return;
|
|
3841
|
+
if (
|
|
3842
|
+
candidateEnvironmentGeneration !== undefined &&
|
|
3843
|
+
!candidateEnvironmentGeneration.retired
|
|
3844
|
+
) {
|
|
3845
|
+
environmentLifecycle.retireActiveForReplacement();
|
|
3846
|
+
candidateEnvironmentLifecycle.publish(candidateEnvironmentGeneration);
|
|
3847
|
+
}
|
|
3848
|
+
published = true;
|
|
3849
|
+
environmentLifecycle = candidateEnvironmentLifecycle;
|
|
3850
|
+
Object.assign(frameState, { environmentLifecycle });
|
|
3851
|
+
},
|
|
3852
|
+
discard: () => {
|
|
3853
|
+
if (published || discarded) return;
|
|
3854
|
+
discarded = true;
|
|
3855
|
+
if (candidateEnvironmentGeneration !== undefined) {
|
|
3856
|
+
candidateEnvironmentLifecycle.discard(candidateEnvironmentGeneration);
|
|
3857
|
+
candidateEnvironmentGeneration = undefined;
|
|
3858
|
+
}
|
|
3859
|
+
candidateEnvironmentLifecycle.discardRecoveryCandidates();
|
|
3860
|
+
// The transaction can fail before it reaches any graph root (for
|
|
3861
|
+
// example while the environment root is created). Keep the graph,
|
|
3862
|
+
// feature, GPU-driven, and residency resources on the same real
|
|
3863
|
+
// candidate owner in that case; later scope/factory cleanup is a
|
|
3864
|
+
// harmless repeat through the candidate latch.
|
|
3865
|
+
graphCandidate?.release();
|
|
3866
|
+
runtime.scope.abandon();
|
|
3867
|
+
},
|
|
3868
|
+
};
|
|
3600
3869
|
},
|
|
3601
3870
|
};
|
|
3602
3871
|
}
|
|
3603
3872
|
|
|
3604
|
-
function buildOcclusionProxyVertices(
|
|
3605
|
-
aabb: ArrayLike<number>,
|
|
3606
|
-
world: ArrayLike<number>,
|
|
3607
|
-
camera: import('./render-contract').CameraSnapshot,
|
|
3608
|
-
): Float32Array {
|
|
3609
|
-
const view = computeViewMatrix(camera);
|
|
3610
|
-
const projection = computeProjectionMatrix(camera);
|
|
3611
|
-
const viewProjection = mat4.create();
|
|
3612
|
-
mat4.multiply(viewProjection, projection, view);
|
|
3613
|
-
const corners = [
|
|
3614
|
-
[aabb[0] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0],
|
|
3615
|
-
[aabb[3] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0],
|
|
3616
|
-
[aabb[3] ?? 0, aabb[4] ?? 0, aabb[2] ?? 0],
|
|
3617
|
-
[aabb[0] ?? 0, aabb[4] ?? 0, aabb[2] ?? 0],
|
|
3618
|
-
[aabb[0] ?? 0, aabb[1] ?? 0, aabb[5] ?? 0],
|
|
3619
|
-
[aabb[3] ?? 0, aabb[1] ?? 0, aabb[5] ?? 0],
|
|
3620
|
-
[aabb[3] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0],
|
|
3621
|
-
[aabb[0] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0],
|
|
3622
|
-
];
|
|
3623
|
-
const triangles = [
|
|
3624
|
-
0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1, 3, 2, 6, 3, 6, 7, 0, 3, 7, 0, 7, 4, 1, 5,
|
|
3625
|
-
6, 1, 6, 2,
|
|
3626
|
-
];
|
|
3627
|
-
const output = new Float32Array(triangles.length * 4);
|
|
3628
|
-
for (let index = 0; index < triangles.length; index += 1) {
|
|
3629
|
-
const corner = corners[triangles[index] ?? 0] ?? [0, 0, 0];
|
|
3630
|
-
const cx = corner[0] ?? 0;
|
|
3631
|
-
const cy = corner[1] ?? 0;
|
|
3632
|
-
const cz = corner[2] ?? 0;
|
|
3633
|
-
const x = (world[0] ?? 0) * cx + (world[4] ?? 0) * cy + (world[8] ?? 0) * cz + (world[12] ?? 0);
|
|
3634
|
-
const y = (world[1] ?? 0) * cx + (world[5] ?? 0) * cy + (world[9] ?? 0) * cz + (world[13] ?? 0);
|
|
3635
|
-
const z =
|
|
3636
|
-
(world[2] ?? 0) * cx + (world[6] ?? 0) * cy + (world[10] ?? 0) * cz + (world[14] ?? 0);
|
|
3637
|
-
const w =
|
|
3638
|
-
(world[3] ?? 0) * cx + (world[7] ?? 0) * cy + (world[11] ?? 0) * cz + (world[15] ?? 1);
|
|
3639
|
-
const base = index * 4;
|
|
3640
|
-
output[base] =
|
|
3641
|
-
(viewProjection[0] ?? 0) * x +
|
|
3642
|
-
(viewProjection[4] ?? 0) * y +
|
|
3643
|
-
(viewProjection[8] ?? 0) * z +
|
|
3644
|
-
(viewProjection[12] ?? 0) * w;
|
|
3645
|
-
output[base + 1] =
|
|
3646
|
-
(viewProjection[1] ?? 0) * x +
|
|
3647
|
-
(viewProjection[5] ?? 0) * y +
|
|
3648
|
-
(viewProjection[9] ?? 0) * z +
|
|
3649
|
-
(viewProjection[13] ?? 0) * w;
|
|
3650
|
-
output[base + 2] =
|
|
3651
|
-
(viewProjection[2] ?? 0) * x +
|
|
3652
|
-
(viewProjection[6] ?? 0) * y +
|
|
3653
|
-
(viewProjection[10] ?? 0) * z +
|
|
3654
|
-
(viewProjection[14] ?? 0) * w;
|
|
3655
|
-
output[base + 3] =
|
|
3656
|
-
(viewProjection[3] ?? 0) * x +
|
|
3657
|
-
(viewProjection[7] ?? 0) * y +
|
|
3658
|
-
(viewProjection[11] ?? 0) * z +
|
|
3659
|
-
(viewProjection[15] ?? 0) * w;
|
|
3660
|
-
}
|
|
3661
|
-
return output;
|
|
3662
|
-
}
|
|
3663
|
-
|
|
3664
3873
|
/** Resolve the build identity used by every inspection submit. */
|
|
3665
3874
|
export function resolveInspectionBuild(internals: Pick<RenderSystemInternals, 'build'>): string {
|
|
3666
3875
|
return internals.build ?? 'render-system';
|