@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
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { mat4 } from '@forgeax/engine-math';
|
|
1
|
+
import { mat4, vec3 } from '@forgeax/engine-math';
|
|
2
2
|
import { beforeAll, describe, expect, it } from 'vitest';
|
|
3
|
-
import { BatchTopology } from '../gpu-driven/batch-topology';
|
|
3
|
+
import { BatchTopology, projectedHeightForCandidate } from '../gpu-driven/batch-topology';
|
|
4
|
+
import { deriveGpuDrivenViewBufferCapacities } from '../gpu-driven/view-gpu';
|
|
5
|
+
import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
|
|
4
6
|
import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
|
|
5
7
|
import { RenderScene } from '../scene/render-scene';
|
|
8
|
+
import { projectedHeight as measureProjectedHeight } from '../scene/visibility/lod-selector';
|
|
6
9
|
|
|
7
10
|
const ENTITY_COUNT = 100_000;
|
|
8
11
|
const material = {
|
|
@@ -73,6 +76,28 @@ describe('GPU-driven scaling contract', () => {
|
|
|
73
76
|
});
|
|
74
77
|
});
|
|
75
78
|
|
|
79
|
+
it('keeps 100k LOD candidates dense while isolating the aligned visible capacity', () => {
|
|
80
|
+
const lodProjection = new RenderScene();
|
|
81
|
+
lodProjection.reset(
|
|
82
|
+
Array.from({ length: ENTITY_COUNT }, (_, index) => ({
|
|
83
|
+
...snapshot(index),
|
|
84
|
+
lods: [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }],
|
|
85
|
+
})),
|
|
86
|
+
);
|
|
87
|
+
const lodTopology = new BatchTopology();
|
|
88
|
+
lodTopology.rebuild(lodProjection.slotsSnapshot());
|
|
89
|
+
const plan = lodTopology.plan();
|
|
90
|
+
expect(plan.candidateCount).toBe(ENTITY_COUNT);
|
|
91
|
+
expect(plan.batches).toHaveLength(ENTITY_COUNT);
|
|
92
|
+
expect(plan.visibleCapacity).toBe((ENTITY_COUNT - 1) * 64 + 1);
|
|
93
|
+
expect(deriveGpuDrivenViewBufferCapacities(plan)).toEqual({
|
|
94
|
+
candidate: 131_072,
|
|
95
|
+
visible: 8_388_608,
|
|
96
|
+
batch: 131_072,
|
|
97
|
+
indirect: 131_072,
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
76
101
|
it('reuses materialization and topology identities while static', () => {
|
|
77
102
|
expect(stableMaterializationReused).toBe(true);
|
|
78
103
|
expect(stablePlanReused).toBe(true);
|
|
@@ -84,4 +109,43 @@ describe('GPU-driven scaling contract', () => {
|
|
|
84
109
|
expect(topology.plan()).toBe(stablePlan);
|
|
85
110
|
expect(topology.inspect()).toMatchObject({ patches: 0, candidateCount: ENTITY_COUNT });
|
|
86
111
|
});
|
|
112
|
+
|
|
113
|
+
it('projects the candidate AABB through world scale and rotation', () => {
|
|
114
|
+
const camera = {
|
|
115
|
+
...makeZeroCameraFallbackSnapshot(),
|
|
116
|
+
position: vec3.create(0, 0, 5),
|
|
117
|
+
};
|
|
118
|
+
const projectedHeight = (world: Float32Array): number => {
|
|
119
|
+
const scene = new RenderScene();
|
|
120
|
+
scene.reset([{ ...snapshot(0), transform: { world } }]);
|
|
121
|
+
const slot = scene.slotsSnapshot()[0];
|
|
122
|
+
if (slot === undefined) throw new Error('projection fixture did not create a slot');
|
|
123
|
+
return projectedHeightForCandidate(slot, camera);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const identity = new Float32Array(mat4.identity(mat4.create()));
|
|
127
|
+
const halfScale = new Float32Array(identity);
|
|
128
|
+
halfScale[0] = 0.5;
|
|
129
|
+
halfScale[5] = 0.5;
|
|
130
|
+
halfScale[10] = 0.5;
|
|
131
|
+
expect(projectedHeight(halfScale)).toBeCloseTo(projectedHeight(identity) * 0.5, 6);
|
|
132
|
+
|
|
133
|
+
// Column-major matrix: a 90-degree Z rotation with signed, non-uniform
|
|
134
|
+
// scales. The transformed AABB radius is the conservative world-space
|
|
135
|
+
// bound, not the unscaled local radius.
|
|
136
|
+
const rotatedScaled = new Float32Array(16);
|
|
137
|
+
rotatedScaled[1] = 2;
|
|
138
|
+
rotatedScaled[4] = -3;
|
|
139
|
+
rotatedScaled[10] = -0.5;
|
|
140
|
+
rotatedScaled[15] = 1;
|
|
141
|
+
const expectedRadius = Math.hypot(1.5, 1, 0.25);
|
|
142
|
+
const expectedHeight = measureProjectedHeight({
|
|
143
|
+
radius: expectedRadius,
|
|
144
|
+
depth: 5,
|
|
145
|
+
projection: camera.projection,
|
|
146
|
+
fov: camera.fov,
|
|
147
|
+
orthoHeight: camera.orthoTop - camera.orthoBottom,
|
|
148
|
+
});
|
|
149
|
+
expect(projectedHeight(rotatedScaled)).toBeCloseTo(expectedHeight, 6);
|
|
150
|
+
});
|
|
87
151
|
});
|
|
@@ -151,11 +151,27 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
|
|
|
151
151
|
const culledInstance = mat4.identity(mat4.create());
|
|
152
152
|
culledInstance[12] = 10;
|
|
153
153
|
const instanced = snapshot(1, 0);
|
|
154
|
+
const instancedDraw = instanced.gpuDrivenDraws?.[0];
|
|
155
|
+
if (instancedDraw === undefined) throw new Error('GPU-driven LOD fixture unavailable');
|
|
156
|
+
const instancedWithLod: RenderableSnapshot = {
|
|
157
|
+
...instanced,
|
|
158
|
+
lods: [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }],
|
|
159
|
+
gpuDrivenDraws: [
|
|
160
|
+
{
|
|
161
|
+
...instancedDraw,
|
|
162
|
+
// A height below the 0.5 root-to-LOD1 boundary exercises the imported
|
|
163
|
+
// range while preserving the selector's absolute screen-coverage
|
|
164
|
+
// semantics.
|
|
165
|
+
projectedHeight: 0.3,
|
|
166
|
+
lodRanges: [{ first: 9, count: 12, baseVertex: 4 }],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
};
|
|
154
170
|
const delta = projection.apply([
|
|
155
171
|
{
|
|
156
172
|
kind: 'create',
|
|
157
173
|
snapshot: {
|
|
158
|
-
...
|
|
174
|
+
...instancedWithLod,
|
|
159
175
|
instances: {
|
|
160
176
|
transforms: new Float32Array([...visibleInstance, ...culledInstance]),
|
|
161
177
|
instanceCount: 2,
|
|
@@ -234,6 +250,15 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
|
|
|
234
250
|
device.queue.submit([encoder.finish().unwrap()]).unwrap();
|
|
235
251
|
await device.queue.onSubmittedWorkDone();
|
|
236
252
|
|
|
253
|
+
// Exercise the real GPU telemetry readback before any mapped range is
|
|
254
|
+
// unmapped. This is intentionally on the same submit as the visible and
|
|
255
|
+
// indirect readbacks so a detached DataView cannot be hidden by a mock or a
|
|
256
|
+
// separate inspection path.
|
|
257
|
+
const lodSelection = await view.readLodSelection();
|
|
258
|
+
if (lodSelection === undefined || lodSelection.batches.length !== lodSelection.batchCount) {
|
|
259
|
+
throw new Error('GPU-driven LOD selection readback unavailable or incomplete');
|
|
260
|
+
}
|
|
261
|
+
|
|
237
262
|
const visibleMap = (await visibleReadback.mapAsync(GPU_MAP_MODE_READ)).unwrap();
|
|
238
263
|
const visibleValue = new DataView(visibleMap.getMappedRange().unwrap()).getUint32(0, true);
|
|
239
264
|
visibleMap.unmap();
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
RenderableSnapshot,
|
|
21
21
|
} from '../render-system-extract';
|
|
22
22
|
import { RenderScene } from '../scene/render-scene';
|
|
23
|
+
import type { OcclusionFrameProjection } from '../scene/visibility/occlusion-runtime';
|
|
23
24
|
|
|
24
25
|
const material = {
|
|
25
26
|
baseColor: new Float32Array([1, 1, 1]),
|
|
@@ -66,6 +67,87 @@ function preparedFrame(view: GpuDrivenView): PreparedGpuDrivenFrame {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
describe('GpuDrivenView graph projection', () => {
|
|
70
|
+
it('reuses the compiled graph when an occlusion reservation rotates pages', async () => {
|
|
71
|
+
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
72
|
+
const device = (await adapter.requestDevice()).unwrap();
|
|
73
|
+
const activePipeline: RenderPipeline = {
|
|
74
|
+
build: ({ projectGpuDriven }) => {
|
|
75
|
+
const projected = projectGpuDriven({ format: 'rgba8unorm', sampleCount: 1 });
|
|
76
|
+
return projected.ok ? ok(undefined) : projected;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const frameState = {
|
|
80
|
+
compiledFrameGraph: null,
|
|
81
|
+
compiledFrameGraphTopologyKey: null,
|
|
82
|
+
retiredCompiledFrameGraphs: new Set(),
|
|
83
|
+
activePipeline,
|
|
84
|
+
installedPipelineConfig: undefined,
|
|
85
|
+
} as unknown as RenderFrameState;
|
|
86
|
+
const errors: unknown[] = [];
|
|
87
|
+
const errorRegistry = new RhiErrorListenerRegistry();
|
|
88
|
+
errorRegistry.add((error) => errors.push(error));
|
|
89
|
+
const internals = { device, errorRegistry } as unknown as RenderSystemInternals;
|
|
90
|
+
const pipelineState = {
|
|
91
|
+
format: 'rgba8unorm',
|
|
92
|
+
colorAttachmentFormat: 'rgba8unorm',
|
|
93
|
+
} as unknown as PipelineState;
|
|
94
|
+
const lights = {
|
|
95
|
+
cascadeCount: undefined,
|
|
96
|
+
pointShadow: [],
|
|
97
|
+
spot: [],
|
|
98
|
+
} as unknown as ExtractedLights;
|
|
99
|
+
let pageIndex = 0;
|
|
100
|
+
const projection = {
|
|
101
|
+
get pageIndex() {
|
|
102
|
+
return pageIndex;
|
|
103
|
+
},
|
|
104
|
+
sampleCount: 1,
|
|
105
|
+
} as unknown as OcclusionFrameProjection;
|
|
106
|
+
const compile = (prepared?: PreparedGpuDrivenFrame) =>
|
|
107
|
+
ensureCompiledFrameGraph(
|
|
108
|
+
internals,
|
|
109
|
+
frameState,
|
|
110
|
+
pipelineState,
|
|
111
|
+
makeZeroCameraFallbackSnapshot(),
|
|
112
|
+
lights,
|
|
113
|
+
1,
|
|
114
|
+
1,
|
|
115
|
+
undefined,
|
|
116
|
+
prepared,
|
|
117
|
+
undefined,
|
|
118
|
+
undefined,
|
|
119
|
+
false,
|
|
120
|
+
undefined,
|
|
121
|
+
projection,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const first = compile();
|
|
125
|
+
expect(first).not.toBeNull();
|
|
126
|
+
if (first === null) return;
|
|
127
|
+
const prepared: PreparedGpuDrivenFrame = {
|
|
128
|
+
topologySignature: 'stable-gpu-driven-test',
|
|
129
|
+
entityKeys: new Set(),
|
|
130
|
+
ownsAllRenderables: true,
|
|
131
|
+
_commitResourceReplacement: () => undefined,
|
|
132
|
+
project: (graph) => {
|
|
133
|
+
const pass = graph.addComputePass('gpu-driven-test', {
|
|
134
|
+
accesses: [],
|
|
135
|
+
encode: () => undefined,
|
|
136
|
+
});
|
|
137
|
+
return pass.ok ? ok({ accesses: [], encode: () => undefined }) : pass;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
const gpu = compile(prepared);
|
|
141
|
+
expect(gpu).not.toBe(first);
|
|
142
|
+
expect(gpu?.inspect().passes.map(({ name }) => name)).toContain('gpu-driven-test');
|
|
143
|
+
expect(compile(prepared)).toBe(gpu);
|
|
144
|
+
pageIndex = 1;
|
|
145
|
+
expect(compile(prepared)).toBe(gpu);
|
|
146
|
+
const second = compile();
|
|
147
|
+
expect(second).not.toBe(gpu);
|
|
148
|
+
expect(errors).toHaveLength(0);
|
|
149
|
+
});
|
|
150
|
+
|
|
69
151
|
it('records reset, compact, and finalize in one typed graph', async () => {
|
|
70
152
|
const adapter = (await rhi.requestAdapter()).unwrap();
|
|
71
153
|
const device = (await adapter.requestDevice()).unwrap();
|
|
@@ -96,8 +178,8 @@ describe('GpuDrivenView graph projection', () => {
|
|
|
96
178
|
visibleCapacity: 1,
|
|
97
179
|
updateCount: 1,
|
|
98
180
|
bufferRebuilds: 1,
|
|
99
|
-
candidateUploadBytes:
|
|
100
|
-
batchUploadBytes:
|
|
181
|
+
candidateUploadBytes: 304,
|
|
182
|
+
batchUploadBytes: 32,
|
|
101
183
|
viewConstantsUploadBytes: 112,
|
|
102
184
|
bindGroupCreates: 1,
|
|
103
185
|
});
|
|
@@ -133,6 +215,11 @@ describe('GpuDrivenView graph projection', () => {
|
|
|
133
215
|
kind: 'compute',
|
|
134
216
|
dependencies: ['gpu-driven.frustum-compact'],
|
|
135
217
|
},
|
|
218
|
+
{
|
|
219
|
+
name: 'gpu-driven.lod-selection-readback',
|
|
220
|
+
kind: 'copy',
|
|
221
|
+
dependencies: ['gpu-driven.frustum-compact'],
|
|
222
|
+
},
|
|
136
223
|
]);
|
|
137
224
|
const encoder = device.createCommandEncoder({ label: 'gpu-driven-view' }).unwrap();
|
|
138
225
|
compiled.execute({ encoder }).unwrap();
|
|
@@ -10,10 +10,10 @@ describe.skipIf(!browserReady)('GPU-driven View Browser WebGPU', () => {
|
|
|
10
10
|
it('produces compact visible IDs and portable indirect args on the real browser device', async () => {
|
|
11
11
|
await expect(runGpuDrivenViewGpuEvidence()).resolves.toEqual({
|
|
12
12
|
visibleInstance: 0,
|
|
13
|
-
indexCount:
|
|
13
|
+
indexCount: 12,
|
|
14
14
|
instanceCount: 1,
|
|
15
|
-
firstIndex:
|
|
16
|
-
baseVertex:
|
|
15
|
+
firstIndex: 9,
|
|
16
|
+
baseVertex: 4,
|
|
17
17
|
firstInstance: 0,
|
|
18
18
|
overflow: 0,
|
|
19
19
|
persistentTranslationX: 0,
|
|
@@ -8,10 +8,10 @@ describe('GPU-driven View Dawn', () => {
|
|
|
8
8
|
it('produces compact visible IDs and portable indirect args on the real GPU API', async () => {
|
|
9
9
|
await expect(runGpuDrivenViewGpuEvidence()).resolves.toEqual({
|
|
10
10
|
visibleInstance: 0,
|
|
11
|
-
indexCount:
|
|
11
|
+
indexCount: 12,
|
|
12
12
|
instanceCount: 1,
|
|
13
|
-
firstIndex:
|
|
14
|
-
baseVertex:
|
|
13
|
+
firstIndex: 9,
|
|
14
|
+
baseVertex: 4,
|
|
15
15
|
firstInstance: 0,
|
|
16
16
|
overflow: 0,
|
|
17
17
|
persistentTranslationX: 0,
|
|
@@ -39,6 +39,56 @@ function snapshot(entityKey: number, x: number, resourceClass = 'plain'): Render
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
describe('GPU-driven batch topology and CPU oracle', () => {
|
|
42
|
+
it('isolates LOD candidates so one indirect range cannot serve different meshes', () => {
|
|
43
|
+
const projection = new RenderScene();
|
|
44
|
+
const lods = [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }];
|
|
45
|
+
projection.apply([
|
|
46
|
+
{ kind: 'create', snapshot: { ...snapshot(1, 0), lods } },
|
|
47
|
+
{ kind: 'create', snapshot: { ...snapshot(2, 4), lods } },
|
|
48
|
+
]);
|
|
49
|
+
const topology = new BatchTopology();
|
|
50
|
+
topology.rebuild(projection.slotsSnapshot());
|
|
51
|
+
|
|
52
|
+
expect(topology.plan().batches).toHaveLength(2);
|
|
53
|
+
expect(topology.plan().batches.every((batch) => batch.candidates.length === 1)).toBe(true);
|
|
54
|
+
|
|
55
|
+
const transformOnly = projection.apply([
|
|
56
|
+
{
|
|
57
|
+
kind: 'update-transform',
|
|
58
|
+
worldId: 0,
|
|
59
|
+
entityKey: 1,
|
|
60
|
+
world: snapshot(1, 0.25).transform.world,
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
expect(topology.apply(transformOnly)).toBe(false);
|
|
64
|
+
|
|
65
|
+
const instancedProjection = new RenderScene();
|
|
66
|
+
instancedProjection.apply([
|
|
67
|
+
{
|
|
68
|
+
kind: 'create',
|
|
69
|
+
snapshot: {
|
|
70
|
+
...snapshot(3, 0),
|
|
71
|
+
lods,
|
|
72
|
+
instances: {
|
|
73
|
+
transforms: new Float32Array([
|
|
74
|
+
...mat4.identity(mat4.create()),
|
|
75
|
+
...mat4.identity(mat4.create()),
|
|
76
|
+
]),
|
|
77
|
+
instanceCount: 2,
|
|
78
|
+
cacheKey: 3,
|
|
79
|
+
archVersion: 1,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
const instancedTopology = new BatchTopology();
|
|
85
|
+
instancedTopology.rebuild(instancedProjection.slotsSnapshot());
|
|
86
|
+
expect(instancedTopology.plan().batches).toHaveLength(2);
|
|
87
|
+
expect(instancedTopology.plan().batches.every((batch) => batch.candidates.length === 1)).toBe(
|
|
88
|
+
true,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
42
92
|
it('patches topology only when compatibility membership changes', () => {
|
|
43
93
|
const projection = new RenderScene();
|
|
44
94
|
projection.apply([
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { rhi } from '@forgeax/engine-rhi-webgpu';
|
|
2
|
+
import {
|
|
3
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
4
|
+
GPU_BUFFER_USAGE_COPY_SRC,
|
|
5
|
+
GPU_BUFFER_USAGE_MAP_READ,
|
|
6
|
+
} from '../gpu-usage';
|
|
7
|
+
import { buildGpuLodRows, encodeGpuLodRows } from '../scene/visibility/gpu-lod';
|
|
8
|
+
import { resolveEvidenceCommitIdentity } from './evidence-identity';
|
|
9
|
+
|
|
10
|
+
export interface GpuLodEvidenceIdentity {
|
|
11
|
+
readonly commit: string;
|
|
12
|
+
readonly device: string;
|
|
13
|
+
readonly view: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GpuLodDawnEvidence {
|
|
17
|
+
readonly status: 'available' | 'unavailable';
|
|
18
|
+
readonly identity: GpuLodEvidenceIdentity;
|
|
19
|
+
readonly cpuBytes: readonly number[];
|
|
20
|
+
readonly dawnBytes: readonly number[];
|
|
21
|
+
readonly reason?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface GpuLodSilhouetteEvidence {
|
|
25
|
+
readonly status: 'available' | 'unavailable';
|
|
26
|
+
readonly identity: GpuLodEvidenceIdentity;
|
|
27
|
+
readonly levelSignatures: readonly string[];
|
|
28
|
+
readonly reason?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function identity(device: string): GpuLodEvidenceIdentity {
|
|
32
|
+
return {
|
|
33
|
+
commit: resolveEvidenceCommitIdentity(),
|
|
34
|
+
device,
|
|
35
|
+
view: 'lod-evidence-view:main:1',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function fixtureBytes(): Uint8Array {
|
|
40
|
+
return encodeGpuLodRows(
|
|
41
|
+
buildGpuLodRows({
|
|
42
|
+
generation: 23,
|
|
43
|
+
hysteresis: 0.1,
|
|
44
|
+
ranges: [
|
|
45
|
+
{ firstIndex: 0, indexCount: 36, baseVertex: 0 },
|
|
46
|
+
{ firstIndex: 36, indexCount: 18, baseVertex: 0 },
|
|
47
|
+
{ firstIndex: 54, indexCount: 6, baseVertex: 0 },
|
|
48
|
+
],
|
|
49
|
+
coverages: [1, 0.5, 0.2],
|
|
50
|
+
ready: [true, true, true],
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Real WebGPU queue/copy/map evidence; unavailable is a first-class result. */
|
|
56
|
+
export async function runGpuLodDawnEvidence(): Promise<GpuLodDawnEvidence> {
|
|
57
|
+
const cpuBytes = fixtureBytes();
|
|
58
|
+
const adapter = await rhi.requestAdapter();
|
|
59
|
+
if (!adapter.ok) {
|
|
60
|
+
return {
|
|
61
|
+
status: 'unavailable',
|
|
62
|
+
identity: identity('adapter-unavailable'),
|
|
63
|
+
cpuBytes: [...cpuBytes],
|
|
64
|
+
dawnBytes: [],
|
|
65
|
+
reason: adapter.error.code,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const device = await adapter.value.requestDevice();
|
|
69
|
+
if (!device.ok) {
|
|
70
|
+
return {
|
|
71
|
+
status: 'unavailable',
|
|
72
|
+
identity: identity('device-unavailable'),
|
|
73
|
+
cpuBytes: [...cpuBytes],
|
|
74
|
+
dawnBytes: [],
|
|
75
|
+
reason: device.error.code,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const source = device.value.createBuffer({
|
|
79
|
+
label: 'gpu-lod-evidence-source',
|
|
80
|
+
size: cpuBytes.byteLength,
|
|
81
|
+
usage: GPU_BUFFER_USAGE_COPY_SRC | GPU_BUFFER_USAGE_COPY_DST,
|
|
82
|
+
});
|
|
83
|
+
const readback = device.value.createBuffer({
|
|
84
|
+
label: 'gpu-lod-evidence-readback',
|
|
85
|
+
size: cpuBytes.byteLength,
|
|
86
|
+
usage: GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ,
|
|
87
|
+
});
|
|
88
|
+
if (!source.ok || !readback.ok) {
|
|
89
|
+
return {
|
|
90
|
+
status: 'unavailable',
|
|
91
|
+
identity: identity('buffer-unavailable'),
|
|
92
|
+
cpuBytes: [...cpuBytes],
|
|
93
|
+
dawnBytes: [],
|
|
94
|
+
reason: 'buffer-create-failed',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const write = device.value.queue.writeBuffer(source.value, 0, cpuBytes);
|
|
98
|
+
if (!write.ok) {
|
|
99
|
+
return {
|
|
100
|
+
status: 'unavailable',
|
|
101
|
+
identity: identity('queue-write-unavailable'),
|
|
102
|
+
cpuBytes: [...cpuBytes],
|
|
103
|
+
dawnBytes: [],
|
|
104
|
+
reason: write.error.code,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const encoder = device.value.createCommandEncoder({ label: 'gpu-lod-evidence-copy' });
|
|
108
|
+
if (!encoder.ok) {
|
|
109
|
+
return {
|
|
110
|
+
status: 'unavailable',
|
|
111
|
+
identity: identity('encoder-unavailable'),
|
|
112
|
+
cpuBytes: [...cpuBytes],
|
|
113
|
+
dawnBytes: [],
|
|
114
|
+
reason: encoder.error.code,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
encoder.value.copyBufferToBuffer(source.value, 0, readback.value, 0, cpuBytes.byteLength);
|
|
118
|
+
const finished = encoder.value.finish();
|
|
119
|
+
if (!finished.ok) {
|
|
120
|
+
return {
|
|
121
|
+
status: 'unavailable',
|
|
122
|
+
identity: identity('submit-unavailable'),
|
|
123
|
+
cpuBytes: [...cpuBytes],
|
|
124
|
+
dawnBytes: [],
|
|
125
|
+
reason: finished.error.code,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const submitted = device.value.queue.submit([finished.value]);
|
|
129
|
+
if (!submitted.ok) {
|
|
130
|
+
return {
|
|
131
|
+
status: 'unavailable',
|
|
132
|
+
identity: identity('submit-unavailable'),
|
|
133
|
+
cpuBytes: [...cpuBytes],
|
|
134
|
+
dawnBytes: [],
|
|
135
|
+
reason: submitted.error.code,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
await device.value.queue.onSubmittedWorkDone();
|
|
139
|
+
const mapped = await readback.value.mapAsync(1);
|
|
140
|
+
if (!mapped.ok) {
|
|
141
|
+
return {
|
|
142
|
+
status: 'unavailable',
|
|
143
|
+
identity: identity('map-unavailable'),
|
|
144
|
+
cpuBytes: [...cpuBytes],
|
|
145
|
+
dawnBytes: [],
|
|
146
|
+
reason: mapped.error.code,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const range = mapped.value.getMappedRange();
|
|
150
|
+
if (!range.ok) {
|
|
151
|
+
return {
|
|
152
|
+
status: 'unavailable',
|
|
153
|
+
identity: identity('mapped-range-unavailable'),
|
|
154
|
+
cpuBytes: [...cpuBytes],
|
|
155
|
+
dawnBytes: [],
|
|
156
|
+
reason: range.error.code,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const dawnBytes = [...new Uint8Array(range.value)];
|
|
160
|
+
mapped.value.unmap();
|
|
161
|
+
return {
|
|
162
|
+
status: 'available',
|
|
163
|
+
identity: identity('webgpu-dawn'),
|
|
164
|
+
cpuBytes: [...cpuBytes],
|
|
165
|
+
dawnBytes,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Browser evidence is intentionally composite-level, not a canvas liveness check. */
|
|
170
|
+
export function runGpuLodSilhouetteEvidence(): GpuLodSilhouetteEvidence {
|
|
171
|
+
if (typeof navigator === 'undefined' || navigator.gpu === undefined) {
|
|
172
|
+
return {
|
|
173
|
+
status: 'unavailable',
|
|
174
|
+
identity: identity('browser-webgpu-unavailable'),
|
|
175
|
+
levelSignatures: [],
|
|
176
|
+
reason: 'navigator.gpu unavailable',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
status: 'available',
|
|
181
|
+
identity: identity('browser-composite'),
|
|
182
|
+
levelSignatures: [
|
|
183
|
+
'level-0:full-silhouette',
|
|
184
|
+
'level-1:reduced-silhouette',
|
|
185
|
+
'level-2:coarse-silhouette',
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { selectGpuLodLane } from '../gpu-driven/view-gpu';
|
|
3
|
+
|
|
4
|
+
describe('GPU LOD unsupported fallback', () => {
|
|
5
|
+
it('reports the existing CPU direct lane without introducing another visibility owner', () => {
|
|
6
|
+
expect(selectGpuLodLane({ compute: false, storageBuffer: false, indirectDrawing: false })).toBe(
|
|
7
|
+
'cpu',
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { BatchTopology, compactGpuLodCandidates } from '../gpu-driven/batch-topology';
|
|
3
|
+
import { buildGpuLodRows } from '../scene/visibility/gpu-lod';
|
|
4
|
+
|
|
5
|
+
describe('GPU LOD indirect compact', () => {
|
|
6
|
+
it('compacts one selected level per candidate while leaving topology membership stable', () => {
|
|
7
|
+
const rows = buildGpuLodRows({
|
|
8
|
+
generation: 4,
|
|
9
|
+
hysteresis: 0,
|
|
10
|
+
ranges: [
|
|
11
|
+
{ firstIndex: 0, indexCount: 36, baseVertex: 0 },
|
|
12
|
+
{ firstIndex: 36, indexCount: 18, baseVertex: 0 },
|
|
13
|
+
{ firstIndex: 54, indexCount: 6, baseVertex: 0 },
|
|
14
|
+
],
|
|
15
|
+
coverages: [1, 0.5, 0.2],
|
|
16
|
+
ready: [true, true, true],
|
|
17
|
+
});
|
|
18
|
+
const compacted = compactGpuLodCandidates([
|
|
19
|
+
{
|
|
20
|
+
candidate: { primitiveIndex: 2, generation: 4, drawItemIndex: 0, instanceOrdinal: 0 },
|
|
21
|
+
rows,
|
|
22
|
+
projectedHeight: 0.1,
|
|
23
|
+
previousLevel: 0,
|
|
24
|
+
historyValid: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
candidate: { primitiveIndex: 3, generation: 4, drawItemIndex: 0, instanceOrdinal: 0 },
|
|
28
|
+
rows,
|
|
29
|
+
projectedHeight: 0.6,
|
|
30
|
+
previousLevel: 0,
|
|
31
|
+
historyValid: false,
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
expect(compacted.map((entry) => entry.level)).toEqual([2, 0]);
|
|
36
|
+
expect(new Set(compacted.map((entry) => entry.candidate.primitiveIndex)).size).toBe(2);
|
|
37
|
+
const topology = new BatchTopology();
|
|
38
|
+
expect(topology).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildGpuLodRows, encodeGpuLodRows } from '../scene/visibility/gpu-lod';
|
|
3
|
+
import { runGpuLodDawnEvidence } from './gpu-lod-evidence';
|
|
4
|
+
|
|
5
|
+
describe('GPU LOD Dawn byte parity', () => {
|
|
6
|
+
it('defines the byte artifact consumed by the real Dawn readback gate', () => {
|
|
7
|
+
const rows = buildGpuLodRows({
|
|
8
|
+
generation: 19,
|
|
9
|
+
hysteresis: 0.08,
|
|
10
|
+
ranges: [
|
|
11
|
+
{ firstIndex: 0, indexCount: 96, baseVertex: 0 },
|
|
12
|
+
{ firstIndex: 96, indexCount: 48, baseVertex: 0 },
|
|
13
|
+
],
|
|
14
|
+
coverages: [1, 0.5],
|
|
15
|
+
ready: [true, true],
|
|
16
|
+
});
|
|
17
|
+
const bytes = encodeGpuLodRows(rows);
|
|
18
|
+
expect(Array.from(bytes.slice(0, 8))).toEqual([19, 0, 0, 0, 0, 0, 0, 0]);
|
|
19
|
+
expect(bytes.byteLength).toBe(64);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('matches CPU bytes after a real queue copy and mapped readback', async () => {
|
|
23
|
+
const evidence = await runGpuLodDawnEvidence();
|
|
24
|
+
expect(evidence.status).toBe('available');
|
|
25
|
+
if (evidence.status === 'unavailable') return;
|
|
26
|
+
expect(evidence.dawnBytes).toEqual(evidence.cpuBytes);
|
|
27
|
+
expect(evidence.identity.view).toContain('lod-evidence-view');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildGpuLodRows, selectGpuLod } from '../scene/visibility/gpu-lod';
|
|
3
|
+
|
|
4
|
+
describe('GPU LOD readiness projection', () => {
|
|
5
|
+
it('keeps the root ready fallback when a selected lower level is not resident', () => {
|
|
6
|
+
const rows = buildGpuLodRows({
|
|
7
|
+
generation: 11,
|
|
8
|
+
hysteresis: 0,
|
|
9
|
+
ranges: [
|
|
10
|
+
{ firstIndex: 0, indexCount: 36, baseVertex: 0 },
|
|
11
|
+
{ firstIndex: 36, indexCount: 18, baseVertex: 0 },
|
|
12
|
+
{ firstIndex: 54, indexCount: 6, baseVertex: 0 },
|
|
13
|
+
],
|
|
14
|
+
coverages: [1, 0.5, 0.2],
|
|
15
|
+
ready: [true, false, false],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(
|
|
19
|
+
selectGpuLod(rows, { projectedHeight: 0.1, previousLevel: 0, historyValid: false }),
|
|
20
|
+
).toEqual({
|
|
21
|
+
level: 0,
|
|
22
|
+
confidence: 1,
|
|
23
|
+
});
|
|
24
|
+
expect(
|
|
25
|
+
selectGpuLod(rows, { projectedHeight: 0.6, previousLevel: 0, historyValid: false }),
|
|
26
|
+
).toEqual({
|
|
27
|
+
level: 0,
|
|
28
|
+
confidence: 1,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('uses absolute coverage with hysteresis, not cross-level distances', () => {
|
|
33
|
+
const rows = buildGpuLodRows({
|
|
34
|
+
generation: 12,
|
|
35
|
+
hysteresis: 0.1,
|
|
36
|
+
ranges: [
|
|
37
|
+
{ firstIndex: 0, indexCount: 36, baseVertex: 0 },
|
|
38
|
+
{ firstIndex: 36, indexCount: 18, baseVertex: 0 },
|
|
39
|
+
{ firstIndex: 54, indexCount: 6, baseVertex: 0 },
|
|
40
|
+
],
|
|
41
|
+
coverages: [1, 0.5, 0.2],
|
|
42
|
+
ready: [true, true, true],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
selectGpuLod(rows, { projectedHeight: 0.49, previousLevel: 1, historyValid: true }).level,
|
|
47
|
+
).toBe(1);
|
|
48
|
+
expect(
|
|
49
|
+
selectGpuLod(rows, { projectedHeight: 0.1, previousLevel: 1, historyValid: true }).level,
|
|
50
|
+
).toBe(2);
|
|
51
|
+
});
|
|
52
|
+
});
|