@forgeax/engine-render 0.1.29 → 0.1.31
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 +248 -21
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +1 -1
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts +4 -3
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/dynamic-geometry-host.d.ts +6 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +6 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +26 -3
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
- package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
- package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/capture/scene-projection.d.ts +41 -0
- package/dist/capture/scene-projection.d.ts.map +1 -0
- package/dist/{chunk-BQVQOQJH.mjs → chunk-7MQ3XBSP.mjs} +20 -14
- package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
- package/dist/{chunk-S4DNKQYQ.mjs → chunk-C3DDSXYK.mjs} +86 -33
- package/dist/chunk-C3DDSXYK.mjs.map +1 -0
- package/dist/{chunk-BTWH7BP4.mjs → chunk-FUUO4UND.mjs} +73 -29
- package/dist/chunk-FUUO4UND.mjs.map +1 -0
- package/dist/{chunk-NRNXBLIJ.mjs → chunk-JRBDO2KB.mjs} +46 -6
- package/dist/chunk-JRBDO2KB.mjs.map +1 -0
- package/dist/{chunk-NICXSHAT.mjs → chunk-MDQJPY3T.mjs} +8 -6
- package/dist/chunk-MDQJPY3T.mjs.map +1 -0
- package/dist/{chunk-JQTSEI3Z.mjs → chunk-NO4WZCAH.mjs} +3 -3
- package/dist/chunk-NO4WZCAH.mjs.map +1 -0
- package/dist/{chunk-FUODTTIA.mjs → chunk-PSEQ6QPH.mjs} +621 -108
- package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
- package/dist/{chunk-33XZFTQU.mjs → chunk-S5UE2T5P.mjs} +48 -6
- package/dist/chunk-S5UE2T5P.mjs.map +1 -0
- package/dist/components/directional-light.d.ts +2 -2
- package/dist/components/dynamic-resolution.d.ts +15 -0
- package/dist/components/dynamic-resolution.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/reflection-probe.d.ts +1 -0
- package/dist/components/reflection-probe.d.ts.map +1 -1
- package/dist/components/skylight.d.ts +4 -1
- package/dist/components/skylight.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +2172 -796
- 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 +2 -2
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/dynamic-geometry.d.ts.map +1 -1
- package/dist/environment/background.d.ts +3 -2
- package/dist/environment/background.d.ts.map +1 -1
- package/dist/environment/ibl.d.ts +11 -0
- package/dist/environment/ibl.d.ts.map +1 -0
- package/dist/environment/storage.d.ts +14 -0
- package/dist/environment/storage.d.ts.map +1 -0
- package/dist/errors/gpu-driven.d.ts +2 -2
- package/dist/errors/gpu-driven.d.ts.map +1 -1
- package/dist/errors/render.d.ts +35 -1
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +7 -0
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +18 -1
- package/dist/extract/gpu-driven.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts +24 -0
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
- package/dist/gpu-driven/material-bindings.d.ts +7 -0
- package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
- package/dist/gpu-driven/prepared-draw.d.ts +2 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +27 -6
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-resource.d.ts +5 -2
- package/dist/gpu-resource.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +3 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +2 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +3 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +83 -81
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +7 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +16 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- package/dist/material-row.d.ts +10 -0
- package/dist/material-row.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +3 -1
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/render-extent.d.ts +51 -0
- package/dist/pipeline/render-extent.d.ts.map +1 -0
- package/dist/pipeline/standard-build-context.d.ts +7 -0
- package/dist/pipeline/standard-build-context.d.ts.map +1 -0
- package/dist/pipeline/standard-forward-lane.d.ts +3 -2
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +2 -1
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +3 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/post-process-errors.d.ts +9 -2
- package/dist/post-process-errors.d.ts.map +1 -1
- package/dist/prepare/extended-lighting/resources.d.ts +3 -3
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +3 -8
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +3 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +7 -2
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +6 -3
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts.map +1 -1
- package/dist/record/recovery-pipeline.d.ts +9 -1
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +34 -3
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -2
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +5 -2
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/reflection/background.d.ts +14 -0
- package/dist/reflection/background.d.ts.map +1 -0
- package/dist/reflection/filter.d.ts.map +1 -1
- package/dist/reflection/projection.d.ts +1 -0
- package/dist/reflection/projection.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts +4 -2
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +47 -32
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +17 -1
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +9 -2
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +29 -2
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +2 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/ssao-config.d.ts +8 -6
- package/dist/ssao-config.d.ts.map +1 -1
- package/dist/ssr/graph.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/temporal/standard-scene-data.d.ts +2 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/target-coverage-attachment.d.ts +15 -0
- package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
- package/dist/temporal/temporal-view.d.ts +6 -2
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/temporal/view.d.ts +6 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +6 -0
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +1 -0
- package/dist/volume/component.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
- package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
- package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
- package/src/__tests__/factory-contract.integration.test.ts +164 -1
- package/src/__tests__/frame-observation.contract.test.ts +21 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +1 -1
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +20 -1
- package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +0 -3
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +0 -3
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +11 -6
- package/src/__tests__/gpu-driven-production.integration.test.ts +13 -25
- package/src/__tests__/gpu-driven-view.unit.test.ts +1 -0
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +14 -1
- package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
- package/src/__tests__/gpu-scene.unit.test.ts +55 -1
- package/src/__tests__/materials-standard-contract.unit.test.ts +5 -1
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
- package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +6 -1
- package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +216 -5
- package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +68 -1
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
- package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
- package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -15
- package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +3 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +5 -1
- package/src/__tests__/render-extent.unit.test.ts +63 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +3 -2
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +173 -9
- package/src/__tests__/shader-manifest-fixture.ts +6 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +8 -1
- package/src/__tests__/specular-environment-scale.dawn.test.ts +4 -2
- package/src/__tests__/ssao-config.unit.test.ts +7 -0
- package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
- package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +51 -43
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +4 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +10 -5
- package/src/__tests__/ssr-graph-topology.unit.test.ts +2 -2
- package/src/__tests__/ssr-hiz.browser.test.ts +1 -1
- package/src/__tests__/ssr-hiz.dawn.test.ts +1 -1
- package/src/__tests__/ssr-spatial.integration.test.ts +1 -1
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +40 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +238 -1
- package/src/__tests__/standard-profile-oracle.unit.test.ts +27 -0
- package/src/__tests__/taa-coverage-edge.dawn.test.ts +6 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +5 -1
- package/src/__tests__/taa-stability-feedback.ts +1 -0
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +2 -2
- package/src/__tests__/temporal-view.unit.test.ts +17 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
- package/src/assembly/atmosphere-shader-prewarm.ts +3 -2
- package/src/assembly/device-feature-admission.ts +9 -4
- package/src/assembly/dynamic-geometry-host.ts +236 -163
- package/src/assembly/host-contract.ts +9 -0
- package/src/assembly/material/assembly.ts +350 -6
- package/src/assembly/material/pipeline-helpers.ts +100 -0
- package/src/assembly/material-shader-policy.ts +39 -18
- package/src/assembly/recovery/device-loss-fanout.ts +5 -0
- package/src/assembly/recovery/recovery-attempt.ts +2 -29
- package/src/assembly/recovery/renderer-recover.ts +6 -9
- package/src/assembly/renderer-facade.ts +22 -1
- package/src/assembly/shader-prewarm-policy.ts +14 -1
- package/src/assembly/webgpu-ready.ts +28 -19
- package/src/assembly/webgpu-renderer.ts +95 -95
- package/src/capture/scene-projection.ts +37 -0
- package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
- package/src/components/__tests__/light-radius.integration.test.ts +59 -0
- package/src/components/directional-light.ts +3 -3
- package/src/components/dynamic-resolution.ts +67 -0
- package/src/components/index.ts +1 -0
- package/src/components/light-helpers.ts +6 -1
- package/src/components/reflection-probe.ts +1 -0
- package/src/components/skylight.ts +4 -1
- package/src/device/device-scope.ts +13 -7
- package/src/device/gpu-residency.ts +11 -11
- package/src/dynamic-geometry.ts +13 -8
- package/src/environment/background.ts +30 -12
- package/src/environment/ibl.ts +211 -0
- package/src/environment/storage.ts +63 -0
- package/src/errors/gpu-driven.ts +10 -3
- package/src/errors/render.ts +64 -0
- package/src/extract/camera.ts +12 -1
- package/src/extract/contracts.ts +10 -0
- package/src/extract/gpu-driven.ts +72 -17
- package/src/gpu-driven/batch-topology.ts +33 -11
- package/src/gpu-driven/material-artifacts.ts +179 -0
- package/src/gpu-driven/material-bindings.ts +41 -0
- package/src/gpu-driven/prepared-draw.ts +13 -2
- package/src/gpu-driven/production-raster.ts +681 -164
- package/src/gpu-resource.ts +21 -2
- package/src/gpu-scene-schema.ts +11 -2
- package/src/gpu-scene.ts +14 -3
- package/src/hdrp-buffers.ts +69 -0
- package/src/ibl/IblPipelineCache.ts +5 -0
- package/src/ibl/skylight-bind-group.ts +27 -17
- package/src/index.ts +92 -81
- package/src/inspection-types.ts +16 -1
- package/src/material-row.ts +60 -11
- package/src/pbr-pipeline.ts +13 -2
- package/src/pipeline/render-extent.ts +111 -0
- package/src/pipeline/standard-build-context.ts +7 -0
- package/src/pipeline/standard-forward-lane.ts +41 -24
- package/src/pipeline/standard-pipeline.ts +47 -18
- package/src/pipeline/standard-post.ts +4 -0
- package/src/pipeline/standard-profile.ts +2 -1
- package/src/pipeline-builder.ts +12 -3
- package/src/plugin.ts +4 -0
- package/src/post-process-errors.ts +26 -18
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
- package/src/prepare/extended-lighting/resources.ts +3 -3
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
- package/src/record/__tests__/material-slot-plan.unit.test.ts +9 -0
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +47 -1
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +7 -3
- package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
- package/src/record/frame-lighting.ts +3 -1
- package/src/record/frame-snapshot.ts +13 -11
- package/src/record/frame.ts +150 -74
- package/src/record/main-pass-geometry.ts +60 -9
- package/src/record/main-pass-material.ts +47 -19
- package/src/record/main-pass.ts +63 -4
- package/src/record/prepared-material-bindings.ts +4 -0
- package/src/record/recovery-pipeline.ts +56 -0
- package/src/record/render-context.ts +34 -3
- package/src/record/shadow-pass.ts +187 -50
- package/src/record/typed-frame-graph.ts +60 -3
- package/src/record/view-ubo.ts +1 -1
- package/src/recovery/render-system-candidate.ts +54 -11
- package/src/reflection/background.ts +151 -0
- package/src/reflection/filter.ts +3 -0
- package/src/reflection/projection.ts +16 -6
- package/src/reflection/record-owner.ts +282 -86
- package/src/render-contract.ts +50 -32
- package/src/render-graph-primitives.ts +28 -3
- package/src/render-pipeline.ts +20 -1
- package/src/render-system-extract-tail.ts +23 -3
- package/src/render-system-extract.ts +127 -42
- package/src/render-system.ts +35 -7
- package/src/scene/render-scene.ts +8 -0
- package/src/ssao-config.ts +25 -8
- package/src/ssr/graph.ts +78 -58
- package/src/temporal/gpu.ts +5 -0
- package/src/temporal/standard-scene-data.ts +7 -1
- package/src/temporal/target-coverage-attachment.ts +72 -0
- package/src/temporal/temporal-view.ts +10 -4
- package/src/temporal/view.ts +12 -2
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +24 -9
- package/src/typed-render-graph-primitives.ts +38 -0
- package/src/volume/component.ts +1 -0
- package/src/volume/passes.ts +3 -3
- package/dist/chunk-33XZFTQU.mjs.map +0 -1
- package/dist/chunk-BQVQOQJH.mjs.map +0 -1
- package/dist/chunk-BTWH7BP4.mjs.map +0 -1
- package/dist/chunk-FUODTTIA.mjs.map +0 -1
- package/dist/chunk-JQTSEI3Z.mjs.map +0 -1
- package/dist/chunk-NICXSHAT.mjs.map +0 -1
- package/dist/chunk-NRNXBLIJ.mjs.map +0 -1
- package/dist/chunk-S4DNKQYQ.mjs.map +0 -1
package/src/errors/gpu-driven.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { MaterialShaderArtifactReceipt } from '@forgeax/engine-shader';
|
|
|
2
2
|
|
|
3
3
|
export type GpuDrivenPreparationErrorCode =
|
|
4
4
|
| 'missing-material-receipt'
|
|
5
|
+
| 'material-abi-not-executable'
|
|
5
6
|
| 'missing-uv'
|
|
6
7
|
| 'reflection-mismatch'
|
|
7
8
|
| 'vertex-input-mismatch'
|
|
@@ -13,6 +14,7 @@ export type GpuDrivenPreparationErrorCode =
|
|
|
13
14
|
|
|
14
15
|
export type GpuDrivenPreparationReason =
|
|
15
16
|
| 'material-receipt-missing'
|
|
17
|
+
| 'material-abi-not-executable'
|
|
16
18
|
| 'uv-set-missing'
|
|
17
19
|
| 'reflection-receipt-mismatch'
|
|
18
20
|
| 'vertex-semantic-mismatch'
|
|
@@ -51,10 +53,15 @@ const ERROR_POLICY: Readonly<
|
|
|
51
53
|
>
|
|
52
54
|
> = {
|
|
53
55
|
'missing-material-receipt': {
|
|
54
|
-
expected: 'a producer-owned
|
|
55
|
-
hint: 'cook and load the
|
|
56
|
+
expected: 'a producer-owned material program receipt is present',
|
|
57
|
+
hint: 'cook and load the selected material program artifact, then retry preparation',
|
|
56
58
|
recovery: 'rebuild-producer',
|
|
57
59
|
},
|
|
60
|
+
'material-abi-not-executable': {
|
|
61
|
+
expected: 'the published material ABI fits the GPU Scene submission layout',
|
|
62
|
+
hint: 'recook the material with a scene-index ABI supported by the GPU-driven owner or keep it on its authored lane',
|
|
63
|
+
recovery: 'route-cpu-lane',
|
|
64
|
+
},
|
|
58
65
|
'missing-uv': {
|
|
59
66
|
expected: 'the geometry exposes every UV set named by the material receipt',
|
|
60
67
|
hint: 'repair the geometry UV semantic or route the draw to the CPU semantic lane',
|
|
@@ -72,7 +79,7 @@ const ERROR_POLICY: Readonly<
|
|
|
72
79
|
},
|
|
73
80
|
'alpha-mask-mismatch': {
|
|
74
81
|
expected: 'the Alpha Mask cutoff is present in the material receipt',
|
|
75
|
-
hint: 'declare
|
|
82
|
+
hint: 'declare the pass coverage cutoff in the material schema and recook it',
|
|
76
83
|
recovery: 'rebuild-producer',
|
|
77
84
|
},
|
|
78
85
|
'resource-not-ready': {
|
package/src/errors/render.ts
CHANGED
|
@@ -1320,6 +1320,67 @@ export class AtmosphereInvalidParameterError extends Error {
|
|
|
1320
1320
|
}
|
|
1321
1321
|
}
|
|
1322
1322
|
|
|
1323
|
+
export interface DynamicResolutionInvalidParameterDetail {
|
|
1324
|
+
readonly field: 'targetGpuMs' | 'minScale' | 'maxScale';
|
|
1325
|
+
readonly value: number;
|
|
1326
|
+
readonly expected: string;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
export class DynamicResolutionInvalidParameterError extends Error {
|
|
1330
|
+
readonly code = 'dynamic-resolution-invalid-parameter' as const;
|
|
1331
|
+
readonly expected: string;
|
|
1332
|
+
readonly hint: string;
|
|
1333
|
+
readonly detail: DynamicResolutionInvalidParameterDetail;
|
|
1334
|
+
|
|
1335
|
+
constructor(detail: DynamicResolutionInvalidParameterDetail) {
|
|
1336
|
+
super(`DynamicResolution.${detail.field} is invalid`);
|
|
1337
|
+
this.name = 'DynamicResolutionInvalidParameterError';
|
|
1338
|
+
this.expected = `DynamicResolution.${detail.field} must be ${detail.expected}`;
|
|
1339
|
+
this.hint = `set DynamicResolution.${detail.field} to a value that is ${detail.expected}`;
|
|
1340
|
+
this.detail = detail;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
export interface DynamicResolutionRequiresTaaDetail {
|
|
1345
|
+
readonly antialias: 'none' | 'fxaa' | 'msaa';
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
export class DynamicResolutionRequiresTaaError extends Error {
|
|
1349
|
+
readonly code = 'dynamic-resolution-requires-taa' as const;
|
|
1350
|
+
readonly expected = "DynamicResolution requires Camera.antialias to be 'taa'";
|
|
1351
|
+
readonly hint = "set Camera.antialias to 'taa' or remove DynamicResolution before retrying";
|
|
1352
|
+
readonly detail: DynamicResolutionRequiresTaaDetail;
|
|
1353
|
+
|
|
1354
|
+
constructor(detail: DynamicResolutionRequiresTaaDetail) {
|
|
1355
|
+
super('DynamicResolution requires TAA');
|
|
1356
|
+
this.name = 'DynamicResolutionRequiresTaaError';
|
|
1357
|
+
this.detail = detail;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
export interface DynamicResolutionTimingUnavailableDetail {
|
|
1362
|
+
readonly generation: number;
|
|
1363
|
+
readonly operation: 'timestamp-query';
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
export class DynamicResolutionTimingUnavailableError extends Error {
|
|
1367
|
+
readonly code = 'dynamic-resolution-timing-unavailable' as const;
|
|
1368
|
+
readonly expected = 'adaptive resolution receives a completed GPU timing sample';
|
|
1369
|
+
readonly hint = 'use fixed minScale=maxScale or retry after timestamp support is available';
|
|
1370
|
+
readonly detail: DynamicResolutionTimingUnavailableDetail;
|
|
1371
|
+
|
|
1372
|
+
constructor(detail: DynamicResolutionTimingUnavailableDetail) {
|
|
1373
|
+
super(`DynamicResolution timing is unavailable for generation ${detail.generation}`);
|
|
1374
|
+
this.name = 'DynamicResolutionTimingUnavailableError';
|
|
1375
|
+
this.detail = detail;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
export type DynamicResolutionError =
|
|
1380
|
+
| DynamicResolutionInvalidParameterError
|
|
1381
|
+
| DynamicResolutionRequiresTaaError
|
|
1382
|
+
| DynamicResolutionTimingUnavailableError;
|
|
1383
|
+
|
|
1323
1384
|
export interface OwnerStageFailedDetail {
|
|
1324
1385
|
readonly owner: 'environment' | 'temporal' | 'fog' | 'renderer';
|
|
1325
1386
|
readonly stage: 'extract' | 'prepare' | 'record' | 'finish' | 'submit';
|
|
@@ -1908,6 +1969,9 @@ export type RenderError =
|
|
|
1908
1969
|
| TaaCapsInsufficientError
|
|
1909
1970
|
| EnvironmentGenerationFailedError
|
|
1910
1971
|
| AtmosphereInvalidParameterError
|
|
1972
|
+
| DynamicResolutionInvalidParameterError
|
|
1973
|
+
| DynamicResolutionRequiresTaaError
|
|
1974
|
+
| DynamicResolutionTimingUnavailableError
|
|
1911
1975
|
| OwnerStageFailedError
|
|
1912
1976
|
| PointsLinesInvalidStyleError
|
|
1913
1977
|
| PointsLinesTopologyMismatchError
|
package/src/extract/camera.ts
CHANGED
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
tonemapFromF32,
|
|
12
12
|
validateCameraColorGrading,
|
|
13
13
|
} from '../components/camera';
|
|
14
|
+
import {
|
|
15
|
+
DynamicResolution,
|
|
16
|
+
validateDynamicResolutionCamera,
|
|
17
|
+
} from '../components/dynamic-resolution';
|
|
14
18
|
import { MotionBlur } from '../components/motion-blur';
|
|
15
19
|
import { ScreenSpaceReflection } from '../components/screen-space-reflection';
|
|
16
20
|
import type { CameraSnapshot } from '../render-contract';
|
|
@@ -28,7 +32,7 @@ export function extractCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
28
32
|
const cameraQuery = world
|
|
29
33
|
.query({
|
|
30
34
|
read: [Camera],
|
|
31
|
-
optional: [MotionBlur, ScreenSpaceReflection],
|
|
35
|
+
optional: [MotionBlur, DynamicResolution, ScreenSpaceReflection],
|
|
32
36
|
with: [GlobalTransform],
|
|
33
37
|
})
|
|
34
38
|
.unwrap();
|
|
@@ -36,10 +40,14 @@ export function extractCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
36
40
|
const cam = row.get(Camera);
|
|
37
41
|
const motionBlur = row.get(MotionBlur);
|
|
38
42
|
validateCameraColorGrading(cam.temperature, cam.tint, cam.colorLutStrength);
|
|
43
|
+
const dynamicResolution = row.get(DynamicResolution);
|
|
39
44
|
const screenSpaceReflection = row.get(ScreenSpaceReflection);
|
|
40
45
|
const entity = row.entity as EntityHandle;
|
|
41
46
|
const worldMat = readWorldMatrix(world, entity);
|
|
42
47
|
if (worldMat === undefined) continue;
|
|
48
|
+
const antialias = antialiasFromF32(cam.antialias);
|
|
49
|
+
const dynamicResolutionResult = validateDynamicResolutionCamera(dynamicResolution, antialias);
|
|
50
|
+
if (!dynamicResolutionResult.ok) throw dynamicResolutionResult.error;
|
|
43
51
|
cameras.push({
|
|
44
52
|
entityKey: entity as number,
|
|
45
53
|
historyVersion: cam.historyVersion,
|
|
@@ -65,6 +73,9 @@ export function extractCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
65
73
|
colorLutStrength: cam.colorLutStrength,
|
|
66
74
|
},
|
|
67
75
|
antialias: antialiasFromF32(cam.antialias),
|
|
76
|
+
...(dynamicResolutionResult.value === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: { dynamicResolution: dynamicResolutionResult.value }),
|
|
68
79
|
bloom: bloomEnabledFromF32(cam.bloom),
|
|
69
80
|
bloomThreshold: cam.bloomThreshold,
|
|
70
81
|
bloomIntensity: cam.bloomIntensity,
|
package/src/extract/contracts.ts
CHANGED
|
@@ -660,6 +660,10 @@ export interface MaterialSnapshot {
|
|
|
660
660
|
*/
|
|
661
661
|
readonly materialShaderId?: string | undefined;
|
|
662
662
|
readonly materialProgramKeys?: Readonly<Record<string, string>> | undefined;
|
|
663
|
+
/** Producer-selected scene-index program keys, when a cooked publication carries them. */
|
|
664
|
+
readonly materialSceneIndexProgramKeys?:
|
|
665
|
+
| Readonly<Record<string, MaterialSceneIndexProgramKey>>
|
|
666
|
+
| undefined;
|
|
663
667
|
/**
|
|
664
668
|
* Stable source handle for the material asset. Record uses this only for
|
|
665
669
|
* same-frame assembly reuse; GPU bind-group validity still comes from the
|
|
@@ -776,6 +780,12 @@ export interface MaterialSnapshot {
|
|
|
776
780
|
readonly transparent?: boolean | undefined;
|
|
777
781
|
}
|
|
778
782
|
|
|
783
|
+
/** Producer-selected scene-index key plus the Pass kind that owns it. */
|
|
784
|
+
export interface MaterialSceneIndexProgramKey {
|
|
785
|
+
readonly specializationKey: string;
|
|
786
|
+
readonly pass: 'forward' | 'shadow' | 'depth';
|
|
787
|
+
}
|
|
788
|
+
|
|
779
789
|
export interface MaterialCacheChainIdentity {
|
|
780
790
|
readonly asset: MaterialAsset;
|
|
781
791
|
readonly parentGuid: string | undefined;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type PrepareGpuDrivenDrawInput,
|
|
11
11
|
prepareGpuDrivenDraw,
|
|
12
12
|
} from '../gpu-driven/prepared-draw';
|
|
13
|
-
import {
|
|
13
|
+
import { isCanonicalStandardPbrMaterialShader } from '../pbr-pipeline';
|
|
14
14
|
import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
|
|
15
15
|
|
|
16
16
|
export {
|
|
@@ -72,6 +72,26 @@ export function gpuDrivenShadowDrawKey(
|
|
|
72
72
|
return `${gpuDrivenDrawKey(worldEntity, materialHandle, drawItemIndex)}:${passIndex}`;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Stable lookup key for the producer-published material artifact selected by
|
|
77
|
+
* one prepared draw. The receipt identity is intentionally part of the key:
|
|
78
|
+
* a COLOR_0 variant (or any later ABI variant) must never share a pipeline or
|
|
79
|
+
* projection cache entry with its no-color sibling.
|
|
80
|
+
*/
|
|
81
|
+
export function gpuDrivenMaterialArtifactKey(input: {
|
|
82
|
+
readonly material: string;
|
|
83
|
+
readonly deformation: 'rigid' | 'skin';
|
|
84
|
+
readonly receiptIdentity: string | undefined;
|
|
85
|
+
readonly receiptGeneration: number;
|
|
86
|
+
}): string {
|
|
87
|
+
return [
|
|
88
|
+
input.material,
|
|
89
|
+
input.deformation,
|
|
90
|
+
input.receiptIdentity ?? '<legacy-receipt>',
|
|
91
|
+
input.receiptGeneration,
|
|
92
|
+
].join('|');
|
|
93
|
+
}
|
|
94
|
+
|
|
75
95
|
/** Resolve the producer-owned source identity for one compact draw snapshot. */
|
|
76
96
|
export function gpuDrivenSourceDrawItemIndex(
|
|
77
97
|
draw: GpuDrivenDrawSnapshot,
|
|
@@ -136,7 +156,12 @@ interface BuildGpuDrivenDrawsInput {
|
|
|
136
156
|
readonly baseSnapshot: RenderableSnapshot;
|
|
137
157
|
readonly getMaterialShaderArtifact?: (
|
|
138
158
|
materialShaderId: string,
|
|
139
|
-
|
|
159
|
+
request?: {
|
|
160
|
+
readonly vertexColorAvailable?: boolean;
|
|
161
|
+
readonly deformation?: 'rigid' | 'skin';
|
|
162
|
+
readonly pass?: 'forward' | 'shadow' | 'depth';
|
|
163
|
+
readonly address?: 'direct' | 'scene-index';
|
|
164
|
+
},
|
|
140
165
|
) => MaterialShaderArtifact | undefined;
|
|
141
166
|
/** Resolved lower-detail meshes, in the same order as baseSnapshot.lods. */
|
|
142
167
|
readonly lodMeshes?: readonly (MeshAsset | undefined)[];
|
|
@@ -224,16 +249,38 @@ export function buildGpuDrivenDraws(
|
|
|
224
249
|
? undefined
|
|
225
250
|
: (() => {
|
|
226
251
|
const materialShaderId = drawMaterial.materialShaderId;
|
|
252
|
+
const vertexColorAvailable = geometry.vertexInputs.some(
|
|
253
|
+
(input) => input.semantic === 'color',
|
|
254
|
+
);
|
|
255
|
+
// The snapshot keeps the direct program key as its logical
|
|
256
|
+
// material identity. A published scene-index program has a
|
|
257
|
+
// separate specialization key; resolve that concrete artifact
|
|
258
|
+
// before preparing the draw so the indirect pipeline receives the
|
|
259
|
+
// scene entry rather than silently reusing the direct shader.
|
|
260
|
+
const sceneIndexProgramKey = Object.entries(
|
|
261
|
+
drawMaterial.materialSceneIndexProgramKeys ?? {},
|
|
262
|
+
).find(
|
|
263
|
+
([passName, program]) =>
|
|
264
|
+
program.pass === 'forward' &&
|
|
265
|
+
drawMaterial.materialProgramKeys?.[passName] === materialShaderId,
|
|
266
|
+
)?.[1]?.specializationKey;
|
|
227
267
|
const artifact =
|
|
228
268
|
materialShaderId === undefined
|
|
229
269
|
? undefined
|
|
230
|
-
: input.getMaterialShaderArtifact?.(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
270
|
+
: input.getMaterialShaderArtifact?.(sceneIndexProgramKey ?? materialShaderId, {
|
|
271
|
+
vertexColorAvailable,
|
|
272
|
+
deformation: input.baseSnapshot.skin === undefined ? 'rigid' : 'skin',
|
|
273
|
+
pass: 'forward',
|
|
274
|
+
address: 'scene-index',
|
|
275
|
+
});
|
|
276
|
+
// Canonical roots and explicitly published scene-index programs
|
|
277
|
+
// require their producer receipt. Legacy direct-only aliases do not.
|
|
278
|
+
const requiresReceipt =
|
|
279
|
+
isCanonicalStandardPbrMaterialShader(materialShaderId) ||
|
|
280
|
+
sceneIndexProgramKey !== undefined;
|
|
281
|
+
|
|
235
282
|
if (artifact === undefined) {
|
|
236
|
-
return input.getMaterialShaderArtifact !== undefined &&
|
|
283
|
+
return input.getMaterialShaderArtifact !== undefined && requiresReceipt
|
|
237
284
|
? new GpuDrivenPreparationError('missing-material-receipt', {
|
|
238
285
|
reason: 'material-receipt-missing',
|
|
239
286
|
owner: 'material',
|
|
@@ -243,13 +290,11 @@ export function buildGpuDrivenDraws(
|
|
|
243
290
|
}
|
|
244
291
|
const receipt = artifact.receipt;
|
|
245
292
|
if (receipt === undefined) {
|
|
246
|
-
return
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
})
|
|
252
|
-
: undefined;
|
|
293
|
+
return new GpuDrivenPreparationError('missing-material-receipt', {
|
|
294
|
+
reason: 'material-receipt-missing',
|
|
295
|
+
owner: 'material',
|
|
296
|
+
expected: `receipt for ${materialShaderId}`,
|
|
297
|
+
});
|
|
253
298
|
}
|
|
254
299
|
const skinReceipt: GpuDrivenSkinReceipt | undefined =
|
|
255
300
|
input.baseSnapshot.skin !== undefined && receipt.skinPaletteAddress !== undefined
|
|
@@ -261,15 +306,25 @@ export function buildGpuDrivenDraws(
|
|
|
261
306
|
byteOffset: input.baseSnapshot.skin.byteOffset,
|
|
262
307
|
}
|
|
263
308
|
: undefined;
|
|
309
|
+
const selectedArtifact =
|
|
310
|
+
artifact === undefined ||
|
|
311
|
+
sceneIndexProgramKey === undefined ||
|
|
312
|
+
materialShaderId === undefined
|
|
313
|
+
? artifact
|
|
314
|
+
: {
|
|
315
|
+
...artifact,
|
|
316
|
+
material: materialShaderId,
|
|
317
|
+
specializationKey: sceneIndexProgramKey,
|
|
318
|
+
};
|
|
264
319
|
const result = resolvePreparedGpuDrivenDraw({
|
|
265
320
|
snapshot: { ...input.baseSnapshot, material: drawMaterial },
|
|
266
|
-
artifact,
|
|
321
|
+
artifact: selectedArtifact ?? artifact,
|
|
267
322
|
geometry: { ...geometry, topology: draw.topology, indexed: draw.kind === 'indexed' },
|
|
268
323
|
generation: receipt.generation,
|
|
269
324
|
draw,
|
|
270
325
|
...(skinReceipt === undefined ? {} : { skinReceipt }),
|
|
271
326
|
});
|
|
272
|
-
return result.ok ? result.value :
|
|
327
|
+
return result.ok ? result.value : result.error;
|
|
273
328
|
})();
|
|
274
329
|
if (prepared instanceof GpuDrivenPreparationError) {
|
|
275
330
|
return [{ ...draw, preparationError: prepared, ...withLodRanges }];
|
|
@@ -211,6 +211,26 @@ interface MembershipProjection {
|
|
|
211
211
|
readonly draws: readonly MembershipDraw[];
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
function hasActiveAlphaMask(
|
|
215
|
+
material: RenderSceneSlot['snapshot']['material'],
|
|
216
|
+
prepared: PreparedGpuDrivenDraw,
|
|
217
|
+
): boolean {
|
|
218
|
+
const { cutoff, source } = prepared.alphaMask;
|
|
219
|
+
if (cutoff.length === 0 || source.length === 0) return false;
|
|
220
|
+
// Standard's shared receipt describes the alpha-clip interface; the actual
|
|
221
|
+
// mode is selected by its authored cutoff. Custom producers own coverage in
|
|
222
|
+
// their receipt, so a declared clip remains masked without guessing a field.
|
|
223
|
+
if (
|
|
224
|
+
prepared.identity.material === 'forgeax::default-standard-pbr' ||
|
|
225
|
+
prepared.identity.material === 'forgeax::pbr-skin' ||
|
|
226
|
+
prepared.identity.material === 'forgeax::default-standard-pbr-skin'
|
|
227
|
+
) {
|
|
228
|
+
const value = material.paramSnapshot?.[cutoff];
|
|
229
|
+
return typeof value === 'number' && value > 0;
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
214
234
|
function eligibleDraws(slot: RenderSceneSlot): readonly EligibleGpuDrivenDraw[] {
|
|
215
235
|
const snapshot = slot.snapshot;
|
|
216
236
|
const draws = snapshot.gpuDrivenDraws;
|
|
@@ -232,13 +252,11 @@ function eligibleDraws(slot: RenderSceneSlot): readonly EligibleGpuDrivenDraw[]
|
|
|
232
252
|
const prepared = draw.prepared;
|
|
233
253
|
const material = snapshot.materials[draw.materialSlot] ?? snapshot.material;
|
|
234
254
|
if (prepared !== undefined) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
(prepared.identity.material === 'forgeax::pbr-skin' ||
|
|
241
|
-
prepared.identity.material === 'forgeax::default-standard-pbr-skin'));
|
|
255
|
+
// Prepared producer identity is the admission proof; the material
|
|
256
|
+
// identifier is only a consistency check, never a built-in allow-list.
|
|
257
|
+
const materialIdentityMatches =
|
|
258
|
+
material.materialShaderId === undefined ||
|
|
259
|
+
prepared.identity.material === material.materialShaderId;
|
|
242
260
|
const isAlphaBlend =
|
|
243
261
|
material.transparent === true || material.renderState?.blend !== undefined;
|
|
244
262
|
const cpuOnlyMaterialResources =
|
|
@@ -251,16 +269,20 @@ function eligibleDraws(slot: RenderSceneSlot): readonly EligibleGpuDrivenDraw[]
|
|
|
251
269
|
hasFiniteOrderedBounds(snapshot.skin.bounds) &&
|
|
252
270
|
prepared.skinPaletteAddress !== undefined);
|
|
253
271
|
if (
|
|
254
|
-
!
|
|
272
|
+
!materialIdentityMatches ||
|
|
255
273
|
isAlphaBlend ||
|
|
256
274
|
cpuOnlyMaterialResources ||
|
|
257
275
|
(prepared.identity.deformation !== 'rigid' && prepared.identity.deformation !== 'skin') ||
|
|
258
276
|
!skinReady
|
|
259
277
|
)
|
|
260
278
|
continue;
|
|
261
|
-
const preparedIdentity =
|
|
262
|
-
|
|
263
|
-
|
|
279
|
+
const preparedIdentity = [
|
|
280
|
+
prepared.identity.material,
|
|
281
|
+
prepared.identity.geometry,
|
|
282
|
+
prepared.identity.deformation,
|
|
283
|
+
...(prepared.receiptIdentity === undefined ? [] : [prepared.receiptIdentity]),
|
|
284
|
+
].join('|');
|
|
285
|
+
const admission = hasActiveAlphaMask(material, prepared) ? 'alpha-mask' : 'opaque';
|
|
264
286
|
result.push({
|
|
265
287
|
sourceDrawIndex: drawItemIndex,
|
|
266
288
|
key: {
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
|
|
2
|
+
import { gpuDrivenMaterialArtifactKey } from '../extract/gpu-driven';
|
|
3
|
+
import type { RenderSystemInternals } from '../record/render-context';
|
|
4
|
+
import type { DispatchEntry, RenderableSnapshot } from '../render-system-extract';
|
|
5
|
+
|
|
6
|
+
type ArtifactRow = { readonly source: RenderableSnapshot; readonly renderableIndex: number };
|
|
7
|
+
|
|
8
|
+
/** Resolve the same producer programs for normal frames and replacement devices. */
|
|
9
|
+
export function collectGpuDrivenMaterialArtifacts({
|
|
10
|
+
rows,
|
|
11
|
+
shadowRows,
|
|
12
|
+
dispatch,
|
|
13
|
+
shadowDispatch,
|
|
14
|
+
resolve,
|
|
15
|
+
clustered,
|
|
16
|
+
reflectionFallback,
|
|
17
|
+
}: {
|
|
18
|
+
readonly rows: readonly ArtifactRow[];
|
|
19
|
+
readonly shadowRows: readonly ArtifactRow[];
|
|
20
|
+
readonly dispatch: readonly DispatchEntry[];
|
|
21
|
+
readonly shadowDispatch: readonly DispatchEntry[];
|
|
22
|
+
readonly resolve: RenderSystemInternals['getMaterialShaderArtifact'];
|
|
23
|
+
readonly clustered: boolean;
|
|
24
|
+
readonly reflectionFallback: boolean;
|
|
25
|
+
}) {
|
|
26
|
+
const materialArtifacts = new Map<string, MaterialShaderArtifact>();
|
|
27
|
+
const shadowMaterialArtifacts = new Map<string, MaterialShaderArtifact>();
|
|
28
|
+
const shadowProgramIds = new Map<string, string>();
|
|
29
|
+
const forwardProgramEntries = new Map<
|
|
30
|
+
string,
|
|
31
|
+
{ readonly vertexEntry?: string; readonly fragmentEntry?: string }
|
|
32
|
+
>();
|
|
33
|
+
for (const entry of dispatch) {
|
|
34
|
+
if (entry.tags.LightMode !== 'Forward' || entry.materialShaderId === undefined) continue;
|
|
35
|
+
const key = `${entry.renderableIndex}:${entry.materialHandle}`;
|
|
36
|
+
if (forwardProgramEntries.has(key)) continue;
|
|
37
|
+
forwardProgramEntries.set(key, {
|
|
38
|
+
...(entry.vertexEntry === undefined ? {} : { vertexEntry: entry.vertexEntry }),
|
|
39
|
+
...(entry.fragmentEntry === undefined ? {} : { fragmentEntry: entry.fragmentEntry }),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
for (const entry of shadowDispatch) {
|
|
43
|
+
if (entry.tags.LightMode !== 'ShadowCaster' || entry.materialShaderId === undefined) continue;
|
|
44
|
+
shadowProgramIds.set(
|
|
45
|
+
`${entry.renderableIndex}:${entry.materialHandle}`,
|
|
46
|
+
entry.materialShaderId,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const collectArtifacts = (
|
|
50
|
+
rows: readonly ArtifactRow[],
|
|
51
|
+
pass: 'forward' | 'shadow',
|
|
52
|
+
target: Map<string, MaterialShaderArtifact>,
|
|
53
|
+
): void => {
|
|
54
|
+
if (resolve === undefined) return;
|
|
55
|
+
for (const row of rows) {
|
|
56
|
+
for (const draw of row.source.gpuDrivenDraws ?? []) {
|
|
57
|
+
const prepared = draw.prepared;
|
|
58
|
+
if (prepared === undefined) continue;
|
|
59
|
+
const material = row.source.materials[draw.materialSlot] ?? row.source.material;
|
|
60
|
+
const logicalMaterialShaderId = material.materialShaderId ?? prepared.identity.material;
|
|
61
|
+
const sceneIndexProgramKeys = material.materialSceneIndexProgramKeys;
|
|
62
|
+
const directMaterialShaderId =
|
|
63
|
+
pass === 'shadow'
|
|
64
|
+
? shadowProgramIds.get(`${row.renderableIndex}:${material.materialHandle ?? -1}`)
|
|
65
|
+
: logicalMaterialShaderId;
|
|
66
|
+
const directPassNames = new Set(
|
|
67
|
+
Object.entries(material.materialProgramKeys ?? {})
|
|
68
|
+
.filter(([, key]) => key === directMaterialShaderId)
|
|
69
|
+
.map(([passName]) => passName),
|
|
70
|
+
);
|
|
71
|
+
const sceneIndexProgramKey = Object.entries(sceneIndexProgramKeys ?? {}).find(
|
|
72
|
+
([passName, program]) => program.pass === pass && directPassNames.has(passName),
|
|
73
|
+
)?.[1]?.specializationKey;
|
|
74
|
+
// A modern publication may expose a scene-index pair for its
|
|
75
|
+
// forward Pass but omit ShadowCaster. Keep that material on its
|
|
76
|
+
// explicit CPU shadow lane; do not reinterpret its direct WGSL
|
|
77
|
+
// as a scene-index source and fail later at pipeline creation.
|
|
78
|
+
if (sceneIndexProgramKeys !== undefined && sceneIndexProgramKey === undefined) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const materialShaderId =
|
|
82
|
+
pass === 'shadow'
|
|
83
|
+
? (sceneIndexProgramKey ??
|
|
84
|
+
shadowProgramIds.get(`${row.renderableIndex}:${material.materialHandle ?? -1}`) ??
|
|
85
|
+
logicalMaterialShaderId)
|
|
86
|
+
: (sceneIndexProgramKey ?? logicalMaterialShaderId);
|
|
87
|
+
const artifact = resolve(materialShaderId, {
|
|
88
|
+
vertexColorAvailable: prepared.vertexInputs.some((input) => input.semantic === 'color'),
|
|
89
|
+
deformation: prepared.identity.deformation,
|
|
90
|
+
pass,
|
|
91
|
+
address: 'scene-index',
|
|
92
|
+
...(pass === 'forward'
|
|
93
|
+
? {
|
|
94
|
+
clustered: clustered,
|
|
95
|
+
reflectionFallback: reflectionFallback,
|
|
96
|
+
}
|
|
97
|
+
: {}),
|
|
98
|
+
});
|
|
99
|
+
const programEntry =
|
|
100
|
+
pass === 'shadow'
|
|
101
|
+
? shadowDispatch.find(
|
|
102
|
+
(entry) =>
|
|
103
|
+
entry.tags.LightMode === 'ShadowCaster' &&
|
|
104
|
+
entry.renderableIndex === row.renderableIndex &&
|
|
105
|
+
entry.materialHandle === (material.materialHandle ?? -1),
|
|
106
|
+
)
|
|
107
|
+
: forwardProgramEntries.get(`${row.renderableIndex}:${material.materialHandle ?? -1}`);
|
|
108
|
+
const selectedArtifact =
|
|
109
|
+
artifact === undefined
|
|
110
|
+
? undefined
|
|
111
|
+
: programEntry === undefined
|
|
112
|
+
? artifact
|
|
113
|
+
: {
|
|
114
|
+
...artifact,
|
|
115
|
+
...(programEntry.vertexEntry === undefined
|
|
116
|
+
? {}
|
|
117
|
+
: { vertexEntry: programEntry.vertexEntry }),
|
|
118
|
+
...(programEntry.fragmentEntry === undefined
|
|
119
|
+
? {}
|
|
120
|
+
: { fragmentEntry: programEntry.fragmentEntry }),
|
|
121
|
+
};
|
|
122
|
+
// A scene-index program has a distinct registry key but the
|
|
123
|
+
// prepared draw keeps the logical material identity for batching
|
|
124
|
+
// and admission. Carry both facts: `material` remains the logical
|
|
125
|
+
// identity, while `specializationKey` tells the raster owner which
|
|
126
|
+
// published WGSL key must be used for pipeline construction.
|
|
127
|
+
const selectedIdentityArtifact =
|
|
128
|
+
selectedArtifact === undefined || sceneIndexProgramKey === undefined
|
|
129
|
+
? selectedArtifact
|
|
130
|
+
: {
|
|
131
|
+
...selectedArtifact,
|
|
132
|
+
material: logicalMaterialShaderId,
|
|
133
|
+
specializationKey: sceneIndexProgramKey,
|
|
134
|
+
};
|
|
135
|
+
const receipt = selectedIdentityArtifact?.receipt;
|
|
136
|
+
const shadowVertexInputsMatch =
|
|
137
|
+
pass !== 'shadow' ||
|
|
138
|
+
(receipt !== undefined &&
|
|
139
|
+
receipt.vertexInputs.length === prepared.vertexInputs.length &&
|
|
140
|
+
receipt.vertexInputs.every(
|
|
141
|
+
(input, index) =>
|
|
142
|
+
input.semantic === prepared.vertexInputs[index]?.semantic &&
|
|
143
|
+
input.location === prepared.vertexInputs[index]?.location &&
|
|
144
|
+
input.format === prepared.vertexInputs[index]?.format,
|
|
145
|
+
));
|
|
146
|
+
if (
|
|
147
|
+
selectedIdentityArtifact === undefined ||
|
|
148
|
+
receipt === undefined ||
|
|
149
|
+
!shadowVertexInputsMatch ||
|
|
150
|
+
(pass !== 'shadow' &&
|
|
151
|
+
(receipt.receiptIdentity !== prepared.receiptIdentity ||
|
|
152
|
+
receipt.generation !== prepared.receiptGeneration))
|
|
153
|
+
) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
target.set(
|
|
157
|
+
gpuDrivenMaterialArtifactKey({
|
|
158
|
+
material: prepared.identity.material,
|
|
159
|
+
deformation: prepared.identity.deformation,
|
|
160
|
+
receiptIdentity: prepared.receiptIdentity,
|
|
161
|
+
receiptGeneration: prepared.receiptGeneration,
|
|
162
|
+
}),
|
|
163
|
+
selectedIdentityArtifact,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
collectArtifacts(rows, 'forward', materialArtifacts);
|
|
169
|
+
collectArtifacts(shadowRows, 'forward', materialArtifacts);
|
|
170
|
+
collectArtifacts(rows, 'shadow', shadowMaterialArtifacts);
|
|
171
|
+
collectArtifacts(shadowRows, 'shadow', shadowMaterialArtifacts);
|
|
172
|
+
const firstArtifact = materialArtifacts.values().next().value as
|
|
173
|
+
| MaterialShaderArtifact
|
|
174
|
+
| undefined;
|
|
175
|
+
const firstSkinArtifact = [...materialArtifacts.values()].find(
|
|
176
|
+
(artifact) => artifact.receipt?.skinPaletteAddress !== undefined,
|
|
177
|
+
);
|
|
178
|
+
return { materialArtifacts, shadowMaterialArtifacts, firstArtifact, firstSkinArtifact };
|
|
179
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { resolveReflectionProbeBinding } from '../record/frame-lighting';
|
|
2
|
+
import type { ValidatedRenderable } from '../record/frame-snapshot';
|
|
3
|
+
import type { ReflectionProbeRecordState } from '../record/render-context';
|
|
4
|
+
import type { DispatchEntry, RenderableSnapshot } from '../render-system-extract';
|
|
5
|
+
|
|
6
|
+
export function materialBindingKey(
|
|
7
|
+
source: Pick<RenderableSnapshot, 'worldId' | 'entityKey'>,
|
|
8
|
+
handle: number,
|
|
9
|
+
): string {
|
|
10
|
+
return `${source.worldId}:${source.entityKey}:${handle}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Per-object bindings and pass membership are part of an indirect batch's resource identity. */
|
|
14
|
+
export function projectMaterialBindingClasses(
|
|
15
|
+
rows: readonly ValidatedRenderable[],
|
|
16
|
+
dispatch: readonly DispatchEntry[],
|
|
17
|
+
probes?: ReflectionProbeRecordState,
|
|
18
|
+
): ReadonlyMap<string, string> {
|
|
19
|
+
const deferred = new Set(
|
|
20
|
+
dispatch
|
|
21
|
+
.filter((entry) => entry.tags.LightMode === 'Deferred')
|
|
22
|
+
.map((entry) => `${entry.renderableIndex}:${entry.materialHandle}`),
|
|
23
|
+
);
|
|
24
|
+
const classes = new Map<string, string>();
|
|
25
|
+
for (const row of rows) {
|
|
26
|
+
const selection = probes?.selections.get(`${row.source.worldId}:${row.source.entityKey}`);
|
|
27
|
+
const probe =
|
|
28
|
+
selection === undefined
|
|
29
|
+
? undefined
|
|
30
|
+
: resolveReflectionProbeBinding(selection, probes?.table).probeIndex;
|
|
31
|
+
const materials =
|
|
32
|
+
row.source.materials.length > 0 ? row.source.materials : [row.source.material];
|
|
33
|
+
for (const material of materials) {
|
|
34
|
+
const handle = material.materialHandle ?? -1;
|
|
35
|
+
const passClass = deferred.has(`${row.renderableIndex}:${handle}`) ? '' : 'forward';
|
|
36
|
+
if (probe !== undefined || passClass !== '')
|
|
37
|
+
classes.set(materialBindingKey(row.source, handle), `${passClass}|probe:${probe ?? ''}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return classes;
|
|
41
|
+
}
|
|
@@ -32,6 +32,8 @@ export interface PreparedGpuDrivenDraw {
|
|
|
32
32
|
readonly deformation: 'rigid' | 'skin';
|
|
33
33
|
};
|
|
34
34
|
readonly receiptGeneration: number;
|
|
35
|
+
/** Content/layout identity that separates ABI variants (for example COLOR_0). */
|
|
36
|
+
readonly receiptIdentity?: string;
|
|
35
37
|
readonly directEntry: string;
|
|
36
38
|
readonly sceneIndexEntry: string;
|
|
37
39
|
readonly materialRow: MaterialShaderArtifactReceipt['materialRow'];
|
|
@@ -111,6 +113,10 @@ function hasMaterialResources(
|
|
|
111
113
|
// is a readiness requirement; this keeps numeric-only PBR on the same GPU
|
|
112
114
|
// lane while still rejecting an authored handle that failed to resolve.
|
|
113
115
|
return receipt.resourceSlots.every((slot) => {
|
|
116
|
+
// Storage-buffer resources have no MaterialSnapshot handle surface yet;
|
|
117
|
+
// admitting them as samplers would silently turn an authored resource
|
|
118
|
+
// failure into a prepared draw with an unrelated binding.
|
|
119
|
+
if (slot.kind === 'storage-buffer') return false;
|
|
114
120
|
if (slot.kind === 'texture') {
|
|
115
121
|
if (!authoredTextures?.has(slot.parameter)) return true;
|
|
116
122
|
return (
|
|
@@ -169,11 +175,15 @@ export function prepareGpuDrivenDraw(
|
|
|
169
175
|
actual: input.geometry.vertexInputs.map((input) => input.semantic).join(','),
|
|
170
176
|
});
|
|
171
177
|
}
|
|
172
|
-
|
|
178
|
+
const hasAlphaMask = receipt.alphaMask.cutoff.length > 0 || receipt.alphaMask.source.length > 0;
|
|
179
|
+
if (
|
|
180
|
+
hasAlphaMask &&
|
|
181
|
+
(receipt.alphaMask.cutoff.length === 0 || receipt.alphaMask.source.length === 0)
|
|
182
|
+
) {
|
|
173
183
|
return failure('alpha-mask-mismatch', {
|
|
174
184
|
reason: 'alpha-mask-receipt-missing',
|
|
175
185
|
owner: 'material',
|
|
176
|
-
expected: 'alphaMask.cutoff',
|
|
186
|
+
expected: 'alphaMask.cutoff and alphaMask.source for a masked Pass',
|
|
177
187
|
});
|
|
178
188
|
}
|
|
179
189
|
if (!hasMaterialResources(input.snapshot, receipt)) {
|
|
@@ -215,6 +225,7 @@ export function prepareGpuDrivenDraw(
|
|
|
215
225
|
deformation,
|
|
216
226
|
},
|
|
217
227
|
receiptGeneration: receipt.generation,
|
|
228
|
+
receiptIdentity: receipt.receiptIdentity,
|
|
218
229
|
directEntry: receipt.directEntry,
|
|
219
230
|
sceneIndexEntry: receipt.sceneIndexEntry,
|
|
220
231
|
materialRow: receipt.materialRow,
|