@forgeax/engine-render 0.1.27 → 0.1.29
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 +842 -124
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-host.d.ts +52 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
- package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +3 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/gpu-driven-owner.d.ts +10 -0
- package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +19 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +7 -0
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material-shader-policy.d.ts +3 -1
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/recovery/failure-location.d.ts +12 -0
- package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
- package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +3 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -1
- package/dist/assembly/renderer-inspection.d.ts +4 -0
- package/dist/assembly/renderer-inspection.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
- package/dist/assembly/skin-palette-owner.d.ts +8 -0
- package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
- package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-device-loss.d.ts +15 -0
- package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
- package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
- package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +7 -3
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
- package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
- package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
- package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
- package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
- package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +7 -141
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
- package/dist/authoring.mjs +3 -3
- package/dist/{chunk-ADHHVYLW.mjs → chunk-33XZFTQU.mjs} +245 -39
- package/dist/chunk-33XZFTQU.mjs.map +1 -0
- package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
- package/dist/chunk-BLXGONR2.mjs.map +1 -0
- package/dist/{chunk-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
- package/dist/chunk-BQVQOQJH.mjs.map +1 -0
- package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
- package/dist/chunk-BTWH7BP4.mjs.map +1 -0
- package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
- package/dist/chunk-FUODTTIA.mjs.map +1 -0
- package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
- package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
- package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
- package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
- package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
- package/dist/chunk-NICXSHAT.mjs.map +1 -0
- package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
- package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
- package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
- package/dist/chunk-PA5DRJNI.mjs.map +1 -0
- package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
- package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
- package/dist/components/camera.d.ts +62 -2
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/instances.d.ts +6 -20
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/components/scene-instance.d.ts +2 -1
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/screen-space-reflection.d.ts +14 -0
- package/dist/components/screen-space-reflection.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +13805 -7186
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +11 -17
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/mesh-residency-lifetime.d.ts +21 -0
- package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
- package/dist/dynamic-geometry.d.ts +125 -0
- package/dist/dynamic-geometry.d.ts.map +1 -0
- package/dist/environment/background.d.ts +6 -0
- package/dist/environment/background.d.ts.map +1 -0
- package/dist/errors/gpu-driven.d.ts +6 -3
- package/dist/errors/gpu-driven.d.ts.map +1 -1
- package/dist/errors/render.d.ts +7 -4
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +33 -3
- package/dist/extract/gpu-driven.d.ts.map +1 -1
- package/dist/features/host.d.ts +23 -1
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/noise-texture.d.ts +17 -0
- package/dist/features/noise-texture.d.ts.map +1 -0
- package/dist/features/plan.d.ts +83 -2
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +37 -4
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/features/prepared-graphics.d.ts +5 -0
- package/dist/features/prepared-graphics.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts +5 -3
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts +11 -3
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/render-graph-raster.d.ts +19 -3
- package/dist/features/render-graph-raster.d.ts.map +1 -1
- package/dist/features/types.d.ts +11 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +2 -0
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/pbr-program.d.ts +10 -0
- package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +144 -23
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-views.d.ts +126 -0
- package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +18 -2
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +2 -8
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +14 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-texture-usage.d.ts +1 -0
- package/dist/gpu-texture-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +6 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts +8 -0
- package/dist/ibl/frame-resources.d.ts.map +1 -0
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +8 -7
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +164 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +11 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -1
- package/dist/instances.d.ts +89 -44
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +118 -10
- package/dist/internal.mjs.map +1 -1
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/material-row.d.ts +5 -0
- package/dist/material-row.d.ts.map +1 -0
- package/dist/materials.d.ts +15 -0
- package/dist/materials.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +21 -5
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
- package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
- package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
- package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
- package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
- package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
- package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
- package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
- package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
- package/dist/pipeline/standard-output/graph.d.ts +15 -0
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
- package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
- package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
- package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
- package/dist/pipeline/standard-output/resources.d.ts +10 -0
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
- package/dist/pipeline/standard-output/types.d.ts +39 -0
- package/dist/pipeline/standard-output/types.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +2 -2
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec-types.d.ts +4 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +1 -9
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +1 -1
- package/dist/points-lines/standard-owner.d.ts.map +1 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
- package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
- package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +3 -3
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +106 -5
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +39 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +7 -8
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +38 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +2 -10
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +12 -2
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +15 -6
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts +19 -0
- package/dist/record/prepared-material-bindings.d.ts.map +1 -0
- package/dist/record/recovery-pipeline.d.ts +2 -9
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +76 -26
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +62 -7
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +35 -7
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +1 -0
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +10 -2
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/recovery/render-system-roots.d.ts +11 -0
- package/dist/recovery/render-system-roots.d.ts.map +1 -0
- package/dist/recovery/types.d.ts +76 -0
- package/dist/recovery/types.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +0 -2
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +185 -30
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +64 -5
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +7 -5
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +101 -41
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-projections.d.ts +22 -0
- package/dist/render-system-projections.d.ts.map +1 -0
- package/dist/render-system.d.ts +34 -77
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +14 -7
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +29 -11
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/shadow-visibility.d.ts +5 -0
- package/dist/scene/shadow-visibility.d.ts.map +1 -0
- package/dist/ssr/admission.d.ts +70 -1
- package/dist/ssr/admission.d.ts.map +1 -1
- package/dist/ssr/compose.d.ts +13 -0
- package/dist/ssr/compose.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +35 -0
- package/dist/ssr/errors.d.ts.map +1 -1
- package/dist/ssr/graph.d.ts +60 -0
- package/dist/ssr/graph.d.ts.map +1 -0
- package/dist/ssr/history.d.ts +97 -0
- package/dist/ssr/history.d.ts.map +1 -0
- package/dist/ssr/hiz.d.ts +37 -0
- package/dist/ssr/hiz.d.ts.map +1 -0
- package/dist/ssr/inspection.d.ts +42 -0
- package/dist/ssr/inspection.d.ts.map +1 -0
- package/dist/ssr/resources.d.ts +58 -0
- package/dist/ssr/resources.d.ts.map +1 -0
- package/dist/ssr/temporal.d.ts +38 -0
- package/dist/ssr/temporal.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +32 -10
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +15 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +9 -0
- package/dist/systems/transparent-dispatch.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +7 -0
- package/dist/targets/material-source.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts +7 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts +8 -0
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/history.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/inspection.d.ts.map +1 -1
- package/dist/temporal/standard-scene-data.d.ts +2 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/temporal-view.d.ts +2 -0
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +35 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +3 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +6 -0
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
- package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
- package/src/__tests__/auto-exposure.browser.test.ts +33 -0
- package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
- package/src/__tests__/detached-inspection.integration.test.ts +5 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
- package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
- package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
- package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
- package/src/__tests__/factory-contract.integration.test.ts +73 -22
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
- package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
- package/src/__tests__/feature-noise.unit.test.ts +38 -0
- package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
- package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
- package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
- package/src/__tests__/frame-targets.unit.test.ts +24 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
- package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
- package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
- package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
- package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
- package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
- package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
- package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
- package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
- package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
- package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
- package/src/__tests__/gpu-scene.unit.test.ts +559 -33
- package/src/__tests__/instances-culling.integration.test.ts +36 -10
- package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
- package/src/__tests__/instances-store.unit.test.ts +242 -39
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
- package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
- package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
- package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
- package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
- package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
- package/src/__tests__/material-texture-source.unit.test.ts +87 -0
- package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
- package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
- package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
- package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
- package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
- package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
- package/src/__tests__/public-surface.test-d.ts +3 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
- package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
- package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
- package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
- package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
- package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
- package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
- package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
- package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
- package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
- package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
- package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
- package/src/__tests__/render-target-material-source.test-d.ts +27 -3
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
- package/src/__tests__/shader-manifest-fixture.ts +39 -0
- package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
- package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
- package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
- package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
- package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
- package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
- package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
- package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
- package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
- package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
- package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
- package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
- package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
- package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
- package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
- package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
- package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
- package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
- package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
- package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
- package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
- package/src/__tests__/ssr-reprojection-probe.ts +90 -0
- package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
- package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
- package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
- package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
- package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
- package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
- package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
- package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
- package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
- package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
- package/src/__tests__/standard-output.browser.test.ts +15 -0
- package/src/__tests__/standard-output.dawn.test.ts +15 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
- package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
- package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
- package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
- package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
- package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
- package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
- package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
- package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
- package/src/__tests__/taa-stability-feedback.ts +341 -0
- package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
- package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
- package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
- package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
- package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
- package/src/__tests__/visibility-extract.perf.test.ts +20 -8
- package/src/__tests__/visibility-instances.integration.test.ts +19 -7
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
- package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
- package/src/assembly/dynamic-geometry-host.ts +887 -0
- package/src/assembly/dynamic-geometry-runtime.ts +71 -0
- package/src/assembly/factory.ts +4 -0
- package/src/assembly/gpu-driven-owner.ts +29 -0
- package/src/assembly/host-contract.ts +36 -0
- package/src/assembly/material/assembly.ts +38 -0
- package/src/assembly/material-shader-policy.ts +71 -28
- package/src/assembly/recovery/failure-location.ts +62 -0
- package/src/assembly/recovery/renderer-recover.ts +19 -17
- package/src/assembly/renderer-facade.ts +29 -0
- package/src/assembly/renderer-feature-inspection.ts +13 -0
- package/src/assembly/renderer-frame-transaction.ts +63 -0
- package/src/assembly/renderer-helpers.ts +26 -0
- package/src/assembly/renderer-inspection.ts +6 -0
- package/src/assembly/shader-prewarm-policy.ts +32 -0
- package/src/assembly/skin-palette-owner.ts +21 -0
- package/src/assembly/ssr-shader-prewarm.ts +41 -0
- package/src/assembly/webgpu-device-loss.ts +75 -0
- package/src/assembly/webgpu-pbr-ready.ts +232 -0
- package/src/assembly/webgpu-ready-mipmap.ts +26 -0
- package/src/assembly/webgpu-ready.ts +142 -121
- package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
- package/src/assembly/webgpu-renderer-contract.ts +69 -0
- package/src/assembly/webgpu-renderer-guards.ts +32 -0
- package/src/assembly/webgpu-renderer-observation.ts +43 -0
- package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
- package/src/assembly/webgpu-renderer.ts +675 -614
- package/src/assembly/webgpu-vertex-layouts.ts +233 -87
- package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
- package/src/components/camera.ts +287 -7
- package/src/components/index.ts +1 -0
- package/src/components/instances.ts +45 -18
- package/src/components/scene-instance.ts +2 -1
- package/src/components/screen-space-reflection.ts +21 -0
- package/src/components/sprite-animation.ts +3 -3
- package/src/components/sprite-instances.ts +1 -1
- package/src/device/gpu-residency.ts +96 -93
- package/src/device/mesh-residency-lifetime.ts +73 -0
- package/src/dynamic-geometry.ts +736 -0
- package/src/environment/background.ts +263 -0
- package/src/errors/gpu-driven.ts +39 -5
- package/src/errors/render.ts +27 -3
- package/src/extract/camera.ts +26 -1
- package/src/extract/gpu-driven.ts +185 -10
- package/src/features/host.ts +237 -19
- package/src/features/noise-texture.ts +91 -0
- package/src/features/plan.ts +553 -24
- package/src/features/prepared-gpu-work.ts +631 -49
- package/src/features/prepared-graphics-store.ts +31 -0
- package/src/features/prepared-graphics.ts +5 -0
- package/src/features/render-graph-compute.ts +41 -8
- package/src/features/render-graph-contribution.ts +141 -3
- package/src/features/render-graph-raster.ts +143 -30
- package/src/features/types.ts +11 -0
- package/src/fullscreen-post-process-pass.ts +3 -0
- package/src/gpu-driven/batch-topology.ts +424 -102
- package/src/gpu-driven/pbr-program.ts +31 -0
- package/src/gpu-driven/prepared-draw.ts +29 -4
- package/src/gpu-driven/production-raster.ts +2267 -694
- package/src/gpu-driven/shadow-views.ts +539 -0
- package/src/gpu-driven/view-gpu.ts +188 -41
- package/src/gpu-scene-schema.ts +52 -11
- package/src/gpu-scene.ts +186 -99
- package/src/gpu-texture-usage.ts +1 -0
- package/src/hdrp-buffers.ts +72 -36
- package/src/ibl/frame-resources.ts +38 -0
- package/src/ibl/skylight-bind-group.ts +5 -4
- package/src/index.ts +125 -1
- package/src/inspection-types.ts +191 -0
- package/src/instances-derived-bounds.ts +258 -34
- package/src/instances.ts +459 -150
- package/src/internal.ts +22 -0
- package/src/lifecycle.ts +2 -0
- package/src/material-row.ts +145 -0
- package/src/materials.ts +44 -8
- package/src/pbr-pipeline.ts +159 -17
- package/src/pipeline/standard-forward-lane.ts +52 -24
- package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
- package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
- package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
- package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
- package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
- package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
- package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
- package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
- package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
- package/src/pipeline/standard-output/color-transform.ts +124 -0
- package/src/pipeline/standard-output/graph.ts +139 -0
- package/src/pipeline/standard-output/lut-admission.ts +121 -0
- package/src/pipeline/standard-output/lut-gpu.ts +399 -0
- package/src/pipeline/standard-output/lut-state.ts +176 -0
- package/src/pipeline/standard-output/resources.ts +121 -0
- package/src/pipeline/standard-output/types.ts +58 -0
- package/src/pipeline/standard-pipeline.ts +192 -59
- package/src/pipeline/standard-post.ts +186 -27
- package/src/pipeline-builder.ts +16 -6
- package/src/pipeline-spec-types.ts +4 -0
- package/src/pipeline-spec.ts +37 -29
- package/src/points-lines/standard-owner.ts +6 -14
- package/src/prepare/prepared-graphics-resolver.ts +9 -2
- package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
- package/src/record/dynamic-geometry-consumption.ts +59 -0
- package/src/record/frame-lighting.ts +12 -11
- package/src/record/frame-snapshot.ts +133 -23
- package/src/record/frame-targets.ts +9 -0
- package/src/record/frame.ts +727 -133
- package/src/record/main-pass-geometry.ts +162 -98
- package/src/record/main-pass-material.ts +115 -26
- package/src/record/main-pass-sprite-draws.ts +18 -107
- package/src/record/main-pass.ts +78 -8
- package/src/record/material-uniforms.ts +1 -1
- package/src/record/mesh-ssbo.ts +59 -15
- package/src/record/prepared-material-bindings.ts +234 -0
- package/src/record/recovery-pipeline.ts +59 -136
- package/src/record/render-context.ts +92 -22
- package/src/record/shadow-pass.ts +1231 -209
- package/src/record/skybox-post-pass.ts +14 -3
- package/src/record/typed-frame-graph.ts +1006 -53
- package/src/record/view-ubo.ts +11 -0
- package/src/recovery/render-system-candidate.ts +90 -66
- package/src/recovery/render-system-roots.ts +113 -0
- package/src/recovery/types.ts +77 -0
- package/src/reflection/record-owner.ts +26 -54
- package/src/render-contract.ts +219 -25
- package/src/render-graph-primitives.ts +6 -2
- package/src/render-pipeline.ts +70 -1
- package/src/render-system-extract-tail.ts +409 -160
- package/src/render-system-extract.ts +376 -102
- package/src/render-system-projections.ts +241 -0
- package/src/render-system.ts +692 -791
- package/src/scene/render-scene-types.ts +13 -4
- package/src/scene/render-scene.ts +569 -355
- package/src/scene/shadow-visibility.ts +21 -0
- package/src/ssr/admission.ts +380 -14
- package/src/ssr/compose.ts +144 -0
- package/src/ssr/errors.ts +48 -0
- package/src/ssr/graph.ts +777 -0
- package/src/ssr/history.ts +436 -0
- package/src/ssr/hiz.ts +124 -0
- package/src/ssr/inspection.ts +124 -0
- package/src/ssr/resources.ts +202 -0
- package/src/ssr/temporal.ts +164 -0
- package/src/systems/skin-palette-allocator.ts +495 -148
- package/src/systems/skin-palette-types.ts +17 -0
- package/src/systems/transparent-dispatch.ts +90 -0
- package/src/targets/material-source.ts +34 -0
- package/src/temporal/coverage.ts +1 -1
- package/src/temporal/frame-coordinator.ts +23 -2
- package/src/temporal/gpu.ts +74 -12
- package/src/temporal/history.ts +3 -1
- package/src/temporal/inspection.ts +2 -1
- package/src/temporal/standard-scene-data.ts +3 -1
- package/src/temporal/temporal-view.ts +3 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
- package/src/typed-render-graph-primitives.ts +266 -74
- package/src/typed-shadow-passes.ts +122 -12
- package/src/volume/temporal.ts +10 -0
- package/dist/chunk-ADHHVYLW.mjs.map +0 -1
- package/dist/chunk-FJ6P52EE.mjs.map +0 -1
- package/dist/chunk-GE3SDD72.mjs.map +0 -1
- package/dist/chunk-GF523LHF.mjs.map +0 -1
- package/dist/chunk-HKXTW355.mjs.map +0 -1
- package/dist/chunk-JDWARUOI.mjs.map +0 -1
- package/dist/chunk-KM2NOX2I.mjs.map +0 -1
- package/dist/chunk-TMIASV2N.mjs.map +0 -1
- package/dist/chunk-XDP6XE5V.mjs.map +0 -1
- package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
- package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
import type { EntityHandle, World } from '@forgeax/engine-ecs';
|
|
2
|
+
import { FixedTime } from '@forgeax/engine-ecs';
|
|
3
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
4
|
+
import { Transform } from '@forgeax/engine-scene';
|
|
5
|
+
import { err, type Handle, type Result } from '@forgeax/engine-types';
|
|
6
|
+
import { MeshFilter } from '../components/mesh-filter';
|
|
7
|
+
import { MeshRenderer } from '../components/mesh-renderer';
|
|
8
|
+
import type { GpuResidencyCache } from '../device/gpu-residency';
|
|
9
|
+
import type { MeshResidencyLease } from '../device/mesh-residency-lifetime';
|
|
10
|
+
import {
|
|
11
|
+
createDynamicGeometryLifecycle,
|
|
12
|
+
type DynamicGeometryCandidate,
|
|
13
|
+
DynamicGeometryError,
|
|
14
|
+
type DynamicGeometryLifecycle,
|
|
15
|
+
type DynamicGeometryOrdering,
|
|
16
|
+
type DynamicGeometryPrepareInput,
|
|
17
|
+
type DynamicGeometryReceipt,
|
|
18
|
+
sameCandidateCredential,
|
|
19
|
+
} from '../dynamic-geometry';
|
|
20
|
+
|
|
21
|
+
export interface DynamicGeometryHostImplementation {
|
|
22
|
+
prepareDynamicGeometry(
|
|
23
|
+
input: DynamicGeometryPrepareInput,
|
|
24
|
+
): Result<DynamicGeometryCandidate, DynamicGeometryError>;
|
|
25
|
+
acceptDynamicGeometry(
|
|
26
|
+
candidate: DynamicGeometryCandidate,
|
|
27
|
+
ordering: DynamicGeometryOrdering,
|
|
28
|
+
): Result<DynamicGeometryCandidate, DynamicGeometryError>;
|
|
29
|
+
dynamicGeometryReceipt(candidate: DynamicGeometryCandidate): DynamicGeometryReceipt | undefined;
|
|
30
|
+
cancelDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
|
|
31
|
+
retireDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
|
|
32
|
+
/** @internal Detach cleanup keeps candidate GPU residency receipt-safe. */
|
|
33
|
+
invalidateDynamicGeometryWorld(world: World): void;
|
|
34
|
+
/** Publish only candidates consumed by the attached ECS render snapshot. */
|
|
35
|
+
publishDynamicGeometry(
|
|
36
|
+
frame: {
|
|
37
|
+
readonly frameId: number;
|
|
38
|
+
readonly deviceGeneration: number;
|
|
39
|
+
readonly completed?: Promise<unknown>;
|
|
40
|
+
},
|
|
41
|
+
worlds?: readonly object[],
|
|
42
|
+
fixedStep?: number,
|
|
43
|
+
): readonly DynamicGeometryReceipt[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface PhysicsPublicationLike {
|
|
47
|
+
readonly revision: number;
|
|
48
|
+
readonly fixedStep: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface HostCandidateRecord {
|
|
52
|
+
candidate: DynamicGeometryCandidate;
|
|
53
|
+
readonly world: World;
|
|
54
|
+
readonly entity: number;
|
|
55
|
+
readonly previousMeshHandle: Handle<'MeshAsset', 'shared'>;
|
|
56
|
+
readonly meshHandle: Handle<'MeshAsset', 'shared'>;
|
|
57
|
+
readonly residency: MeshResidencyLease | undefined;
|
|
58
|
+
readonly generation: number;
|
|
59
|
+
/** Renderer lease retained while the candidate is staged but not ECS-owned. */
|
|
60
|
+
meshLeaseHeld: boolean;
|
|
61
|
+
/** Previous ECS MeshFilter lease retained until the candidate lifecycle ends. */
|
|
62
|
+
previousMeshLeaseHeld: boolean;
|
|
63
|
+
accepted: boolean;
|
|
64
|
+
published: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function fixedStepOf(world: World): number | undefined {
|
|
68
|
+
try {
|
|
69
|
+
return world.getResource(FixedTime).tick;
|
|
70
|
+
} catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function hasRenderableBinding(world: World, entity: number | undefined): boolean {
|
|
76
|
+
if (entity === undefined || !Number.isInteger(entity)) return false;
|
|
77
|
+
const transform = world.get(entity as EntityHandle, Transform);
|
|
78
|
+
const mesh = world.get(entity as EntityHandle, MeshFilter);
|
|
79
|
+
const material = world.get(entity as EntityHandle, MeshRenderer);
|
|
80
|
+
if (!transform.ok || !mesh.ok || !material.ok) return false;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function meshHandleOf(
|
|
85
|
+
world: World,
|
|
86
|
+
entity: number | undefined,
|
|
87
|
+
): Handle<'MeshAsset', 'shared'> | undefined {
|
|
88
|
+
if (entity === undefined || !Number.isInteger(entity)) return undefined;
|
|
89
|
+
const mesh = world.get(entity as EntityHandle, MeshFilter);
|
|
90
|
+
if (!mesh.ok) return undefined;
|
|
91
|
+
return (mesh.value as { readonly assetHandle?: Handle<'MeshAsset', 'shared'> }).assetHandle;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hasMaterialIdentity(
|
|
95
|
+
world: World,
|
|
96
|
+
entity: number | undefined,
|
|
97
|
+
mesh: DynamicGeometryPrepareInput['mesh'],
|
|
98
|
+
identity: string | undefined,
|
|
99
|
+
): boolean {
|
|
100
|
+
if (identity === undefined) return true;
|
|
101
|
+
const normalized = identity.trim();
|
|
102
|
+
if (normalized.length === 0 || entity === undefined) return false;
|
|
103
|
+
const material = world.get(entity as EntityHandle, MeshRenderer);
|
|
104
|
+
if (!material.ok) return false;
|
|
105
|
+
const values = (material.value as { readonly materials?: readonly unknown[] }).materials ?? [];
|
|
106
|
+
if (
|
|
107
|
+
mesh.materialSlots.some((slot) => slot.sourceKey === normalized || slot.slotName === normalized)
|
|
108
|
+
)
|
|
109
|
+
return true;
|
|
110
|
+
if (
|
|
111
|
+
mesh.materialSlots.some(
|
|
112
|
+
(slot) =>
|
|
113
|
+
slot.defaultMaterial !== undefined &&
|
|
114
|
+
AssetGuid.format(slot.defaultMaterial).toLowerCase() === normalized.toLowerCase(),
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
return true;
|
|
118
|
+
return values.some((value) => {
|
|
119
|
+
if (String(value) === normalized) return true;
|
|
120
|
+
try {
|
|
121
|
+
const resolved = world.sharedRefs.resolve(value as never);
|
|
122
|
+
if (!resolved.ok || typeof resolved.value !== 'object' || resolved.value === null)
|
|
123
|
+
return false;
|
|
124
|
+
const payload = resolved.value as { readonly sourceKey?: unknown; readonly guid?: unknown };
|
|
125
|
+
return payload.sourceKey === normalized || payload.guid === normalized;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hasMeshBinding(world: World, handle: number): boolean {
|
|
133
|
+
try {
|
|
134
|
+
const query = world.query({ read: [MeshFilter] }).unwrap();
|
|
135
|
+
for (const row of query) {
|
|
136
|
+
if (Number(row.get(MeshFilter).assetHandle) === handle) return true;
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// An already-detached World cannot own a live binding, so eviction is
|
|
140
|
+
// safe. Normal attached Worlds take the query path above.
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function ownsMeshPayload(
|
|
146
|
+
world: World,
|
|
147
|
+
meshHandle: DynamicGeometryPrepareInput['meshHandle'],
|
|
148
|
+
mesh: DynamicGeometryPrepareInput['mesh'],
|
|
149
|
+
): boolean {
|
|
150
|
+
if (meshHandle === undefined) return false;
|
|
151
|
+
try {
|
|
152
|
+
const resolved = world.sharedRefs.resolve(meshHandle);
|
|
153
|
+
return resolved.ok && resolved.value === mesh;
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function physicsPublicationOf(
|
|
160
|
+
world: World,
|
|
161
|
+
entity: number,
|
|
162
|
+
admitting = false,
|
|
163
|
+
): PhysicsPublicationLike | undefined {
|
|
164
|
+
try {
|
|
165
|
+
const physics = world.getResource<{
|
|
166
|
+
readonly getDerivedPublication?: (body: number) => PhysicsPublicationLike | undefined;
|
|
167
|
+
readonly getDerivedAdmission?: (body: number) => PhysicsPublicationLike | undefined;
|
|
168
|
+
}>('PhysicsWorld');
|
|
169
|
+
return admitting
|
|
170
|
+
? physics.getDerivedAdmission?.(entity)
|
|
171
|
+
: physics.getDerivedPublication?.(entity);
|
|
172
|
+
} catch {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function createDynamicGeometryHost(options: {
|
|
178
|
+
readonly lifecycle: DynamicGeometryLifecycle;
|
|
179
|
+
readonly attachedWorlds: ReadonlySet<World>;
|
|
180
|
+
/** Resolve the store owned by the current Renderer device generation. */
|
|
181
|
+
readonly getGpuStore: () => GpuResidencyCache;
|
|
182
|
+
readonly currentGeneration: () => number;
|
|
183
|
+
/** Renderer record-stage proof for the most recently submitted frame. */
|
|
184
|
+
readonly isConsumedByRenderFrame: (candidate: DynamicGeometryCandidate) => boolean;
|
|
185
|
+
/** Existing temporal owner discards the old surface history after an ECS swap. */
|
|
186
|
+
readonly onTopologyChanged?: () => void;
|
|
187
|
+
}): DynamicGeometryHostImplementation {
|
|
188
|
+
const { lifecycle, attachedWorlds, getGpuStore, currentGeneration, isConsumedByRenderFrame } =
|
|
189
|
+
options;
|
|
190
|
+
const hostCandidates = new Map<string, HostCandidateRecord>();
|
|
191
|
+
let observedGeneration = -1;
|
|
192
|
+
const releaseMeshLease = (record: HostCandidateRecord): DynamicGeometryError | undefined => {
|
|
193
|
+
if (!record.meshLeaseHeld) return undefined;
|
|
194
|
+
const released = record.world.sharedRefs.release(record.meshHandle);
|
|
195
|
+
if (!released.ok) {
|
|
196
|
+
return new DynamicGeometryError(
|
|
197
|
+
'dynamic-geometry-invalid',
|
|
198
|
+
'the Renderer candidate lease remains a live World shared reference',
|
|
199
|
+
'retain the MeshAsset handle until candidate cancellation or ECS acceptance completes',
|
|
200
|
+
{ candidateId: record.candidate.candidateId, actual: released.error },
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
record.meshLeaseHeld = false;
|
|
204
|
+
return undefined;
|
|
205
|
+
};
|
|
206
|
+
const releasePreviousMeshLease = (
|
|
207
|
+
record: HostCandidateRecord,
|
|
208
|
+
): DynamicGeometryError | undefined => {
|
|
209
|
+
if (!record.previousMeshLeaseHeld) return undefined;
|
|
210
|
+
const released = record.world.sharedRefs.release(record.previousMeshHandle);
|
|
211
|
+
if (!released.ok) {
|
|
212
|
+
return new DynamicGeometryError(
|
|
213
|
+
'dynamic-geometry-invalid',
|
|
214
|
+
'the previous ECS MeshFilter binding remains a live World shared reference',
|
|
215
|
+
'retain the previous MeshAsset until candidate cancellation, retirement, or invalidation completes',
|
|
216
|
+
{ candidateId: record.candidate.candidateId, actual: released.error },
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
record.previousMeshLeaseHeld = false;
|
|
220
|
+
return undefined;
|
|
221
|
+
};
|
|
222
|
+
const releaseMeshLeases = (record: HostCandidateRecord): DynamicGeometryError | undefined => {
|
|
223
|
+
const candidateError = releaseMeshLease(record);
|
|
224
|
+
const previousError = releasePreviousMeshLease(record);
|
|
225
|
+
return candidateError ?? previousError;
|
|
226
|
+
};
|
|
227
|
+
const synchronizeGeneration = (): number => {
|
|
228
|
+
const generation = currentGeneration();
|
|
229
|
+
lifecycle.invalidateGeneration(generation);
|
|
230
|
+
if (observedGeneration !== generation) {
|
|
231
|
+
for (const [candidateId, record] of hostCandidates) {
|
|
232
|
+
if (record.generation !== generation) {
|
|
233
|
+
hostCandidates.delete(candidateId);
|
|
234
|
+
// The old DeviceScope owns the GPU destruction. The World shared
|
|
235
|
+
// reference is independent and must be released here even when a
|
|
236
|
+
// generation changes before the next explicit cancellation.
|
|
237
|
+
scheduleCandidateCleanup(record, true);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
observedGeneration = generation;
|
|
241
|
+
}
|
|
242
|
+
return generation;
|
|
243
|
+
};
|
|
244
|
+
const candidateRecord = (
|
|
245
|
+
candidate: DynamicGeometryCandidate,
|
|
246
|
+
): HostCandidateRecord | undefined => {
|
|
247
|
+
const record = hostCandidates.get(candidate.candidateId);
|
|
248
|
+
if (record === undefined || !sameCandidateCredential(candidate, record.candidate))
|
|
249
|
+
return undefined;
|
|
250
|
+
return record;
|
|
251
|
+
};
|
|
252
|
+
const cleanupCandidateResidency = (
|
|
253
|
+
record: HostCandidateRecord,
|
|
254
|
+
force = false,
|
|
255
|
+
): Promise<void> | undefined =>
|
|
256
|
+
record.residency?.release(force || !hasMeshBinding(record.world, record.meshHandle));
|
|
257
|
+
const scheduleCandidateCleanup = (
|
|
258
|
+
record: HostCandidateRecord,
|
|
259
|
+
force = false,
|
|
260
|
+
): Promise<void> | undefined => {
|
|
261
|
+
hostCandidates.delete(record.candidate.candidateId);
|
|
262
|
+
const completion = cleanupCandidateResidency(record, force);
|
|
263
|
+
const finish = (): void => {
|
|
264
|
+
releaseMeshLeases(record);
|
|
265
|
+
lifecycle.finalizeRetirement(record.candidate);
|
|
266
|
+
if (hostCandidates.get(record.candidate.candidateId) === record)
|
|
267
|
+
hostCandidates.delete(record.candidate.candidateId);
|
|
268
|
+
};
|
|
269
|
+
if (completion === undefined) finish();
|
|
270
|
+
else void completion.then(finish, finish);
|
|
271
|
+
return completion;
|
|
272
|
+
};
|
|
273
|
+
return {
|
|
274
|
+
prepareDynamicGeometry(input) {
|
|
275
|
+
const generation = synchronizeGeneration();
|
|
276
|
+
const world = input.world as World;
|
|
277
|
+
if (!attachedWorlds.has(world))
|
|
278
|
+
return err(
|
|
279
|
+
new DynamicGeometryError(
|
|
280
|
+
'dynamic-geometry-world-not-attached',
|
|
281
|
+
'the candidate World is attached to this Renderer',
|
|
282
|
+
'call renderer.attach(world) before preparing dynamic geometry',
|
|
283
|
+
),
|
|
284
|
+
);
|
|
285
|
+
if (input.entity === undefined || !hasRenderableBinding(world, input.entity)) {
|
|
286
|
+
return err(
|
|
287
|
+
new DynamicGeometryError(
|
|
288
|
+
'dynamic-geometry-invalid',
|
|
289
|
+
'the candidate entity and MeshAsset payload belong to the attached World',
|
|
290
|
+
'attach MeshFilter/MeshRenderer and pass the payload resolved by its World shared handle',
|
|
291
|
+
{ actual: { entity: input.entity, meshHandle: input.meshHandle } },
|
|
292
|
+
),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
if (input.meshHandle === undefined)
|
|
296
|
+
return err(
|
|
297
|
+
new DynamicGeometryError(
|
|
298
|
+
'dynamic-geometry-gpu-not-ready',
|
|
299
|
+
'the standard MeshAsset shared handle is present before Renderer admission',
|
|
300
|
+
'allocate the MeshAsset through world.allocSharedRef and retry after asset projection',
|
|
301
|
+
{ actual: { entity: input.entity, meshHandle: input.meshHandle } },
|
|
302
|
+
),
|
|
303
|
+
);
|
|
304
|
+
const meshHandle = input.meshHandle;
|
|
305
|
+
if (
|
|
306
|
+
!ownsMeshPayload(world, meshHandle, input.mesh) ||
|
|
307
|
+
!hasMaterialIdentity(world, input.entity, input.mesh, input.materialIdentity)
|
|
308
|
+
) {
|
|
309
|
+
return err(
|
|
310
|
+
new DynamicGeometryError(
|
|
311
|
+
'dynamic-geometry-invalid',
|
|
312
|
+
'the candidate MeshAsset and material identity belong to the attached World',
|
|
313
|
+
'pass the payload resolved by its World shared handle and its live material slot identity',
|
|
314
|
+
{ actual: { entity: input.entity, meshHandle } },
|
|
315
|
+
),
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
const fixedStep = fixedStepOf(world);
|
|
319
|
+
if (fixedStep === undefined) {
|
|
320
|
+
return err(
|
|
321
|
+
new DynamicGeometryError(
|
|
322
|
+
'dynamic-geometry-ordering-required',
|
|
323
|
+
'the attached World exposes its ECS FixedTime resource',
|
|
324
|
+
'prepare geometry from an initialized World fixed-step schedule',
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
const previousMeshHandle = meshHandleOf(world, input.entity);
|
|
329
|
+
if (previousMeshHandle === undefined) {
|
|
330
|
+
return err(
|
|
331
|
+
new DynamicGeometryError(
|
|
332
|
+
'dynamic-geometry-invalid',
|
|
333
|
+
'the prepared entity retains a live MeshFilter binding',
|
|
334
|
+
'keep the ECS render entity alive while staging geometry',
|
|
335
|
+
{ actual: { entity: input.entity } },
|
|
336
|
+
),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const previousRetained = world.sharedRefs.retain(previousMeshHandle);
|
|
340
|
+
if (!previousRetained.ok)
|
|
341
|
+
return err(
|
|
342
|
+
new DynamicGeometryError(
|
|
343
|
+
'dynamic-geometry-invalid',
|
|
344
|
+
'the previous ECS MeshFilter binding remains live for Renderer preparation',
|
|
345
|
+
'retain the current MeshAsset handle before staging replacement geometry',
|
|
346
|
+
{ actual: previousRetained.error },
|
|
347
|
+
),
|
|
348
|
+
);
|
|
349
|
+
const retained = world.sharedRefs.retain(meshHandle);
|
|
350
|
+
if (!retained.ok) {
|
|
351
|
+
const releasedPrevious = world.sharedRefs.release(previousMeshHandle);
|
|
352
|
+
return releasedPrevious.ok
|
|
353
|
+
? err(
|
|
354
|
+
new DynamicGeometryError(
|
|
355
|
+
'dynamic-geometry-invalid',
|
|
356
|
+
'the candidate MeshAsset shared reference remains live for Renderer preparation',
|
|
357
|
+
'retain the World-owned MeshAsset handle before staging a candidate',
|
|
358
|
+
{ actual: retained.error },
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
: err(
|
|
362
|
+
new DynamicGeometryError(
|
|
363
|
+
'dynamic-geometry-invalid',
|
|
364
|
+
'candidate preparation leaves both MeshAsset leases recoverable',
|
|
365
|
+
'repair the World shared references before retrying geometry preparation',
|
|
366
|
+
{ actual: { candidate: retained.error, previous: releasedPrevious.error } },
|
|
367
|
+
),
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const releaseRetained = (): DynamicGeometryError | undefined => {
|
|
371
|
+
const releasedCandidate = world.sharedRefs.release(meshHandle);
|
|
372
|
+
const releasedPrevious = world.sharedRefs.release(previousMeshHandle);
|
|
373
|
+
if (!releasedCandidate.ok || !releasedPrevious.ok)
|
|
374
|
+
return new DynamicGeometryError(
|
|
375
|
+
'dynamic-geometry-invalid',
|
|
376
|
+
'failed candidate preparation releases both World shared-reference leases',
|
|
377
|
+
'repair the World shared references before retrying geometry preparation',
|
|
378
|
+
{
|
|
379
|
+
actual: {
|
|
380
|
+
candidate: releasedCandidate.ok ? undefined : releasedCandidate.error,
|
|
381
|
+
previous: releasedPrevious.ok ? undefined : releasedPrevious.error,
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
);
|
|
385
|
+
return undefined;
|
|
386
|
+
};
|
|
387
|
+
const prepared = lifecycle.prepare({ ...input, fixedStep }, generation);
|
|
388
|
+
if (!prepared.ok) {
|
|
389
|
+
const released = releaseRetained();
|
|
390
|
+
return released === undefined ? prepared : err(released);
|
|
391
|
+
}
|
|
392
|
+
const residencyStore = getGpuStore();
|
|
393
|
+
const resident = residencyStore.ensureResident(
|
|
394
|
+
meshHandle,
|
|
395
|
+
prepared.value.mesh,
|
|
396
|
+
input.world as World,
|
|
397
|
+
);
|
|
398
|
+
if (!resident.ok) {
|
|
399
|
+
lifecycle.cancel(prepared.value);
|
|
400
|
+
cleanupCandidateResidency({
|
|
401
|
+
candidate: prepared.value,
|
|
402
|
+
world,
|
|
403
|
+
entity: input.entity,
|
|
404
|
+
previousMeshHandle,
|
|
405
|
+
meshHandle,
|
|
406
|
+
residency: undefined,
|
|
407
|
+
generation,
|
|
408
|
+
accepted: false,
|
|
409
|
+
published: false,
|
|
410
|
+
meshLeaseHeld: true,
|
|
411
|
+
previousMeshLeaseHeld: true,
|
|
412
|
+
});
|
|
413
|
+
const released = releaseMeshLeases({
|
|
414
|
+
candidate: prepared.value,
|
|
415
|
+
world,
|
|
416
|
+
entity: input.entity,
|
|
417
|
+
previousMeshHandle,
|
|
418
|
+
meshHandle,
|
|
419
|
+
residency: undefined,
|
|
420
|
+
generation,
|
|
421
|
+
accepted: false,
|
|
422
|
+
published: false,
|
|
423
|
+
meshLeaseHeld: true,
|
|
424
|
+
previousMeshLeaseHeld: true,
|
|
425
|
+
});
|
|
426
|
+
if (released !== undefined) return err(released);
|
|
427
|
+
return err(
|
|
428
|
+
new DynamicGeometryError(
|
|
429
|
+
'dynamic-geometry-gpu-failed',
|
|
430
|
+
'the standard MeshAsset has GPU residency for the active device generation',
|
|
431
|
+
'wait for Renderer.initialization or repair the RHI resource failure before retrying',
|
|
432
|
+
{ candidateId: prepared.value.candidateId, actual: resident.error },
|
|
433
|
+
),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
const residency = residencyStore.retainMeshResidency(meshHandle, world);
|
|
437
|
+
if (residency === undefined) {
|
|
438
|
+
lifecycle.cancel(prepared.value);
|
|
439
|
+
residencyStore.invalidateMesh(meshHandle, world);
|
|
440
|
+
const released = releaseRetained();
|
|
441
|
+
return released === undefined
|
|
442
|
+
? err(
|
|
443
|
+
new DynamicGeometryError(
|
|
444
|
+
'dynamic-geometry-gpu-failed',
|
|
445
|
+
'the standard MeshAsset retains a shared GPU residency owner',
|
|
446
|
+
'repair the active GPU residency before retrying geometry preparation',
|
|
447
|
+
{ candidateId: prepared.value.candidateId },
|
|
448
|
+
),
|
|
449
|
+
)
|
|
450
|
+
: err(released);
|
|
451
|
+
}
|
|
452
|
+
hostCandidates.set(prepared.value.candidateId, {
|
|
453
|
+
candidate: prepared.value,
|
|
454
|
+
world,
|
|
455
|
+
entity: input.entity as number,
|
|
456
|
+
previousMeshHandle,
|
|
457
|
+
meshHandle,
|
|
458
|
+
residency,
|
|
459
|
+
generation,
|
|
460
|
+
meshLeaseHeld: true,
|
|
461
|
+
previousMeshLeaseHeld: true,
|
|
462
|
+
accepted: false,
|
|
463
|
+
published: false,
|
|
464
|
+
});
|
|
465
|
+
return prepared;
|
|
466
|
+
},
|
|
467
|
+
acceptDynamicGeometry(candidate, ordering) {
|
|
468
|
+
synchronizeGeneration();
|
|
469
|
+
const world = candidate.world as World;
|
|
470
|
+
const hostRecordById = hostCandidates.get(candidate.candidateId);
|
|
471
|
+
const hostRecord = candidateRecord(candidate);
|
|
472
|
+
if (hostRecord === undefined && hostRecordById !== undefined)
|
|
473
|
+
return err(
|
|
474
|
+
new DynamicGeometryError(
|
|
475
|
+
'dynamic-geometry-receipt-mismatch',
|
|
476
|
+
'admission uses the exact host candidate owner and lifecycle credential',
|
|
477
|
+
'discard the altered credential and use the value returned by preparation',
|
|
478
|
+
{ candidateId: candidate.candidateId },
|
|
479
|
+
),
|
|
480
|
+
);
|
|
481
|
+
if (hostRecord !== undefined && candidate.state !== 'prepared')
|
|
482
|
+
return err(
|
|
483
|
+
new DynamicGeometryError(
|
|
484
|
+
'dynamic-geometry-candidate-state',
|
|
485
|
+
'admission consumes a prepared candidate exactly once',
|
|
486
|
+
'retain the accepted or published candidate receipt instead of admitting it again',
|
|
487
|
+
{ candidateId: candidate.candidateId },
|
|
488
|
+
),
|
|
489
|
+
);
|
|
490
|
+
if (
|
|
491
|
+
ordering === undefined ||
|
|
492
|
+
!attachedWorlds.has(world) ||
|
|
493
|
+
ordering.world !== candidate.world
|
|
494
|
+
)
|
|
495
|
+
return err(
|
|
496
|
+
new DynamicGeometryError(
|
|
497
|
+
'dynamic-geometry-world-not-attached',
|
|
498
|
+
'candidate and fixed-step ordering belong to the same attached World',
|
|
499
|
+
'attach the World and submit its PhysicsWorld ordering before accepting geometry',
|
|
500
|
+
{ candidateId: candidate.candidateId },
|
|
501
|
+
),
|
|
502
|
+
);
|
|
503
|
+
if (!candidate.gpuReady)
|
|
504
|
+
return err(
|
|
505
|
+
new DynamicGeometryError(
|
|
506
|
+
'dynamic-geometry-gpu-not-ready',
|
|
507
|
+
'candidate has standard GPU residency before acceptance',
|
|
508
|
+
'prepare with an existing MeshAsset shared handle and repair residency failures',
|
|
509
|
+
{ candidateId: candidate.candidateId },
|
|
510
|
+
),
|
|
511
|
+
);
|
|
512
|
+
if (
|
|
513
|
+
hostRecord === undefined ||
|
|
514
|
+
!hasRenderableBinding(world, candidate.entity) ||
|
|
515
|
+
!hasMaterialIdentity(world, candidate.entity, candidate.mesh, candidate.materialIdentity)
|
|
516
|
+
)
|
|
517
|
+
return err(
|
|
518
|
+
new DynamicGeometryError(
|
|
519
|
+
'dynamic-geometry-invalid',
|
|
520
|
+
'the candidate is bound to a live ECS MeshFilter and MeshRenderer',
|
|
521
|
+
'keep the candidate MeshFilter binding intact until the consuming draw',
|
|
522
|
+
{ candidateId: candidate.candidateId },
|
|
523
|
+
),
|
|
524
|
+
);
|
|
525
|
+
if (meshHandleOf(world, candidate.entity) !== hostRecord.previousMeshHandle)
|
|
526
|
+
return err(
|
|
527
|
+
new DynamicGeometryError(
|
|
528
|
+
'dynamic-geometry-invalid',
|
|
529
|
+
'the old MeshFilter binding remains visible until candidate acceptance',
|
|
530
|
+
'do not replace the live ECS binding while geometry is prepared',
|
|
531
|
+
{ candidateId: candidate.candidateId },
|
|
532
|
+
),
|
|
533
|
+
);
|
|
534
|
+
const currentFixedStep = fixedStepOf(world);
|
|
535
|
+
if (
|
|
536
|
+
currentFixedStep === undefined ||
|
|
537
|
+
candidate.fixedStep === undefined ||
|
|
538
|
+
candidate.fixedStep > currentFixedStep ||
|
|
539
|
+
ordering.fixedStep !== currentFixedStep
|
|
540
|
+
)
|
|
541
|
+
return err(
|
|
542
|
+
new DynamicGeometryError(
|
|
543
|
+
'dynamic-geometry-ordering-required',
|
|
544
|
+
'ordering matches the current World tick, no earlier than preparation',
|
|
545
|
+
'accept a prepared candidate during the current fixed-step admission',
|
|
546
|
+
{
|
|
547
|
+
candidateId: candidate.candidateId,
|
|
548
|
+
actual: {
|
|
549
|
+
candidate: candidate.fixedStep,
|
|
550
|
+
currentFixedStep,
|
|
551
|
+
ordering: ordering.fixedStep,
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
),
|
|
555
|
+
);
|
|
556
|
+
if (candidate.physicsEntity !== undefined) {
|
|
557
|
+
const publication = physicsPublicationOf(world, candidate.physicsEntity, true);
|
|
558
|
+
if (
|
|
559
|
+
publication === undefined ||
|
|
560
|
+
publication.fixedStep !== currentFixedStep ||
|
|
561
|
+
publication.revision !== candidate.revision
|
|
562
|
+
)
|
|
563
|
+
return err(
|
|
564
|
+
new DynamicGeometryError(
|
|
565
|
+
'dynamic-geometry-ordering-required',
|
|
566
|
+
'candidate physicsEntity has the active paired PhysicsWorld admission',
|
|
567
|
+
'accept geometry inside admitDerivedShapeCandidate commitGeometry, before physics step',
|
|
568
|
+
{ candidateId: candidate.candidateId, actual: publication },
|
|
569
|
+
),
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
// One ECS MeshFilter has one visible geometry owner. Once this
|
|
573
|
+
// candidate is accepted, any older prepared/accepted candidate for the
|
|
574
|
+
// same entity can no longer become a receipt-bearing draw. Cancel it
|
|
575
|
+
// after the new ECS swap so its candidate-only residency is no longer
|
|
576
|
+
// protected as the live draw owner. Published candidates stay
|
|
577
|
+
// receipt-bound and are retired independently.
|
|
578
|
+
const superseded = [...hostCandidates.values()].filter(
|
|
579
|
+
(record) =>
|
|
580
|
+
record.candidate.candidateId !== candidate.candidateId &&
|
|
581
|
+
record.world === world &&
|
|
582
|
+
record.entity === candidate.entity &&
|
|
583
|
+
!record.published,
|
|
584
|
+
);
|
|
585
|
+
const accepted = lifecycle.accept(candidate, ordering);
|
|
586
|
+
if (!accepted.ok) return accepted;
|
|
587
|
+
const swapped = world.set(candidate.entity as EntityHandle, MeshFilter, {
|
|
588
|
+
assetHandle: hostRecord.meshHandle,
|
|
589
|
+
});
|
|
590
|
+
if (!swapped.ok) {
|
|
591
|
+
lifecycle.cancel(accepted.value);
|
|
592
|
+
hostCandidates.delete(candidate.candidateId);
|
|
593
|
+
cleanupCandidateResidency(hostRecord);
|
|
594
|
+
const released = releaseMeshLeases(hostRecord);
|
|
595
|
+
if (released !== undefined) return err(released);
|
|
596
|
+
return err(
|
|
597
|
+
new DynamicGeometryError(
|
|
598
|
+
'dynamic-geometry-invalid',
|
|
599
|
+
'the candidate MeshFilter swap commits through the ECS write barrier',
|
|
600
|
+
'restore the live entity and retry from a fresh candidate',
|
|
601
|
+
{ candidateId: candidate.candidateId, actual: swapped.error },
|
|
602
|
+
),
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
hostRecord.candidate = accepted.value;
|
|
606
|
+
hostRecord.accepted = true;
|
|
607
|
+
options.onTopologyChanged?.();
|
|
608
|
+
// Keep the World handle leased through the final GPU submission; its slot
|
|
609
|
+
// must not be reused while a retired frame still references the old mesh.
|
|
610
|
+
// Cleanup happens only after the new ECS MeshFilter is visible. Before
|
|
611
|
+
// the swap, hasMeshBinding intentionally protects the older candidate's
|
|
612
|
+
// GPU copy because it is still the live draw owner.
|
|
613
|
+
for (const record of superseded) {
|
|
614
|
+
const completion = cleanupCandidateResidency(record);
|
|
615
|
+
const cancelled = lifecycle.cancel(record.candidate, completion);
|
|
616
|
+
if (!cancelled.ok && cancelled.error.code !== 'dynamic-geometry-candidate-not-found') {
|
|
617
|
+
const restored = world.set(candidate.entity as EntityHandle, MeshFilter, {
|
|
618
|
+
assetHandle: hostRecord.previousMeshHandle,
|
|
619
|
+
});
|
|
620
|
+
lifecycle.cancel(accepted.value);
|
|
621
|
+
hostCandidates.delete(candidate.candidateId);
|
|
622
|
+
cleanupCandidateResidency(hostRecord);
|
|
623
|
+
releasePreviousMeshLease(hostRecord);
|
|
624
|
+
return err(
|
|
625
|
+
new DynamicGeometryError(
|
|
626
|
+
'dynamic-geometry-invalid',
|
|
627
|
+
'candidate supersession cancels the prior credential atomically',
|
|
628
|
+
'keep the prior ECS binding and candidate lifecycle intact when supersession fails',
|
|
629
|
+
{
|
|
630
|
+
candidateId: candidate.candidateId,
|
|
631
|
+
actual: {
|
|
632
|
+
cancelled: cancelled.error,
|
|
633
|
+
restored: restored.ok ? undefined : restored.error,
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
),
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
hostCandidates.delete(record.candidate.candidateId);
|
|
640
|
+
scheduleCandidateCleanup(record);
|
|
641
|
+
}
|
|
642
|
+
return accepted;
|
|
643
|
+
},
|
|
644
|
+
dynamicGeometryReceipt(candidate) {
|
|
645
|
+
synchronizeGeneration();
|
|
646
|
+
return lifecycle.receipt(candidate);
|
|
647
|
+
},
|
|
648
|
+
cancelDynamicGeometry(candidate) {
|
|
649
|
+
synchronizeGeneration();
|
|
650
|
+
const record = candidateRecord(candidate);
|
|
651
|
+
if (record === undefined) {
|
|
652
|
+
// A candidate id is not an authority. If this host still has that id,
|
|
653
|
+
// reject the forged owner/lifecycle credential before touching ECS or
|
|
654
|
+
// delegating to the lifecycle state machine.
|
|
655
|
+
if (hostCandidates.has(candidate.candidateId))
|
|
656
|
+
return err(
|
|
657
|
+
new DynamicGeometryError(
|
|
658
|
+
'dynamic-geometry-receipt-mismatch',
|
|
659
|
+
'cancellation uses the exact host candidate owner and lifecycle credential',
|
|
660
|
+
'discard the altered credential and use the value returned by preparation',
|
|
661
|
+
{ candidateId: candidate.candidateId },
|
|
662
|
+
),
|
|
663
|
+
);
|
|
664
|
+
return lifecycle.cancel(candidate);
|
|
665
|
+
}
|
|
666
|
+
if (
|
|
667
|
+
(!record.accepted && candidate.state !== 'prepared') ||
|
|
668
|
+
(record.accepted && !record.published && candidate.state !== 'accepted') ||
|
|
669
|
+
(record.published && candidate.state !== 'accepted' && candidate.state !== 'published')
|
|
670
|
+
)
|
|
671
|
+
return err(
|
|
672
|
+
new DynamicGeometryError(
|
|
673
|
+
'dynamic-geometry-receipt-mismatch',
|
|
674
|
+
'cancellation uses the candidate state issued by the host lifecycle',
|
|
675
|
+
'discard the altered credential and use the value returned by preparation or acceptance',
|
|
676
|
+
{ candidateId: candidate.candidateId },
|
|
677
|
+
),
|
|
678
|
+
);
|
|
679
|
+
if (record.published)
|
|
680
|
+
return err(
|
|
681
|
+
new DynamicGeometryError(
|
|
682
|
+
'dynamic-geometry-candidate-state',
|
|
683
|
+
'published geometry remains until its receipt retires',
|
|
684
|
+
'retire the receipt-bound candidate instead of cancelling it',
|
|
685
|
+
{ candidateId: candidate.candidateId },
|
|
686
|
+
),
|
|
687
|
+
);
|
|
688
|
+
if (record.accepted) {
|
|
689
|
+
const currentHandle = meshHandleOf(record.world, record.entity);
|
|
690
|
+
if (currentHandle !== record.meshHandle && currentHandle !== record.previousMeshHandle)
|
|
691
|
+
return err(
|
|
692
|
+
new DynamicGeometryError(
|
|
693
|
+
'dynamic-geometry-invalid',
|
|
694
|
+
'cancel does not overwrite a newer ECS MeshFilter binding',
|
|
695
|
+
'reconcile the live entity before cancelling this candidate',
|
|
696
|
+
{ candidateId: candidate.candidateId },
|
|
697
|
+
),
|
|
698
|
+
);
|
|
699
|
+
if (currentHandle === record.meshHandle) {
|
|
700
|
+
const restored = record.world.set(record.entity as EntityHandle, MeshFilter, {
|
|
701
|
+
assetHandle: record.previousMeshHandle,
|
|
702
|
+
});
|
|
703
|
+
if (!restored.ok)
|
|
704
|
+
return err(
|
|
705
|
+
new DynamicGeometryError(
|
|
706
|
+
'dynamic-geometry-invalid',
|
|
707
|
+
'cancel restores the previous MeshFilter through the ECS write barrier',
|
|
708
|
+
'restore the entity before retrying cancellation',
|
|
709
|
+
{ candidateId: candidate.candidateId, actual: restored.error },
|
|
710
|
+
),
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const completion = cleanupCandidateResidency(record);
|
|
715
|
+
const cancelled = lifecycle.cancel(candidate, completion);
|
|
716
|
+
if (!cancelled.ok) return cancelled;
|
|
717
|
+
hostCandidates.delete(candidate.candidateId);
|
|
718
|
+
scheduleCandidateCleanup(record);
|
|
719
|
+
return cancelled;
|
|
720
|
+
},
|
|
721
|
+
invalidateDynamicGeometryWorld(world) {
|
|
722
|
+
const doomed = [...hostCandidates.values()].filter((record) => record.world === world);
|
|
723
|
+
const completions = doomed
|
|
724
|
+
.map((record) => scheduleCandidateCleanup(record, true))
|
|
725
|
+
.filter((completion): completion is Promise<void> => completion !== undefined);
|
|
726
|
+
lifecycle.invalidateWorld(
|
|
727
|
+
world,
|
|
728
|
+
completions.length === 0 ? undefined : Promise.allSettled(completions),
|
|
729
|
+
);
|
|
730
|
+
},
|
|
731
|
+
retireDynamicGeometry(candidate) {
|
|
732
|
+
synchronizeGeneration();
|
|
733
|
+
const record = candidateRecord(candidate);
|
|
734
|
+
if (record === undefined) {
|
|
735
|
+
if (hostCandidates.has(candidate.candidateId))
|
|
736
|
+
return err(
|
|
737
|
+
new DynamicGeometryError(
|
|
738
|
+
'dynamic-geometry-receipt-mismatch',
|
|
739
|
+
'retirement uses the exact host candidate owner and lifecycle credential',
|
|
740
|
+
'discard the altered credential and use the value returned by acceptance',
|
|
741
|
+
{ candidateId: candidate.candidateId },
|
|
742
|
+
),
|
|
743
|
+
);
|
|
744
|
+
return lifecycle.retire(candidate);
|
|
745
|
+
}
|
|
746
|
+
if (candidate.state !== 'accepted' && candidate.state !== 'published')
|
|
747
|
+
return err(
|
|
748
|
+
new DynamicGeometryError(
|
|
749
|
+
'dynamic-geometry-receipt-mismatch',
|
|
750
|
+
'retirement uses a candidate state issued by the host lifecycle',
|
|
751
|
+
'discard the altered credential and use the value returned by acceptance',
|
|
752
|
+
{ candidateId: candidate.candidateId },
|
|
753
|
+
),
|
|
754
|
+
);
|
|
755
|
+
if (meshHandleOf(record.world, record.entity) === record.meshHandle)
|
|
756
|
+
return err(
|
|
757
|
+
new DynamicGeometryError(
|
|
758
|
+
'dynamic-geometry-invalid',
|
|
759
|
+
'retirement does not evict the MeshFilter that still consumes the candidate',
|
|
760
|
+
'swap the ECS MeshFilter through its write barrier before retiring this geometry',
|
|
761
|
+
{ candidateId: candidate.candidateId },
|
|
762
|
+
),
|
|
763
|
+
);
|
|
764
|
+
const retired = lifecycle.retire(candidate);
|
|
765
|
+
if (!retired.ok) return retired;
|
|
766
|
+
record.candidate = Object.freeze({ ...record.candidate, state: 'retired' as const });
|
|
767
|
+
const previousReleased = releasePreviousMeshLease(record);
|
|
768
|
+
if (previousReleased !== undefined) return err(previousReleased);
|
|
769
|
+
scheduleCandidateCleanup(record);
|
|
770
|
+
return retired;
|
|
771
|
+
},
|
|
772
|
+
publishDynamicGeometry(frame, worlds, fixedStep) {
|
|
773
|
+
synchronizeGeneration();
|
|
774
|
+
const targetWorlds = new Set<World>();
|
|
775
|
+
for (const candidateWorld of worlds ?? attachedWorlds) {
|
|
776
|
+
const world = candidateWorld as World;
|
|
777
|
+
if (attachedWorlds.has(world)) targetWorlds.add(world);
|
|
778
|
+
}
|
|
779
|
+
const receipts: DynamicGeometryReceipt[] = [];
|
|
780
|
+
for (const world of targetWorlds) {
|
|
781
|
+
const worldFixedStep = fixedStepOf(world);
|
|
782
|
+
if (worldFixedStep === undefined) continue;
|
|
783
|
+
// Derive the lifecycle filter input from the attached World's actual
|
|
784
|
+
// FixedTime before publishFrame can reject an accepted candidate. An
|
|
785
|
+
// omitted host tick therefore publishes the latest fixed-step result
|
|
786
|
+
// instead of silently producing zero receipts.
|
|
787
|
+
const publicationFixedStep = fixedStep ?? worldFixedStep;
|
|
788
|
+
receipts.push(
|
|
789
|
+
...lifecycle.publishFrame(frame, [world], publicationFixedStep, (candidate) => {
|
|
790
|
+
const candidateWorld = candidate.world as World;
|
|
791
|
+
if (candidateWorld !== world || !attachedWorlds.has(candidateWorld)) return false;
|
|
792
|
+
const hostRecord = candidateRecord(candidate);
|
|
793
|
+
if (
|
|
794
|
+
hostRecord === undefined ||
|
|
795
|
+
!hostRecord.accepted ||
|
|
796
|
+
!hasRenderableBinding(candidateWorld, candidate.entity) ||
|
|
797
|
+
meshHandleOf(candidateWorld, candidate.entity) !== candidate.meshHandle ||
|
|
798
|
+
!hasMaterialIdentity(
|
|
799
|
+
candidateWorld,
|
|
800
|
+
candidate.entity,
|
|
801
|
+
candidate.mesh,
|
|
802
|
+
candidate.materialIdentity,
|
|
803
|
+
)
|
|
804
|
+
)
|
|
805
|
+
return false;
|
|
806
|
+
if (!isConsumedByRenderFrame(candidate)) return false;
|
|
807
|
+
const actualFixedStep = fixedStep ?? worldFixedStep;
|
|
808
|
+
if (candidate.fixedStep === undefined || actualFixedStep === undefined) return false;
|
|
809
|
+
// RenderFrameInput.fixedStep is an assertion from the host, not a
|
|
810
|
+
// second clock. It may name a later World tick in a multi-fixed-step
|
|
811
|
+
// host frame, but it cannot manufacture a step the attached World
|
|
812
|
+
// has not actually published. An explicit mismatch remains a hard
|
|
813
|
+
// rejection; omission uses the World-derived value above.
|
|
814
|
+
if (worldFixedStep !== actualFixedStep || actualFixedStep < candidate.fixedStep)
|
|
815
|
+
return false;
|
|
816
|
+
if (candidate.physicsEntity !== undefined) {
|
|
817
|
+
const publication = physicsPublicationOf(candidateWorld, candidate.physicsEntity);
|
|
818
|
+
if (
|
|
819
|
+
publication === undefined ||
|
|
820
|
+
publication.fixedStep < candidate.fixedStep ||
|
|
821
|
+
publication.revision !== candidate.revision
|
|
822
|
+
)
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
return true;
|
|
826
|
+
}),
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
for (const receipt of receipts) {
|
|
830
|
+
const record = hostCandidates.get(receipt.candidateId);
|
|
831
|
+
if (record !== undefined) {
|
|
832
|
+
record.published = true;
|
|
833
|
+
if (receipt.frame.completed !== undefined)
|
|
834
|
+
record.residency?.track(receipt.frame.completed);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return receipts;
|
|
838
|
+
},
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Renderer assembly adapter for the host's temporal geometry owner.
|
|
844
|
+
*
|
|
845
|
+
* Keeping the render-system callback at this boundary prevents the concrete
|
|
846
|
+
* WebGPU assembly from growing another owner-specific block while preserving
|
|
847
|
+
* the same lifecycle and residency contracts.
|
|
848
|
+
*/
|
|
849
|
+
export function createRendererDynamicGeometryHost(options: {
|
|
850
|
+
readonly lifecycle: DynamicGeometryLifecycle;
|
|
851
|
+
readonly attachedWorlds: ReadonlySet<World>;
|
|
852
|
+
readonly getGpuStore: () => GpuResidencyCache;
|
|
853
|
+
readonly currentGeneration: () => number;
|
|
854
|
+
readonly renderSystem: {
|
|
855
|
+
readonly invalidateGeometryHistory: () => void;
|
|
856
|
+
readonly isDynamicGeometryConsumed: (
|
|
857
|
+
world: World,
|
|
858
|
+
entity: number,
|
|
859
|
+
meshHandle: DynamicGeometryCandidate['meshHandle'],
|
|
860
|
+
) => boolean;
|
|
861
|
+
};
|
|
862
|
+
}): DynamicGeometryHostImplementation {
|
|
863
|
+
const { renderSystem, ...hostOptions } = options;
|
|
864
|
+
return createDynamicGeometryHost({
|
|
865
|
+
...hostOptions,
|
|
866
|
+
onTopologyChanged: () => renderSystem.invalidateGeometryHistory(),
|
|
867
|
+
isConsumedByRenderFrame: (candidate) =>
|
|
868
|
+
renderSystem.isDynamicGeometryConsumed(
|
|
869
|
+
candidate.world as World,
|
|
870
|
+
candidate.entity ?? -1,
|
|
871
|
+
candidate.meshHandle,
|
|
872
|
+
),
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export function createRendererDynamicGeometryController(
|
|
877
|
+
options: Omit<Parameters<typeof createRendererDynamicGeometryHost>[0], 'lifecycle'>,
|
|
878
|
+
): {
|
|
879
|
+
readonly lifecycle: DynamicGeometryLifecycle;
|
|
880
|
+
readonly host: DynamicGeometryHostImplementation;
|
|
881
|
+
} {
|
|
882
|
+
const lifecycle = createDynamicGeometryLifecycle();
|
|
883
|
+
return {
|
|
884
|
+
lifecycle,
|
|
885
|
+
host: createRendererDynamicGeometryHost({ ...options, lifecycle }),
|
|
886
|
+
};
|
|
887
|
+
}
|