@forgeax/engine-render 0.1.21 → 0.1.23
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 +150 -29
- package/dist/assembly/backend-contract.d.ts +13 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/factory.d.ts +14 -6
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +10 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
- package/dist/chunk-4L3P6DT5.mjs.map +1 -0
- package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
- package/dist/chunk-4NPUASZR.mjs.map +1 -0
- package/dist/chunk-DIYW3M5D.mjs +5085 -0
- package/dist/chunk-DIYW3M5D.mjs.map +1 -0
- package/dist/chunk-FBC4R44J.mjs +1003 -0
- package/dist/chunk-FBC4R44J.mjs.map +1 -0
- package/dist/{chunk-FS2KNUQH.mjs → chunk-FF7K3FSO.mjs} +253 -489
- package/dist/chunk-FF7K3FSO.mjs.map +1 -0
- package/dist/chunk-HZUPSYW7.mjs +1104 -0
- package/dist/chunk-HZUPSYW7.mjs.map +1 -0
- package/dist/{chunk-6OPXDQ2E.mjs → chunk-QODA5UKU.mjs} +33 -294
- package/dist/chunk-QODA5UKU.mjs.map +1 -0
- package/dist/chunk-YALPS44A.mjs +296 -0
- package/dist/chunk-YALPS44A.mjs.map +1 -0
- package/dist/{chunk-5YADUCA7.mjs → chunk-ZUWKAZ2I.mjs} +585 -991
- package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +5 -0
- package/dist/cluster-binner.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts +4 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/light-probe.d.ts +9 -0
- package/dist/components/light-probe.d.ts.map +1 -0
- package/dist/components/rect-area-light.d.ts +12 -0
- package/dist/components/rect-area-light.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +4 -0
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +3 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +10437 -10044
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +8 -1
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/lifecycle-transaction.d.ts +2 -0
- package/dist/device/lifecycle-transaction.d.ts.map +1 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/gpu-driven.d.ts +26 -0
- package/dist/errors/gpu-driven.d.ts.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +9 -1
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +26 -1
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +50 -0
- package/dist/extract/gpu-driven.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +63 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +98 -2
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +64 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +14 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +8 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +0 -1
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +3 -3
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +90 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +163 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +23 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/light-buffer-layout.d.ts +58 -149
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +26 -12
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -0
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +1 -1
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/resources.d.ts +72 -0
- package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/state.d.ts +24 -0
- package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +8 -6
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +50 -10
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +7 -3
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-timing.d.ts +19 -7
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +0 -20
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +6 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +3 -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/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +57 -6
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +49 -2
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +1 -2
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/inspection.d.ts +10 -0
- package/dist/reflection/inspection.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +49 -0
- package/dist/reflection/projection.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts +60 -0
- package/dist/reflection/record-owner.d.ts.map +1 -0
- package/dist/render-contract.d.ts +14 -8
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +7 -0
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +104 -16
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +12 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/probe-blend-record.d.ts +22 -0
- package/dist/scene/probe-blend-record.d.ts.map +1 -0
- package/dist/scene/probe-blend.d.ts +142 -0
- package/dist/scene/probe-blend.d.ts.map +1 -0
- package/dist/scene/render-scene-types.d.ts +0 -6
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +103 -9
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/visibility/bounds.d.ts +20 -0
- package/dist/scene/visibility/bounds.d.ts.map +1 -0
- package/dist/scene/visibility/budget.d.ts +16 -0
- package/dist/scene/visibility/budget.d.ts.map +1 -0
- package/dist/scene/visibility/facet.d.ts +73 -0
- package/dist/scene/visibility/facet.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-lod.d.ts +46 -0
- package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
- package/dist/scene/visibility/inspection.d.ts +132 -0
- package/dist/scene/visibility/inspection.d.ts.map +1 -0
- package/dist/scene/visibility/lod-selector.d.ts +25 -0
- package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
- package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
- package/dist/scene/visibility/types.d.ts +34 -0
- package/dist/scene/visibility/types.d.ts.map +1 -0
- package/dist/ssr/admission.d.ts +122 -0
- package/dist/ssr/admission.d.ts.map +1 -0
- package/dist/ssr/composition.d.ts +32 -0
- package/dist/ssr/composition.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +39 -0
- package/dist/ssr/errors.d.ts.map +1 -0
- package/dist/ssr/identity.d.ts +13 -0
- package/dist/ssr/identity.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +1 -1
- package/dist/temporal/frame-coordinator.d.ts +13 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +2 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -5
- package/dist/temporal/view.d.ts +7 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/tilemap-chunk-extract-system.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts +4 -0
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +8 -4
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
- package/src/__tests__/detached-inspection.integration.test.ts +6 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +13 -1
- package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
- package/src/__tests__/environment-generation.integration.test.ts +16 -0
- package/src/__tests__/evidence-identity.ts +13 -0
- package/src/__tests__/evidence-identity.unit.test.ts +24 -0
- package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
- package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
- package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
- package/src/__tests__/factory-contract.integration.test.ts +33 -0
- package/src/__tests__/frame-observation.contract.test.ts +15 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
- package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
- package/src/__tests__/gpu-lod-evidence.ts +188 -0
- package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
- package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
- package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
- package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
- package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
- package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
- package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
- package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
- package/src/__tests__/gpu-scene.unit.test.ts +49 -3
- package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
- package/src/__tests__/light-inspection.unit.test.ts +55 -0
- package/src/__tests__/light-resource-error.unit.test.ts +43 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
- package/src/__tests__/lod-bounds.unit.test.ts +42 -0
- package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
- package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
- package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
- package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
- package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
- package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
- package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
- package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
- package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
- package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
- package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
- package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
- package/src/__tests__/lod-selector.profile.test.ts +35 -0
- package/src/__tests__/lod-selector.unit.test.ts +68 -0
- package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
- package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
- package/src/__tests__/ltc-resources.unit.test.ts +32 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
- package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
- package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
- package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
- package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
- package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
- package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
- package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
- package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
- package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
- package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
- package/src/__tests__/occlusion-query.evidence.ts +178 -0
- package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
- package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
- package/src/__tests__/probe-bindings.unit.test.ts +200 -0
- package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
- package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
- package/src/__tests__/probe-capacity.integration.test.ts +97 -0
- package/src/__tests__/probe-recovery.integration.test.ts +104 -0
- package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
- package/src/__tests__/profiler-phase-token.test.ts +1 -0
- package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
- package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
- package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
- package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
- package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
- package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
- package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
- package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
- package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
- package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
- package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
- package/src/__tests__/ssr-composition.unit.test.ts +46 -0
- package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
- package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
- package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
- package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
- package/src/__tests__/temporal-view.unit.test.ts +3 -3
- package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
- package/src/__tests__/visibility-budget.integration.test.ts +140 -0
- package/src/__tests__/visibility-budget.unit.test.ts +70 -0
- package/src/__tests__/visibility-extract.integration.test.ts +11 -6
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
- package/src/assembly/backend-contract.ts +13 -0
- package/src/assembly/device-feature-admission.ts +16 -9
- package/src/assembly/factory.ts +547 -93
- package/src/assembly/host-contract.ts +10 -0
- package/src/cluster-binner.ts +31 -0
- package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
- package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
- package/src/components/index.ts +3 -0
- package/src/components/light-helpers.ts +52 -1
- package/src/components/light-probe.ts +13 -0
- package/src/components/rect-area-light.ts +15 -0
- package/src/components/scene-instance.ts +6 -0
- package/src/components/spot-light.ts +3 -0
- package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
- package/src/device/gpu-residency.ts +140 -9
- package/src/device/lifecycle-transaction.ts +12 -0
- package/src/environment/lifecycle.ts +8 -1
- package/src/errors/gpu-driven.ts +92 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recover.ts +32 -0
- package/src/errors/render.ts +55 -1
- package/src/extract/camera.ts +3 -3
- package/src/extract/gpu-driven.ts +108 -0
- package/src/extract/visibility.ts +0 -23
- package/src/gpu-driven/batch-topology.ts +206 -28
- package/src/gpu-driven/prepared-draw.ts +208 -0
- package/src/gpu-driven/production-raster.ts +953 -41
- package/src/gpu-driven/view-gpu.ts +411 -29
- package/src/gpu-scene-schema.ts +13 -0
- package/src/gpu-scene.ts +134 -65
- package/src/gpu-usage.ts +0 -1
- package/src/hdrp-buffers.ts +6 -6
- package/src/index.ts +74 -0
- package/src/inspection-types.ts +220 -1
- package/src/internal.ts +38 -0
- package/src/light-buffer-layout.ts +154 -324
- package/src/materials.ts +16 -2
- package/src/pbr-pipeline.ts +151 -45
- package/src/pipeline/standard-forward-lane.ts +47 -0
- package/src/pipeline/standard-lighting/graph.ts +2 -1
- package/src/pipeline/standard-lighting/light-frame.ts +3 -2
- package/src/pipeline/standard-lighting/topology.ts +1 -1
- package/src/pipeline/standard-lighting/transport.ts +2 -1
- package/src/pipeline/standard-pipeline.ts +2 -0
- package/src/pipeline-builder.ts +5 -2
- package/src/pipeline-spec.ts +12 -1
- package/src/plugin.ts +4 -0
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
- package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
- package/src/prepare/extended-lighting/resources.ts +389 -0
- package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
- package/src/prepare/extended-lighting/state.ts +92 -0
- package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +23 -1
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
- package/src/record/frame-lighting.ts +252 -53
- package/src/record/frame-snapshot.ts +57 -12
- package/src/record/frame.ts +410 -93
- package/src/record/gpu-timing.ts +108 -44
- package/src/record/helpers.ts +0 -58
- package/src/record/main-pass-geometry.ts +204 -39
- package/src/record/main-pass-material.ts +8 -1
- package/src/record/main-pass-sprite-draws.ts +52 -85
- package/src/record/mesh-ssbo.ts +47 -36
- package/src/record/render-context.ts +65 -4
- package/src/record/shadow-pass.ts +66 -40
- package/src/record/typed-frame-graph.ts +225 -83
- package/src/record/view-ubo.ts +14 -24
- package/src/reflection/inspection.ts +67 -0
- package/src/reflection/projection.ts +196 -1
- package/src/reflection/record-owner.ts +1283 -0
- package/src/render-contract.ts +17 -5
- package/src/render-pipeline.ts +5 -0
- package/src/render-system-extract.ts +493 -134
- package/src/render-system.ts +811 -615
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
- package/src/scene/probe-blend-record.ts +54 -0
- package/src/scene/probe-blend.ts +813 -0
- package/src/scene/render-scene-types.ts +0 -7
- package/src/scene/render-scene.ts +848 -183
- package/src/scene/visibility/bounds.ts +57 -0
- package/src/scene/visibility/budget.ts +40 -0
- package/src/scene/visibility/facet.ts +400 -0
- package/src/scene/visibility/gpu-frame-timing.ts +101 -0
- package/src/scene/visibility/gpu-lod.ts +119 -0
- package/src/scene/visibility/inspection.ts +313 -0
- package/src/scene/visibility/lod-selector.ts +84 -0
- package/src/scene/visibility/occlusion-confidence.ts +478 -0
- package/src/scene/visibility/occlusion-pass.ts +165 -0
- package/src/scene/visibility/occlusion-query-pool.ts +178 -0
- package/src/scene/visibility/occlusion-runtime.ts +672 -0
- package/src/scene/visibility/types.ts +50 -0
- package/src/ssr/admission.ts +451 -0
- package/src/ssr/composition.ts +73 -0
- package/src/ssr/errors.ts +76 -0
- package/src/ssr/identity.ts +12 -0
- package/src/systems/skin-palette-allocator.ts +1 -1
- package/src/temporal/frame-coordinator.ts +26 -0
- package/src/temporal/frame.ts +14 -1
- package/src/temporal/view.ts +11 -0
- package/src/tilemap-chunk-extract-system.ts +4 -4
- package/src/typed-render-graph-primitives.ts +48 -0
- package/src/volume/passes.ts +46 -49
- package/dist/.tsbuildinfo +0 -1
- package/dist/chunk-5YADUCA7.mjs.map +0 -1
- package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
- package/dist/chunk-BXBQQKFQ.mjs +0 -67
- package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
- package/dist/chunk-FS2KNUQH.mjs.map +0 -1
- package/dist/chunk-PCHBPYML.mjs.map +0 -1
- package/dist/chunk-VFPLTKCU.mjs.map +0 -1
package/src/errors/recover.ts
CHANGED
|
@@ -7,6 +7,38 @@
|
|
|
7
7
|
// channel (see render-contract.ts RendererError composition).
|
|
8
8
|
|
|
9
9
|
import type { DeviceScopeReceipt } from '../device/resource-types';
|
|
10
|
+
import type { ReflectionFallbackRecoveryAction } from '../inspection-types';
|
|
11
|
+
import type { SsrAdmissionError, SsrOwnerRecoveryAction } from '../ssr/errors';
|
|
12
|
+
|
|
13
|
+
export type { ReflectionFallbackRecoveryAction } from '../inspection-types';
|
|
14
|
+
|
|
15
|
+
export function reflectionFallbackRecoveryAction(
|
|
16
|
+
source: 'probe' | 'skylight' | 'neutral',
|
|
17
|
+
hasLastKnownGood: boolean,
|
|
18
|
+
): ReflectionFallbackRecoveryAction {
|
|
19
|
+
if (source === 'probe' && hasLastKnownGood) return 'use-LKG';
|
|
20
|
+
if (source === 'skylight') return 'use-Skylight';
|
|
21
|
+
return 'use-neutral';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maps a detached admission failure to the owning producer action. This is
|
|
26
|
+
* an instruction projection only; SSR never performs the recovery itself.
|
|
27
|
+
*/
|
|
28
|
+
export function ssrAdmissionRecoveryAction(error: SsrAdmissionError): SsrOwnerRecoveryAction {
|
|
29
|
+
switch (error.code) {
|
|
30
|
+
case 'ssr-not-requested':
|
|
31
|
+
case 'ssr-receipt-stale':
|
|
32
|
+
return 'retry';
|
|
33
|
+
case 'ssr-reflection-fallback-unavailable':
|
|
34
|
+
return error.detail.action;
|
|
35
|
+
case 'ssr-format-unavailable':
|
|
36
|
+
case 'ssr-temporal-unavailable':
|
|
37
|
+
return error.detail.action;
|
|
38
|
+
case 'ssr-receipt-identity-mismatch':
|
|
39
|
+
return 'rebuild';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
10
42
|
|
|
11
43
|
// ── RecoverError (feat-20260621-renderer-health-recover-skeleton M1) ─────────
|
|
12
44
|
|
package/src/errors/render.ts
CHANGED
|
@@ -116,6 +116,60 @@ export function createRenderSurfaceError(
|
|
|
116
116
|
return new RenderSurfaceError(`surface-${kind}-failed` as SurfaceFailureCode, detail);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
export type LightResourceFailureReason = 'asset' | 'format' | 'capability' | 'capacity';
|
|
120
|
+
|
|
121
|
+
export interface LightResourceUnavailableDetail {
|
|
122
|
+
readonly entity: number;
|
|
123
|
+
readonly feature: 'ies' | 'cookie' | 'probe';
|
|
124
|
+
readonly generation: number;
|
|
125
|
+
readonly sourceKey: string;
|
|
126
|
+
readonly reason: LightResourceFailureReason;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class LightResourceUnavailableError extends Error {
|
|
130
|
+
readonly code = 'light-resource-unavailable' as const;
|
|
131
|
+
readonly expected: string;
|
|
132
|
+
readonly hint: string;
|
|
133
|
+
readonly detail: LightResourceUnavailableDetail;
|
|
134
|
+
|
|
135
|
+
constructor(
|
|
136
|
+
detail: LightResourceUnavailableDetail,
|
|
137
|
+
expected: string,
|
|
138
|
+
actual: string,
|
|
139
|
+
hint: string,
|
|
140
|
+
) {
|
|
141
|
+
super(`light-resource-unavailable: ${detail.feature} ${actual}`);
|
|
142
|
+
this.name = 'LightResourceUnavailableError';
|
|
143
|
+
this.expected = expected;
|
|
144
|
+
this.hint = hint;
|
|
145
|
+
this.detail = detail;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function createLightResourceUnavailable(input: {
|
|
150
|
+
readonly entity: number;
|
|
151
|
+
readonly feature: LightResourceUnavailableDetail['feature'];
|
|
152
|
+
readonly generation: number;
|
|
153
|
+
readonly sourceKey: string;
|
|
154
|
+
readonly reason: LightResourceFailureReason;
|
|
155
|
+
readonly expected: string;
|
|
156
|
+
readonly actual: string;
|
|
157
|
+
readonly hint: string;
|
|
158
|
+
}): LightResourceUnavailableError {
|
|
159
|
+
return new LightResourceUnavailableError(
|
|
160
|
+
{
|
|
161
|
+
entity: input.entity,
|
|
162
|
+
feature: input.feature,
|
|
163
|
+
generation: input.generation,
|
|
164
|
+
sourceKey: input.sourceKey,
|
|
165
|
+
reason: input.reason,
|
|
166
|
+
},
|
|
167
|
+
input.expected,
|
|
168
|
+
input.actual,
|
|
169
|
+
input.hint,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
119
173
|
export interface TemporalFrameSubmitDetail {
|
|
120
174
|
readonly reason: 'queue-submit-failed';
|
|
121
175
|
}
|
|
@@ -132,7 +186,6 @@ export class TemporalFrameSubmitError extends Error {
|
|
|
132
186
|
this.detail = { reason };
|
|
133
187
|
}
|
|
134
188
|
}
|
|
135
|
-
|
|
136
189
|
// ── ShadowInvalidConfigError ──────────────────────────────────────────────
|
|
137
190
|
|
|
138
191
|
/**
|
|
@@ -1821,6 +1874,7 @@ export type RenderError =
|
|
|
1821
1874
|
| PointsLinesMaterialUnsupportedError
|
|
1822
1875
|
| PointsLinesBudgetExceededError
|
|
1823
1876
|
| PointsLinesPrepareFailedError
|
|
1877
|
+
| LightResourceUnavailableError
|
|
1824
1878
|
| RenderTargetDescriptorInvalidError
|
|
1825
1879
|
| RenderTargetCapabilityMissingError
|
|
1826
1880
|
| RenderTargetStateInvalidError
|
package/src/extract/camera.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EntityHandle, World } from '@forgeax/engine-ecs';
|
|
2
2
|
import { readRenderArrayView } from '@forgeax/engine-ecs/projection';
|
|
3
3
|
import { mat4, vec3 } from '@forgeax/engine-math';
|
|
4
|
-
import {
|
|
4
|
+
import { GlobalTransform } from '@forgeax/engine-scene';
|
|
5
5
|
import {
|
|
6
6
|
antialiasFromF32,
|
|
7
7
|
bloomEnabledFromF32,
|
|
@@ -14,7 +14,7 @@ import type { CameraSnapshot } from '../render-contract';
|
|
|
14
14
|
import { getActiveCamera, selectActiveCameraIndex } from '../systems/active-camera';
|
|
15
15
|
|
|
16
16
|
function readWorldMatrix(world: World, entity: EntityHandle): Float32Array | undefined {
|
|
17
|
-
const view = readRenderArrayView(world, entity,
|
|
17
|
+
const view = readRenderArrayView(world, entity, GlobalTransform, 'world');
|
|
18
18
|
return view === undefined ? undefined : new Float32Array(view);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ export function extractCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
23
23
|
const cameras: CameraSnapshot[] = [];
|
|
24
24
|
const cameraEntities: number[] = [];
|
|
25
25
|
const cameraQuery = world
|
|
26
|
-
.query({ read: [Camera], optional: [MotionBlur], with: [
|
|
26
|
+
.query({ read: [Camera], optional: [MotionBlur], with: [GlobalTransform] })
|
|
27
27
|
.unwrap();
|
|
28
28
|
for (const row of cameraQuery) {
|
|
29
29
|
const cam = row.get(Camera);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
|
|
2
|
+
import type { MeshAsset } from '@forgeax/engine-types';
|
|
3
|
+
import {
|
|
4
|
+
type GpuDrivenDrawRange,
|
|
5
|
+
type PreparedGpuDrivenDraw,
|
|
6
|
+
type PrepareGpuDrivenDrawInput,
|
|
7
|
+
prepareGpuDrivenDraw,
|
|
8
|
+
} from '../gpu-driven/prepared-draw';
|
|
9
|
+
import type { MaterialSnapshot } from '../render-system-extract';
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
GpuDrivenPreparationError,
|
|
13
|
+
type GpuDrivenPreparationErrorCode,
|
|
14
|
+
type GpuDrivenPreparationErrorDetail,
|
|
15
|
+
type GpuDrivenPreparationReason,
|
|
16
|
+
} from '../errors/gpu-driven';
|
|
17
|
+
export {
|
|
18
|
+
type GpuDrivenDrawRange,
|
|
19
|
+
type GpuDrivenGeometryReceipt,
|
|
20
|
+
type GpuDrivenSkinReceipt,
|
|
21
|
+
type PreparedGpuDrivenDraw,
|
|
22
|
+
type PrepareGpuDrivenDrawInput,
|
|
23
|
+
prepareGpuDrivenDraw,
|
|
24
|
+
} from '../gpu-driven/prepared-draw';
|
|
25
|
+
|
|
26
|
+
export interface GpuDrivenDrawSnapshot {
|
|
27
|
+
readonly kind: GpuDrivenDrawRange['kind'];
|
|
28
|
+
readonly first: GpuDrivenDrawRange['first'];
|
|
29
|
+
readonly count: GpuDrivenDrawRange['count'];
|
|
30
|
+
readonly baseVertex: GpuDrivenDrawRange['baseVertex'];
|
|
31
|
+
readonly materialSlot: number;
|
|
32
|
+
readonly topology: GpuDrivenDrawRange['topology'];
|
|
33
|
+
readonly pipelineClass: string;
|
|
34
|
+
readonly materialResourceClass: string;
|
|
35
|
+
/** Producer-owned prepared ABI facts used by the PBR GPU lane. */
|
|
36
|
+
readonly prepared?: PreparedGpuDrivenDraw;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolves prepared facts once at extract time; record never re-reads assets.
|
|
41
|
+
* The resolver surfaces UV, reflection, vertex, alpha, resource, and
|
|
42
|
+
* generation failures as GpuDrivenPreparationError before recording.
|
|
43
|
+
*/
|
|
44
|
+
export function resolvePreparedGpuDrivenDraw(input: PrepareGpuDrivenDrawInput):
|
|
45
|
+
| { readonly ok: true; readonly value: PreparedGpuDrivenDraw }
|
|
46
|
+
| {
|
|
47
|
+
readonly ok: false;
|
|
48
|
+
readonly error: import('../errors/gpu-driven').GpuDrivenPreparationError;
|
|
49
|
+
} {
|
|
50
|
+
return prepareGpuDrivenDraw(input);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Keeps the artifact type visible at this owner boundary for contract checks. */
|
|
54
|
+
export type PreparedGpuDrivenArtifact = MaterialShaderArtifact;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Derives the resource identity consumed by the existing GPU-driven batch
|
|
58
|
+
* owner. The extract stage is the only place that resolves material values;
|
|
59
|
+
* this helper keeps the batch adapter from re-reading assets later.
|
|
60
|
+
*/
|
|
61
|
+
export function gpuDrivenMaterialResourceClass(material: MaterialSnapshot): string {
|
|
62
|
+
const textures = [...(material.textureHandles?.entries() ?? [])]
|
|
63
|
+
.map(([name, handle]) => [name, Number(handle)] as const)
|
|
64
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
65
|
+
const samplers = [...(material.samplerHandles?.entries() ?? [])]
|
|
66
|
+
.map(([name, handle]) => [name, Number(handle)] as const)
|
|
67
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
68
|
+
return JSON.stringify({
|
|
69
|
+
textures,
|
|
70
|
+
samplers,
|
|
71
|
+
video: [...(material.videoTextureFields?.keys() ?? [])].sort(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Projects already-resolved mesh facts into the existing GPU-driven draw
|
|
77
|
+
* snapshot. No World or AssetRegistry access is permitted at this boundary.
|
|
78
|
+
*/
|
|
79
|
+
export function buildGpuDrivenDraws(input: {
|
|
80
|
+
readonly submeshes: readonly MeshAsset['submeshes'][number][];
|
|
81
|
+
readonly indexed: boolean;
|
|
82
|
+
readonly materials: readonly MaterialSnapshot[];
|
|
83
|
+
readonly fallbackMaterial: MaterialSnapshot;
|
|
84
|
+
readonly prepare?: (
|
|
85
|
+
draw: GpuDrivenDrawSnapshot,
|
|
86
|
+
material: MaterialSnapshot,
|
|
87
|
+
) => PreparedGpuDrivenDraw | undefined;
|
|
88
|
+
}): GpuDrivenDrawSnapshot[] {
|
|
89
|
+
let nonIndexedFirst = 0;
|
|
90
|
+
return input.submeshes.flatMap((submesh) => {
|
|
91
|
+
const drawMaterial = input.materials[submesh.materialSlot] ?? input.fallbackMaterial;
|
|
92
|
+
const first = input.indexed ? submesh.indexOffset : nonIndexedFirst;
|
|
93
|
+
nonIndexedFirst += submesh.vertexCount;
|
|
94
|
+
if (drawMaterial.transparent === true) return [];
|
|
95
|
+
const draw: GpuDrivenDrawSnapshot = {
|
|
96
|
+
kind: input.indexed ? ('indexed' as const) : ('non-indexed' as const),
|
|
97
|
+
first,
|
|
98
|
+
count: input.indexed ? submesh.indexCount : submesh.vertexCount,
|
|
99
|
+
baseVertex: 0,
|
|
100
|
+
materialSlot: submesh.materialSlot,
|
|
101
|
+
topology: submesh.topology,
|
|
102
|
+
pipelineClass: `${drawMaterial.materialShaderId ?? 'forgeax::default-unlit'}|${submesh.topology}|${JSON.stringify(drawMaterial.renderState ?? null)}`,
|
|
103
|
+
materialResourceClass: gpuDrivenMaterialResourceClass(drawMaterial),
|
|
104
|
+
};
|
|
105
|
+
const prepared = input.prepare?.(draw, drawMaterial);
|
|
106
|
+
return [prepared === undefined ? draw : { ...draw, prepared }];
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { EntityHandle, World } from '@forgeax/engine-ecs';
|
|
2
|
-
import { createWorldProjection } from '@forgeax/engine-ecs/projection';
|
|
3
2
|
import {
|
|
4
3
|
projectHierarchy,
|
|
5
4
|
type SceneHierarchyDiagnostic,
|
|
@@ -38,14 +37,6 @@ interface ResolutionState {
|
|
|
38
37
|
readonly source: VisibilitySource;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
interface VisibilityCacheEntry {
|
|
42
|
-
readonly changes: ReturnType<typeof createWorldProjection>;
|
|
43
|
-
readonly hierarchy: SceneHierarchySnapshot;
|
|
44
|
-
readonly snapshot: VisibilitySnapshot;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const VISIBILITY_CACHE = new WeakMap<World, VisibilityCacheEntry>();
|
|
48
|
-
|
|
49
40
|
/**
|
|
50
41
|
* Resolve author intent against the scene-owned valid parent projection.
|
|
51
42
|
* Diagnostics are preserved for callers to repair hierarchy input before retry.
|
|
@@ -54,19 +45,6 @@ export function resolveVisibility(
|
|
|
54
45
|
world: World,
|
|
55
46
|
hierarchy: SceneHierarchySnapshot = projectHierarchy(world),
|
|
56
47
|
): VisibilitySnapshot {
|
|
57
|
-
const cached = VISIBILITY_CACHE.get(world);
|
|
58
|
-
const changes = cached?.changes ?? createWorldProjection(world, { components: [Visibility] });
|
|
59
|
-
if (cached !== undefined) {
|
|
60
|
-
const evidence = changes.poll();
|
|
61
|
-
if (
|
|
62
|
-
evidence.status === 'delta' &&
|
|
63
|
-
evidence.changes.length === 0 &&
|
|
64
|
-
cached.hierarchy === hierarchy
|
|
65
|
-
) {
|
|
66
|
-
return cached.snapshot;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
48
|
// Visibility is an opt-in component. Query only the owning archetypes
|
|
71
49
|
// instead of walking every Entity row with an optional column; the latter
|
|
72
50
|
// made a World with no visibility intents pay a full-scene scan on each
|
|
@@ -158,6 +136,5 @@ export function resolveVisibility(
|
|
|
158
136
|
return resolveEntity?.(entity)?.effective ?? 'visible';
|
|
159
137
|
},
|
|
160
138
|
};
|
|
161
|
-
VISIBILITY_CACHE.set(world, { changes, hierarchy, snapshot });
|
|
162
139
|
return snapshot;
|
|
163
140
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { PrimitiveTopology } from '@forgeax/engine-types';
|
|
2
2
|
import type { BatchTopologyInspection } from '../inspection-types';
|
|
3
|
+
import type { CameraSnapshot } from '../render-contract';
|
|
3
4
|
import type { RenderSceneApplyResult, RenderSceneSlot } from '../scene/render-scene-types';
|
|
5
|
+
import { type GpuLodRow, selectGpuLod } from '../scene/visibility/gpu-lod';
|
|
6
|
+
import { projectedHeight as measureProjectedHeight } from '../scene/visibility/lod-selector';
|
|
7
|
+
import type { PreparedGpuDrivenDraw } from './prepared-draw';
|
|
4
8
|
|
|
5
9
|
export type { BatchTopologyInspection } from '../inspection-types';
|
|
6
10
|
|
|
@@ -14,6 +18,12 @@ export interface GpuDrivenBatchKey {
|
|
|
14
18
|
readonly topology: PrimitiveTopology;
|
|
15
19
|
readonly pipelineClass: string;
|
|
16
20
|
readonly materialResourceClass: string;
|
|
21
|
+
/** Stable producer-owned identity; topology never derives this from a shader name. */
|
|
22
|
+
readonly preparedIdentity?: string;
|
|
23
|
+
/** Resource identity projected by the prepared material contract. */
|
|
24
|
+
readonly resourceIdentity?: string;
|
|
25
|
+
/** Closed admission lane for the prepared Standard PBR draw. */
|
|
26
|
+
readonly admission?: 'opaque' | 'alpha-mask';
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
export interface GpuDrivenCandidate {
|
|
@@ -21,6 +31,16 @@ export interface GpuDrivenCandidate {
|
|
|
21
31
|
readonly generation: number;
|
|
22
32
|
readonly drawItemIndex: number;
|
|
23
33
|
readonly instanceOrdinal: number;
|
|
34
|
+
/** Absolute coverage rows uploaded to the GPU selector; root is implicit. */
|
|
35
|
+
readonly lodCoverages?: readonly number[];
|
|
36
|
+
readonly lodHysteresis?: number;
|
|
37
|
+
readonly projectedHeight?: number;
|
|
38
|
+
readonly lodRanges?: readonly {
|
|
39
|
+
readonly first: number;
|
|
40
|
+
readonly count: number;
|
|
41
|
+
readonly baseVertex: number;
|
|
42
|
+
}[];
|
|
43
|
+
readonly prepared?: PreparedGpuDrivenDraw;
|
|
24
44
|
}
|
|
25
45
|
|
|
26
46
|
export interface GpuDrivenBatch {
|
|
@@ -40,6 +60,32 @@ export interface SubmissionPlan {
|
|
|
40
60
|
readonly visibleCapacity: number;
|
|
41
61
|
}
|
|
42
62
|
|
|
63
|
+
export interface GpuLodCandidateInput {
|
|
64
|
+
readonly candidate: GpuDrivenCandidate;
|
|
65
|
+
readonly rows: readonly GpuLodRow[];
|
|
66
|
+
readonly projectedHeight: number;
|
|
67
|
+
readonly previousLevel: number;
|
|
68
|
+
readonly historyValid: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface GpuLodCompactCandidate {
|
|
72
|
+
readonly candidate: GpuDrivenCandidate;
|
|
73
|
+
readonly level: number;
|
|
74
|
+
readonly confidence: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Selects one level per stable topology candidate without mutating membership. */
|
|
78
|
+
export function compactGpuLodCandidates(
|
|
79
|
+
inputs: readonly GpuLodCandidateInput[],
|
|
80
|
+
): readonly GpuLodCompactCandidate[] {
|
|
81
|
+
return Object.freeze(
|
|
82
|
+
inputs.map((input) => {
|
|
83
|
+
const selection = selectGpuLod(input.rows, input);
|
|
84
|
+
return Object.freeze({ candidate: input.candidate, ...selection });
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
43
89
|
interface MutableBatch {
|
|
44
90
|
readonly batchId: number;
|
|
45
91
|
readonly generation: number;
|
|
@@ -56,27 +102,97 @@ function eligibleKeys(slot: RenderSceneSlot): readonly GpuDrivenBatchKey[] {
|
|
|
56
102
|
snapshot.instances?.instanceCount === 0 ||
|
|
57
103
|
snapshot.localAabb === undefined ||
|
|
58
104
|
snapshot.skin !== undefined ||
|
|
105
|
+
snapshot.morph !== undefined ||
|
|
59
106
|
snapshot.spriteInstances !== undefined
|
|
60
107
|
) {
|
|
61
108
|
return [];
|
|
62
109
|
}
|
|
63
|
-
return draws.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
110
|
+
return draws.flatMap((draw) => {
|
|
111
|
+
const prepared = draw.prepared;
|
|
112
|
+
const material = snapshot.materials[draw.materialSlot] ?? snapshot.material;
|
|
113
|
+
if (prepared !== undefined) {
|
|
114
|
+
const isStandardPbr =
|
|
115
|
+
(material.materialShaderId === 'forgeax::default-standard-pbr' &&
|
|
116
|
+
prepared.identity.material === 'forgeax::default-standard-pbr') ||
|
|
117
|
+
(material.materialShaderId === 'forgeax::default-standard-pbr-skin' &&
|
|
118
|
+
prepared.identity.material === 'forgeax::default-standard-pbr-skin');
|
|
119
|
+
const isAlphaBlend =
|
|
120
|
+
material.transparent === true || material.renderState?.blend !== undefined;
|
|
121
|
+
if (!isStandardPbr || isAlphaBlend || prepared.identity.deformation !== 'rigid') return [];
|
|
122
|
+
const preparedIdentity = `${prepared.identity.material}|${prepared.identity.geometry}|${prepared.identity.deformation}`;
|
|
123
|
+
const admission =
|
|
124
|
+
typeof material.paramSnapshot?.alphaCutoff === 'number' ? 'alpha-mask' : 'opaque';
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
assetHandle: snapshot.assetHandle,
|
|
128
|
+
drawKind: draw.kind,
|
|
129
|
+
first: prepared.first,
|
|
130
|
+
count: prepared.count,
|
|
131
|
+
baseVertex: prepared.baseVertex,
|
|
132
|
+
materialSlot: draw.materialSlot,
|
|
133
|
+
topology: prepared.topology,
|
|
134
|
+
pipelineClass: preparedIdentity,
|
|
135
|
+
materialResourceClass: draw.materialResourceClass,
|
|
136
|
+
preparedIdentity,
|
|
137
|
+
resourceIdentity: draw.materialResourceClass,
|
|
138
|
+
admission,
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
assetHandle: snapshot.assetHandle,
|
|
145
|
+
drawKind: draw.kind,
|
|
146
|
+
first: draw.first,
|
|
147
|
+
count: draw.count,
|
|
148
|
+
baseVertex: draw.baseVertex,
|
|
149
|
+
materialSlot: draw.materialSlot,
|
|
150
|
+
topology: draw.topology,
|
|
151
|
+
pipelineClass: draw.pipelineClass,
|
|
152
|
+
materialResourceClass: draw.materialResourceClass,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
});
|
|
74
156
|
}
|
|
75
157
|
|
|
76
158
|
function keyText(key: GpuDrivenBatchKey): string {
|
|
77
159
|
return JSON.stringify(key);
|
|
78
160
|
}
|
|
79
161
|
|
|
162
|
+
/**
|
|
163
|
+
* A GPU indirect draw has one index/vertex range for the whole batch. LOD
|
|
164
|
+
* selection is per candidate, so sharing a batch between LOD candidates would
|
|
165
|
+
* let `finalizeView` pick the first candidate's range for every visible
|
|
166
|
+
* instance. Keep LOD candidates in single-candidate batches; ordinary meshes
|
|
167
|
+
* retain the existing compatibility batching path.
|
|
168
|
+
*/
|
|
169
|
+
function candidateBatchText(
|
|
170
|
+
key: GpuDrivenBatchKey,
|
|
171
|
+
primitiveIndex: number,
|
|
172
|
+
drawItemIndex: number,
|
|
173
|
+
instanceOrdinal: number,
|
|
174
|
+
hasLod: boolean,
|
|
175
|
+
): string {
|
|
176
|
+
return hasLod
|
|
177
|
+
? `${keyText(key)}|lod:${primitiveIndex}:${drawItemIndex}:${instanceOrdinal}`
|
|
178
|
+
: keyText(key);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function membershipTexts(slot: RenderSceneSlot): readonly string[] {
|
|
182
|
+
const keys = eligibleKeys(slot);
|
|
183
|
+
const instanceCount = slot.snapshot.instances?.instanceCount ?? 1;
|
|
184
|
+
const hasLod = (slot.snapshot.lods?.length ?? 0) > 0;
|
|
185
|
+
const texts: string[] = [];
|
|
186
|
+
for (let drawItemIndex = 0; drawItemIndex < keys.length; drawItemIndex += 1) {
|
|
187
|
+
const key = keys[drawItemIndex];
|
|
188
|
+
if (key === undefined) continue;
|
|
189
|
+
for (let instanceOrdinal = 0; instanceOrdinal < instanceCount; instanceOrdinal += 1) {
|
|
190
|
+
texts.push(candidateBatchText(key, slot.slot, drawItemIndex, instanceOrdinal, hasLod));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return texts;
|
|
194
|
+
}
|
|
195
|
+
|
|
80
196
|
function alignInstanceBase(value: number): number {
|
|
81
197
|
// Raster binds each batch's visible-index and compact-transform segment at
|
|
82
198
|
// a static storage-buffer offset. WebGPU requires those offsets to satisfy
|
|
@@ -86,6 +202,42 @@ function alignInstanceBase(value: number): number {
|
|
|
86
202
|
return Math.ceil(value / 64) * 64;
|
|
87
203
|
}
|
|
88
204
|
|
|
205
|
+
export function projectedHeightForCandidate(slot: RenderSceneSlot, camera: CameraSnapshot): number {
|
|
206
|
+
const aabb = slot.snapshot.localAabb;
|
|
207
|
+
if (aabb === undefined || aabb.length < 6) return Number.NaN;
|
|
208
|
+
const halfX = Math.abs((aabb[3] ?? 0) - (aabb[0] ?? 0)) * 0.5;
|
|
209
|
+
const halfY = Math.abs((aabb[4] ?? 0) - (aabb[1] ?? 0)) * 0.5;
|
|
210
|
+
const halfZ = Math.abs((aabb[5] ?? 0) - (aabb[2] ?? 0)) * 0.5;
|
|
211
|
+
const world = slot.snapshot.transform.world;
|
|
212
|
+
// Transform the local AABB's half extents through the absolute value of the
|
|
213
|
+
// world 3x3. This is the conservative world-space sphere bound for every
|
|
214
|
+
// rotation and signed/non-uniform scale, and keeps CPU projection aligned
|
|
215
|
+
// with the world transform uploaded to the GPU selector.
|
|
216
|
+
const worldHalfX =
|
|
217
|
+
Math.abs(world[0] ?? 0) * halfX +
|
|
218
|
+
Math.abs(world[4] ?? 0) * halfY +
|
|
219
|
+
Math.abs(world[8] ?? 0) * halfZ;
|
|
220
|
+
const worldHalfY =
|
|
221
|
+
Math.abs(world[1] ?? 0) * halfX +
|
|
222
|
+
Math.abs(world[5] ?? 0) * halfY +
|
|
223
|
+
Math.abs(world[9] ?? 0) * halfZ;
|
|
224
|
+
const worldHalfZ =
|
|
225
|
+
Math.abs(world[2] ?? 0) * halfX +
|
|
226
|
+
Math.abs(world[6] ?? 0) * halfY +
|
|
227
|
+
Math.abs(world[10] ?? 0) * halfZ;
|
|
228
|
+
const radius = Math.hypot(worldHalfX, worldHalfY, worldHalfZ);
|
|
229
|
+
const dx = (world[12] ?? 0) - (camera.position[0] ?? 0);
|
|
230
|
+
const dy = (world[13] ?? 0) - (camera.position[1] ?? 0);
|
|
231
|
+
const dz = (world[14] ?? 0) - (camera.position[2] ?? 0);
|
|
232
|
+
return measureProjectedHeight({
|
|
233
|
+
radius,
|
|
234
|
+
depth: Math.hypot(dx, dy, dz),
|
|
235
|
+
projection: camera.projection,
|
|
236
|
+
fov: camera.fov,
|
|
237
|
+
orthoHeight: Math.abs(camera.orthoTop - camera.orthoBottom),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
89
241
|
/** Stable compatibility grouping; per-view visibility never mutates this owner. */
|
|
90
242
|
export class BatchTopology {
|
|
91
243
|
private readonly batches = new Map<string, MutableBatch>();
|
|
@@ -120,15 +272,17 @@ export class BatchTopology {
|
|
|
120
272
|
changed = this.remove(slot.slot) || changed;
|
|
121
273
|
changed = this.add(slot) || changed;
|
|
122
274
|
}
|
|
275
|
+
// Most updated slots are transform-only. A full renderable update can
|
|
276
|
+
// still carry a changed material/geometry/draw topology, so compare the
|
|
277
|
+
// stable batch memberships and patch only when that key actually changed.
|
|
278
|
+
// This keeps moving entities on the zero-work transform lane while making
|
|
279
|
+
// same-entity resource edits visible to the GPU-driven owner.
|
|
123
280
|
for (const slot of delta.updatedSlots) {
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
Array.from({ length: instanceCount }, () => keyText(key)),
|
|
127
|
-
);
|
|
128
|
-
const current = this.membershipByPrimitive.get(slot.slot) ?? [];
|
|
281
|
+
const previous = this.membershipByPrimitive.get(slot.slot) ?? [];
|
|
282
|
+
const next = membershipTexts(slot);
|
|
129
283
|
if (
|
|
130
|
-
|
|
131
|
-
|
|
284
|
+
previous.length === next.length &&
|
|
285
|
+
previous.every((membership, index) => membership.batchText === next[index])
|
|
132
286
|
) {
|
|
133
287
|
continue;
|
|
134
288
|
}
|
|
@@ -199,26 +353,50 @@ export class BatchTopology {
|
|
|
199
353
|
this.ineligiblePrimitives.delete(slot.slot);
|
|
200
354
|
const memberships: Array<{ batchText: string; candidateKey: string }> = [];
|
|
201
355
|
const instanceCount = slot.snapshot.instances?.instanceCount ?? 1;
|
|
356
|
+
const hasLod = (slot.snapshot.lods?.length ?? 0) > 0;
|
|
202
357
|
for (let drawItemIndex = 0; drawItemIndex < keys.length; drawItemIndex += 1) {
|
|
203
358
|
const key = keys[drawItemIndex];
|
|
204
359
|
if (key === undefined) continue;
|
|
205
|
-
const text = keyText(key);
|
|
206
|
-
let batch = this.batches.get(text);
|
|
207
|
-
if (batch === undefined) {
|
|
208
|
-
const reused = this.freeBatchIds.pop();
|
|
209
|
-
const batchId = reused ?? this.generationByBatchId.length;
|
|
210
|
-
const generation = reused === undefined ? 0 : (this.generationByBatchId[batchId] ?? -1) + 1;
|
|
211
|
-
this.generationByBatchId[batchId] = generation;
|
|
212
|
-
batch = { batchId, generation, key, candidates: new Map() };
|
|
213
|
-
this.batches.set(text, batch);
|
|
214
|
-
}
|
|
215
360
|
for (let instanceOrdinal = 0; instanceOrdinal < instanceCount; instanceOrdinal += 1) {
|
|
361
|
+
const text = candidateBatchText(key, slot.slot, drawItemIndex, instanceOrdinal, hasLod);
|
|
362
|
+
let batch = this.batches.get(text);
|
|
363
|
+
if (batch === undefined) {
|
|
364
|
+
const reused = this.freeBatchIds.pop();
|
|
365
|
+
const batchId = reused ?? this.generationByBatchId.length;
|
|
366
|
+
const generation =
|
|
367
|
+
reused === undefined ? 0 : (this.generationByBatchId[batchId] ?? -1) + 1;
|
|
368
|
+
this.generationByBatchId[batchId] = generation;
|
|
369
|
+
batch = { batchId, generation, key, candidates: new Map() };
|
|
370
|
+
this.batches.set(text, batch);
|
|
371
|
+
}
|
|
216
372
|
const candidateKey = `${slot.slot}:${drawItemIndex}:${instanceOrdinal}`;
|
|
373
|
+
const draw = slot.snapshot.gpuDrivenDraws?.[drawItemIndex];
|
|
374
|
+
const prepared = draw?.prepared;
|
|
217
375
|
batch.candidates.set(candidateKey, {
|
|
218
376
|
primitiveIndex: slot.slot,
|
|
219
377
|
generation: slot.generation,
|
|
220
378
|
drawItemIndex,
|
|
221
379
|
instanceOrdinal,
|
|
380
|
+
...(draw?.projectedHeight === undefined ? {} : { projectedHeight: draw.projectedHeight }),
|
|
381
|
+
...(slot.snapshot.lods === undefined
|
|
382
|
+
? {}
|
|
383
|
+
: {
|
|
384
|
+
lodCoverages: [1, ...slot.snapshot.lods.map((lod) => lod.screenCoverage)],
|
|
385
|
+
...(slot.snapshot.gpuDrivenDraws?.[drawItemIndex]?.lodRanges === undefined
|
|
386
|
+
? {}
|
|
387
|
+
: {
|
|
388
|
+
lodRanges: slot.snapshot.gpuDrivenDraws[drawItemIndex]
|
|
389
|
+
?.lodRanges as readonly {
|
|
390
|
+
readonly first: number;
|
|
391
|
+
readonly count: number;
|
|
392
|
+
readonly baseVertex: number;
|
|
393
|
+
}[],
|
|
394
|
+
}),
|
|
395
|
+
...(slot.snapshot.lodHysteresis === undefined
|
|
396
|
+
? {}
|
|
397
|
+
: { lodHysteresis: slot.snapshot.lodHysteresis }),
|
|
398
|
+
}),
|
|
399
|
+
...(prepared === undefined ? {} : { prepared }),
|
|
222
400
|
});
|
|
223
401
|
memberships.push({ batchText: text, candidateKey });
|
|
224
402
|
}
|