@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,194 @@
|
|
|
1
|
+
import type { TextureAsset } from '@forgeax/engine-types';
|
|
2
|
+
import { COOKIE_SLICE_SIZE } from './resources';
|
|
3
|
+
|
|
4
|
+
export interface SpotModifierFactors {
|
|
5
|
+
readonly brdf: number;
|
|
6
|
+
readonly range: number;
|
|
7
|
+
readonly cone: number;
|
|
8
|
+
readonly ies?: number | undefined;
|
|
9
|
+
readonly cookie?: number | undefined;
|
|
10
|
+
readonly shadow: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IesCoordinates {
|
|
14
|
+
readonly azimuth: number;
|
|
15
|
+
readonly elevation: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CookieUv {
|
|
19
|
+
readonly u: number;
|
|
20
|
+
readonly v: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The fixed GPU representation consumed by the extended-lighting Cookie
|
|
25
|
+
* array. The matrix is deliberately keyed by Cookie slice rather than by
|
|
26
|
+
* light: it carries only the source texture aspect correction. The shader
|
|
27
|
+
* still owns the light-local basis, roll, and outer-cone projection, so one
|
|
28
|
+
* Cookie can be shared by Spots with different directions or cone angles.
|
|
29
|
+
*/
|
|
30
|
+
export interface CookieProjection {
|
|
31
|
+
readonly data: Uint8Array;
|
|
32
|
+
readonly matrix: Float32Array;
|
|
33
|
+
readonly aspect: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEG_TO_RAD = Math.PI / 180;
|
|
37
|
+
|
|
38
|
+
const COOKIE_PROJECTION_CACHE = new WeakMap<TextureAsset, CookieProjection | null>();
|
|
39
|
+
|
|
40
|
+
function srgbToLinear(value: number): number {
|
|
41
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function clamp01(value: number): number {
|
|
45
|
+
return Math.min(1, Math.max(0, value));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sourceChannel(asset: TextureAsset, x: number, y: number, channel: number): number {
|
|
49
|
+
const offset = (y * asset.shape.extent.width + x) * 4 + channel;
|
|
50
|
+
const encoded = (asset.data[offset] ?? 0) / 255;
|
|
51
|
+
return channel < 3 && asset.colorSpace === 'srgb' ? srgbToLinear(encoded) : encoded;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sampleSourceChannel(asset: TextureAsset, x: number, y: number, channel: number): number {
|
|
55
|
+
const sourceX = clamp01((x + 0.5) / COOKIE_SLICE_SIZE) * (asset.shape.extent.width - 1);
|
|
56
|
+
const sourceY = clamp01((y + 0.5) / COOKIE_SLICE_SIZE) * (asset.shape.extent.height - 1);
|
|
57
|
+
const x0 = Math.floor(sourceX);
|
|
58
|
+
const y0 = Math.floor(sourceY);
|
|
59
|
+
const x1 = Math.min(asset.shape.extent.width - 1, x0 + 1);
|
|
60
|
+
const y1 = Math.min(asset.shape.extent.height - 1, y0 + 1);
|
|
61
|
+
const tx = sourceX - x0;
|
|
62
|
+
const ty = sourceY - y0;
|
|
63
|
+
const top =
|
|
64
|
+
sourceChannel(asset, x0, y0, channel) * (1 - tx) + sourceChannel(asset, x1, y0, channel) * tx;
|
|
65
|
+
const bottom =
|
|
66
|
+
sourceChannel(asset, x0, y1, channel) * (1 - tx) + sourceChannel(asset, x1, y1, channel) * tx;
|
|
67
|
+
return top * (1 - ty) + bottom * ty;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Project a filterable 2D RGBA Cookie into the renderer's fixed linear
|
|
72
|
+
* 256x256 array slice. This is a producer-boundary operation and is cached by
|
|
73
|
+
* the immutable TextureAsset object, so the frame path never decodes or
|
|
74
|
+
* resamples the source repeatedly.
|
|
75
|
+
*/
|
|
76
|
+
export function prepareCookieProjection(asset: TextureAsset): CookieProjection | undefined {
|
|
77
|
+
if (COOKIE_PROJECTION_CACHE.has(asset)) {
|
|
78
|
+
return COOKIE_PROJECTION_CACHE.get(asset) ?? undefined;
|
|
79
|
+
}
|
|
80
|
+
const supportedFormat = asset.format === 'rgba8unorm' || asset.format === 'rgba8unorm-srgb';
|
|
81
|
+
const validDimensions =
|
|
82
|
+
Number.isSafeInteger(asset.shape.extent.width) &&
|
|
83
|
+
Number.isSafeInteger(asset.shape.extent.height) &&
|
|
84
|
+
asset.shape.extent.width > 0 &&
|
|
85
|
+
asset.shape.extent.height > 0;
|
|
86
|
+
const validFormatPair =
|
|
87
|
+
(asset.format === 'rgba8unorm-srgb' && asset.colorSpace === 'srgb') ||
|
|
88
|
+
(asset.format === 'rgba8unorm' && asset.colorSpace === 'linear');
|
|
89
|
+
const validPayload =
|
|
90
|
+
asset.data.byteLength >= asset.shape.extent.width * asset.shape.extent.height * 4;
|
|
91
|
+
if (!supportedFormat || !validDimensions || !validFormatPair || !validPayload) {
|
|
92
|
+
COOKIE_PROJECTION_CACHE.set(asset, null);
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = new Uint8Array(COOKIE_SLICE_SIZE * COOKIE_SLICE_SIZE * 4);
|
|
97
|
+
for (let y = 0; y < COOKIE_SLICE_SIZE; y += 1) {
|
|
98
|
+
for (let x = 0; x < COOKIE_SLICE_SIZE; x += 1) {
|
|
99
|
+
const target = (y * COOKIE_SLICE_SIZE + x) * 4;
|
|
100
|
+
data[target] = Math.round(clamp01(sampleSourceChannel(asset, x, y, 0)) * 255);
|
|
101
|
+
data[target + 1] = Math.round(clamp01(sampleSourceChannel(asset, x, y, 1)) * 255);
|
|
102
|
+
data[target + 2] = Math.round(clamp01(sampleSourceChannel(asset, x, y, 2)) * 255);
|
|
103
|
+
// Alpha stays linear and is intentionally preserved: the shader
|
|
104
|
+
// multiplies sampled RGB by this lane as part of Cookie semantics.
|
|
105
|
+
data[target + 3] = Math.round(clamp01(sampleSourceChannel(asset, x, y, 3)) * 255);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Column-major mat4. The shader multiplies the unprojected local
|
|
110
|
+
// (x/depth, y/depth) pair by this aspect-only matrix, then applies the
|
|
111
|
+
// Spot's outer-cone tangent and the [0,1] translation. Keeping outerCone
|
|
112
|
+
// out of this slice-keyed matrix allows one Cookie to be shared by lights.
|
|
113
|
+
const matrix = new Float32Array(16);
|
|
114
|
+
matrix[0] = asset.shape.extent.height / asset.shape.extent.width;
|
|
115
|
+
matrix[5] = 1;
|
|
116
|
+
matrix[10] = 1;
|
|
117
|
+
matrix[15] = 1;
|
|
118
|
+
const projection = {
|
|
119
|
+
data,
|
|
120
|
+
matrix,
|
|
121
|
+
aspect: asset.shape.extent.width / asset.shape.extent.height,
|
|
122
|
+
};
|
|
123
|
+
COOKIE_PROJECTION_CACHE.set(asset, projection);
|
|
124
|
+
return projection;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Create the deterministic identity payload used for unused Cookie slices. */
|
|
128
|
+
export function createCookieProjectionMatrixData(count: number): Float32Array {
|
|
129
|
+
const data = new Float32Array(count * 16);
|
|
130
|
+
for (let index = 0; index < count; index += 1) {
|
|
131
|
+
const base = index * 16;
|
|
132
|
+
data[base] = 1;
|
|
133
|
+
data[base + 5] = 1;
|
|
134
|
+
data[base + 10] = 1;
|
|
135
|
+
data[base + 15] = 1;
|
|
136
|
+
}
|
|
137
|
+
return data;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalized(vector: ArrayLike<number>): [number, number, number] | undefined {
|
|
141
|
+
const x = vector[0] ?? 0;
|
|
142
|
+
const y = vector[1] ?? 0;
|
|
143
|
+
const z = vector[2] ?? 0;
|
|
144
|
+
const length = Math.hypot(x, y, z);
|
|
145
|
+
return length > 0 && Number.isFinite(length) ? [x / length, y / length, z / length] : undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function spotModifierProduct(factors: SpotModifierFactors): number {
|
|
149
|
+
return (
|
|
150
|
+
factors.brdf *
|
|
151
|
+
factors.range *
|
|
152
|
+
factors.cone *
|
|
153
|
+
(factors.ies ?? 1) *
|
|
154
|
+
(factors.cookie ?? 1) *
|
|
155
|
+
factors.shadow
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function projectIesCoordinates(
|
|
160
|
+
toPoint: ArrayLike<number>,
|
|
161
|
+
rollDeg: number,
|
|
162
|
+
): IesCoordinates | undefined {
|
|
163
|
+
const direction = normalized(toPoint);
|
|
164
|
+
if (direction === undefined || direction[2] >= 0) return undefined;
|
|
165
|
+
const roll = rollDeg * DEG_TO_RAD;
|
|
166
|
+
const rolledX = direction[0] * Math.cos(roll) - direction[1] * Math.sin(roll);
|
|
167
|
+
const rolledY = direction[0] * Math.sin(roll) + direction[1] * Math.cos(roll);
|
|
168
|
+
return {
|
|
169
|
+
azimuth: (Math.atan2(rolledY, rolledX) + Math.PI * 2) % (Math.PI * 2),
|
|
170
|
+
elevation: Math.acos(Math.min(1, Math.max(-1, -direction[2]))) / Math.PI,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function projectCookieUv(
|
|
175
|
+
toPoint: ArrayLike<number>,
|
|
176
|
+
rollDeg: number,
|
|
177
|
+
aspect: number,
|
|
178
|
+
outerConeDeg = 45,
|
|
179
|
+
): CookieUv | undefined {
|
|
180
|
+
const direction = normalized(toPoint);
|
|
181
|
+
if (direction === undefined || direction[2] >= 0 || !Number.isFinite(aspect) || aspect <= 0)
|
|
182
|
+
return undefined;
|
|
183
|
+
const cone = Math.atan2(Math.hypot(direction[0], direction[1]), -direction[2]);
|
|
184
|
+
const outerCone = outerConeDeg * DEG_TO_RAD;
|
|
185
|
+
if (cone > outerCone) return undefined;
|
|
186
|
+
const roll = rollDeg * DEG_TO_RAD;
|
|
187
|
+
const x = direction[0] * Math.cos(roll) - direction[1] * Math.sin(roll);
|
|
188
|
+
const y = direction[0] * Math.sin(roll) + direction[1] * Math.cos(roll);
|
|
189
|
+
const scale = Math.tan(outerCone);
|
|
190
|
+
return {
|
|
191
|
+
u: 0.5 + (x / -direction[2] / scale / aspect) * 0.5,
|
|
192
|
+
v: 0.5 + (y / -direction[2] / scale) * 0.5,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { LightResourceUnavailableError } from '../../errors/render';
|
|
2
|
+
import {
|
|
3
|
+
type LightInspection,
|
|
4
|
+
lightInspectionIdentity,
|
|
5
|
+
projectLightInspection,
|
|
6
|
+
} from '../../inspection-types';
|
|
7
|
+
import type { ExtendedLightingResourceCandidate } from './resources';
|
|
8
|
+
|
|
9
|
+
export type ExtendedLightingResourceStatus = 'empty' | 'candidate' | 'accepted';
|
|
10
|
+
|
|
11
|
+
export interface ExtendedLightingState {
|
|
12
|
+
readonly enabled: boolean;
|
|
13
|
+
readonly generation: number;
|
|
14
|
+
readonly status: ExtendedLightingResourceStatus;
|
|
15
|
+
readonly candidate: ExtendedLightingResourceCandidate | undefined;
|
|
16
|
+
readonly accepted: ExtendedLightingResourceCandidate | undefined;
|
|
17
|
+
readonly lastKnownGood: ExtendedLightingResourceCandidate | undefined;
|
|
18
|
+
readonly resourceCount: number;
|
|
19
|
+
readonly descriptorBytes: number;
|
|
20
|
+
readonly uploadCount: number;
|
|
21
|
+
readonly passCount: number;
|
|
22
|
+
readonly bindGroupCount: number;
|
|
23
|
+
readonly failure: LightResourceUnavailableError | undefined;
|
|
24
|
+
readonly failureKeys: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createExtendedLightingState(generation: number): ExtendedLightingState {
|
|
28
|
+
return {
|
|
29
|
+
enabled: false,
|
|
30
|
+
generation,
|
|
31
|
+
status: 'empty',
|
|
32
|
+
candidate: undefined,
|
|
33
|
+
accepted: undefined,
|
|
34
|
+
lastKnownGood: undefined,
|
|
35
|
+
resourceCount: 0,
|
|
36
|
+
descriptorBytes: 0,
|
|
37
|
+
uploadCount: 0,
|
|
38
|
+
passCount: 0,
|
|
39
|
+
bindGroupCount: 0,
|
|
40
|
+
failure: undefined,
|
|
41
|
+
failureKeys: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function promoteExtendedLightingCandidate(
|
|
46
|
+
state: ExtendedLightingState,
|
|
47
|
+
candidate: ExtendedLightingResourceCandidate | undefined,
|
|
48
|
+
): ExtendedLightingState {
|
|
49
|
+
if (candidate === undefined) return state;
|
|
50
|
+
return {
|
|
51
|
+
...state,
|
|
52
|
+
enabled: true,
|
|
53
|
+
generation: candidate.generation,
|
|
54
|
+
status: 'accepted',
|
|
55
|
+
candidate: undefined,
|
|
56
|
+
accepted: candidate,
|
|
57
|
+
lastKnownGood: candidate,
|
|
58
|
+
resourceCount: 4,
|
|
59
|
+
descriptorBytes: candidate.descriptorBytes,
|
|
60
|
+
uploadCount: candidate.uploadCount,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function recordExtendedLightingFailure(
|
|
65
|
+
state: ExtendedLightingState,
|
|
66
|
+
failure: LightResourceUnavailableError,
|
|
67
|
+
): ExtendedLightingState {
|
|
68
|
+
const key = `${failure.detail.entity}:${failure.detail.feature}:${failure.detail.generation}`;
|
|
69
|
+
if (state.failureKeys.includes(key)) return state;
|
|
70
|
+
return {
|
|
71
|
+
...state,
|
|
72
|
+
failure,
|
|
73
|
+
failureKeys: [...state.failureKeys, key],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function projectExtendedLightingInspection(state: ExtendedLightingState): LightInspection {
|
|
78
|
+
const identity = (candidate: ExtendedLightingResourceCandidate | undefined) =>
|
|
79
|
+
candidate === undefined
|
|
80
|
+
? undefined
|
|
81
|
+
: lightInspectionIdentity(candidate.topology, candidate.generation);
|
|
82
|
+
return projectLightInspection({
|
|
83
|
+
generation: state.generation,
|
|
84
|
+
candidate: identity(state.candidate),
|
|
85
|
+
accepted: identity(state.accepted),
|
|
86
|
+
lastKnownGood: identity(state.lastKnownGood),
|
|
87
|
+
failure: state.failure?.code,
|
|
88
|
+
failureKeys: state.failureKeys,
|
|
89
|
+
resourceCount: state.resourceCount,
|
|
90
|
+
uploadBytes: state.descriptorBytes,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { Buffer, QuerySet, RhiCommandEncoder, RhiDevice } from '@forgeax/engine-rhi';
|
|
2
|
+
import { ok } from '@forgeax/engine-types';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { GPU_BUFFER_USAGE_MAP_READ } from '../../gpu-usage';
|
|
5
|
+
import { GpuTimingCapture } from '../gpu-timing';
|
|
6
|
+
|
|
7
|
+
function timestampDevice(readbackBytes: ArrayBuffer): {
|
|
8
|
+
readonly device: RhiDevice;
|
|
9
|
+
readonly encoder: RhiCommandEncoder;
|
|
10
|
+
readonly resolveQuerySet: ReturnType<typeof vi.fn>;
|
|
11
|
+
readonly readbackBuffer: Buffer;
|
|
12
|
+
readonly getMappedRange: ReturnType<typeof vi.fn>;
|
|
13
|
+
readonly destroyBuffer: ReturnType<typeof vi.fn>;
|
|
14
|
+
readonly destroyQuerySet: ReturnType<typeof vi.fn>;
|
|
15
|
+
} {
|
|
16
|
+
const querySet = {} as QuerySet;
|
|
17
|
+
const resolveBuffer = {} as Buffer;
|
|
18
|
+
const getMappedRange = vi.fn(() => ok(readbackBytes));
|
|
19
|
+
const unmap = vi.fn();
|
|
20
|
+
const readbackBuffer = {
|
|
21
|
+
mapAsync: vi.fn(async (mode: number) => {
|
|
22
|
+
expect(mode).toBe(GPU_BUFFER_USAGE_MAP_READ);
|
|
23
|
+
return ok({
|
|
24
|
+
getMappedRange,
|
|
25
|
+
unmap,
|
|
26
|
+
} as never);
|
|
27
|
+
}),
|
|
28
|
+
} as unknown as Buffer;
|
|
29
|
+
let bufferIndex = 0;
|
|
30
|
+
const resolveQuerySet = vi.fn(() => ok(undefined));
|
|
31
|
+
const destroyBuffer = vi.fn(() => ok(undefined));
|
|
32
|
+
const destroyQuerySet = vi.fn(() => ok(undefined));
|
|
33
|
+
const encoder = {
|
|
34
|
+
resolveQuerySet,
|
|
35
|
+
copyBufferToBuffer: vi.fn(),
|
|
36
|
+
} as unknown as RhiCommandEncoder;
|
|
37
|
+
const device = {
|
|
38
|
+
caps: {
|
|
39
|
+
backendKind: 'webgpu',
|
|
40
|
+
compute: true,
|
|
41
|
+
storageBuffer: true,
|
|
42
|
+
storageTexture: true,
|
|
43
|
+
timestampQuery: true,
|
|
44
|
+
timestampPeriodNanoseconds: 1,
|
|
45
|
+
indirectDrawing: true,
|
|
46
|
+
textureCompressionBc: false,
|
|
47
|
+
textureCompressionEtc2: false,
|
|
48
|
+
textureCompressionAstc: false,
|
|
49
|
+
multiDrawIndirect: false,
|
|
50
|
+
pushConstants: false,
|
|
51
|
+
textureBindingArray: false,
|
|
52
|
+
samplerAliasing: true,
|
|
53
|
+
firstInstanceIndirect: true,
|
|
54
|
+
rgba16floatRenderable: true,
|
|
55
|
+
rg11b10ufloatRenderable: true,
|
|
56
|
+
float32Filterable: true,
|
|
57
|
+
maxColorAttachments: 8,
|
|
58
|
+
},
|
|
59
|
+
createQuerySet: vi.fn(() => ok(querySet)),
|
|
60
|
+
createBuffer: vi.fn(() => {
|
|
61
|
+
bufferIndex += 1;
|
|
62
|
+
return ok(bufferIndex === 1 ? resolveBuffer : readbackBuffer);
|
|
63
|
+
}),
|
|
64
|
+
destroyQuerySet,
|
|
65
|
+
destroyBuffer,
|
|
66
|
+
queue: { onSubmittedWorkDone: vi.fn(async () => undefined) },
|
|
67
|
+
} as unknown as RhiDevice;
|
|
68
|
+
return {
|
|
69
|
+
device,
|
|
70
|
+
encoder,
|
|
71
|
+
resolveQuerySet,
|
|
72
|
+
readbackBuffer,
|
|
73
|
+
getMappedRange,
|
|
74
|
+
destroyBuffer,
|
|
75
|
+
destroyQuerySet,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeTimestampPair(bytes: ArrayBuffer, index: number, begin: bigint, end: bigint): void {
|
|
80
|
+
const view = new DataView(bytes);
|
|
81
|
+
view.setBigUint64(index * 16, begin, true);
|
|
82
|
+
view.setBigUint64(index * 16 + 8, end, true);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe('GpuTimingCapture pass descriptor contract', () => {
|
|
86
|
+
it('resolves real pass pairs without calling command-encoder writeTimestamp', async () => {
|
|
87
|
+
const bytes = new ArrayBuffer(64 * 8);
|
|
88
|
+
writeTimestampPair(bytes, 0, 100n, 200n);
|
|
89
|
+
writeTimestampPair(bytes, 1, 210n, 450n);
|
|
90
|
+
const { device, encoder, resolveQuerySet } = timestampDevice(bytes);
|
|
91
|
+
const created = GpuTimingCapture.create(device);
|
|
92
|
+
expect(created.ok).toBe(true);
|
|
93
|
+
if (!created.ok || created.value === undefined) return;
|
|
94
|
+
|
|
95
|
+
const first = created.value.beginPass('volume-inject', 'compute', 3);
|
|
96
|
+
const second = created.value.beginPass('volume-integrate', 'raster', 9);
|
|
97
|
+
expect(first).toMatchObject({ beginningOfPassWriteIndex: 0, endOfPassWriteIndex: 1 });
|
|
98
|
+
expect(second).toMatchObject({ beginningOfPassWriteIndex: 2, endOfPassWriteIndex: 3 });
|
|
99
|
+
expect((encoder as unknown as { writeTimestamp?: unknown }).writeTimestamp).toBeUndefined();
|
|
100
|
+
|
|
101
|
+
expect(created.value.resolve(encoder).ok).toBe(true);
|
|
102
|
+
expect(resolveQuerySet).toHaveBeenCalledWith(expect.anything(), 0, 4, expect.anything(), 0);
|
|
103
|
+
created.value.markSubmitted();
|
|
104
|
+
await expect(created.value.observation()).resolves.toEqual({
|
|
105
|
+
status: 'ready',
|
|
106
|
+
unit: 'ms',
|
|
107
|
+
frameMs: 0.00035,
|
|
108
|
+
totalMs: 0.00035,
|
|
109
|
+
passes: [
|
|
110
|
+
{ name: 'volume-inject', milliseconds: 0.0001 },
|
|
111
|
+
{ name: 'volume-integrate', milliseconds: 0.00024 },
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('fails closed when a producer already owns the pass timestamp pair', async () => {
|
|
117
|
+
const { device, encoder } = timestampDevice(new ArrayBuffer(64 * 8));
|
|
118
|
+
const created = GpuTimingCapture.create(device);
|
|
119
|
+
expect(created.ok).toBe(true);
|
|
120
|
+
if (!created.ok || created.value === undefined) return;
|
|
121
|
+
expect(created.value.beginPass('volume-inject', 'compute', 0)).toBeDefined();
|
|
122
|
+
created.value.markOwnerConflict('volume-inject');
|
|
123
|
+
expect(created.value.resolve(encoder).ok).toBe(true);
|
|
124
|
+
created.value.markSubmitted();
|
|
125
|
+
await expect(created.value.observation()).resolves.toEqual({
|
|
126
|
+
status: 'unavailable',
|
|
127
|
+
reason: "timestamp pass 'volume-inject' already has producer-owned timestampWrites",
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('disposes query and buffer resources when readback mapping fails', async () => {
|
|
132
|
+
const { device, encoder, readbackBuffer, destroyBuffer, destroyQuerySet } = timestampDevice(
|
|
133
|
+
new ArrayBuffer(64 * 8),
|
|
134
|
+
);
|
|
135
|
+
const mapAsync = readbackBuffer.mapAsync as unknown as ReturnType<typeof vi.fn>;
|
|
136
|
+
mapAsync.mockResolvedValue({
|
|
137
|
+
ok: false,
|
|
138
|
+
error: { code: 'webgpu-runtime-error' },
|
|
139
|
+
});
|
|
140
|
+
const created = GpuTimingCapture.create(device);
|
|
141
|
+
expect(created.ok).toBe(true);
|
|
142
|
+
if (!created.ok || created.value === undefined) return;
|
|
143
|
+
|
|
144
|
+
expect(created.value.beginPass('volume-inject', 'compute', 0)).toBeDefined();
|
|
145
|
+
expect(created.value.resolve(encoder).ok).toBe(true);
|
|
146
|
+
created.value.markSubmitted();
|
|
147
|
+
await expect(created.value.observation()).resolves.toEqual({
|
|
148
|
+
status: 'unavailable',
|
|
149
|
+
reason: 'timestamp readback failed: webgpu-runtime-error',
|
|
150
|
+
});
|
|
151
|
+
expect(destroyBuffer).toHaveBeenCalledTimes(2);
|
|
152
|
+
expect(destroyQuerySet).toHaveBeenCalledTimes(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('disposes query and buffer resources when mapped range access fails', async () => {
|
|
156
|
+
const { device, encoder, getMappedRange, destroyBuffer, destroyQuerySet } = timestampDevice(
|
|
157
|
+
new ArrayBuffer(64 * 8),
|
|
158
|
+
);
|
|
159
|
+
getMappedRange.mockReturnValue({
|
|
160
|
+
ok: false,
|
|
161
|
+
error: { code: 'webgpu-runtime-error' },
|
|
162
|
+
});
|
|
163
|
+
const created = GpuTimingCapture.create(device);
|
|
164
|
+
expect(created.ok).toBe(true);
|
|
165
|
+
if (!created.ok || created.value === undefined) return;
|
|
166
|
+
|
|
167
|
+
expect(created.value.beginPass('volume-inject', 'compute', 0)).toBeDefined();
|
|
168
|
+
expect(created.value.resolve(encoder).ok).toBe(true);
|
|
169
|
+
created.value.markSubmitted();
|
|
170
|
+
await expect(created.value.observation()).resolves.toEqual({
|
|
171
|
+
status: 'unavailable',
|
|
172
|
+
reason: 'timestamp mapping failed: webgpu-runtime-error',
|
|
173
|
+
});
|
|
174
|
+
expect(destroyBuffer).toHaveBeenCalledTimes(2);
|
|
175
|
+
expect(destroyQuerySet).toHaveBeenCalledTimes(1);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -132,7 +132,6 @@ describe('temporal GPU payloads', () => {
|
|
|
132
132
|
{ point: [], spot: [projector] } as never,
|
|
133
133
|
[projector],
|
|
134
134
|
undefined,
|
|
135
|
-
undefined,
|
|
136
135
|
0,
|
|
137
136
|
);
|
|
138
137
|
const payload = new Float32Array(uploads[0]?.buffer ?? new ArrayBuffer(0));
|
|
@@ -165,6 +164,29 @@ describe('temporal GPU payloads', () => {
|
|
|
165
164
|
expect(payload[230]).toBe(0);
|
|
166
165
|
});
|
|
167
166
|
|
|
167
|
+
it('keeps the accepted previous camera in the temporal ABI', () => {
|
|
168
|
+
const { queue, uploads } = recordingQueue();
|
|
169
|
+
const acceptedCamera = [7, 8, 9] as const;
|
|
170
|
+
const temporal = { ...temporalView(), previousCameraPosition: acceptedCamera };
|
|
171
|
+
writeViewUbo(
|
|
172
|
+
queue,
|
|
173
|
+
{} as Buffer,
|
|
174
|
+
camera(),
|
|
175
|
+
{
|
|
176
|
+
kind: 'directional',
|
|
177
|
+
direction: vec3.create(0, -1, 0),
|
|
178
|
+
color: vec3.create(1, 1, 1),
|
|
179
|
+
intensity: 1,
|
|
180
|
+
},
|
|
181
|
+
{ point: [], spot: [] } as never,
|
|
182
|
+
[],
|
|
183
|
+
temporal as never,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const payload = new Float32Array(uploads[0]?.buffer ?? new ArrayBuffer(0));
|
|
187
|
+
expect(Array.from(payload.slice(232, 236))).toEqual([...acceptedCamera, 1]);
|
|
188
|
+
});
|
|
189
|
+
|
|
168
190
|
it('seeds previousWorld on the first frame and uses the submitted snapshot later', () => {
|
|
169
191
|
const first = translatedMatrix(4);
|
|
170
192
|
const previous = translatedMatrix(2);
|
|
@@ -19,6 +19,7 @@ const captureDescriptor = {
|
|
|
19
19
|
|
|
20
20
|
type CaptureOverrides = {
|
|
21
21
|
readonly target?: boolean;
|
|
22
|
+
readonly onSubmitted?: () => void;
|
|
22
23
|
readonly descriptor?: {
|
|
23
24
|
readonly format?: 'rgba16float' | 'rgba8unorm';
|
|
24
25
|
readonly size?: { readonly width: number; readonly height: number };
|
|
@@ -74,7 +75,14 @@ function executeCapture(overrides: CaptureOverrides = {}) {
|
|
|
74
75
|
errorRegistry: { fire: (error: unknown) => errors.push(error) },
|
|
75
76
|
device: { queue: { submit: () => ok(undefined) } },
|
|
76
77
|
} as never;
|
|
77
|
-
const submitted = executeCompiledFrameGraph(
|
|
78
|
+
const submitted = executeCompiledFrameGraph(
|
|
79
|
+
internals,
|
|
80
|
+
frameState,
|
|
81
|
+
{} as never,
|
|
82
|
+
encoder,
|
|
83
|
+
undefined,
|
|
84
|
+
overrides.onSubmitted === undefined ? undefined : { onSubmitted: overrides.onSubmitted },
|
|
85
|
+
);
|
|
78
86
|
return { submitted, errors, copies };
|
|
79
87
|
}
|
|
80
88
|
|
|
@@ -142,6 +150,17 @@ describe('typed frame graph feature plan authority', () => {
|
|
|
142
150
|
expect(result.copies).toHaveLength(1);
|
|
143
151
|
});
|
|
144
152
|
|
|
153
|
+
it('keeps the submit hook on the successful commit path', () => {
|
|
154
|
+
let calls = 0;
|
|
155
|
+
const result = executeCapture({
|
|
156
|
+
onSubmitted: () => {
|
|
157
|
+
calls += 1;
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
expect(result.submitted).toBe(true);
|
|
161
|
+
expect(calls).toBe(1);
|
|
162
|
+
});
|
|
163
|
+
|
|
145
164
|
it.each([
|
|
146
165
|
[
|
|
147
166
|
'missing same-submit copy / untouched sentinel',
|