@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,7 +1,10 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { HANDLE_CUBE } from '@forgeax/engine-assets-runtime';
|
|
3
5
|
import { World } from '@forgeax/engine-ecs';
|
|
4
6
|
import { createProfiler } from '@forgeax/engine-profiler';
|
|
7
|
+
import { DEFAULT_STANDARD_PROFILE } from '@forgeax/engine-render';
|
|
5
8
|
import { RhiError, type ShaderModule } from '@forgeax/engine-rhi';
|
|
6
9
|
import {
|
|
7
10
|
RhiNullAdapter,
|
|
@@ -15,6 +18,7 @@ import { err, ok } from '@forgeax/engine-types';
|
|
|
15
18
|
import { describe, expect, it, vi } from 'vitest';
|
|
16
19
|
import type { RhiBackendPack } from '../assembly/backend-contract';
|
|
17
20
|
import { createRenderer as constructRenderer } from '../assembly/factory';
|
|
21
|
+
import { DirectionalLight, MeshFilter, MeshRenderer, PointLight, SpotLight } from '../components';
|
|
18
22
|
import {
|
|
19
23
|
ANTIALIAS_NONE,
|
|
20
24
|
ANTIALIAS_TAA,
|
|
@@ -26,6 +30,10 @@ import {
|
|
|
26
30
|
import type { RenderFeature } from '../features/types';
|
|
27
31
|
import { renderLifecycleManifestUrl } from './shader-manifest-fixture';
|
|
28
32
|
|
|
33
|
+
const assemblyDirectory = dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
const factorySourcePath = resolve(assemblyDirectory, '../assembly/factory.ts');
|
|
35
|
+
const renderTargetHostSourcePath = resolve(assemblyDirectory, '../assembly/render-target-host.ts');
|
|
36
|
+
|
|
29
37
|
function manifestUrl(): string {
|
|
30
38
|
return `data:application/json,${encodeURIComponent(JSON.stringify({ schemaVersion: '1.0.0', entries: [] }))}`;
|
|
31
39
|
}
|
|
@@ -92,6 +100,17 @@ async function createBloomScenario() {
|
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
describe('factory contract', () => {
|
|
103
|
+
it('requires one Renderer-owned target lifecycle seam', () => {
|
|
104
|
+
expect(existsSync(renderTargetHostSourcePath)).toBe(true);
|
|
105
|
+
const factorySource = readFileSync(factorySourcePath, 'utf8');
|
|
106
|
+
expect(factorySource.match(/createRenderTargetHost\(/g)).toHaveLength(2);
|
|
107
|
+
expect(factorySource).toContain("from './render-target-host'");
|
|
108
|
+
expect(factorySource).toContain('renderTargetHost.beginFrame()');
|
|
109
|
+
expect(factorySource).toContain('renderTargetHost.onFrameSubmitted(completed)');
|
|
110
|
+
expect(factorySource).toContain('renderTargetHost.recover()');
|
|
111
|
+
expect(factorySource).toContain('renderTargetHost.dispose()');
|
|
112
|
+
});
|
|
113
|
+
|
|
95
114
|
it('keeps the public output-transform identity and closes removed tone symbols', () => {
|
|
96
115
|
const source = [
|
|
97
116
|
'render-contract.ts',
|
|
@@ -148,6 +167,22 @@ describe('factory contract', () => {
|
|
|
148
167
|
renderer.dispose();
|
|
149
168
|
});
|
|
150
169
|
|
|
170
|
+
it('rejects the removed Standard reflection-probe profile field at the Renderer boundary', async () => {
|
|
171
|
+
const renderer = await constructRenderer(
|
|
172
|
+
{ getContext: () => null },
|
|
173
|
+
{ rhi },
|
|
174
|
+
{ shaderManifestUrl: manifestUrl() },
|
|
175
|
+
);
|
|
176
|
+
const staleProfile = {
|
|
177
|
+
...DEFAULT_STANDARD_PROFILE,
|
|
178
|
+
reflectionProbes: false,
|
|
179
|
+
} as unknown as Parameters<typeof renderer.setProfile>[0];
|
|
180
|
+
const result = renderer.setProfile(staleProfile);
|
|
181
|
+
expect(result.ok).toBe(false);
|
|
182
|
+
if (!result.ok) expect(result.error.code).toBe('frame-input-invalid');
|
|
183
|
+
renderer.dispose();
|
|
184
|
+
});
|
|
185
|
+
|
|
151
186
|
it('projects stable output and observation identity without pixel payloads', async () => {
|
|
152
187
|
const renderer = await constructRenderer(
|
|
153
188
|
{ getContext: () => null },
|
|
@@ -175,6 +210,59 @@ describe('factory contract', () => {
|
|
|
175
210
|
renderer.dispose();
|
|
176
211
|
});
|
|
177
212
|
|
|
213
|
+
it('delivers a target readback only through its matching receipt', async () => {
|
|
214
|
+
const renderer = await constructRenderer(
|
|
215
|
+
{ getContext: () => null },
|
|
216
|
+
{ rhi },
|
|
217
|
+
{ shaderManifestUrl: manifestUrl() },
|
|
218
|
+
);
|
|
219
|
+
const target = renderer.createRenderTarget({
|
|
220
|
+
shape: '2d',
|
|
221
|
+
width: 17,
|
|
222
|
+
height: 9,
|
|
223
|
+
format: 'rgba8unorm',
|
|
224
|
+
mipLevels: 1,
|
|
225
|
+
sampleCount: 1,
|
|
226
|
+
sampled: false,
|
|
227
|
+
readback: true,
|
|
228
|
+
});
|
|
229
|
+
expect(target.ok).toBe(true);
|
|
230
|
+
if (!target.ok) return;
|
|
231
|
+
const ticket = renderer.requestTargetReadback(target.value, { mipLevel: 0 });
|
|
232
|
+
expect(ticket.ok).toBe(true);
|
|
233
|
+
if (!ticket.ok) return;
|
|
234
|
+
const world = new World();
|
|
235
|
+
const attached = renderer.attach(world);
|
|
236
|
+
expect(attached.ok).toBe(true);
|
|
237
|
+
if (!attached.ok) return;
|
|
238
|
+
const frame = renderer.draw({
|
|
239
|
+
leases: [attached.value],
|
|
240
|
+
camera: { lease: attached.value },
|
|
241
|
+
environment: { lease: attached.value },
|
|
242
|
+
});
|
|
243
|
+
expect(frame.ok).toBe(true);
|
|
244
|
+
if (!frame.ok || frame.value === undefined) return;
|
|
245
|
+
const observed = await renderer.observe(frame.value, {
|
|
246
|
+
include: ['target-readbacks'],
|
|
247
|
+
targetReadbacks: [ticket.value],
|
|
248
|
+
});
|
|
249
|
+
expect(observed.ok).toBe(true);
|
|
250
|
+
if (!observed.ok) return;
|
|
251
|
+
expect(observed.value.targetReadbacks?.[0]).toMatchObject({
|
|
252
|
+
frameId: frame.value.frameId,
|
|
253
|
+
deviceGeneration: frame.value.deviceGeneration,
|
|
254
|
+
bytesPerRow: 256,
|
|
255
|
+
byteLength: 256 * 9,
|
|
256
|
+
});
|
|
257
|
+
const repeated = await renderer.observe(frame.value, {
|
|
258
|
+
include: ['target-readbacks'],
|
|
259
|
+
targetReadbacks: [ticket.value],
|
|
260
|
+
});
|
|
261
|
+
expect(repeated.ok).toBe(false);
|
|
262
|
+
if (!repeated.ok) expect(repeated.error.code).toBe('render-target-state-invalid');
|
|
263
|
+
renderer.dispose();
|
|
264
|
+
});
|
|
265
|
+
|
|
178
266
|
it('does not promote a graph feature without a fullscreen effect into post-effects', async () => {
|
|
179
267
|
let planCalls = 0;
|
|
180
268
|
const feature = {
|
|
@@ -205,13 +293,16 @@ describe('factory contract', () => {
|
|
|
205
293
|
renderer.dispose();
|
|
206
294
|
});
|
|
207
295
|
|
|
208
|
-
it('keeps the active graph as LKG
|
|
296
|
+
it('keeps the active graph as LKG while a feature candidate is pending', async () => {
|
|
209
297
|
let invalid = false;
|
|
298
|
+
let featurePlanCalls = 0;
|
|
210
299
|
const featureErrors: string[] = [];
|
|
211
300
|
const feature: RenderFeature<undefined> = {
|
|
212
301
|
identity: 'synthetic.candidate-lkg',
|
|
302
|
+
shaderModuleMode: 'immediate',
|
|
213
303
|
extract: () => ok(undefined),
|
|
214
304
|
plan: () => {
|
|
305
|
+
featurePlanCalls += 1;
|
|
215
306
|
if (!invalid) return ok({ resources: [], passes: [] });
|
|
216
307
|
return ok({
|
|
217
308
|
resources: [
|
|
@@ -245,7 +336,7 @@ describe('factory contract', () => {
|
|
|
245
336
|
passes: [
|
|
246
337
|
{
|
|
247
338
|
kind: 'compute',
|
|
248
|
-
name: '
|
|
339
|
+
name: 'forward',
|
|
249
340
|
program: 'candidate.program',
|
|
250
341
|
bindings: 'candidate.bindings',
|
|
251
342
|
dispatches: [{ kind: 'direct', entryPoint: 'main', workgroups: [1] }],
|
|
@@ -257,7 +348,7 @@ describe('factory contract', () => {
|
|
|
257
348
|
const renderer = await constructRenderer(
|
|
258
349
|
{ width: 64, height: 64, getContext: () => null },
|
|
259
350
|
{ rhi, features: [feature] },
|
|
260
|
-
{ shaderManifestUrl:
|
|
351
|
+
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
261
352
|
);
|
|
262
353
|
expect((await renderer.initialization).ok).toBe(true);
|
|
263
354
|
const unsubscribe = renderer.onError((error) => featureErrors.push(error.code));
|
|
@@ -269,29 +360,43 @@ describe('factory contract', () => {
|
|
|
269
360
|
world
|
|
270
361
|
.spawn(
|
|
271
362
|
{ component: Transform, data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
|
|
272
|
-
{
|
|
363
|
+
{
|
|
364
|
+
component: LocalCamera,
|
|
365
|
+
data: {
|
|
366
|
+
fov: 1,
|
|
367
|
+
aspect: 1,
|
|
368
|
+
near: 0.1,
|
|
369
|
+
far: 100,
|
|
370
|
+
antialias: ANTIALIAS_NONE,
|
|
371
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
372
|
+
bloom: BLOOM_DISABLED,
|
|
373
|
+
},
|
|
374
|
+
},
|
|
273
375
|
)
|
|
274
376
|
.unwrap();
|
|
275
377
|
expect(world.update().ok).toBe(true);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
).toBe(true);
|
|
283
|
-
const lastKnownGoodPasses = [...renderer.perFramePassNames];
|
|
284
|
-
expect(lastKnownGoodPasses.length).toBeGreaterThan(0);
|
|
285
|
-
|
|
378
|
+
const initialDraw = renderer.draw({
|
|
379
|
+
leases: [attached.value],
|
|
380
|
+
camera: { lease: attached.value },
|
|
381
|
+
environment: { lease: attached.value },
|
|
382
|
+
});
|
|
383
|
+
expect(initialDraw.ok).toBe(true);
|
|
286
384
|
invalid = true;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
).toBe(true);
|
|
385
|
+
const candidateDraw = renderer.draw({
|
|
386
|
+
leases: [attached.value],
|
|
387
|
+
camera: { lease: attached.value },
|
|
388
|
+
environment: { lease: attached.value },
|
|
389
|
+
});
|
|
390
|
+
expect(candidateDraw.ok).toBe(true);
|
|
294
391
|
await Promise.resolve();
|
|
392
|
+
const pendingDraw = renderer.draw({
|
|
393
|
+
leases: [attached.value],
|
|
394
|
+
camera: { lease: attached.value },
|
|
395
|
+
environment: { lease: attached.value },
|
|
396
|
+
});
|
|
397
|
+
expect(pendingDraw.ok).toBe(true);
|
|
398
|
+
const lastKnownGoodPasses = [...renderer.perFramePassNames];
|
|
399
|
+
expect(lastKnownGoodPasses.length).toBeGreaterThan(0);
|
|
295
400
|
expect(
|
|
296
401
|
renderer.draw({
|
|
297
402
|
leases: [attached.value],
|
|
@@ -299,12 +404,124 @@ describe('factory contract', () => {
|
|
|
299
404
|
environment: { lease: attached.value },
|
|
300
405
|
}).ok,
|
|
301
406
|
).toBe(true);
|
|
302
|
-
expect(
|
|
407
|
+
expect(featurePlanCalls).toBeGreaterThan(1);
|
|
408
|
+
expect(featureErrors).toEqual([]);
|
|
409
|
+
expect(renderer.inspect().featureDiagnostics[0]).toMatchObject({ status: 'active' });
|
|
303
410
|
expect(renderer.perFramePassNames).toEqual(lastKnownGoodPasses);
|
|
304
411
|
unsubscribe();
|
|
305
412
|
renderer.dispose();
|
|
306
413
|
});
|
|
307
414
|
|
|
415
|
+
it('records a no-storage directional-shadow frame without Cluster resources', async () => {
|
|
416
|
+
const nullAdapter = new RhiNullAdapter();
|
|
417
|
+
const noStorageRhi = {
|
|
418
|
+
...rhi,
|
|
419
|
+
requestAdapter: async () => {
|
|
420
|
+
const created = await nullAdapter.requestDevice();
|
|
421
|
+
if (!created.ok) return created;
|
|
422
|
+
const baseDevice = created.value;
|
|
423
|
+
const device = new Proxy(baseDevice, {
|
|
424
|
+
get(target, property, receiver) {
|
|
425
|
+
if (property === 'caps') return { ...target.caps, storageBuffer: false };
|
|
426
|
+
return Reflect.get(target, property, receiver);
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
return ok({
|
|
430
|
+
features: nullAdapter.features,
|
|
431
|
+
limits: nullAdapter.limits,
|
|
432
|
+
requestDevice: async () => ok(device),
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
const renderer = await constructRenderer(
|
|
437
|
+
{ width: 64, height: 64, getContext: () => null },
|
|
438
|
+
{ rhi: noStorageRhi as never },
|
|
439
|
+
{ shaderManifestUrl: renderLifecycleManifestUrl() },
|
|
440
|
+
);
|
|
441
|
+
expect((await renderer.initialization).ok).toBe(true);
|
|
442
|
+
const errors: string[] = [];
|
|
443
|
+
const unsubscribe = renderer.onError((error) => errors.push(error.code));
|
|
444
|
+
const world = new World();
|
|
445
|
+
const attached = renderer.attach(world);
|
|
446
|
+
expect(attached.ok).toBe(true);
|
|
447
|
+
if (!attached.ok) return;
|
|
448
|
+
registerPropagateTransforms(world);
|
|
449
|
+
world.spawn(
|
|
450
|
+
{ component: Transform, data: { pos: [0, 0, 4], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
|
|
451
|
+
{
|
|
452
|
+
component: LocalCamera,
|
|
453
|
+
data: {
|
|
454
|
+
fov: 1,
|
|
455
|
+
aspect: 1,
|
|
456
|
+
near: 0.1,
|
|
457
|
+
far: 100,
|
|
458
|
+
antialias: ANTIALIAS_NONE,
|
|
459
|
+
tonemap: TONEMAP_ACES_FILMIC,
|
|
460
|
+
bloom: BLOOM_DISABLED,
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
);
|
|
464
|
+
world.spawn({
|
|
465
|
+
component: DirectionalLight,
|
|
466
|
+
data: {
|
|
467
|
+
direction: [0, -1, 0],
|
|
468
|
+
color: [1, 1, 1],
|
|
469
|
+
intensity: 1,
|
|
470
|
+
cascadeCount: 1,
|
|
471
|
+
mapSize: 64,
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
world.spawn(
|
|
475
|
+
{ component: Transform, data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
|
|
476
|
+
{ component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
|
|
477
|
+
{ component: MeshRenderer, data: {} },
|
|
478
|
+
);
|
|
479
|
+
expect(world.update().ok).toBe(true);
|
|
480
|
+
const frame = renderer.draw({
|
|
481
|
+
leases: [attached.value],
|
|
482
|
+
camera: { lease: attached.value },
|
|
483
|
+
environment: { lease: attached.value },
|
|
484
|
+
});
|
|
485
|
+
expect(frame.ok).toBe(true);
|
|
486
|
+
expect(renderer.inspect().standardLighting).toMatchObject({
|
|
487
|
+
producer: 'none',
|
|
488
|
+
requested: 0,
|
|
489
|
+
admitted: 0,
|
|
490
|
+
});
|
|
491
|
+
expect(renderer.perFramePassNames).not.toContain('cluster-membership-producer');
|
|
492
|
+
expect(errors).toEqual([]);
|
|
493
|
+
|
|
494
|
+
// The no-storage topology is valid only while the scene has no local
|
|
495
|
+
// lights. A Point/Spot admission must fail before graph/encoder work and
|
|
496
|
+
// retain the last successful pass list rather than silently submitting a
|
|
497
|
+
// direct-light fallback with an incompatible ABI.
|
|
498
|
+
const lastSuccessfulPasses = [...renderer.perFramePassNames];
|
|
499
|
+
const localLight = world.spawn(
|
|
500
|
+
{ component: Transform, data: { pos: [0, 0, 1] } },
|
|
501
|
+
{ component: PointLight, data: { intensity: 8, range: 10 } },
|
|
502
|
+
);
|
|
503
|
+
expect(localLight.ok).toBe(true);
|
|
504
|
+
const spotLight = world.spawn(
|
|
505
|
+
{ component: Transform, data: { pos: [1, 0, 1] } },
|
|
506
|
+
{
|
|
507
|
+
component: SpotLight,
|
|
508
|
+
data: { direction: [0, -1, 0], intensity: 8, range: 10 },
|
|
509
|
+
},
|
|
510
|
+
);
|
|
511
|
+
expect(spotLight.ok).toBe(true);
|
|
512
|
+
expect(world.update().ok).toBe(true);
|
|
513
|
+
const rejected = renderer.draw({
|
|
514
|
+
leases: [attached.value],
|
|
515
|
+
camera: { lease: attached.value },
|
|
516
|
+
environment: { lease: attached.value },
|
|
517
|
+
});
|
|
518
|
+
expect(rejected.ok).toBe(false);
|
|
519
|
+
expect(errors).toContain('standard-cluster-transport-unavailable');
|
|
520
|
+
expect(renderer.perFramePassNames).toEqual(lastSuccessfulPasses);
|
|
521
|
+
unsubscribe();
|
|
522
|
+
await renderer.dispose();
|
|
523
|
+
});
|
|
524
|
+
|
|
308
525
|
it('attaches derived-state systems once without putting writes in draw', async () => {
|
|
309
526
|
const renderer = await constructRenderer(
|
|
310
527
|
{ getContext: () => null },
|
|
@@ -412,7 +629,7 @@ describe('factory contract', () => {
|
|
|
412
629
|
createShaderModule: (_device, desc) => {
|
|
413
630
|
const label = desc.label ?? '<unlabeled>';
|
|
414
631
|
moduleCalls.push(label);
|
|
415
|
-
if (label
|
|
632
|
+
if (!label.startsWith('post-process-forgeax.taa-resolve-pso-')) {
|
|
416
633
|
return Promise.resolve(ok({} as ShaderModule));
|
|
417
634
|
}
|
|
418
635
|
let resolve!: (value: ReturnType<typeof ok<ShaderModule>>) => void;
|
|
@@ -473,7 +690,7 @@ describe('factory contract', () => {
|
|
|
473
690
|
expect(off.ok).toBe(true);
|
|
474
691
|
|
|
475
692
|
expect(
|
|
476
|
-
moduleCalls.filter((label) => label
|
|
693
|
+
moduleCalls.filter((label) => label.startsWith('post-process-forgeax.taa-resolve-pso-')),
|
|
477
694
|
).toHaveLength(1);
|
|
478
695
|
|
|
479
696
|
world.set(camera, LocalCamera, { antialias: ANTIALIAS_TAA }).unwrap();
|
|
@@ -501,7 +718,7 @@ describe('factory contract', () => {
|
|
|
501
718
|
pending[2]?.resolve(ok({} as ShaderModule));
|
|
502
719
|
await pending[2]?.promise;
|
|
503
720
|
expect(
|
|
504
|
-
moduleCalls.filter((label) => label
|
|
721
|
+
moduleCalls.filter((label) => label.startsWith('post-process-forgeax.taa-resolve-pso-')),
|
|
505
722
|
).toHaveLength(3);
|
|
506
723
|
});
|
|
507
724
|
|
|
@@ -594,7 +811,7 @@ describe('factory contract', () => {
|
|
|
594
811
|
.slice(-4);
|
|
595
812
|
expect(firstGeneration).toHaveLength(4);
|
|
596
813
|
expect(
|
|
597
|
-
moduleCalls.filter((label) => label
|
|
814
|
+
moduleCalls.filter((label) => label.startsWith('post-process-forgeax.taa-resolve-pso-')),
|
|
598
815
|
).toHaveLength(1);
|
|
599
816
|
const firstFrame = renderer.inspect().frame.frameId;
|
|
600
817
|
|
|
@@ -618,7 +835,7 @@ describe('factory contract', () => {
|
|
|
618
835
|
expect(renderer.inspect().frame.frameId).toBe(firstFrame + 1);
|
|
619
836
|
expect(renderer.inspect().state).toBe('alive');
|
|
620
837
|
expect(
|
|
621
|
-
moduleCalls.filter((label) => label
|
|
838
|
+
moduleCalls.filter((label) => label.startsWith('post-process-forgeax.taa-resolve-pso-')),
|
|
622
839
|
).toHaveLength(1);
|
|
623
840
|
world.set(camera, LocalCamera, { antialias: ANTIALIAS_NONE }).unwrap();
|
|
624
841
|
expect(world.update().ok).toBe(true);
|
|
@@ -1460,10 +1677,19 @@ describe('factory contract', () => {
|
|
|
1460
1677
|
const failed = await renderer.recover();
|
|
1461
1678
|
expect(failed.ok).toBe(false);
|
|
1462
1679
|
expect(renderer.health().reason).toBe('device-lost');
|
|
1680
|
+
expect(renderer.inspect().perFramePassNames).toEqual([]);
|
|
1681
|
+
expect(renderer.inspect().standardLighting).toBeUndefined();
|
|
1463
1682
|
const recovered = await renderer.recover();
|
|
1464
1683
|
expect(recovered.ok).toBe(true);
|
|
1684
|
+
// Recovery publishes a device but not a graph. The first post-recovery
|
|
1685
|
+
// draw must rebuild both the graph accessors and Standard inspection
|
|
1686
|
+
// against the replacement device.
|
|
1687
|
+
expect(renderer.inspect().perFramePassNames).toEqual([]);
|
|
1688
|
+
expect(renderer.inspect().standardLighting).toBeUndefined();
|
|
1465
1689
|
expect(world.update().ok).toBe(true);
|
|
1466
1690
|
expect(renderer.draw(input()).ok).toBe(true);
|
|
1691
|
+
expect(renderer.inspect().perFramePassNames.length).toBeGreaterThan(0);
|
|
1692
|
+
expect(renderer.inspect().standardLighting).toBeDefined();
|
|
1467
1693
|
expect(renderer.inspect().bloom.state).toBe('active');
|
|
1468
1694
|
expect(renderer.inspect().bloom.generation).toBeGreaterThan(0);
|
|
1469
1695
|
expect(renderer.inspect().temporal.deviceGeneration).toBeGreaterThan(beforeDeviceGeneration);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { World } from '@forgeax/engine-ecs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { Fog } from '../components/fog';
|
|
4
|
+
import { selectEnvironment } from '../environment/frame';
|
|
5
|
+
|
|
6
|
+
describe('Fog component contract', () => {
|
|
7
|
+
it('exposes one bounded authoring component with stable defaults', () => {
|
|
8
|
+
expect(Fog.name).toBe('Fog');
|
|
9
|
+
expect(Fog.fields.color.type).toBe('array<f32, 3>');
|
|
10
|
+
expect(Fog.fields.density.default).toBe(0.01);
|
|
11
|
+
expect(Fog.fields.heightFalloff.default).toBe(0);
|
|
12
|
+
expect(Fog.fields.maxOpacity.default).toBe(1);
|
|
13
|
+
|
|
14
|
+
const world = new World();
|
|
15
|
+
const entity = world.spawn({ component: Fog, data: {} }).unwrap();
|
|
16
|
+
const value = world.get(entity, Fog).unwrap();
|
|
17
|
+
expect(Array.from(value.color)).toEqual([0.5, 0.5, 0.5]);
|
|
18
|
+
expect(value.density).toBeCloseTo(0.01);
|
|
19
|
+
expect(value.heightFalloff).toBe(0);
|
|
20
|
+
expect(value.maxOpacity).toBe(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it.each([
|
|
24
|
+
['color', { color: [1.1, 0.2, 0.3] }],
|
|
25
|
+
['density', { density: -0.1 }],
|
|
26
|
+
['heightFalloff', { heightFalloff: Number.NaN }],
|
|
27
|
+
['maxOpacity', { maxOpacity: 1.1 }],
|
|
28
|
+
] as const)('rejects invalid %s at the shared selection owner', (field, override) => {
|
|
29
|
+
const result = selectEnvironment({
|
|
30
|
+
environments: [],
|
|
31
|
+
fogs: [
|
|
32
|
+
{
|
|
33
|
+
entityKey: 1,
|
|
34
|
+
color: [0.2, 0.3, 0.4],
|
|
35
|
+
density: 0.01,
|
|
36
|
+
heightFalloff: 0,
|
|
37
|
+
maxOpacity: 1,
|
|
38
|
+
...override,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
suns: [],
|
|
42
|
+
lane: 'direct',
|
|
43
|
+
});
|
|
44
|
+
expect(result.ok).toBe(false);
|
|
45
|
+
if (result.ok) return;
|
|
46
|
+
expect(result.error.code).toBe('environment-selection-invalid');
|
|
47
|
+
expect(result.error.detail.field).toContain(field);
|
|
48
|
+
expect(result.error.expected).toContain('finite');
|
|
49
|
+
expect(result.error.hint).toContain(field);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('rejects a second Fog owner without choosing a first-hit winner', () => {
|
|
53
|
+
const result = selectEnvironment({
|
|
54
|
+
environments: [],
|
|
55
|
+
fogs: [
|
|
56
|
+
{ entityKey: 2, color: [0.2, 0.3, 0.4], density: 0.01, heightFalloff: 0, maxOpacity: 1 },
|
|
57
|
+
{ entityKey: 3, color: [0.4, 0.3, 0.2], density: 0.02, heightFalloff: 0, maxOpacity: 1 },
|
|
58
|
+
],
|
|
59
|
+
suns: [],
|
|
60
|
+
lane: 'direct',
|
|
61
|
+
});
|
|
62
|
+
expect(result).toMatchObject({
|
|
63
|
+
ok: false,
|
|
64
|
+
error: { code: 'fog-cardinality', detail: { count: 2 } },
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { World } from '@forgeax/engine-ecs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { Atmosphere } from '../components/atmosphere';
|
|
4
|
+
import { DirectionalLight } from '../components/directional-light';
|
|
5
|
+
import { Fog } from '../components/fog';
|
|
6
|
+
import { extractFrames } from '../render-system-extract';
|
|
7
|
+
|
|
8
|
+
function worldWithFog() {
|
|
9
|
+
const world = new World();
|
|
10
|
+
world
|
|
11
|
+
.spawn({
|
|
12
|
+
component: Atmosphere,
|
|
13
|
+
data: {
|
|
14
|
+
turbidity: 2,
|
|
15
|
+
rayleigh: 1,
|
|
16
|
+
mieCoefficient: 0.005,
|
|
17
|
+
mieDirectionalG: 0.8,
|
|
18
|
+
sunAngularRadius: 0.004675,
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
.unwrap();
|
|
22
|
+
world
|
|
23
|
+
.spawn({ component: DirectionalLight, data: { direction: [0, -1, 0], intensity: 1 } })
|
|
24
|
+
.unwrap();
|
|
25
|
+
const entity = world
|
|
26
|
+
.spawn({
|
|
27
|
+
component: Fog,
|
|
28
|
+
data: { color: [0.2, 0.3, 0.4], density: 0.03, heightFalloff: 0, maxOpacity: 0.8 },
|
|
29
|
+
})
|
|
30
|
+
.unwrap();
|
|
31
|
+
return { world, entity };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('Fog frame owner integration', () => {
|
|
35
|
+
it('retains the last valid Fog frame after an invalid update and exposes failure', () => {
|
|
36
|
+
const { world, entity } = worldWithFog();
|
|
37
|
+
const initial = extractFrames([world], 0);
|
|
38
|
+
expect(initial.fog?.density).toBeCloseTo(0.03);
|
|
39
|
+
expect(initial.fog?.heightFalloff).toBe(0);
|
|
40
|
+
|
|
41
|
+
world
|
|
42
|
+
.set(entity, Fog, {
|
|
43
|
+
color: [0.2, 0.3, 0.4],
|
|
44
|
+
density: -1,
|
|
45
|
+
heightFalloff: 0,
|
|
46
|
+
maxOpacity: 0.8,
|
|
47
|
+
})
|
|
48
|
+
.unwrap();
|
|
49
|
+
const invalid = extractFrames([world], 0);
|
|
50
|
+
expect(invalid.fog).toEqual(initial.fog);
|
|
51
|
+
expect(invalid.fogFailure).toMatchObject({
|
|
52
|
+
code: 'environment-selection-invalid',
|
|
53
|
+
detail: { field: 'density', value: -1 },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
world
|
|
57
|
+
.set(entity, Fog, {
|
|
58
|
+
color: [1.2, 0.3, 0.4],
|
|
59
|
+
density: 0.03,
|
|
60
|
+
heightFalloff: 0,
|
|
61
|
+
maxOpacity: 0.8,
|
|
62
|
+
})
|
|
63
|
+
.unwrap();
|
|
64
|
+
const invalidColor = extractFrames([world], 0);
|
|
65
|
+
expect(invalidColor.fog).toEqual(initial.fog);
|
|
66
|
+
expect(invalidColor.fogFailure).toMatchObject({ detail: { field: 'color[0]' } });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('clears the retained Fog only after a valid removal, never after an invalid frame', () => {
|
|
70
|
+
const { world, entity } = worldWithFog();
|
|
71
|
+
expect(extractFrames([world], 0).fog).toBeDefined();
|
|
72
|
+
world
|
|
73
|
+
.set(entity, Fog, {
|
|
74
|
+
color: [0.2, 0.3, 0.4],
|
|
75
|
+
density: -1,
|
|
76
|
+
heightFalloff: 0,
|
|
77
|
+
maxOpacity: 0.8,
|
|
78
|
+
})
|
|
79
|
+
.unwrap();
|
|
80
|
+
expect(extractFrames([world], 0).fog).toBeDefined();
|
|
81
|
+
world.removeComponent(entity, Fog).unwrap();
|
|
82
|
+
const removed = extractFrames([world], 0);
|
|
83
|
+
expect(removed.fog).toBeUndefined();
|
|
84
|
+
expect(removed.fogFailure).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { createFullscreenRenderFeature } from '../features/fullscreen';
|
|
3
3
|
import { freezeRenderFeaturePlan } from '../features/plan';
|
|
4
|
+
import {
|
|
5
|
+
postProcessShaderEntrySignature,
|
|
6
|
+
postProcessShaderModuleLabel,
|
|
7
|
+
postProcessShaderPipelineLabel,
|
|
8
|
+
} from '../fullscreen-post-process-pass';
|
|
4
9
|
import { createSceneDataCatalog } from '../temporal/scene-data-catalog';
|
|
5
10
|
|
|
6
11
|
describe('fullscreen RenderFeature plan', () => {
|
|
@@ -23,6 +28,12 @@ describe('fullscreen RenderFeature plan', () => {
|
|
|
23
28
|
}),
|
|
24
29
|
});
|
|
25
30
|
|
|
31
|
+
expect(feature.requiredFullscreenPostProcesses).toEqual([
|
|
32
|
+
{
|
|
33
|
+
identity: 'test::fullscreen',
|
|
34
|
+
source: '@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1); }',
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
26
37
|
expect(planned.ok).toBe(true);
|
|
27
38
|
if (!planned.ok) return;
|
|
28
39
|
expect(
|
|
@@ -36,4 +47,30 @@ describe('fullscreen RenderFeature plan', () => {
|
|
|
36
47
|
});
|
|
37
48
|
expect(planned.value.passes).toEqual([]);
|
|
38
49
|
});
|
|
50
|
+
|
|
51
|
+
it('treats source, params, and reads as one cached declaration identity', () => {
|
|
52
|
+
const base = {
|
|
53
|
+
source: 'fn fs_main() -> vec4f { return vec4f(1); }',
|
|
54
|
+
params: { byteSize: 16, defaultValue: new Uint8Array(16) },
|
|
55
|
+
reads: ['scene-color'],
|
|
56
|
+
} as const;
|
|
57
|
+
expect(postProcessShaderEntrySignature(base)).not.toBe(
|
|
58
|
+
postProcessShaderEntrySignature({ ...base, source: `${base.source}\n// next` }),
|
|
59
|
+
);
|
|
60
|
+
expect(postProcessShaderEntrySignature(base)).not.toBe(
|
|
61
|
+
postProcessShaderEntrySignature({
|
|
62
|
+
...base,
|
|
63
|
+
params: { byteSize: 16, defaultValue: new Uint8Array([1, ...new Uint8Array(15)]) },
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
expect(postProcessShaderEntrySignature(base)).not.toBe(
|
|
67
|
+
postProcessShaderEntrySignature({ ...base, reads: ['other-color'] }),
|
|
68
|
+
);
|
|
69
|
+
expect(postProcessShaderModuleLabel('test::fullscreen', base.source)).not.toBe(
|
|
70
|
+
postProcessShaderModuleLabel('test::fullscreen', `${base.source}\n// next`),
|
|
71
|
+
);
|
|
72
|
+
expect(postProcessShaderPipelineLabel('test::fullscreen', base.source)).not.toBe(
|
|
73
|
+
postProcessShaderPipelineLabel('test::fullscreen', `${base.source}\n// next`),
|
|
74
|
+
);
|
|
75
|
+
});
|
|
39
76
|
});
|
|
@@ -87,6 +87,15 @@ export interface GpuDrivenIndirectRasterEvidence {
|
|
|
87
87
|
readonly passNames: readonly string[];
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
export const GPU_DRIVEN_PRODUCTION_VIEW_BGL_ENTRIES = [
|
|
91
|
+
{ binding: 0, visibility: 0x1, buffer: { type: 'uniform', hasDynamicOffset: true } },
|
|
92
|
+
{
|
|
93
|
+
binding: 10,
|
|
94
|
+
visibility: 0x1,
|
|
95
|
+
buffer: { type: 'uniform', hasDynamicOffset: true },
|
|
96
|
+
},
|
|
97
|
+
] as const satisfies readonly GPUBindGroupLayoutEntry[];
|
|
98
|
+
|
|
90
99
|
export async function runGpuDrivenIndirectRasterEvidence(): Promise<GpuDrivenIndirectRasterEvidence> {
|
|
91
100
|
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
92
101
|
const device = (await adapter.requestDevice()).unwrap();
|
|
@@ -102,14 +111,7 @@ export async function runGpuDrivenIndirectRasterEvidence(): Promise<GpuDrivenInd
|
|
|
102
111
|
const viewLayout = device
|
|
103
112
|
.createBindGroupLayout({
|
|
104
113
|
label: 'gpu-driven-production-view-bgl',
|
|
105
|
-
entries: [
|
|
106
|
-
{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } },
|
|
107
|
-
{
|
|
108
|
-
binding: 10,
|
|
109
|
-
visibility: 0x1,
|
|
110
|
-
buffer: { type: 'uniform', hasDynamicOffset: true },
|
|
111
|
-
},
|
|
112
|
-
],
|
|
114
|
+
entries: [...GPU_DRIVEN_PRODUCTION_VIEW_BGL_ENTRIES],
|
|
113
115
|
})
|
|
114
116
|
.unwrap();
|
|
115
117
|
const viewUniform = device
|
|
@@ -249,7 +251,6 @@ export async function runGpuDrivenIndirectRasterEvidence(): Promise<GpuDrivenInd
|
|
|
249
251
|
]),
|
|
250
252
|
viewBindGroupLayout: viewLayout,
|
|
251
253
|
meshResidencyEpoch: 1,
|
|
252
|
-
hdrp: false,
|
|
253
254
|
})
|
|
254
255
|
.unwrap();
|
|
255
256
|
if (prepared === undefined) throw new Error('GPU-driven production lane inactive');
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { buildPbrViewBglEntries } from '../pbr-pipeline';
|
|
3
|
+
import {
|
|
4
|
+
GPU_DRIVEN_PRODUCTION_VIEW_BGL_ENTRIES,
|
|
5
|
+
runGpuDrivenIndirectRasterEvidence,
|
|
6
|
+
} from './gpu-driven-indirect-raster-evidence';
|
|
3
7
|
|
|
4
8
|
describe('GPU-driven indirect raster Dawn', () => {
|
|
9
|
+
it('matches the shared PBR View BGL dynamic-offset slots', () => {
|
|
10
|
+
const fixtureDynamicBindings = GPU_DRIVEN_PRODUCTION_VIEW_BGL_ENTRIES.filter(
|
|
11
|
+
(entry) => entry.buffer?.hasDynamicOffset === true,
|
|
12
|
+
).map((entry) => entry.binding);
|
|
13
|
+
const sharedDynamicBindings = buildPbrViewBglEntries({ storageBuffer: true })
|
|
14
|
+
.filter((entry) => entry.buffer?.hasDynamicOffset === true)
|
|
15
|
+
.map((entry) => entry.binding);
|
|
16
|
+
|
|
17
|
+
expect(fixtureDynamicBindings).toEqual([0, 10]);
|
|
18
|
+
expect(fixtureDynamicBindings).toEqual(sharedDynamicBindings);
|
|
19
|
+
});
|
|
20
|
+
|
|
5
21
|
it('consumes indexed, non-indexed, and multi-submesh indirect args without CPU readback', async () => {
|
|
6
22
|
const evidence = await runGpuDrivenIndirectRasterEvidence();
|
|
7
23
|
expect(evidence.pixel).toEqual([64, 128, 191, 255]);
|