@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
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { createVisibilityBudget, type VisibilityBudget } from './budget';
|
|
2
|
+
|
|
3
|
+
export type InvalidationReason =
|
|
4
|
+
| 'cut'
|
|
5
|
+
| 'teleport'
|
|
6
|
+
| 'resize'
|
|
7
|
+
| 'projection'
|
|
8
|
+
| 'primitive'
|
|
9
|
+
| 'bounds'
|
|
10
|
+
| 'topology'
|
|
11
|
+
| 'world-reorder'
|
|
12
|
+
| 'detach'
|
|
13
|
+
| 'failed-submit'
|
|
14
|
+
| 'device-loss'
|
|
15
|
+
| 'transparent';
|
|
16
|
+
|
|
17
|
+
export interface ConfidenceState {
|
|
18
|
+
readonly status: 'visible' | 'hidden';
|
|
19
|
+
readonly zeroStreak: number;
|
|
20
|
+
readonly successfulSubmits: number;
|
|
21
|
+
readonly queryable: boolean;
|
|
22
|
+
readonly dirty: boolean;
|
|
23
|
+
readonly lastResultGeneration: number | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ConfidenceEvent =
|
|
27
|
+
| { readonly type: 'result'; readonly samples: number; readonly submissionGeneration: number }
|
|
28
|
+
| { readonly type: 'submit'; readonly submissionGeneration: number }
|
|
29
|
+
| { readonly type: 'failure'; readonly submissionGeneration: number }
|
|
30
|
+
| { readonly type: 'invalidate'; readonly reason: InvalidationReason };
|
|
31
|
+
|
|
32
|
+
export function createConfidenceState(): ConfidenceState {
|
|
33
|
+
return {
|
|
34
|
+
status: 'visible',
|
|
35
|
+
zeroStreak: 0,
|
|
36
|
+
successfulSubmits: 0,
|
|
37
|
+
queryable: true,
|
|
38
|
+
dirty: true,
|
|
39
|
+
lastResultGeneration: undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function applyConfidenceEvent(
|
|
44
|
+
state: ConfidenceState,
|
|
45
|
+
event: ConfidenceEvent,
|
|
46
|
+
budget: VisibilityBudget = createVisibilityBudget(),
|
|
47
|
+
): ConfidenceState {
|
|
48
|
+
if (event.type === 'result') {
|
|
49
|
+
if (!Number.isFinite(event.samples) || event.samples < 0) {
|
|
50
|
+
return { ...state, status: 'visible', dirty: true, queryable: true };
|
|
51
|
+
}
|
|
52
|
+
if (event.samples > 0) {
|
|
53
|
+
return {
|
|
54
|
+
...state,
|
|
55
|
+
status: 'visible',
|
|
56
|
+
zeroStreak: 0,
|
|
57
|
+
successfulSubmits: 0,
|
|
58
|
+
queryable: true,
|
|
59
|
+
dirty: false,
|
|
60
|
+
lastResultGeneration: event.submissionGeneration,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const zeroStreak = state.zeroStreak + 1;
|
|
64
|
+
const hidden = zeroStreak >= 2;
|
|
65
|
+
return {
|
|
66
|
+
...state,
|
|
67
|
+
status: hidden ? 'hidden' : 'visible',
|
|
68
|
+
zeroStreak,
|
|
69
|
+
successfulSubmits: 0,
|
|
70
|
+
queryable: true,
|
|
71
|
+
// A first zero is only provisional evidence. Keep it dirty so the
|
|
72
|
+
// producer naturally submits the second query; suppression requires two
|
|
73
|
+
// real zero results rather than two hand-applied state transitions.
|
|
74
|
+
dirty: !hidden,
|
|
75
|
+
lastResultGeneration: event.submissionGeneration,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (event.type === 'submit') {
|
|
79
|
+
const successfulSubmits = state.successfulSubmits + 1;
|
|
80
|
+
if (successfulSubmits >= budget.expirySubmits) {
|
|
81
|
+
return {
|
|
82
|
+
...state,
|
|
83
|
+
status: 'visible',
|
|
84
|
+
zeroStreak: 0,
|
|
85
|
+
successfulSubmits: 0,
|
|
86
|
+
queryable: true,
|
|
87
|
+
dirty: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { ...state, successfulSubmits, dirty: state.dirty };
|
|
91
|
+
}
|
|
92
|
+
if (event.type === 'failure') {
|
|
93
|
+
return {
|
|
94
|
+
...state,
|
|
95
|
+
status: 'visible',
|
|
96
|
+
zeroStreak: 0,
|
|
97
|
+
successfulSubmits: 0,
|
|
98
|
+
queryable: true,
|
|
99
|
+
dirty: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
...state,
|
|
104
|
+
status: 'visible',
|
|
105
|
+
zeroStreak: 0,
|
|
106
|
+
successfulSubmits: 0,
|
|
107
|
+
queryable: event.reason !== 'transparent' && event.reason !== 'detach',
|
|
108
|
+
dirty: true,
|
|
109
|
+
lastResultGeneration: undefined,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function shouldIssueRetest(
|
|
114
|
+
state: ConfidenceState,
|
|
115
|
+
budget: VisibilityBudget = createVisibilityBudget(),
|
|
116
|
+
): boolean {
|
|
117
|
+
return (
|
|
118
|
+
state.status === 'hidden' && state.queryable && state.successfulSubmits >= budget.retestSubmits
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function shouldQueryState(state: ConfidenceState, budget: VisibilityBudget): boolean {
|
|
123
|
+
return (state.dirty && state.successfulSubmits === 0) || shouldIssueRetest(state, budget);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class OcclusionConfidenceScheduler {
|
|
127
|
+
private readonly states = new Map<string, ConfidenceState>();
|
|
128
|
+
private readonly wakeByGeneration = new Map<number, Set<string>>();
|
|
129
|
+
private readonly expireByGeneration = new Map<number, Set<string>>();
|
|
130
|
+
private readonly wakeHeap: number[] = [];
|
|
131
|
+
private readonly expireHeap: number[] = [];
|
|
132
|
+
private readonly wakeHeapMembers = new Set<number>();
|
|
133
|
+
private readonly expireHeapMembers = new Set<number>();
|
|
134
|
+
private readonly retestKeys = new Set<string>();
|
|
135
|
+
private readonly scheduleByKey = new Map<
|
|
136
|
+
string,
|
|
137
|
+
{ readonly wake: number; readonly expire: number }
|
|
138
|
+
>();
|
|
139
|
+
|
|
140
|
+
constructor(private readonly budget: VisibilityBudget = createVisibilityBudget()) {}
|
|
141
|
+
|
|
142
|
+
ensure(key: string): ConfidenceState {
|
|
143
|
+
const existing = this.states.get(key);
|
|
144
|
+
if (existing !== undefined) return existing;
|
|
145
|
+
const state = createConfidenceState();
|
|
146
|
+
this.states.set(key, state);
|
|
147
|
+
return state;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
apply(key: string, event: ConfidenceEvent): ConfidenceState {
|
|
151
|
+
this.unschedule(key);
|
|
152
|
+
const next = applyConfidenceEvent(this.ensure(key), event, this.budget);
|
|
153
|
+
this.states.set(key, next);
|
|
154
|
+
this.refreshRetestKey(key, next);
|
|
155
|
+
const anchor =
|
|
156
|
+
event.type === 'result' || event.type === 'submit' || event.type === 'failure'
|
|
157
|
+
? event.submissionGeneration
|
|
158
|
+
: next.lastResultGeneration;
|
|
159
|
+
this.schedule(key, next, anchor);
|
|
160
|
+
return next;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Apply an in-flight query submit without scheduling a same-generation wake. */
|
|
164
|
+
applyInFlightSubmit(
|
|
165
|
+
key: string,
|
|
166
|
+
event: Extract<ConfidenceEvent, { readonly type: 'submit' }>,
|
|
167
|
+
): ConfidenceState {
|
|
168
|
+
this.unschedule(key);
|
|
169
|
+
const next = applyConfidenceEvent(this.ensure(key), event, this.budget);
|
|
170
|
+
this.states.set(key, next);
|
|
171
|
+
this.refreshRetestKey(key, next);
|
|
172
|
+
this.scheduleExpiryOnly(key, next, event.submissionGeneration);
|
|
173
|
+
return next;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
drainRetests(limit: number): string[] {
|
|
177
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) return [];
|
|
178
|
+
const result: string[] = [];
|
|
179
|
+
for (const key of this.retestKeys) {
|
|
180
|
+
if (result.length >= limit) break;
|
|
181
|
+
result.push(key);
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
get(key: string): ConfidenceState | undefined {
|
|
187
|
+
return this.states.get(key);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
remove(key: string): void {
|
|
191
|
+
this.unschedule(key);
|
|
192
|
+
this.retestKeys.delete(key);
|
|
193
|
+
this.states.delete(key);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Advance hidden evidence on a successful frame even when no query was issued for it. */
|
|
197
|
+
advanceSuccessfulSubmits(
|
|
198
|
+
submissionGeneration: number,
|
|
199
|
+
excludedKeys?: ReadonlySet<string>,
|
|
200
|
+
): {
|
|
201
|
+
readonly changed: boolean;
|
|
202
|
+
readonly drawChangedKeys: readonly string[];
|
|
203
|
+
readonly queryChangedKeys: readonly string[];
|
|
204
|
+
} {
|
|
205
|
+
let changed = false;
|
|
206
|
+
const drawChangedKeys: string[] = [];
|
|
207
|
+
const queryChangedKeys: string[] = [];
|
|
208
|
+
// Expiry is consumed first. If a caller advances several generations in
|
|
209
|
+
// one tick, an old expiry must win over a stale wake bucket; otherwise the
|
|
210
|
+
// wake path could postpone visibility recovery indefinitely.
|
|
211
|
+
const expireKeys = this.takeDue(
|
|
212
|
+
this.expireByGeneration,
|
|
213
|
+
this.expireHeap,
|
|
214
|
+
this.expireHeapMembers,
|
|
215
|
+
submissionGeneration,
|
|
216
|
+
);
|
|
217
|
+
for (const key of expireKeys) {
|
|
218
|
+
const state = this.states.get(key);
|
|
219
|
+
if (state === undefined || state.status !== 'hidden' || !state.queryable) continue;
|
|
220
|
+
if (excludedKeys?.has(key)) {
|
|
221
|
+
this.unschedule(key);
|
|
222
|
+
this.schedule(key, state, submissionGeneration);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const beforeQuery = shouldQueryState(state, this.budget);
|
|
226
|
+
this.unschedule(key);
|
|
227
|
+
const expiring =
|
|
228
|
+
state.successfulSubmits >= this.budget.expirySubmits - 1
|
|
229
|
+
? state
|
|
230
|
+
: { ...state, successfulSubmits: this.budget.expirySubmits - 1 };
|
|
231
|
+
const expired = applyConfidenceEvent(
|
|
232
|
+
expiring,
|
|
233
|
+
{
|
|
234
|
+
type: 'submit',
|
|
235
|
+
submissionGeneration,
|
|
236
|
+
},
|
|
237
|
+
this.budget,
|
|
238
|
+
);
|
|
239
|
+
this.states.set(key, expired);
|
|
240
|
+
this.refreshRetestKey(key, expired);
|
|
241
|
+
changed = true;
|
|
242
|
+
if (state.status !== expired.status || state.queryable !== expired.queryable) {
|
|
243
|
+
drawChangedKeys.push(key);
|
|
244
|
+
}
|
|
245
|
+
if (beforeQuery !== shouldQueryState(expired, this.budget)) queryChangedKeys.push(key);
|
|
246
|
+
}
|
|
247
|
+
const wakeKeys = this.takeDue(
|
|
248
|
+
this.wakeByGeneration,
|
|
249
|
+
this.wakeHeap,
|
|
250
|
+
this.wakeHeapMembers,
|
|
251
|
+
submissionGeneration,
|
|
252
|
+
);
|
|
253
|
+
for (const key of wakeKeys) {
|
|
254
|
+
const state = this.states.get(key);
|
|
255
|
+
if (state === undefined || state.status !== 'hidden' || !state.queryable) continue;
|
|
256
|
+
if (excludedKeys?.has(key)) {
|
|
257
|
+
this.unschedule(key);
|
|
258
|
+
this.schedule(key, state, submissionGeneration);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const beforeQuery = shouldQueryState(state, this.budget);
|
|
262
|
+
this.unschedule(key);
|
|
263
|
+
const next: ConfidenceState =
|
|
264
|
+
state.successfulSubmits >= this.budget.retestSubmits
|
|
265
|
+
? state
|
|
266
|
+
: { ...state, successfulSubmits: this.budget.retestSubmits };
|
|
267
|
+
this.states.set(key, next);
|
|
268
|
+
this.refreshRetestKey(key, next);
|
|
269
|
+
changed = changed || next !== state;
|
|
270
|
+
if (state.status !== next.status || state.queryable !== next.queryable) {
|
|
271
|
+
drawChangedKeys.push(key);
|
|
272
|
+
}
|
|
273
|
+
if (beforeQuery !== shouldQueryState(next, this.budget)) queryChangedKeys.push(key);
|
|
274
|
+
this.scheduleExpiry(
|
|
275
|
+
key,
|
|
276
|
+
submissionGeneration + Math.max(1, this.budget.expirySubmits - next.successfulSubmits),
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return { changed, drawChangedKeys, queryChangedKeys };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
clear(): void {
|
|
283
|
+
this.states.clear();
|
|
284
|
+
this.wakeByGeneration.clear();
|
|
285
|
+
this.expireByGeneration.clear();
|
|
286
|
+
this.scheduleByKey.clear();
|
|
287
|
+
this.retestKeys.clear();
|
|
288
|
+
this.wakeHeap.length = 0;
|
|
289
|
+
this.expireHeap.length = 0;
|
|
290
|
+
this.wakeHeapMembers.clear();
|
|
291
|
+
this.expireHeapMembers.clear();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private schedule(key: string, state: ConfidenceState, anchorGeneration?: number): void {
|
|
295
|
+
if (state.status !== 'hidden' || !state.queryable || state.lastResultGeneration === undefined) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const anchor = anchorGeneration ?? state.lastResultGeneration;
|
|
299
|
+
const wake = anchor + Math.max(0, this.budget.retestSubmits - state.successfulSubmits);
|
|
300
|
+
const expire = anchor + Math.max(1, this.budget.expirySubmits - state.successfulSubmits);
|
|
301
|
+
this.scheduleExpiry(key, expire);
|
|
302
|
+
const entry = this.scheduleByKey.get(key);
|
|
303
|
+
if (entry !== undefined && entry.wake === wake) return;
|
|
304
|
+
this.addDue(this.wakeByGeneration, this.wakeHeap, this.wakeHeapMembers, wake, key);
|
|
305
|
+
this.scheduleByKey.set(key, {
|
|
306
|
+
wake,
|
|
307
|
+
expire: entry?.expire ?? expire,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private scheduleExpiry(key: string, expire: number): void {
|
|
312
|
+
this.addDue(this.expireByGeneration, this.expireHeap, this.expireHeapMembers, expire, key);
|
|
313
|
+
const entry = this.scheduleByKey.get(key);
|
|
314
|
+
this.scheduleByKey.set(key, {
|
|
315
|
+
wake: entry?.wake ?? Number.POSITIVE_INFINITY,
|
|
316
|
+
expire,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private scheduleExpiryOnly(key: string, state: ConfidenceState, anchorGeneration: number): void {
|
|
321
|
+
if (state.status !== 'hidden' || !state.queryable || state.lastResultGeneration === undefined) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const expire =
|
|
325
|
+
anchorGeneration + Math.max(1, this.budget.expirySubmits - state.successfulSubmits);
|
|
326
|
+
this.addDue(this.expireByGeneration, this.expireHeap, this.expireHeapMembers, expire, key);
|
|
327
|
+
this.scheduleByKey.set(key, { wake: Number.POSITIVE_INFINITY, expire });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private unschedule(key: string): void {
|
|
331
|
+
const entry = this.scheduleByKey.get(key);
|
|
332
|
+
if (entry === undefined) return;
|
|
333
|
+
if (Number.isFinite(entry.wake)) {
|
|
334
|
+
removeDue(this.wakeByGeneration, this.wakeHeapMembers, entry.wake, key);
|
|
335
|
+
}
|
|
336
|
+
if (Number.isFinite(entry.expire)) {
|
|
337
|
+
removeDue(this.expireByGeneration, this.expireHeapMembers, entry.expire, key);
|
|
338
|
+
}
|
|
339
|
+
this.scheduleByKey.delete(key);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private takeDue(
|
|
343
|
+
source: Map<number, Set<string>>,
|
|
344
|
+
heap: number[],
|
|
345
|
+
heapMembers: Set<number>,
|
|
346
|
+
generation: number,
|
|
347
|
+
): string[] {
|
|
348
|
+
const result: string[] = [];
|
|
349
|
+
while (heap[0] !== undefined && (heap[0] as number) <= generation) {
|
|
350
|
+
const due = popMin(heap);
|
|
351
|
+
heapMembers.delete(due);
|
|
352
|
+
const keys = source.get(due);
|
|
353
|
+
if (keys === undefined) continue;
|
|
354
|
+
source.delete(due);
|
|
355
|
+
result.push(...keys);
|
|
356
|
+
}
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private addDue(
|
|
361
|
+
source: Map<number, Set<string>>,
|
|
362
|
+
heap: number[],
|
|
363
|
+
heapMembers: Set<number>,
|
|
364
|
+
generation: number,
|
|
365
|
+
key: string,
|
|
366
|
+
): void {
|
|
367
|
+
const set = source.get(generation) ?? new Set<string>();
|
|
368
|
+
if (!source.has(generation)) {
|
|
369
|
+
source.set(generation, set);
|
|
370
|
+
if (!heapMembers.has(generation)) {
|
|
371
|
+
heapMembers.add(generation);
|
|
372
|
+
pushMin(heap, generation);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
set.add(key);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private refreshRetestKey(key: string, state: ConfidenceState): void {
|
|
379
|
+
if (shouldIssueRetest(state, this.budget)) this.retestKeys.add(key);
|
|
380
|
+
else this.retestKeys.delete(key);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function removeDue(
|
|
385
|
+
source: Map<number, Set<string>>,
|
|
386
|
+
heapMembers: Set<number>,
|
|
387
|
+
generation: number,
|
|
388
|
+
key: string,
|
|
389
|
+
): void {
|
|
390
|
+
const keys = source.get(generation);
|
|
391
|
+
if (keys === undefined) return;
|
|
392
|
+
keys.delete(key);
|
|
393
|
+
// The heap intentionally has no arbitrary-delete operation. Removing the
|
|
394
|
+
// membership marker lets a later requeue of this same generation push a
|
|
395
|
+
// fresh heap entry; the old heap entry is harmlessly skipped by takeDue.
|
|
396
|
+
if (keys.size === 0) {
|
|
397
|
+
source.delete(generation);
|
|
398
|
+
heapMembers.delete(generation);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function pushMin(heap: number[], value: number): void {
|
|
403
|
+
heap.push(value);
|
|
404
|
+
let index = heap.length - 1;
|
|
405
|
+
while (index > 0) {
|
|
406
|
+
const parent = Math.floor((index - 1) / 2);
|
|
407
|
+
if ((heap[parent] as number) <= value) break;
|
|
408
|
+
heap[index] = heap[parent] as number;
|
|
409
|
+
index = parent;
|
|
410
|
+
}
|
|
411
|
+
heap[index] = value;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function popMin(heap: number[]): number {
|
|
415
|
+
const first = heap[0] as number;
|
|
416
|
+
const last = heap.pop() as number | undefined;
|
|
417
|
+
if (last !== undefined && heap.length > 0) {
|
|
418
|
+
let index = 0;
|
|
419
|
+
while (true) {
|
|
420
|
+
const left = index * 2 + 1;
|
|
421
|
+
if (left >= heap.length) break;
|
|
422
|
+
const right = left + 1;
|
|
423
|
+
const child =
|
|
424
|
+
right < heap.length && (heap[right] as number) < (heap[left] as number) ? right : left;
|
|
425
|
+
if ((heap[child] as number) >= last) break;
|
|
426
|
+
heap[index] = heap[child] as number;
|
|
427
|
+
index = child;
|
|
428
|
+
}
|
|
429
|
+
heap[index] = last;
|
|
430
|
+
}
|
|
431
|
+
return first;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export interface VisibilityDecisionInput {
|
|
435
|
+
readonly authorVisible: boolean;
|
|
436
|
+
readonly validBounds: boolean;
|
|
437
|
+
readonly frustumVisible: boolean;
|
|
438
|
+
readonly lodReady: boolean;
|
|
439
|
+
readonly occlusion: Pick<ConfidenceState, 'status' | 'queryable'>;
|
|
440
|
+
readonly lane?: 'cpu' | 'gpu';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export interface VisibilityDecision {
|
|
444
|
+
readonly draw: boolean;
|
|
445
|
+
readonly stage: 'author' | 'bounds' | 'frustum' | 'lod' | 'occlusion';
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function decideVisibility(input: VisibilityDecisionInput): VisibilityDecision {
|
|
449
|
+
if (!input.authorVisible) return { draw: false, stage: 'author' };
|
|
450
|
+
// Bounds are query input, not draw authority. Unknown or invalid bounds
|
|
451
|
+
// skip frustum/query rejection and remain conservatively visible.
|
|
452
|
+
if (!input.validBounds) return { draw: true, stage: 'bounds' };
|
|
453
|
+
if (!input.frustumVisible) return { draw: false, stage: 'frustum' };
|
|
454
|
+
if (!input.lodReady) return { draw: true, stage: 'lod' };
|
|
455
|
+
if (input.occlusion.status === 'hidden' && input.occlusion.queryable) {
|
|
456
|
+
return { draw: false, stage: 'occlusion' };
|
|
457
|
+
}
|
|
458
|
+
return { draw: true, stage: 'occlusion' };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export interface RecoveredVisibilityState extends ConfidenceState {
|
|
462
|
+
readonly retryCount: 1;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function recoverVisibilityAfterDeviceLoss(
|
|
466
|
+
_state: ConfidenceState,
|
|
467
|
+
_deviceGeneration: number,
|
|
468
|
+
): RecoveredVisibilityState {
|
|
469
|
+
return {
|
|
470
|
+
status: 'visible',
|
|
471
|
+
zeroStreak: 0,
|
|
472
|
+
successfulSubmits: 0,
|
|
473
|
+
queryable: true,
|
|
474
|
+
dirty: true,
|
|
475
|
+
lastResultGeneration: undefined,
|
|
476
|
+
retryCount: 1,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export interface OcclusionBounds {
|
|
2
|
+
readonly min: readonly [number, number, number];
|
|
3
|
+
readonly max: readonly [number, number, number];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface OcclusionPassIdentity {
|
|
7
|
+
readonly primitiveSlot: number;
|
|
8
|
+
readonly slotGeneration: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface OcclusionPassPlan {
|
|
12
|
+
readonly after: string;
|
|
13
|
+
readonly depth: { readonly resource: string; readonly usage: 'depth-read-only' };
|
|
14
|
+
readonly color: { readonly resource: string; readonly usage: 'not-written' };
|
|
15
|
+
readonly proxyBounds: OcclusionBounds;
|
|
16
|
+
readonly queryIndex: number;
|
|
17
|
+
readonly identity?: OcclusionPassIdentity;
|
|
18
|
+
readonly resolve: { readonly queryIndex: number; readonly bytes: number };
|
|
19
|
+
readonly copy: { readonly asynchronous: true; readonly bytes: number };
|
|
20
|
+
readonly writesColor: false;
|
|
21
|
+
readonly writesDepth: false;
|
|
22
|
+
readonly submitCount: 1;
|
|
23
|
+
readonly mapSameFrame: false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OcclusionPassInput {
|
|
27
|
+
readonly opaquePass: string;
|
|
28
|
+
readonly depthResource: string;
|
|
29
|
+
readonly colorResource: string;
|
|
30
|
+
readonly bounds: OcclusionBounds | undefined;
|
|
31
|
+
readonly queryIndex: number;
|
|
32
|
+
readonly identity?: OcclusionPassIdentity;
|
|
33
|
+
readonly transparent?: boolean;
|
|
34
|
+
readonly deformed?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OcclusionQueryResources {
|
|
38
|
+
readonly pageIndex: number;
|
|
39
|
+
readonly querySet: QuerySet;
|
|
40
|
+
readonly resolveBuffer: Buffer;
|
|
41
|
+
readonly stagingBuffer: Buffer;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createOcclusionQueryResources(
|
|
45
|
+
device: RhiDevice,
|
|
46
|
+
pageIndex: number,
|
|
47
|
+
): Result<OcclusionQueryResources, RhiError> {
|
|
48
|
+
const querySet = device.createQuerySet({
|
|
49
|
+
label: `occlusion-page-${pageIndex}`,
|
|
50
|
+
type: 'occlusion',
|
|
51
|
+
count: OCCLUSION_QUERY_PAGE_INDEX_LIMIT,
|
|
52
|
+
});
|
|
53
|
+
if (!querySet.ok) return querySet;
|
|
54
|
+
const resolveBuffer = device.createBuffer({
|
|
55
|
+
label: `occlusion-page-${pageIndex}-resolve`,
|
|
56
|
+
size: OCCLUSION_QUERY_PAGE_BYTES,
|
|
57
|
+
usage: GPU_BUFFER_USAGE_QUERY_RESOLVE | GPU_BUFFER_USAGE_COPY_SRC,
|
|
58
|
+
});
|
|
59
|
+
if (!resolveBuffer.ok) {
|
|
60
|
+
device.destroyQuerySet(querySet.value);
|
|
61
|
+
return resolveBuffer;
|
|
62
|
+
}
|
|
63
|
+
const stagingBuffer = device.createBuffer({
|
|
64
|
+
label: `occlusion-page-${pageIndex}-staging`,
|
|
65
|
+
size: OCCLUSION_QUERY_PAGE_BYTES,
|
|
66
|
+
usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
|
|
67
|
+
});
|
|
68
|
+
if (!stagingBuffer.ok) {
|
|
69
|
+
device.destroyBuffer(resolveBuffer.value);
|
|
70
|
+
device.destroyQuerySet(querySet.value);
|
|
71
|
+
return stagingBuffer;
|
|
72
|
+
}
|
|
73
|
+
return ok({
|
|
74
|
+
pageIndex,
|
|
75
|
+
querySet: querySet.value,
|
|
76
|
+
resolveBuffer: resolveBuffer.value,
|
|
77
|
+
stagingBuffer: stagingBuffer.value,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Records resolve and copy only; caller owns one shared encoder and submit. */
|
|
82
|
+
export function recordOcclusionResolve(
|
|
83
|
+
encoder: RhiCommandEncoder,
|
|
84
|
+
resources: OcclusionQueryResources,
|
|
85
|
+
firstQuery: number,
|
|
86
|
+
queryCount: number,
|
|
87
|
+
): Result<void, RhiError> {
|
|
88
|
+
if (
|
|
89
|
+
!Number.isSafeInteger(firstQuery) ||
|
|
90
|
+
!Number.isSafeInteger(queryCount) ||
|
|
91
|
+
firstQuery < 0 ||
|
|
92
|
+
queryCount <= 0 ||
|
|
93
|
+
firstQuery + queryCount > OCCLUSION_QUERY_PAGE_INDEX_LIMIT
|
|
94
|
+
) {
|
|
95
|
+
return err(
|
|
96
|
+
new RhiErrorClass({
|
|
97
|
+
code: 'webgpu-runtime-error',
|
|
98
|
+
expected: 'query range stays within one 4096-index page',
|
|
99
|
+
hint: 'allocate a bounded page before recording occlusion resolve',
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const resolved = encoder.resolveQuerySet(
|
|
104
|
+
resources.querySet,
|
|
105
|
+
firstQuery,
|
|
106
|
+
queryCount,
|
|
107
|
+
resources.resolveBuffer,
|
|
108
|
+
0,
|
|
109
|
+
);
|
|
110
|
+
if (!resolved.ok) return resolved;
|
|
111
|
+
encoder.copyBufferToBuffer(resources.resolveBuffer, resources.stagingBuffer, queryCount * 8);
|
|
112
|
+
return ok(undefined);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function finiteBounds(bounds: OcclusionBounds | undefined): bounds is OcclusionBounds {
|
|
116
|
+
if (bounds === undefined) return false;
|
|
117
|
+
return [...bounds.min, ...bounds.max].every((value) => Number.isFinite(value));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Creates only the transport plan; confidence remains owned by visibility facet/M4. */
|
|
121
|
+
export function buildOcclusionPassPlan(input: OcclusionPassInput): OcclusionPassPlan | undefined {
|
|
122
|
+
if (
|
|
123
|
+
input.transparent === true ||
|
|
124
|
+
input.deformed === true ||
|
|
125
|
+
!finiteBounds(input.bounds) ||
|
|
126
|
+
!Number.isSafeInteger(input.queryIndex) ||
|
|
127
|
+
input.queryIndex < 0
|
|
128
|
+
) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return Object.freeze({
|
|
132
|
+
after: input.opaquePass,
|
|
133
|
+
depth: { resource: input.depthResource, usage: 'depth-read-only' as const },
|
|
134
|
+
color: { resource: input.colorResource, usage: 'not-written' as const },
|
|
135
|
+
proxyBounds: input.bounds,
|
|
136
|
+
queryIndex: input.queryIndex,
|
|
137
|
+
...(input.identity === undefined ? {} : { identity: Object.freeze({ ...input.identity }) }),
|
|
138
|
+
resolve: { queryIndex: input.queryIndex, bytes: 8 },
|
|
139
|
+
copy: { asynchronous: true as const, bytes: 8 },
|
|
140
|
+
writesColor: false as const,
|
|
141
|
+
writesDepth: false as const,
|
|
142
|
+
submitCount: 1 as const,
|
|
143
|
+
mapSameFrame: false as const,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
import type {
|
|
148
|
+
Buffer,
|
|
149
|
+
QuerySet,
|
|
150
|
+
Result,
|
|
151
|
+
RhiCommandEncoder,
|
|
152
|
+
RhiDevice,
|
|
153
|
+
RhiError,
|
|
154
|
+
} from '@forgeax/engine-rhi';
|
|
155
|
+
import { err, ok, RhiError as RhiErrorClass } from '@forgeax/engine-rhi';
|
|
156
|
+
import {
|
|
157
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
158
|
+
GPU_BUFFER_USAGE_COPY_SRC,
|
|
159
|
+
GPU_BUFFER_USAGE_MAP_READ,
|
|
160
|
+
GPU_BUFFER_USAGE_QUERY_RESOLVE,
|
|
161
|
+
} from '../../gpu-usage';
|
|
162
|
+
import {
|
|
163
|
+
OCCLUSION_QUERY_PAGE_BYTES,
|
|
164
|
+
OCCLUSION_QUERY_PAGE_INDEX_LIMIT,
|
|
165
|
+
} from './occlusion-query-pool';
|