@forgeax/engine-render 0.1.20 → 0.1.21
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 +215 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/assembly/device-feature-admission.d.ts +18 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -0
- package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
- package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +21 -18
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +9 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +46 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -1
- package/dist/authoring.mjs +3 -0
- package/dist/authoring.mjs.map +1 -1
- package/dist/capture/cube-views.d.ts +21 -0
- package/dist/capture/cube-views.d.ts.map +1 -0
- package/dist/capture/scheduler.d.ts +54 -0
- package/dist/capture/scheduler.d.ts.map +1 -0
- package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
- package/dist/chunk-5YADUCA7.mjs.map +1 -0
- package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
- package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
- package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
- package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
- package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
- package/dist/chunk-FS2KNUQH.mjs.map +1 -0
- package/dist/components/camera.d.ts +1 -0
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cube-camera.d.ts +20 -0
- package/dist/components/cube-camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +11 -2
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/directional-shadow-filter.d.ts +25 -0
- package/dist/components/directional-shadow-filter.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -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 +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +5826 -1003
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +5 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/render.d.ts +141 -93
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/directional-shadow-projection.d.ts +23 -0
- package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +14 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/features/fullscreen.d.ts.map +1 -1
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +10 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +13 -2
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +0 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +19 -17
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +4 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/kernel-cache.d.ts +36 -0
- package/dist/ibl/kernel-cache.d.ts.map +1 -0
- package/dist/index.d.ts +28 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +51 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +50 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -0
- package/dist/internal.mjs +1 -1
- package/dist/light-buffer-layout.d.ts +10 -1
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +54 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +14 -4
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +5 -26
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/point-shadow-inspection.d.ts +21 -0
- package/dist/point-shadow-inspection.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +27 -32
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +28 -14
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts +5 -0
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +10 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
- package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
- package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/index.d.ts +8 -0
- package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
- package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/session.d.ts +43 -0
- package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +8 -3
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +22 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.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/render-context.d.ts +43 -2
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +72 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -4
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/filter.d.ts +22 -0
- package/dist/reflection/filter.d.ts.map +1 -0
- package/dist/reflection/gpu-table.d.ts +25 -0
- package/dist/reflection/gpu-table.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +91 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/render-contract.d.ts +49 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +42 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +58 -17
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +17 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts +14 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/shadow-atlas.d.ts +3 -3
- package/dist/shadow-atlas.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +3 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/targets/contracts.d.ts +66 -0
- package/dist/targets/contracts.d.ts.map +1 -0
- package/dist/targets/graph-projection.d.ts +29 -0
- package/dist/targets/graph-projection.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +34 -0
- package/dist/targets/material-source.d.ts.map +1 -0
- package/dist/targets/owner.d.ts +32 -0
- package/dist/targets/owner.d.ts.map +1 -0
- package/dist/targets/physical.d.ts +18 -0
- package/dist/targets/physical.d.ts.map +1 -0
- package/dist/targets/readback.d.ts +39 -0
- package/dist/targets/readback.d.ts.map +1 -0
- package/dist/temporal/index.mjs +3 -3
- package/dist/typed-render-graph-primitives.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +2 -2
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
- package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
- package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
- package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
- package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
- package/src/__tests__/cube-camera.integration.test.ts +42 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/factory-contract.integration.test.ts +255 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
- package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
- package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
- package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
- package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
- package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
- package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
- package/src/__tests__/instances-culling.integration.test.ts +94 -0
- package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/public-surface.test-d.ts +10 -0
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
- package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
- package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
- package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
- package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
- package/src/__tests__/render-target-admission.unit.test.ts +80 -0
- package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
- package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
- package/src/__tests__/render-target-material-source.test-d.ts +30 -0
- package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
- package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
- package/src/__tests__/render-target-owner.unit.test.ts +122 -0
- package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
- package/src/__tests__/render-target-public-schema.json +107 -0
- package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
- package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
- package/src/__tests__/render-target-readback.integration.test.ts +90 -0
- package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
- package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
- package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
- package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
- package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
- package/src/assembly/device-feature-admission.ts +73 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +877 -286
- package/src/assembly/host-contract.ts +25 -0
- package/src/assembly/render-target-host.ts +847 -0
- package/src/assets/asset-decoders.ts +1 -1
- package/src/assets/render-asset-port.ts +7 -1
- package/src/capture/cube-views.ts +69 -0
- package/src/capture/scheduler.ts +265 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
- package/src/components/camera.ts +4 -0
- package/src/components/cube-camera.ts +52 -0
- package/src/components/directional-light.ts +22 -2
- package/src/components/directional-shadow-filter.ts +57 -0
- package/src/components/index.ts +2 -0
- package/src/components/light-helpers.ts +81 -7
- package/src/components/reflection-probe.ts +49 -0
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +12 -0
- package/src/errors/render.ts +286 -142
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/visibility.ts +25 -1
- package/src/features/fullscreen.ts +3 -0
- package/src/features/targets.ts +4 -1
- package/src/features/types.ts +10 -0
- package/src/fullscreen-post-process-pass.ts +43 -2
- package/src/gpu-driven/batch-topology.ts +1 -0
- package/src/gpu-driven/production-raster.ts +1 -3
- package/src/gpu-scene.ts +8 -1
- package/src/hdrp-buffers.ts +165 -59
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +148 -0
- package/src/inspection-types.ts +56 -0
- package/src/instances-derived-bounds.ts +171 -0
- package/src/light-buffer-layout.ts +20 -5
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +106 -17
- package/src/pipeline/standard-forward-lane.ts +46 -16
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +157 -0
- package/src/pipeline/standard-lighting/inspection.ts +88 -0
- package/src/pipeline/standard-lighting/layout.ts +31 -0
- package/src/pipeline/standard-lighting/light-frame.ts +34 -0
- package/src/pipeline/standard-lighting/membership.ts +43 -0
- package/src/pipeline/standard-lighting/prepare.ts +37 -0
- package/src/pipeline/standard-lighting/topology.ts +120 -0
- package/src/pipeline/standard-lighting/transport.ts +77 -0
- package/src/pipeline/standard-pipeline.ts +122 -193
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +17 -47
- package/src/pipeline-spec.ts +84 -21
- package/src/point-shadow-inspection.ts +55 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +367 -427
- package/src/record/frame-snapshot.ts +35 -27
- package/src/record/frame-targets.ts +17 -0
- package/src/record/frame.ts +261 -74
- package/src/record/gpu-pass-timing/contract.ts +180 -0
- package/src/record/gpu-pass-timing/errors.ts +34 -0
- package/src/record/gpu-pass-timing/index.ts +37 -0
- package/src/record/gpu-pass-timing/parser.ts +97 -0
- package/src/record/gpu-pass-timing/session.ts +938 -0
- package/src/record/gpu-timing.ts +21 -12
- package/src/record/main-pass-geometry.ts +120 -80
- package/src/record/main-pass-material.ts +95 -9
- package/src/record/main-pass-sprite-draws.ts +54 -25
- package/src/record/main-pass.ts +71 -15
- package/src/record/render-context.ts +72 -0
- package/src/record/shadow-pass.ts +17 -28
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +893 -44
- package/src/record/view-ubo.ts +63 -28
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/projection.ts +281 -0
- package/src/render-contract.ts +76 -5
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +117 -2
- package/src/render-system-extract.ts +557 -100
- package/src/render-system.ts +1095 -66
- package/src/renderer.ts +222 -0
- package/src/scene/render-scene.ts +115 -9
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- package/src/targets/contracts.ts +182 -0
- package/src/targets/graph-projection.ts +169 -0
- package/src/targets/material-source.ts +143 -0
- package/src/targets/owner.ts +214 -0
- package/src/targets/physical.ts +18 -0
- package/src/targets/readback.ts +153 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
- package/src/typed-render-graph-primitives.ts +5 -3
- package/src/typed-shadow-passes.ts +46 -40
- package/src/volume/passes.ts +15 -9
- package/dist/chunk-4XINDLAY.mjs.map +0 -1
- package/dist/chunk-CDTWTHZR.mjs.map +0 -1
- package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
- package/dist/chunk-TBQJOK2L.mjs.map +0 -1
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { mat4, vec3 } from '@forgeax/engine-math';
|
|
2
3
|
import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
|
|
3
4
|
import type { RhiDevice } from '@forgeax/engine-rhi';
|
|
4
5
|
import { rhi } from '@forgeax/engine-rhi-null';
|
|
5
6
|
import { ok } from '@forgeax/engine-types';
|
|
6
7
|
import { beforeAll, describe, expect, it } from 'vitest';
|
|
8
|
+
import { shouldExecuteStandardClusterMembershipPass } from '../pipeline/standard-lighting/graph';
|
|
9
|
+
import {
|
|
10
|
+
prepareStandardLighting,
|
|
11
|
+
type StandardLightFrame,
|
|
12
|
+
} from '../pipeline/standard-lighting/prepare';
|
|
13
|
+
import {
|
|
14
|
+
deriveStandardTopologyInput,
|
|
15
|
+
standardLightingTopologySignature,
|
|
16
|
+
} from '../pipeline/standard-lighting/topology';
|
|
17
|
+
import { selectStandardClusterTransport } from '../pipeline/standard-lighting/transport';
|
|
7
18
|
import { standardPipeline } from '../pipeline/standard-pipeline';
|
|
8
19
|
import { DEFAULT_STANDARD_PROFILE } from '../pipeline/standard-profile';
|
|
9
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
isMotionBlurTemporalDemand,
|
|
22
|
+
resolvePostProcessChainAdmission,
|
|
23
|
+
} from '../record/typed-frame-graph';
|
|
10
24
|
import type {
|
|
11
25
|
RenderPipeline,
|
|
12
26
|
RenderPipelineFrame,
|
|
13
27
|
RenderPipelineTopology,
|
|
14
28
|
} from '../render-pipeline';
|
|
29
|
+
import { resolveOutputDither } from '../render-pipeline';
|
|
15
30
|
import { standardTemporalPostOrder } from '../temporal/standard-scene-data';
|
|
16
31
|
|
|
17
32
|
let device: RhiDevice;
|
|
@@ -39,8 +54,8 @@ function topology(
|
|
|
39
54
|
},
|
|
40
55
|
camera: { tonemap: 'aces-filmic', antialias: 'fxaa', bloom: 'on' },
|
|
41
56
|
shadow: {
|
|
42
|
-
mapSize: 1024,
|
|
43
|
-
|
|
57
|
+
directional: { mapSize: 1024, cascadeCount: 4 },
|
|
58
|
+
spotMapSize: 1024,
|
|
44
59
|
pointCount: 1,
|
|
45
60
|
pointFaceSize: 512,
|
|
46
61
|
spotCount: 1,
|
|
@@ -57,13 +72,65 @@ function topology(
|
|
|
57
72
|
};
|
|
58
73
|
}
|
|
59
74
|
|
|
75
|
+
function zeroStandardLighting(caps: { compute: boolean; storageBuffer: boolean }, localCount = 1) {
|
|
76
|
+
const prepared = prepareStandardLighting({
|
|
77
|
+
directional: undefined,
|
|
78
|
+
local: Array.from({ length: localCount }, (_, index) => ({
|
|
79
|
+
kind: 'point' as const,
|
|
80
|
+
shadowed: false,
|
|
81
|
+
position: vec3.create(index, 0, -4),
|
|
82
|
+
range: 2,
|
|
83
|
+
})),
|
|
84
|
+
view: mat4.create(),
|
|
85
|
+
projection: mat4.create(),
|
|
86
|
+
near: 0.1,
|
|
87
|
+
far: 100,
|
|
88
|
+
grid: { x: 4, y: 3, z: 4 },
|
|
89
|
+
lightCount: DEFAULT_STANDARD_PROFILE.lightCount,
|
|
90
|
+
renderPath: 'forward',
|
|
91
|
+
});
|
|
92
|
+
if (!prepared.ok) throw prepared.error;
|
|
93
|
+
const transport = selectStandardClusterTransport(
|
|
94
|
+
{ ...caps, membershipPipelineReady: caps.compute },
|
|
95
|
+
prepared.value,
|
|
96
|
+
);
|
|
97
|
+
if (!transport.ok) return transport;
|
|
98
|
+
return deriveStandardTopologyInput({
|
|
99
|
+
kind: 'clustered',
|
|
100
|
+
prepared: prepared.value,
|
|
101
|
+
transport: transport.value,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function noLocalStandardLighting() {
|
|
106
|
+
const prepared = prepareStandardLighting({
|
|
107
|
+
directional: undefined,
|
|
108
|
+
local: [],
|
|
109
|
+
view: mat4.create(),
|
|
110
|
+
projection: mat4.create(),
|
|
111
|
+
near: 0.1,
|
|
112
|
+
far: 100,
|
|
113
|
+
grid: { x: 4, y: 3, z: 4 },
|
|
114
|
+
lightCount: 1,
|
|
115
|
+
renderPath: 'forward',
|
|
116
|
+
});
|
|
117
|
+
if (!prepared.ok) throw prepared.error;
|
|
118
|
+
return deriveStandardTopologyInput({ kind: 'no-local-lights', prepared: prepared.value });
|
|
119
|
+
}
|
|
120
|
+
|
|
60
121
|
function compile(pipeline: RenderPipeline, input: RenderPipelineTopology) {
|
|
61
122
|
const graph = new RenderGraphBuilder<RenderPipelineFrame>();
|
|
123
|
+
const standardLighting = zeroStandardLighting({
|
|
124
|
+
compute: input.lane.compute,
|
|
125
|
+
storageBuffer: input.lane.storageBuffer,
|
|
126
|
+
});
|
|
127
|
+
if (!standardLighting.ok) return standardLighting;
|
|
62
128
|
const importedHistory =
|
|
63
129
|
input.camera.antialias === 'taa' ? createImportedHistory(graph) : undefined;
|
|
64
130
|
const built = pipeline.build(
|
|
65
131
|
{
|
|
66
132
|
graph,
|
|
133
|
+
standardLighting: standardLighting.value,
|
|
67
134
|
capabilities: { rgba16floatRenderable: true },
|
|
68
135
|
...(importedHistory === undefined ? {} : { taaHistory: importedHistory }),
|
|
69
136
|
projectGpuDriven: () => ok(undefined),
|
|
@@ -78,6 +145,38 @@ function compile(pipeline: RenderPipeline, input: RenderPipelineTopology) {
|
|
|
78
145
|
});
|
|
79
146
|
}
|
|
80
147
|
|
|
148
|
+
function disabledDirectionalTopology(): RenderPipelineTopology {
|
|
149
|
+
return topology('forgeax::standard', {
|
|
150
|
+
shadow: {
|
|
151
|
+
directional: 'disabled',
|
|
152
|
+
pointCount: 0,
|
|
153
|
+
pointFaceSize: 512,
|
|
154
|
+
spotCount: 0,
|
|
155
|
+
} as unknown as RenderPipelineTopology['shadow'],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function disabledDirectionalVolumeTopology(lightKind: 'point' | 'spot'): RenderPipelineTopology {
|
|
160
|
+
return topology('forgeax::standard', {
|
|
161
|
+
shadow: {
|
|
162
|
+
directional: 'disabled',
|
|
163
|
+
spotMapSize: 1024,
|
|
164
|
+
pointCount: 0,
|
|
165
|
+
pointFaceSize: 512,
|
|
166
|
+
spotCount: lightKind === 'spot' ? 1 : 0,
|
|
167
|
+
},
|
|
168
|
+
volumetricFog: {
|
|
169
|
+
enabled: true,
|
|
170
|
+
lightKind,
|
|
171
|
+
lightEntity: 1,
|
|
172
|
+
format: 'rgba8unorm',
|
|
173
|
+
extent: { width: 8, height: 8, depth: 8 },
|
|
174
|
+
froxelExtent: { width: 16, height: 16, depth: 8 },
|
|
175
|
+
resolvedExtent: { width: 8, height: 8, depth: 1 },
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
81
180
|
function createImportedHistory(graph: RenderGraphBuilder<RenderPipelineFrame>) {
|
|
82
181
|
const target = (label: string) => {
|
|
83
182
|
const texture = graph.importTexture(
|
|
@@ -104,6 +203,248 @@ function createImportedHistory(graph: RenderGraphBuilder<RenderPipelineFrame>) {
|
|
|
104
203
|
}
|
|
105
204
|
|
|
106
205
|
describe('typed built-in pipeline topology', () => {
|
|
206
|
+
it('defaults final output dither on and accepts an asset-level opt-out', () => {
|
|
207
|
+
expect(resolveOutputDither(undefined)).toBe(true);
|
|
208
|
+
expect(resolveOutputDither({ outputDither: true })).toBe(true);
|
|
209
|
+
expect(resolveOutputDither({ outputDither: false })).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('omits every Directional shadow graph resource and pass when disabled', () => {
|
|
213
|
+
const compiled = compile(standardPipeline, disabledDirectionalTopology());
|
|
214
|
+
expect(compiled.ok).toBe(true);
|
|
215
|
+
if (!compiled.ok) return;
|
|
216
|
+
const info = compiled.value.inspect();
|
|
217
|
+
expect(info.resources.some((resource) => resource.label === 'directional-shadow-depth')).toBe(
|
|
218
|
+
false,
|
|
219
|
+
);
|
|
220
|
+
expect(
|
|
221
|
+
info.passes.some(
|
|
222
|
+
(pass) =>
|
|
223
|
+
pass.name.startsWith('shadowCascade') || pass.name === 'directional-shadow-observation',
|
|
224
|
+
),
|
|
225
|
+
).toBe(false);
|
|
226
|
+
const main = info.passes.find((pass) => pass.name === 'main');
|
|
227
|
+
expect(main?.accesses.some((access) => access.resource === 'directional-shadow-depth')).toBe(
|
|
228
|
+
false,
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('falsifies a test-only shadow-off topology that still builds a Directional pass', () => {
|
|
233
|
+
const compiled = compile(standardPipeline, disabledDirectionalTopology());
|
|
234
|
+
expect(compiled.ok).toBe(true);
|
|
235
|
+
if (!compiled.ok) return;
|
|
236
|
+
const count = compiled.value
|
|
237
|
+
.inspect()
|
|
238
|
+
.passes.filter(
|
|
239
|
+
(pass) =>
|
|
240
|
+
pass.name.startsWith('shadowCascade') || pass.name === 'directional-shadow-observation',
|
|
241
|
+
).length;
|
|
242
|
+
const expected = process.env.FALSIFY === 'shadow-off-still-builds-pass' ? 1 : 0;
|
|
243
|
+
expect(count).toBe(expected);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it.each([
|
|
247
|
+
'point',
|
|
248
|
+
'spot',
|
|
249
|
+
] as const)('keeps %s volumetric fog graphable when directional shadows are disabled', (lightKind) => {
|
|
250
|
+
const compiled = compile(standardPipeline, disabledDirectionalVolumeTopology(lightKind));
|
|
251
|
+
expect(compiled.ok).toBe(true);
|
|
252
|
+
if (!compiled.ok) return;
|
|
253
|
+
const info = compiled.value.inspect();
|
|
254
|
+
expect(info.resources.some((resource) => resource.label === 'directional-shadow-depth')).toBe(
|
|
255
|
+
false,
|
|
256
|
+
);
|
|
257
|
+
expect(info.passes.some((pass) => pass.name === 'volume-inject')).toBe(true);
|
|
258
|
+
expect(
|
|
259
|
+
info.passes.some(
|
|
260
|
+
(pass) =>
|
|
261
|
+
pass.name.startsWith('shadowCascade') || pass.name === 'directional-shadow-observation',
|
|
262
|
+
),
|
|
263
|
+
).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('derives topology only from prepared transport facts', () => {
|
|
267
|
+
const frame: StandardLightFrame = {
|
|
268
|
+
directional: undefined,
|
|
269
|
+
local: [{ kind: 'point', shadowed: false, position: vec3.create(0, 0, -4), range: 2 }],
|
|
270
|
+
view: mat4.lookAt(mat4.create(), [0, 0, 0], [0, 0, -1], [0, 1, 0]),
|
|
271
|
+
projection: mat4.perspective(mat4.create(), Math.PI / 3, 1, 0.1, 100),
|
|
272
|
+
near: 0.1,
|
|
273
|
+
far: 100,
|
|
274
|
+
grid: { x: 4, y: 3, z: 4 },
|
|
275
|
+
lightCount: 32,
|
|
276
|
+
renderPath: 'forward',
|
|
277
|
+
};
|
|
278
|
+
const prepared = prepareStandardLighting(frame);
|
|
279
|
+
expect(prepared.ok).toBe(true);
|
|
280
|
+
if (!prepared.ok) return;
|
|
281
|
+
const transport = selectStandardClusterTransport(
|
|
282
|
+
{ compute: true, storageBuffer: true, membershipPipelineReady: true },
|
|
283
|
+
prepared.value,
|
|
284
|
+
);
|
|
285
|
+
expect(transport.ok).toBe(true);
|
|
286
|
+
if (!transport.ok) return;
|
|
287
|
+
const derived = deriveStandardTopologyInput({
|
|
288
|
+
prepared: prepared.value,
|
|
289
|
+
kind: 'clustered',
|
|
290
|
+
transport: transport.value,
|
|
291
|
+
});
|
|
292
|
+
expect(derived.ok).toBe(true);
|
|
293
|
+
if (!derived.ok) return;
|
|
294
|
+
expect(derived.value.kind).toBe('clustered');
|
|
295
|
+
if (derived.value.kind !== 'clustered') return;
|
|
296
|
+
expect(derived.value.transport.kind).toBe('compute-storage');
|
|
297
|
+
expect(derived.value.prepared).toBe(prepared.value);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('keeps graph identity stable for per-frame light movement and count changes', () => {
|
|
301
|
+
const make = (localCount: number) => {
|
|
302
|
+
const prepared = prepareStandardLighting({
|
|
303
|
+
directional: undefined,
|
|
304
|
+
local: Array.from({ length: localCount }, (_, index) => ({
|
|
305
|
+
kind: index % 2 === 0 ? ('point' as const) : ('spot' as const),
|
|
306
|
+
shadowed: false,
|
|
307
|
+
position: vec3.create(index, 0, -4),
|
|
308
|
+
range: 2,
|
|
309
|
+
})),
|
|
310
|
+
view: mat4.create(),
|
|
311
|
+
projection: mat4.create(),
|
|
312
|
+
near: 0.1,
|
|
313
|
+
far: 100,
|
|
314
|
+
grid: { x: 4, y: 3, z: 4 },
|
|
315
|
+
lightCount: 32,
|
|
316
|
+
renderPath: 'forward',
|
|
317
|
+
});
|
|
318
|
+
if (!prepared.ok) throw prepared.error;
|
|
319
|
+
const transport = selectStandardClusterTransport(
|
|
320
|
+
{ compute: true, storageBuffer: true, membershipPipelineReady: true },
|
|
321
|
+
prepared.value,
|
|
322
|
+
);
|
|
323
|
+
if (!transport.ok) throw transport.error;
|
|
324
|
+
const input = deriveStandardTopologyInput({
|
|
325
|
+
kind: 'clustered',
|
|
326
|
+
prepared: prepared.value,
|
|
327
|
+
transport: transport.value,
|
|
328
|
+
});
|
|
329
|
+
if (!input.ok) throw input.error;
|
|
330
|
+
return input.value;
|
|
331
|
+
};
|
|
332
|
+
expect(standardLightingTopologySignature(make(1))).toBe(
|
|
333
|
+
standardLightingTopologySignature(make(2)),
|
|
334
|
+
);
|
|
335
|
+
const empty = noLocalStandardLighting();
|
|
336
|
+
expect(empty.ok).toBe(true);
|
|
337
|
+
if (!empty.ok) return;
|
|
338
|
+
expect(standardLightingTopologySignature(make(1))).not.toBe(
|
|
339
|
+
standardLightingTopologySignature(empty.value),
|
|
340
|
+
);
|
|
341
|
+
expect(standardLightingTopologySignature(undefined)).not.toBe(
|
|
342
|
+
standardLightingTopologySignature(empty.value),
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('keeps the clustered graph and ABI stable across local-light 1 to 0 to 1', () => {
|
|
347
|
+
const capabilities = { compute: true, storageBuffer: true };
|
|
348
|
+
const one = zeroStandardLighting(capabilities, 1);
|
|
349
|
+
const zero = zeroStandardLighting(capabilities, 0);
|
|
350
|
+
const oneAgain = zeroStandardLighting(capabilities, 1);
|
|
351
|
+
expect(one.ok && zero.ok && oneAgain.ok).toBe(true);
|
|
352
|
+
if (!one.ok || !zero.ok || !oneAgain.ok) return;
|
|
353
|
+
expect(one.value.kind).toBe('clustered');
|
|
354
|
+
expect(zero.value.kind).toBe('clustered');
|
|
355
|
+
expect(oneAgain.value.kind).toBe('clustered');
|
|
356
|
+
expect(standardLightingTopologySignature(zero.value)).toBe(
|
|
357
|
+
standardLightingTopologySignature(one.value),
|
|
358
|
+
);
|
|
359
|
+
expect(standardLightingTopologySignature(oneAgain.value)).toBe(
|
|
360
|
+
standardLightingTopologySignature(one.value),
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const graph = new RenderGraphBuilder<RenderPipelineFrame>();
|
|
364
|
+
const built = standardPipeline.build(
|
|
365
|
+
{
|
|
366
|
+
graph,
|
|
367
|
+
standardLighting: zero.value,
|
|
368
|
+
capabilities: { rgba16floatRenderable: true },
|
|
369
|
+
projectGpuDriven: () => ok(undefined),
|
|
370
|
+
contributeFeatures: () => ok(undefined),
|
|
371
|
+
},
|
|
372
|
+
topology('forgeax::standard', {
|
|
373
|
+
shadow: {
|
|
374
|
+
directional: 'disabled',
|
|
375
|
+
pointCount: 0,
|
|
376
|
+
pointFaceSize: 512,
|
|
377
|
+
spotCount: 0,
|
|
378
|
+
} as unknown as RenderPipelineTopology['shadow'],
|
|
379
|
+
camera: { tonemap: 'none', antialias: 'none', bloom: 'off' },
|
|
380
|
+
}),
|
|
381
|
+
);
|
|
382
|
+
expect(built.ok).toBe(true);
|
|
383
|
+
if (!built.ok) return;
|
|
384
|
+
const compiled = graph.compile({ device, surfaceSize: { width: 800, height: 600 } });
|
|
385
|
+
expect(compiled.ok).toBe(true);
|
|
386
|
+
if (!compiled.ok) return;
|
|
387
|
+
const info = compiled.value.inspect();
|
|
388
|
+
expect(info.passes.map((pass) => pass.name)).toContain('cluster-membership-producer');
|
|
389
|
+
expect(info.resources.map((resource) => resource.label)).toContain('hdrp-light-data');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('evaluates Cluster membership from the current frame, not graph-build light count', () => {
|
|
393
|
+
const one = zeroStandardLighting({ compute: true, storageBuffer: true }, 1);
|
|
394
|
+
const zero = zeroStandardLighting({ compute: true, storageBuffer: true }, 0);
|
|
395
|
+
expect(one.ok && zero.ok).toBe(true);
|
|
396
|
+
if (!one.ok || !zero.ok) return;
|
|
397
|
+
const asFrame = (standardLighting: typeof one.value) =>
|
|
398
|
+
({ standardLighting }) as unknown as RenderPipelineFrame;
|
|
399
|
+
expect(shouldExecuteStandardClusterMembershipPass(asFrame(zero.value))).toBe(false);
|
|
400
|
+
expect(shouldExecuteStandardClusterMembershipPass(asFrame(one.value))).toBe(true);
|
|
401
|
+
expect(shouldExecuteStandardClusterMembershipPass(asFrame(zero.value))).toBe(false);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('rejects raw capability and light data at the topology adapter boundary', () => {
|
|
405
|
+
const empty = zeroStandardLighting({ compute: true, storageBuffer: true });
|
|
406
|
+
expect(empty.ok).toBe(true);
|
|
407
|
+
if (!empty.ok) return;
|
|
408
|
+
expect(empty.value.kind).toBe('clustered');
|
|
409
|
+
if (empty.value.kind !== 'clustered') return;
|
|
410
|
+
const derived = deriveStandardTopologyInput({
|
|
411
|
+
kind: 'clustered',
|
|
412
|
+
prepared: empty.value.prepared,
|
|
413
|
+
transport: empty.value.transport,
|
|
414
|
+
lights: [],
|
|
415
|
+
} as unknown as Parameters<typeof deriveStandardTopologyInput>[0]);
|
|
416
|
+
expect(derived.ok).toBe(false);
|
|
417
|
+
if (derived.ok) return;
|
|
418
|
+
expect(derived.error.code).toBe('topology-input-invalid');
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('accepts an explicit no-local topology without allocating Cluster resources', () => {
|
|
422
|
+
const standardLighting = noLocalStandardLighting();
|
|
423
|
+
expect(standardLighting.ok).toBe(true);
|
|
424
|
+
if (!standardLighting.ok) return;
|
|
425
|
+
const graph = new RenderGraphBuilder<RenderPipelineFrame>();
|
|
426
|
+
const built = standardPipeline.build(
|
|
427
|
+
{
|
|
428
|
+
graph,
|
|
429
|
+
standardLighting: standardLighting.value,
|
|
430
|
+
capabilities: { rgba16floatRenderable: false },
|
|
431
|
+
projectGpuDriven: () => ok(undefined),
|
|
432
|
+
contributeFeatures: () => ok(undefined),
|
|
433
|
+
},
|
|
434
|
+
topology('forgeax::standard', {
|
|
435
|
+
lane: { compute: false, storageBuffer: false, multisample: false, maxColorAttachments: 4 },
|
|
436
|
+
camera: { tonemap: 'none', antialias: 'none', bloom: 'off' },
|
|
437
|
+
}),
|
|
438
|
+
);
|
|
439
|
+
expect(built.ok).toBe(true);
|
|
440
|
+
if (!built.ok) return;
|
|
441
|
+
const compiled = graph.compile({ device, surfaceSize: { width: 800, height: 600 } });
|
|
442
|
+
expect(compiled.ok).toBe(true);
|
|
443
|
+
if (!compiled.ok) return;
|
|
444
|
+
const info = compiled.value.inspect();
|
|
445
|
+
expect(info.passes.map((pass) => pass.name)).not.toContain('cluster-membership-producer');
|
|
446
|
+
expect(info.resources.map((resource) => resource.label)).not.toContain('hdrp-light-data');
|
|
447
|
+
});
|
|
107
448
|
it('declares explicit output target domain and avoids alternate sRGB views', () => {
|
|
108
449
|
const source = readFileSync(new URL('../render-pipeline.ts', import.meta.url), 'utf8');
|
|
109
450
|
const postChainSource = readFileSync(
|
|
@@ -116,6 +457,25 @@ describe('typed built-in pipeline topology', () => {
|
|
|
116
457
|
expect(source).not.toContain('viewFormats: [topology.surface.viewFormat]');
|
|
117
458
|
});
|
|
118
459
|
|
|
460
|
+
it('forwards the output dither override into the fullscreen UBO write', () => {
|
|
461
|
+
const typedSource = readFileSync(
|
|
462
|
+
new URL('../typed-render-graph-primitives.ts', import.meta.url),
|
|
463
|
+
'utf8',
|
|
464
|
+
);
|
|
465
|
+
const dispatcherSource = readFileSync(
|
|
466
|
+
new URL('../render-graph-primitives.ts', import.meta.url),
|
|
467
|
+
'utf8',
|
|
468
|
+
);
|
|
469
|
+
expect(typedSource).toContain(
|
|
470
|
+
'paramsOverride: options.paramsTransform(frame.postProcessParams.get(options.shader))',
|
|
471
|
+
);
|
|
472
|
+
expect(dispatcherSource).toContain('paramsOverride?: Uint8Array');
|
|
473
|
+
expect(dispatcherSource).toContain(
|
|
474
|
+
'const data = paramsOverride ?? ctx.postProcessParams.get(shader);',
|
|
475
|
+
);
|
|
476
|
+
expect(dispatcherSource).toContain('input.paramsOverride,');
|
|
477
|
+
});
|
|
478
|
+
|
|
119
479
|
it('declares stable JSON-safe output and observation inspection facts', () => {
|
|
120
480
|
const source = readFileSync(new URL('../render-contract.ts', import.meta.url), 'utf8');
|
|
121
481
|
for (const field of [
|
|
@@ -138,7 +498,7 @@ describe('typed built-in pipeline topology', () => {
|
|
|
138
498
|
const compiled = compile(
|
|
139
499
|
standardPipeline,
|
|
140
500
|
topology('forgeax::standard', {
|
|
141
|
-
standardProfile: { ...DEFAULT_STANDARD_PROFILE,
|
|
501
|
+
standardProfile: { ...DEFAULT_STANDARD_PROFILE, renderPath: 'deferred', ssao: true },
|
|
142
502
|
}),
|
|
143
503
|
);
|
|
144
504
|
expect(compiled.ok).toBe(true);
|
|
@@ -147,6 +507,7 @@ describe('typed built-in pipeline topology', () => {
|
|
|
147
507
|
const names = info.passes.map((pass) => pass.name);
|
|
148
508
|
const ordered = [
|
|
149
509
|
'cluster-membership-producer',
|
|
510
|
+
'directional-shadow-observation',
|
|
150
511
|
'g-buffer',
|
|
151
512
|
'ssao-calc',
|
|
152
513
|
'ssao-blur',
|
|
@@ -162,6 +523,8 @@ describe('typed built-in pipeline topology', () => {
|
|
|
162
523
|
}
|
|
163
524
|
expect(names.filter((name) => name.startsWith('point-shadow-'))).toHaveLength(6);
|
|
164
525
|
expect(names.filter((name) => name.startsWith('shadowCascade'))).toHaveLength(4);
|
|
526
|
+
expect(names).toContain('directional-shadow-observation');
|
|
527
|
+
expect(names).toContain('linear-hdr-observation');
|
|
165
528
|
|
|
166
529
|
const membership = info.passes.find((pass) => pass.name === 'cluster-membership-producer');
|
|
167
530
|
expect(membership?.kind).toBe('compute');
|
|
@@ -185,11 +548,11 @@ describe('typed built-in pipeline topology', () => {
|
|
|
185
548
|
const compiled = compile(
|
|
186
549
|
standardPipeline,
|
|
187
550
|
topology('forgeax::standard', {
|
|
188
|
-
standardProfile: { ...DEFAULT_STANDARD_PROFILE,
|
|
551
|
+
standardProfile: { ...DEFAULT_STANDARD_PROFILE, renderPath: 'deferred' },
|
|
189
552
|
config: { ssao: { enabled: false } },
|
|
190
553
|
lane: {
|
|
191
554
|
compute: false,
|
|
192
|
-
storageBuffer:
|
|
555
|
+
storageBuffer: true,
|
|
193
556
|
multisample: false,
|
|
194
557
|
maxColorAttachments: 8,
|
|
195
558
|
},
|
|
@@ -245,7 +608,23 @@ describe('typed built-in pipeline topology', () => {
|
|
|
245
608
|
expect(effect1?.dependencies).toContain('post-effect-copy-1');
|
|
246
609
|
});
|
|
247
610
|
|
|
248
|
-
it('
|
|
611
|
+
it('admits the fullscreen post-effect chain atomically', () => {
|
|
612
|
+
const visited: string[] = [];
|
|
613
|
+
const pending = resolvePostProcessChainAdmission(['example::a', 'example::b'], (identity) => {
|
|
614
|
+
visited.push(identity);
|
|
615
|
+
return identity === 'example::a';
|
|
616
|
+
});
|
|
617
|
+
expect(pending).toEqual({ admitted: [], pending: true });
|
|
618
|
+
expect(visited).toEqual(['example::a', 'example::b']);
|
|
619
|
+
|
|
620
|
+
const ready = resolvePostProcessChainAdmission(['example::a', 'example::b'], () => true);
|
|
621
|
+
expect(ready).toEqual({
|
|
622
|
+
admitted: ['example::a', 'example::b'],
|
|
623
|
+
pending: false,
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('removes unsupported MSAA while retaining the CPU storage lane', () => {
|
|
249
628
|
const compiled = compile(
|
|
250
629
|
standardPipeline,
|
|
251
630
|
topology('forgeax::standard', {
|
|
@@ -253,7 +632,7 @@ describe('typed built-in pipeline topology', () => {
|
|
|
253
632
|
config: { postEffects: ['example::a'] },
|
|
254
633
|
lane: {
|
|
255
634
|
compute: false,
|
|
256
|
-
storageBuffer:
|
|
635
|
+
storageBuffer: true,
|
|
257
636
|
multisample: false,
|
|
258
637
|
maxColorAttachments: 4,
|
|
259
638
|
},
|
|
@@ -263,7 +642,7 @@ describe('typed built-in pipeline topology', () => {
|
|
|
263
642
|
if (!compiled.ok) return;
|
|
264
643
|
const info = compiled.value.inspect();
|
|
265
644
|
expect(info.resources.some((resource) => resource.label.includes('msaa'))).toBe(false);
|
|
266
|
-
expect(info.passes.some((pass) => pass.name.startsWith('post-effect-'))).toBe(
|
|
645
|
+
expect(info.passes.some((pass) => pass.name.startsWith('post-effect-'))).toBe(true);
|
|
267
646
|
});
|
|
268
647
|
|
|
269
648
|
it('declares one TAA producer and resolve for TAA demand', () => {
|
|
@@ -299,9 +678,12 @@ describe('typed built-in pipeline topology', () => {
|
|
|
299
678
|
|
|
300
679
|
it('fails closed when TAA history imports are missing', () => {
|
|
301
680
|
const graph = new RenderGraphBuilder<RenderPipelineFrame>();
|
|
681
|
+
const standardLighting = zeroStandardLighting({ compute: true, storageBuffer: true });
|
|
682
|
+
if (!standardLighting.ok) throw standardLighting.error;
|
|
302
683
|
const built = standardPipeline.build(
|
|
303
684
|
{
|
|
304
685
|
graph,
|
|
686
|
+
standardLighting: standardLighting.value,
|
|
305
687
|
capabilities: { rgba16floatRenderable: true },
|
|
306
688
|
projectGpuDriven: () => ok(undefined),
|
|
307
689
|
contributeFeatures: () => ok(undefined),
|
|
@@ -326,7 +708,7 @@ describe('typed built-in pipeline topology', () => {
|
|
|
326
708
|
temporal: { taa: true, motionBlur: true },
|
|
327
709
|
lane: {
|
|
328
710
|
compute: false,
|
|
329
|
-
storageBuffer:
|
|
711
|
+
storageBuffer: true,
|
|
330
712
|
multisample: false,
|
|
331
713
|
maxColorAttachments: 4,
|
|
332
714
|
},
|
|
@@ -412,11 +794,11 @@ describe('typed built-in pipeline topology', () => {
|
|
|
412
794
|
).toHaveLength(0);
|
|
413
795
|
});
|
|
414
796
|
|
|
415
|
-
it('
|
|
797
|
+
it('keeps deferred attachment admission independent from the Cluster transport', () => {
|
|
416
798
|
const built = compile(
|
|
417
799
|
standardPipeline,
|
|
418
800
|
topology('forgeax::standard', {
|
|
419
|
-
standardProfile: { ...DEFAULT_STANDARD_PROFILE,
|
|
801
|
+
standardProfile: { ...DEFAULT_STANDARD_PROFILE, renderPath: 'deferred' },
|
|
420
802
|
lane: {
|
|
421
803
|
compute: true,
|
|
422
804
|
storageBuffer: true,
|
|
@@ -425,8 +807,7 @@ describe('typed built-in pipeline topology', () => {
|
|
|
425
807
|
},
|
|
426
808
|
}),
|
|
427
809
|
);
|
|
428
|
-
expect(built.ok).toBe(
|
|
429
|
-
if (!built.ok) expect(built.error.code).toBe('hdrp-deferred-caps-insufficient');
|
|
810
|
+
expect(built.ok).toBe(true);
|
|
430
811
|
});
|
|
431
812
|
});
|
|
432
813
|
|
|
@@ -40,10 +40,10 @@ describe('urp render feature targets', () => {
|
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
it('
|
|
43
|
+
it('publishes a float target for non-storage-buffer LDR frames', () => {
|
|
44
44
|
expect(targets('none', 'none', false)?.[0]).toMatchObject({
|
|
45
45
|
kind: 'scene-color',
|
|
46
|
-
format: '
|
|
46
|
+
format: 'rgba16float',
|
|
47
47
|
sampleCount: 1,
|
|
48
48
|
});
|
|
49
49
|
});
|
|
@@ -82,4 +82,19 @@ describe('visibility resolver truth table', () => {
|
|
|
82
82
|
expect(result.get(parent)?.intent).toBeUndefined();
|
|
83
83
|
expect(result.get(child)?.effective).toBe('visible');
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
it('invalidates the cached snapshot when visibility intent changes', () => {
|
|
87
|
+
const world = new World();
|
|
88
|
+
const parent = spawn(world, 'visible');
|
|
89
|
+
const child = spawn(world, 'inherited', parent);
|
|
90
|
+
|
|
91
|
+
const visible = resolveVisibility(world);
|
|
92
|
+
expect(visible.effective(child)).toBe('visible');
|
|
93
|
+
|
|
94
|
+
world.set(parent, Visibility, { state: VisibilityStateValue.hidden }).unwrap();
|
|
95
|
+
|
|
96
|
+
const hidden = resolveVisibility(world);
|
|
97
|
+
expect(hidden).not.toBe(visible);
|
|
98
|
+
expect(hidden.effective(child)).toBe('hidden');
|
|
99
|
+
});
|
|
85
100
|
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { RhiAdapter, RhiDevice } from '@forgeax/engine-rhi';
|
|
2
|
+
|
|
3
|
+
const COMPRESSION_FEATURES: GPUFeatureName[] = [
|
|
4
|
+
'texture-compression-bc',
|
|
5
|
+
'texture-compression-etc2',
|
|
6
|
+
'texture-compression-astc',
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
// The Standard transmission fragment ABI needs 17 sampled textures across its
|
|
10
|
+
// material, IBL, clustered-light, and SSAO bindings. Ordinary Standard draws
|
|
11
|
+
// use the non-transmission variant and remain valid on the WebGPU minimum of
|
|
12
|
+
// 16; only request the elevated limit when the adapter can provide it.
|
|
13
|
+
export const STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES = 17;
|
|
14
|
+
|
|
15
|
+
export interface DeviceFeatureAdmissionOptions {
|
|
16
|
+
readonly gpuPassTiming?: object | undefined;
|
|
17
|
+
readonly captureGpuTimings?: boolean | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DeviceFeatureAdmission {
|
|
21
|
+
readonly requiredFeatures: GPUFeatureName[];
|
|
22
|
+
readonly requiredLimits?: {
|
|
23
|
+
readonly maxSampledTexturesPerShaderStage: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type AdapterFeatureProbe = Pick<RhiAdapter, 'features'> & Partial<Pick<RhiAdapter, 'limits'>>;
|
|
28
|
+
|
|
29
|
+
export function deriveDeviceFeatureAdmission(
|
|
30
|
+
adapter: AdapterFeatureProbe,
|
|
31
|
+
options?: DeviceFeatureAdmissionOptions,
|
|
32
|
+
): DeviceFeatureAdmission {
|
|
33
|
+
const requiredFeatures = COMPRESSION_FEATURES.filter((feature) => adapter.features.has(feature));
|
|
34
|
+
const timestampRequested =
|
|
35
|
+
options?.gpuPassTiming !== undefined || options?.captureGpuTimings === true;
|
|
36
|
+
if (timestampRequested && adapter.features.has('timestamp-query')) {
|
|
37
|
+
requiredFeatures.push('timestamp-query');
|
|
38
|
+
}
|
|
39
|
+
const supportsTransmission =
|
|
40
|
+
(adapter.limits?.maxSampledTexturesPerShaderStage ?? 0) >=
|
|
41
|
+
STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
42
|
+
return {
|
|
43
|
+
requiredFeatures,
|
|
44
|
+
...(supportsTransmission
|
|
45
|
+
? {
|
|
46
|
+
requiredLimits: {
|
|
47
|
+
maxSampledTexturesPerShaderStage: STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
: {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function deviceOptionsForAdapter(
|
|
55
|
+
adapter: AdapterFeatureProbe,
|
|
56
|
+
options?: DeviceFeatureAdmissionOptions,
|
|
57
|
+
): DeviceFeatureAdmission | undefined {
|
|
58
|
+
const admission = deriveDeviceFeatureAdmission(adapter, options);
|
|
59
|
+
return admission.requiredFeatures.length === 0 && admission.requiredLimits === undefined
|
|
60
|
+
? undefined
|
|
61
|
+
: admission;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isTimestampQueryAdmitted(
|
|
65
|
+
caps: Pick<RhiDevice['caps'], 'timestampQuery' | 'timestampPeriodNanoseconds'>,
|
|
66
|
+
): boolean {
|
|
67
|
+
return (
|
|
68
|
+
caps.timestampQuery === true &&
|
|
69
|
+
caps.timestampPeriodNanoseconds !== null &&
|
|
70
|
+
Number.isFinite(caps.timestampPeriodNanoseconds) &&
|
|
71
|
+
caps.timestampPeriodNanoseconds > 0
|
|
72
|
+
);
|
|
73
|
+
}
|