@forgeax/engine-render 0.1.23 → 0.1.25
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 +231 -10
- 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/surface-projection.d.ts +24 -0
- package/dist/assembly/material/surface-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +188 -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/assembly/webgpu-vertex-layouts.d.ts +89 -0
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
- package/dist/assets/asset-decoders.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-FF7K3FSO.mjs → chunk-ADHHVYLW.mjs} +89 -89
- package/dist/chunk-ADHHVYLW.mjs.map +1 -0
- package/dist/{chunk-YALPS44A.mjs → chunk-E34VL5VI.mjs} +47 -47
- package/dist/chunk-E34VL5VI.mjs.map +1 -0
- package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
- package/dist/chunk-GE3SDD72.mjs.map +1 -0
- package/dist/{chunk-HZUPSYW7.mjs → chunk-HKXTW355.mjs} +105 -15
- package/dist/chunk-HKXTW355.mjs.map +1 -0
- package/dist/chunk-JSEDTZYS.mjs +413 -0
- package/dist/chunk-JSEDTZYS.mjs.map +1 -0
- package/dist/{chunk-FBC4R44J.mjs → chunk-KM2NOX2I.mjs} +57 -57
- package/dist/chunk-KM2NOX2I.mjs.map +1 -0
- package/dist/{chunk-DIYW3M5D.mjs → chunk-N3RT2EUR.mjs} +2288 -2238
- package/dist/chunk-N3RT2EUR.mjs.map +1 -0
- package/dist/{chunk-ZUWKAZ2I.mjs → chunk-TCLDX7SZ.mjs} +1167 -368
- package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
- package/dist/{chunk-QODA5UKU.mjs → chunk-ZZ4YQ474.mjs} +2 -2
- package/dist/chunk-ZZ4YQ474.mjs.map +1 -0
- package/dist/components/instances.d.ts +13 -31
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +20688 -16314
- 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 +34 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +5 -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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +27 -9
- package/dist/index.mjs.map +1 -1
- package/dist/instance-buffer-cache.d.ts +7 -1
- package/dist/instance-buffer-cache.d.ts.map +1 -1
- package/dist/instances.d.ts +101 -0
- package/dist/instances.d.ts.map +1 -0
- 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 +96 -11
- 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/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.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 +18 -2
- 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 +36 -4
- 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 +28 -5
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +11 -1
- package/dist/record/shadow-pass.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 +68 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +16 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +180 -22
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +131 -4
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +2 -0
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +31 -1
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/systems/skin-palette-allocator.d.ts +6 -0
- package/dist/systems/skin-palette-allocator.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__/gpu-scene.unit.test.ts +38 -2
- 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__/instances-store.unit.test.ts +46 -0
- package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +29 -22
- package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
- package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
- 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__/material-surface-authoring.test-d.ts +62 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
- package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -5
- 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 +127 -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__/skin-motion-regression.unit.test.ts +33 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
- package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
- package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
- package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
- 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 +83 -10088
- package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
- package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
- package/src/assembly/material/assembly.ts +18 -2
- package/src/assembly/material/standard-layer-projection.ts +16 -0
- package/src/assembly/material/surface-projection.ts +90 -0
- package/src/assembly/material-shader-policy.ts +932 -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 +4066 -0
- package/src/assembly/webgpu-renderer.ts +4027 -0
- package/src/assembly/webgpu-vertex-layouts.ts +132 -0
- package/src/assets/asset-decoders.ts +27 -4
- package/src/components/camera.ts +1 -1
- package/src/components/instances.ts +14 -197
- 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 +131 -3
- package/src/gpu-scene.ts +87 -31
- package/src/ibl/IblPipelineCache.ts +134 -50
- package/src/ibl/skylight-bind-group.ts +26 -0
- package/src/index.ts +10 -1
- package/src/instance-buffer-cache.ts +14 -1
- package/src/instances.ts +288 -0
- package/src/light-buffer-layout.ts +3 -0
- package/src/materials.ts +365 -53
- package/src/mesh-material-bindings.ts +59 -0
- package/src/occlusion-proxy.ts +64 -0
- package/src/pbr-pipeline.ts +139 -15
- package/src/pipeline-builder.ts +14 -10
- package/src/pipeline-spec.ts +6 -14
- 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 +34 -2
- package/src/record/frame-targets.ts +11 -1
- package/src/record/frame.ts +37 -4
- package/src/record/helpers.ts +1 -1
- package/src/record/main-pass-geometry.ts +727 -63
- package/src/record/main-pass-material.ts +316 -209
- package/src/record/main-pass-sprite-draws.ts +7 -8
- 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 +39 -4
- package/src/record/shadow-pass.ts +46 -99
- package/src/record/typed-frame-graph.ts +45 -1
- package/src/record/view-ubo.ts +3 -3
- package/src/recovery/render-system-candidate.ts +876 -0
- package/src/render-contract.ts +69 -7
- package/src/render-system-extract-tail.ts +3039 -0
- package/src/render-system-extract.ts +177 -3003
- package/src/render-system.ts +906 -662
- package/src/scene/render-scene-types.ts +2 -0
- package/src/scene/render-scene.ts +703 -37
- package/src/systems/skin-palette-allocator.ts +129 -7
- 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/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
- package/dist/chunk-4L3P6DT5.mjs.map +0 -1
- package/dist/chunk-4NPUASZR.mjs +0 -203
- 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-FF7K3FSO.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
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveVertexLayoutProjection,
|
|
3
|
+
type VertexLayoutProjection,
|
|
4
|
+
} from '@forgeax/engine-geometry';
|
|
5
|
+
import type {
|
|
6
|
+
MaterialRenderState,
|
|
7
|
+
PassKind,
|
|
8
|
+
PrimitiveTopology,
|
|
9
|
+
VertexAttributeMap,
|
|
10
|
+
} from '@forgeax/engine-types';
|
|
11
|
+
import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from '../materials';
|
|
12
|
+
import {
|
|
13
|
+
isStandardPbrMaterialShader,
|
|
14
|
+
SHADOW_CASTER_SHADER_ID,
|
|
15
|
+
SKIN_MATERIAL_SHADER_ID,
|
|
16
|
+
SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET,
|
|
17
|
+
shadowCasterVariantSet,
|
|
18
|
+
} from '../pbr-pipeline';
|
|
19
|
+
import type { StandardTopologyInputValue } from '../pipeline/standard-lighting/topology';
|
|
20
|
+
import {
|
|
21
|
+
cacheKeyOf,
|
|
22
|
+
colorFormatsForPassKind,
|
|
23
|
+
type PipelineSpec,
|
|
24
|
+
PipelineSpecError,
|
|
25
|
+
standardStorageVariantSet,
|
|
26
|
+
standardTopologyVariantSet,
|
|
27
|
+
variantSetFromVertexLayoutProjection,
|
|
28
|
+
} from '../pipeline-spec';
|
|
29
|
+
import { POINTS_LINES_MATERIAL_SHADER_ID } from '../points-lines/record';
|
|
30
|
+
import type { CameraSnapshot } from '../render-contract';
|
|
31
|
+
import type { DispatchEntry, MaterialSnapshot } from '../render-system-extract';
|
|
32
|
+
import type { ValidatedRenderable } from './frame-snapshot';
|
|
33
|
+
import { geometryRenderStateForPass } from './main-pass-geometry';
|
|
34
|
+
import { geometryRenderStateForTopology } from './main-pass-material';
|
|
35
|
+
import type {
|
|
36
|
+
MaterialShaderPipelineEntry,
|
|
37
|
+
PipelineState,
|
|
38
|
+
RecoveryColdWorkGuard,
|
|
39
|
+
RenderSystemInternals,
|
|
40
|
+
} from './render-context';
|
|
41
|
+
|
|
42
|
+
export interface RecoveryPipelineReadiness {
|
|
43
|
+
readonly pipelineSpecs: readonly PipelineSpec[];
|
|
44
|
+
/** Re-resolve every candidate request and assert a stable cache hit. */
|
|
45
|
+
readonly assertFirstRecoveryFrame: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create the one-shot assertion used by the first frame after publication.
|
|
50
|
+
* Recording a miss is a failure at the owner seam; the draw path never gets a
|
|
51
|
+
* chance to submit a frame that hides a shader/pipeline or static-residency
|
|
52
|
+
* cold build behind an apparently successful receipt.
|
|
53
|
+
*/
|
|
54
|
+
export function createRecoveryColdWorkGuard(): RecoveryColdWorkGuard {
|
|
55
|
+
let armed = false;
|
|
56
|
+
let finished = false;
|
|
57
|
+
let pipelineColdWork = 0;
|
|
58
|
+
let uploadColdWork = 0;
|
|
59
|
+
const fail = (kind: 'pipeline' | 'upload'): never => {
|
|
60
|
+
if (kind === 'pipeline') pipelineColdWork += 1;
|
|
61
|
+
else uploadColdWork += 1;
|
|
62
|
+
throw new Error(
|
|
63
|
+
`recovery first frame performed cold ${kind} work ` +
|
|
64
|
+
`(pipeline=${pipelineColdWork}, upload=${uploadColdWork})`,
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
arm(): void {
|
|
69
|
+
armed = true;
|
|
70
|
+
finished = false;
|
|
71
|
+
pipelineColdWork = 0;
|
|
72
|
+
uploadColdWork = 0;
|
|
73
|
+
},
|
|
74
|
+
notePipelineColdWork(): void {
|
|
75
|
+
if (armed && !finished) fail('pipeline');
|
|
76
|
+
},
|
|
77
|
+
noteUploadColdWork(): void {
|
|
78
|
+
if (armed && !finished) fail('upload');
|
|
79
|
+
},
|
|
80
|
+
finish(): void {
|
|
81
|
+
if (!armed || finished) return;
|
|
82
|
+
finished = true;
|
|
83
|
+
armed = false;
|
|
84
|
+
if (pipelineColdWork !== 0 || uploadColdWork !== 0) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`recovery first frame cold-work assertion failed ` +
|
|
87
|
+
`(pipeline=${pipelineColdWork}, upload=${uploadColdWork})`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface RecoveryPipelineRequest {
|
|
95
|
+
readonly spec: PipelineSpec;
|
|
96
|
+
readonly resolve: () => MaterialShaderPipelineEntry | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const DEFAULT_VERTEX_ATTRIBUTES: VertexAttributeMap = {
|
|
100
|
+
position: new Float32Array(0),
|
|
101
|
+
normal: new Float32Array(0),
|
|
102
|
+
uv: new Float32Array(0),
|
|
103
|
+
tangent: new Float32Array(0),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function optionalRenderState(
|
|
107
|
+
state: MaterialRenderState | undefined,
|
|
108
|
+
): { readonly renderState: MaterialRenderState } | Record<string, never> {
|
|
109
|
+
return state === undefined ? {} : { renderState: state };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function optionalVariant(
|
|
113
|
+
variant: string | undefined,
|
|
114
|
+
): { readonly variantSet: string } | Record<string, never> {
|
|
115
|
+
return variant === undefined ? {} : { variantSet: variant };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function optionalColorFormat(
|
|
119
|
+
format: GPUTextureFormat | undefined,
|
|
120
|
+
): { readonly colorFormatOverride: GPUTextureFormat } | Record<string, never> {
|
|
121
|
+
return format === undefined ? {} : { colorFormatOverride: format };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function projectionVariant(
|
|
125
|
+
projection: VertexLayoutProjection,
|
|
126
|
+
requested: string | undefined,
|
|
127
|
+
preserveUndefined = false,
|
|
128
|
+
): string | undefined {
|
|
129
|
+
if (preserveUndefined && requested === undefined) return undefined;
|
|
130
|
+
const resolved = variantSetFromVertexLayoutProjection(projection, requested);
|
|
131
|
+
if (!resolved.ok) {
|
|
132
|
+
throw new PipelineSpecError({
|
|
133
|
+
code: 'spec-inconsistent',
|
|
134
|
+
detail: { reason: resolved.error.message },
|
|
135
|
+
hint: 'recovery candidate variant must agree with the LKG vertex layout projection',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return resolved.value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function recoveryRenderState(
|
|
142
|
+
materialShaderId: string,
|
|
143
|
+
material: MaterialSnapshot,
|
|
144
|
+
topology: PrimitiveTopology,
|
|
145
|
+
): MaterialRenderState | undefined {
|
|
146
|
+
const sprite =
|
|
147
|
+
materialShaderId === 'forgeax::sprite' || materialShaderId === 'forgeax::sprite-lit';
|
|
148
|
+
if (sprite) {
|
|
149
|
+
return {
|
|
150
|
+
...material.renderState,
|
|
151
|
+
depthWriteEnabled: false,
|
|
152
|
+
depthCompare: 'less-equal',
|
|
153
|
+
cullMode: 'none',
|
|
154
|
+
blend: material.renderState?.blend ?? SPRITE_PREMULTIPLIED_ALPHA_BLEND,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return geometryRenderStateForTopology(topology, material.renderState);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function addRequest(input: {
|
|
161
|
+
readonly requests: RecoveryPipelineRequest[];
|
|
162
|
+
readonly requestKeys: Set<string>;
|
|
163
|
+
readonly internals: RenderSystemInternals;
|
|
164
|
+
readonly pipelineState: PipelineState;
|
|
165
|
+
readonly materialShaderId: string;
|
|
166
|
+
readonly isHdrTarget: boolean;
|
|
167
|
+
readonly renderState?: MaterialRenderState;
|
|
168
|
+
readonly topology?: PrimitiveTopology;
|
|
169
|
+
readonly indexFormat?: 'uint16' | 'uint32';
|
|
170
|
+
readonly variantSet?: string;
|
|
171
|
+
readonly passKind?: PassKind;
|
|
172
|
+
readonly sampleCount?: number;
|
|
173
|
+
readonly colorFormatOverride?: GPUTextureFormat;
|
|
174
|
+
readonly additionalColorFormats?: readonly GPUTextureFormat[];
|
|
175
|
+
readonly depthFormatOverride?: GPUTextureFormat | null;
|
|
176
|
+
readonly vertexLayoutProjection?: VertexLayoutProjection;
|
|
177
|
+
readonly preserveUndefinedVariant?: boolean;
|
|
178
|
+
readonly variantAlreadyResolved?: boolean;
|
|
179
|
+
}): void {
|
|
180
|
+
const passKind = input.passKind ?? 'forward';
|
|
181
|
+
const projection = input.vertexLayoutProjection;
|
|
182
|
+
const resolvedVariantSet =
|
|
183
|
+
input.variantAlreadyResolved === true
|
|
184
|
+
? input.variantSet
|
|
185
|
+
: projection === undefined
|
|
186
|
+
? input.variantSet
|
|
187
|
+
: projectionVariant(projection, input.variantSet, input.preserveUndefinedVariant === true);
|
|
188
|
+
const colorFormat = input.isHdrTarget
|
|
189
|
+
? 'rgba16float'
|
|
190
|
+
: (input.colorFormatOverride ?? input.pipelineState.colorAttachmentFormat);
|
|
191
|
+
const colorFormats =
|
|
192
|
+
input.additionalColorFormats === undefined
|
|
193
|
+
? colorFormatsForPassKind(passKind, colorFormat)
|
|
194
|
+
: [colorFormat, ...input.additionalColorFormats];
|
|
195
|
+
const depthFormat =
|
|
196
|
+
passKind === 'shadow-caster' || passKind === 'point-shadow-caster'
|
|
197
|
+
? 'depth32float'
|
|
198
|
+
: input.depthFormatOverride === null
|
|
199
|
+
? undefined
|
|
200
|
+
: (input.depthFormatOverride ?? 'depth24plus-stencil8');
|
|
201
|
+
const shaderUvSetCount = input.internals.getMaterialShaderUvSetCount?.(input.materialShaderId);
|
|
202
|
+
const spec: PipelineSpec = Object.freeze({
|
|
203
|
+
shader: Object.freeze({
|
|
204
|
+
id: input.materialShaderId,
|
|
205
|
+
passKind,
|
|
206
|
+
variantSet: resolvedVariantSet,
|
|
207
|
+
}),
|
|
208
|
+
attachments: Object.freeze({
|
|
209
|
+
colorFormats: Object.freeze([...colorFormats]),
|
|
210
|
+
depthFormat,
|
|
211
|
+
sampleCount: (input.sampleCount === 4 ? 4 : 1) as 1 | 4,
|
|
212
|
+
}),
|
|
213
|
+
geometry: Object.freeze({
|
|
214
|
+
topology: input.topology ?? 'triangle-list',
|
|
215
|
+
stripIndexFormat: input.indexFormat,
|
|
216
|
+
vertexLayout: DEFAULT_VERTEX_ATTRIBUTES,
|
|
217
|
+
...(projection === undefined ? {} : { vertexLayoutProjection: projection }),
|
|
218
|
+
...(shaderUvSetCount !== undefined && shaderUvSetCount > 1
|
|
219
|
+
? {
|
|
220
|
+
shaderUvSetCount,
|
|
221
|
+
}
|
|
222
|
+
: {}),
|
|
223
|
+
}),
|
|
224
|
+
renderState: input.renderState,
|
|
225
|
+
});
|
|
226
|
+
const key = cacheKeyOf(spec);
|
|
227
|
+
if (input.requestKeys.has(key)) return;
|
|
228
|
+
input.requestKeys.add(key);
|
|
229
|
+
const resolve = (): MaterialShaderPipelineEntry | null =>
|
|
230
|
+
input.internals.getMaterialShaderPipelineEntry?.(
|
|
231
|
+
input.materialShaderId,
|
|
232
|
+
input.isHdrTarget,
|
|
233
|
+
input.renderState,
|
|
234
|
+
input.topology,
|
|
235
|
+
input.indexFormat,
|
|
236
|
+
resolvedVariantSet,
|
|
237
|
+
passKind,
|
|
238
|
+
undefined,
|
|
239
|
+
input.sampleCount,
|
|
240
|
+
input.colorFormatOverride,
|
|
241
|
+
undefined,
|
|
242
|
+
undefined,
|
|
243
|
+
undefined,
|
|
244
|
+
projection,
|
|
245
|
+
undefined,
|
|
246
|
+
undefined,
|
|
247
|
+
input.additionalColorFormats,
|
|
248
|
+
) ?? null;
|
|
249
|
+
input.requests.push({ spec, resolve });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function addStandardMaterialRequests(input: {
|
|
253
|
+
readonly requests: RecoveryPipelineRequest[];
|
|
254
|
+
readonly requestKeys: Set<string>;
|
|
255
|
+
readonly internals: RenderSystemInternals;
|
|
256
|
+
readonly pipelineState: PipelineState;
|
|
257
|
+
readonly validated: readonly ValidatedRenderable[];
|
|
258
|
+
readonly standardLighting: StandardTopologyInputValue | undefined;
|
|
259
|
+
readonly sampleCount: number;
|
|
260
|
+
readonly isHdrTarget: boolean;
|
|
261
|
+
readonly colorFormatOverride?: GPUTextureFormat;
|
|
262
|
+
readonly reflectionFallbackAvailable: boolean;
|
|
263
|
+
}): void {
|
|
264
|
+
const pointsLinesProjection = deriveVertexLayoutProjection({
|
|
265
|
+
position: new Float32Array(0),
|
|
266
|
+
normal: new Float32Array(0),
|
|
267
|
+
uv: new Float32Array(0),
|
|
268
|
+
tangent: new Float32Array(0),
|
|
269
|
+
});
|
|
270
|
+
for (const entry of input.validated) {
|
|
271
|
+
if (entry.source.pointsLines !== undefined) {
|
|
272
|
+
const pointsLinesMaterial = entry.source.material;
|
|
273
|
+
addRequest({
|
|
274
|
+
requests: input.requests,
|
|
275
|
+
requestKeys: input.requestKeys,
|
|
276
|
+
internals: input.internals,
|
|
277
|
+
pipelineState: input.pipelineState,
|
|
278
|
+
materialShaderId: POINTS_LINES_MATERIAL_SHADER_ID,
|
|
279
|
+
isHdrTarget: input.isHdrTarget,
|
|
280
|
+
renderState: {
|
|
281
|
+
...pointsLinesMaterial.renderState,
|
|
282
|
+
cullMode: 'none',
|
|
283
|
+
},
|
|
284
|
+
topology: 'triangle-list',
|
|
285
|
+
indexFormat: 'uint32',
|
|
286
|
+
sampleCount: input.sampleCount,
|
|
287
|
+
...optionalColorFormat(input.colorFormatOverride),
|
|
288
|
+
vertexLayoutProjection: pointsLinesProjection,
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const meshProjection = entry.mesh.layoutProjection;
|
|
293
|
+
const hasVertexColor = meshProjection.attributes.some((attribute) => attribute.key === 'color');
|
|
294
|
+
const capabilityVariantSet = standardTopologyVariantSet(
|
|
295
|
+
input.standardLighting,
|
|
296
|
+
input.internals.device.caps.storageBuffer,
|
|
297
|
+
hasVertexColor,
|
|
298
|
+
);
|
|
299
|
+
// The probe record is an object-level ABI extension of the built-in
|
|
300
|
+
// Standard PBR family. Recovery must request the same variant that the
|
|
301
|
+
// live record path will select, otherwise the first post-recovery draw
|
|
302
|
+
// discovers a cold PSO and the cold-work guard correctly rejects it.
|
|
303
|
+
const probeBlendRecordAvailable =
|
|
304
|
+
input.internals.device.caps.storageBuffer && entry.source.probeBlendRecord !== undefined;
|
|
305
|
+
const standardPbrCapabilityVariantSet = standardTopologyVariantSet(
|
|
306
|
+
input.standardLighting,
|
|
307
|
+
input.internals.device.caps.storageBuffer,
|
|
308
|
+
hasVertexColor,
|
|
309
|
+
probeBlendRecordAvailable,
|
|
310
|
+
);
|
|
311
|
+
const unlitProjection = projectionVariant(meshProjection, undefined);
|
|
312
|
+
const unlitHasColor = unlitProjection === 'VERTEX_COLOR_AVAILABLE=true';
|
|
313
|
+
for (const submesh of entry.mesh.submeshes) {
|
|
314
|
+
const material = entry.source.materials[submesh.materialSlot] ?? entry.source.material;
|
|
315
|
+
const materialShaderId =
|
|
316
|
+
entry.source.skin === undefined
|
|
317
|
+
? (material.materialShaderId ?? 'forgeax::default-unlit')
|
|
318
|
+
: SKIN_MATERIAL_SHADER_ID;
|
|
319
|
+
const renderState = geometryRenderStateForPass(
|
|
320
|
+
recoveryRenderState(materialShaderId, material, submesh.topology) as ReturnType<
|
|
321
|
+
typeof geometryRenderStateForTopology
|
|
322
|
+
>,
|
|
323
|
+
'forward',
|
|
324
|
+
);
|
|
325
|
+
if (entry.source.skin !== undefined && submesh === entry.mesh.submeshes[0]) {
|
|
326
|
+
const skinProbeVariant = projectionVariant(meshProjection, standardPbrCapabilityVariantSet);
|
|
327
|
+
addRequest({
|
|
328
|
+
requests: input.requests,
|
|
329
|
+
requestKeys: input.requestKeys,
|
|
330
|
+
internals: input.internals,
|
|
331
|
+
pipelineState: input.pipelineState,
|
|
332
|
+
materialShaderId: SKIN_MATERIAL_SHADER_ID,
|
|
333
|
+
isHdrTarget: input.isHdrTarget,
|
|
334
|
+
...optionalRenderState(entry.source.material.renderState),
|
|
335
|
+
topology: entry.mesh.submeshes[0]?.topology ?? 'triangle-list',
|
|
336
|
+
indexFormat: entry.mesh.indexFormat,
|
|
337
|
+
...optionalVariant(skinProbeVariant),
|
|
338
|
+
sampleCount: input.sampleCount,
|
|
339
|
+
...optionalColorFormat(input.colorFormatOverride),
|
|
340
|
+
vertexLayoutProjection: meshProjection,
|
|
341
|
+
variantAlreadyResolved: true,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (
|
|
345
|
+
materialShaderId === 'forgeax::default-unlit' ||
|
|
346
|
+
materialShaderId === 'forgeax::default-standard-unlit'
|
|
347
|
+
) {
|
|
348
|
+
addRequest({
|
|
349
|
+
requests: input.requests,
|
|
350
|
+
requestKeys: input.requestKeys,
|
|
351
|
+
internals: input.internals,
|
|
352
|
+
pipelineState: input.pipelineState,
|
|
353
|
+
materialShaderId,
|
|
354
|
+
isHdrTarget: input.isHdrTarget,
|
|
355
|
+
...optionalRenderState(renderState),
|
|
356
|
+
topology: submesh.topology,
|
|
357
|
+
indexFormat: entry.mesh.indexFormat,
|
|
358
|
+
variantSet: standardStorageVariantSet(
|
|
359
|
+
input.internals.device.caps.storageBuffer,
|
|
360
|
+
unlitHasColor,
|
|
361
|
+
),
|
|
362
|
+
sampleCount: input.sampleCount,
|
|
363
|
+
...optionalColorFormat(input.colorFormatOverride),
|
|
364
|
+
vertexLayoutProjection: meshProjection,
|
|
365
|
+
});
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const isSprite =
|
|
369
|
+
materialShaderId === 'forgeax::sprite' || materialShaderId === 'forgeax::sprite-lit';
|
|
370
|
+
const materialCapabilityVariantSet = isStandardPbrMaterialShader(materialShaderId)
|
|
371
|
+
? standardPbrCapabilityVariantSet
|
|
372
|
+
: capabilityVariantSet;
|
|
373
|
+
const requestedVariant = isSprite
|
|
374
|
+
? materialShaderId === 'forgeax::sprite' && entry.source.spriteInstances !== undefined
|
|
375
|
+
? SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET
|
|
376
|
+
: undefined
|
|
377
|
+
: entry.variantSet === undefined
|
|
378
|
+
? materialCapabilityVariantSet
|
|
379
|
+
: materialCapabilityVariantSet === ''
|
|
380
|
+
? entry.variantSet
|
|
381
|
+
: `${materialCapabilityVariantSet}+${entry.variantSet}`;
|
|
382
|
+
const reflectionFallbackVariant =
|
|
383
|
+
input.reflectionFallbackAvailable && materialShaderId === 'forgeax::default-standard-pbr'
|
|
384
|
+
? (() => {
|
|
385
|
+
const projected = projectionVariant(meshProjection, requestedVariant);
|
|
386
|
+
return projected === ''
|
|
387
|
+
? projected
|
|
388
|
+
: `${projected}+REFLECTION_FALLBACK_AVAILABLE=true`;
|
|
389
|
+
})()
|
|
390
|
+
: requestedVariant;
|
|
391
|
+
addRequest({
|
|
392
|
+
requests: input.requests,
|
|
393
|
+
requestKeys: input.requestKeys,
|
|
394
|
+
internals: input.internals,
|
|
395
|
+
pipelineState: input.pipelineState,
|
|
396
|
+
materialShaderId,
|
|
397
|
+
isHdrTarget: input.isHdrTarget,
|
|
398
|
+
...optionalRenderState(renderState),
|
|
399
|
+
topology: submesh.topology,
|
|
400
|
+
indexFormat: entry.mesh.indexFormat,
|
|
401
|
+
...optionalVariant(reflectionFallbackVariant),
|
|
402
|
+
passKind: 'forward',
|
|
403
|
+
sampleCount: input.sampleCount,
|
|
404
|
+
...optionalColorFormat(input.colorFormatOverride),
|
|
405
|
+
...(input.reflectionFallbackAvailable &&
|
|
406
|
+
materialShaderId === 'forgeax::default-standard-pbr'
|
|
407
|
+
? { additionalColorFormats: ['rgba16float'] as const }
|
|
408
|
+
: {}),
|
|
409
|
+
vertexLayoutProjection: meshProjection,
|
|
410
|
+
preserveUndefinedVariant: isSprite && requestedVariant === undefined,
|
|
411
|
+
variantAlreadyResolved:
|
|
412
|
+
isSprite ||
|
|
413
|
+
(input.reflectionFallbackAvailable &&
|
|
414
|
+
materialShaderId === 'forgeax::default-standard-pbr'),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function addTransparentSpriteRequests(input: {
|
|
421
|
+
readonly requests: RecoveryPipelineRequest[];
|
|
422
|
+
readonly requestKeys: Set<string>;
|
|
423
|
+
readonly internals: RenderSystemInternals;
|
|
424
|
+
readonly pipelineState: PipelineState;
|
|
425
|
+
readonly validated: readonly ValidatedRenderable[];
|
|
426
|
+
readonly standardLighting: StandardTopologyInputValue | undefined;
|
|
427
|
+
readonly sampleCount: number;
|
|
428
|
+
readonly isHdrTarget: boolean;
|
|
429
|
+
readonly colorFormat: GPUTextureFormat;
|
|
430
|
+
}): void {
|
|
431
|
+
const spriteRenderState: MaterialRenderState = {
|
|
432
|
+
depthWriteEnabled: false,
|
|
433
|
+
depthCompare: 'less-equal',
|
|
434
|
+
cullMode: 'none',
|
|
435
|
+
blend: SPRITE_PREMULTIPLIED_ALPHA_BLEND,
|
|
436
|
+
};
|
|
437
|
+
// recordSpritePass resolves all three built-in sprite pipelines before it
|
|
438
|
+
// walks the visible entities. A generic transparent material is enough to
|
|
439
|
+
// enter that pass, so candidate preparation must warm the same complete
|
|
440
|
+
// request set even when the LKG contains no sprite entity. These calls also
|
|
441
|
+
// intentionally omit vertexLayoutProjection: the record path omits that
|
|
442
|
+
// argument and the factory's default sprite layout is the cache identity.
|
|
443
|
+
for (const request of [
|
|
444
|
+
{
|
|
445
|
+
materialShaderId: 'forgeax::sprite',
|
|
446
|
+
variantSet: undefined,
|
|
447
|
+
preserveUndefinedVariant: true,
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
materialShaderId: 'forgeax::sprite',
|
|
451
|
+
variantSet: SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET,
|
|
452
|
+
preserveUndefinedVariant: false,
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
materialShaderId: 'forgeax::sprite-lit',
|
|
456
|
+
variantSet: standardTopologyVariantSet(
|
|
457
|
+
input.standardLighting,
|
|
458
|
+
input.internals.device.caps.storageBuffer,
|
|
459
|
+
false,
|
|
460
|
+
),
|
|
461
|
+
preserveUndefinedVariant: false,
|
|
462
|
+
},
|
|
463
|
+
] as const) {
|
|
464
|
+
addRequest({
|
|
465
|
+
requests: input.requests,
|
|
466
|
+
requestKeys: input.requestKeys,
|
|
467
|
+
internals: input.internals,
|
|
468
|
+
pipelineState: input.pipelineState,
|
|
469
|
+
materialShaderId: request.materialShaderId,
|
|
470
|
+
isHdrTarget: input.isHdrTarget,
|
|
471
|
+
renderState: spriteRenderState,
|
|
472
|
+
topology: 'triangle-list',
|
|
473
|
+
...optionalVariant(request.variantSet),
|
|
474
|
+
sampleCount: input.sampleCount,
|
|
475
|
+
colorFormatOverride: input.colorFormat,
|
|
476
|
+
preserveUndefinedVariant: request.preserveUndefinedVariant,
|
|
477
|
+
variantAlreadyResolved: true,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
for (const entry of input.validated) {
|
|
482
|
+
const entityShaderId = entry.source.material.materialShaderId;
|
|
483
|
+
if (
|
|
484
|
+
entityShaderId === 'forgeax::sprite' ||
|
|
485
|
+
entityShaderId === 'forgeax::sprite-lit' ||
|
|
486
|
+
entry.source.skin !== undefined ||
|
|
487
|
+
entry.source.instances !== undefined
|
|
488
|
+
) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const projectedVariant = projectionVariant(
|
|
492
|
+
entry.mesh.layoutProjection,
|
|
493
|
+
standardTopologyVariantSet(
|
|
494
|
+
input.standardLighting,
|
|
495
|
+
input.internals.device.caps.storageBuffer,
|
|
496
|
+
entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
|
|
497
|
+
),
|
|
498
|
+
);
|
|
499
|
+
for (const submesh of entry.mesh.submeshes) {
|
|
500
|
+
const material = entry.source.materials[submesh.materialSlot] ?? entry.source.material;
|
|
501
|
+
if (material.transparent !== true || material.materialShaderId === undefined) continue;
|
|
502
|
+
addRequest({
|
|
503
|
+
requests: input.requests,
|
|
504
|
+
requestKeys: input.requestKeys,
|
|
505
|
+
internals: input.internals,
|
|
506
|
+
pipelineState: input.pipelineState,
|
|
507
|
+
materialShaderId: material.materialShaderId,
|
|
508
|
+
isHdrTarget: input.isHdrTarget,
|
|
509
|
+
...optionalRenderState(material.renderState),
|
|
510
|
+
topology: submesh.topology,
|
|
511
|
+
indexFormat: entry.mesh.indexFormat,
|
|
512
|
+
...optionalVariant(projectedVariant),
|
|
513
|
+
sampleCount: input.sampleCount,
|
|
514
|
+
colorFormatOverride: input.colorFormat,
|
|
515
|
+
vertexLayoutProjection: entry.mesh.layoutProjection,
|
|
516
|
+
variantAlreadyResolved: true,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function addShadowRequests(input: {
|
|
523
|
+
readonly requests: RecoveryPipelineRequest[];
|
|
524
|
+
readonly requestKeys: Set<string>;
|
|
525
|
+
readonly internals: RenderSystemInternals;
|
|
526
|
+
readonly pipelineState: PipelineState;
|
|
527
|
+
readonly validated: readonly ValidatedRenderable[];
|
|
528
|
+
readonly dispatch: readonly DispatchEntry[];
|
|
529
|
+
}): void {
|
|
530
|
+
addRequest({
|
|
531
|
+
requests: input.requests,
|
|
532
|
+
requestKeys: input.requestKeys,
|
|
533
|
+
internals: input.internals,
|
|
534
|
+
pipelineState: input.pipelineState,
|
|
535
|
+
materialShaderId: SHADOW_CASTER_SHADER_ID,
|
|
536
|
+
isHdrTarget: false,
|
|
537
|
+
topology: 'triangle-list',
|
|
538
|
+
variantSet: shadowCasterVariantSet(input.internals.device.caps.storageBuffer, false),
|
|
539
|
+
passKind: 'shadow-caster',
|
|
540
|
+
sampleCount: 1,
|
|
541
|
+
variantAlreadyResolved: true,
|
|
542
|
+
});
|
|
543
|
+
const customByRenderable = new Map<
|
|
544
|
+
number,
|
|
545
|
+
Map<number, { id: string; state: MaterialRenderState | undefined }>
|
|
546
|
+
>();
|
|
547
|
+
for (const dispatch of input.dispatch) {
|
|
548
|
+
if (dispatch.tags.LightMode !== 'ShadowCaster' || dispatch.materialShaderId === undefined)
|
|
549
|
+
continue;
|
|
550
|
+
let materials = customByRenderable.get(dispatch.renderableIndex);
|
|
551
|
+
if (materials === undefined) {
|
|
552
|
+
materials = new Map();
|
|
553
|
+
customByRenderable.set(dispatch.renderableIndex, materials);
|
|
554
|
+
}
|
|
555
|
+
materials.set(dispatch.materialHandle, {
|
|
556
|
+
id: dispatch.materialShaderId,
|
|
557
|
+
state: dispatch.renderState,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
for (const entry of input.validated) {
|
|
561
|
+
const triangles = entry.mesh.submeshes.filter(
|
|
562
|
+
(submesh) => submesh.topology === 'triangle-list' || submesh.topology === 'triangle-strip',
|
|
563
|
+
);
|
|
564
|
+
if (triangles.length === 0) continue;
|
|
565
|
+
if (entry.source.skin !== undefined) {
|
|
566
|
+
addRequest({
|
|
567
|
+
requests: input.requests,
|
|
568
|
+
requestKeys: input.requestKeys,
|
|
569
|
+
internals: input.internals,
|
|
570
|
+
pipelineState: input.pipelineState,
|
|
571
|
+
materialShaderId: SHADOW_CASTER_SHADER_ID,
|
|
572
|
+
isHdrTarget: false,
|
|
573
|
+
topology: entry.mesh.submeshes[0]?.topology ?? 'triangle-list',
|
|
574
|
+
indexFormat: entry.mesh.indexFormat,
|
|
575
|
+
variantSet: shadowCasterVariantSet(input.internals.device.caps.storageBuffer, true),
|
|
576
|
+
passKind: 'shadow-caster',
|
|
577
|
+
sampleCount: 1,
|
|
578
|
+
vertexLayoutProjection: entry.mesh.layoutProjection,
|
|
579
|
+
variantAlreadyResolved: true,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
for (const submesh of triangles) {
|
|
583
|
+
const material = entry.source.materials[submesh.materialSlot] ?? entry.source.material;
|
|
584
|
+
const custom = customByRenderable
|
|
585
|
+
.get(entry.renderableIndex)
|
|
586
|
+
?.get(material.materialHandle ?? 0);
|
|
587
|
+
if (
|
|
588
|
+
entry.source.skin === undefined &&
|
|
589
|
+
custom !== undefined &&
|
|
590
|
+
(custom.id !== 'forgeax::default-shadow-caster' || custom.state !== undefined)
|
|
591
|
+
) {
|
|
592
|
+
addRequest({
|
|
593
|
+
requests: input.requests,
|
|
594
|
+
requestKeys: input.requestKeys,
|
|
595
|
+
internals: input.internals,
|
|
596
|
+
pipelineState: input.pipelineState,
|
|
597
|
+
materialShaderId: custom.id,
|
|
598
|
+
isHdrTarget: false,
|
|
599
|
+
...optionalRenderState(custom.state),
|
|
600
|
+
topology: 'triangle-list',
|
|
601
|
+
passKind: 'shadow-caster',
|
|
602
|
+
sampleCount: 1,
|
|
603
|
+
preserveUndefinedVariant: true,
|
|
604
|
+
variantAlreadyResolved: true,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export function prepareRecoveryPipelineReadiness(input: {
|
|
612
|
+
readonly internals: RenderSystemInternals;
|
|
613
|
+
readonly pipelineState: PipelineState;
|
|
614
|
+
readonly camera: CameraSnapshot;
|
|
615
|
+
readonly standardLighting: StandardTopologyInputValue | undefined;
|
|
616
|
+
readonly validated: readonly ValidatedRenderable[];
|
|
617
|
+
readonly dispatch: readonly DispatchEntry[];
|
|
618
|
+
readonly shadowCastersActive: boolean;
|
|
619
|
+
readonly splitLdrSprite: boolean;
|
|
620
|
+
readonly reflectionFallbackAvailable: boolean;
|
|
621
|
+
}): RecoveryPipelineReadiness {
|
|
622
|
+
const requests: RecoveryPipelineRequest[] = [];
|
|
623
|
+
const requestKeys = new Set<string>();
|
|
624
|
+
const sampleCount =
|
|
625
|
+
input.camera.antialias === 'msaa' && input.internals.device.caps.backendKind !== 'wgpu-webgl2'
|
|
626
|
+
? 4
|
|
627
|
+
: 1;
|
|
628
|
+
const isHdrTarget = input.camera.tonemap !== 'none' || input.reflectionFallbackAvailable;
|
|
629
|
+
addStandardMaterialRequests({
|
|
630
|
+
requests,
|
|
631
|
+
requestKeys,
|
|
632
|
+
internals: input.internals,
|
|
633
|
+
pipelineState: input.pipelineState,
|
|
634
|
+
validated: input.validated,
|
|
635
|
+
standardLighting: input.standardLighting,
|
|
636
|
+
sampleCount,
|
|
637
|
+
isHdrTarget,
|
|
638
|
+
reflectionFallbackAvailable: input.reflectionFallbackAvailable,
|
|
639
|
+
});
|
|
640
|
+
if (input.splitLdrSprite) {
|
|
641
|
+
// The split pass is the non-tonemapped Standard scene target, not the
|
|
642
|
+
// swap-chain storage/view format. StandardForwardLane keeps that target
|
|
643
|
+
// rgba16float on every non-clear linear-LDR path; using the surface format
|
|
644
|
+
// here would prepare a different PipelineSpec from the record seam and
|
|
645
|
+
// force a first-frame PSO miss.
|
|
646
|
+
const colorFormat = 'rgba16float' as GPUTextureFormat;
|
|
647
|
+
addTransparentSpriteRequests({
|
|
648
|
+
requests,
|
|
649
|
+
requestKeys,
|
|
650
|
+
internals: input.internals,
|
|
651
|
+
pipelineState: input.pipelineState,
|
|
652
|
+
validated: input.validated,
|
|
653
|
+
standardLighting: input.standardLighting,
|
|
654
|
+
sampleCount,
|
|
655
|
+
isHdrTarget: isHdrTarget || colorFormat === 'rgba16float',
|
|
656
|
+
colorFormat,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
const hasTriangleGeometry = input.validated.some((entry) =>
|
|
660
|
+
entry.mesh.submeshes.some(
|
|
661
|
+
(submesh) => submesh.topology === 'triangle-list' || submesh.topology === 'triangle-strip',
|
|
662
|
+
),
|
|
663
|
+
);
|
|
664
|
+
if (input.shadowCastersActive && hasTriangleGeometry) {
|
|
665
|
+
addShadowRequests({
|
|
666
|
+
requests,
|
|
667
|
+
requestKeys,
|
|
668
|
+
internals: input.internals,
|
|
669
|
+
pipelineState: input.pipelineState,
|
|
670
|
+
validated: input.validated,
|
|
671
|
+
dispatch: input.dispatch,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
const assertFirstRecoveryFrame = (): void => {
|
|
675
|
+
for (const request of requests) {
|
|
676
|
+
const first = request.resolve();
|
|
677
|
+
const second = request.resolve();
|
|
678
|
+
if (first === null || second === null || first.pipeline !== second.pipeline) {
|
|
679
|
+
throw new PipelineSpecError({
|
|
680
|
+
code: 'pipeline-build-failed',
|
|
681
|
+
detail: {
|
|
682
|
+
spec: request.spec,
|
|
683
|
+
stablePipeline: first !== null && second !== null && first.pipeline === second.pipeline,
|
|
684
|
+
},
|
|
685
|
+
hint: 'the recovery candidate must resolve every LKG pipeline twice from its prepared cache',
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
assertFirstRecoveryFrame();
|
|
691
|
+
return {
|
|
692
|
+
pipelineSpecs: Object.freeze(requests.map((request) => request.spec)),
|
|
693
|
+
assertFirstRecoveryFrame,
|
|
694
|
+
};
|
|
695
|
+
}
|