@forgeax/engine-render 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +647 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/authoring-path.unit.test.d.ts +2 -0
- package/dist/__tests__/authoring-path.unit.test.d.ts.map +1 -0
- package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts +2 -0
- package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts.map +1 -0
- package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
- package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
- package/dist/__tests__/cluster-binner.test.d.ts +2 -0
- package/dist/__tests__/cluster-binner.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/construct-renderer-identity.unit.test.d.ts +2 -0
- package/dist/__tests__/construct-renderer-identity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts +2 -0
- package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/direct-light-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/direct-light-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/factory-contract.integration.test.d.ts +2 -0
- package/dist/__tests__/factory-contract.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/frame-observation.contract.test.d.ts +2 -0
- package/dist/__tests__/frame-observation.contract.test.d.ts.map +1 -0
- package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts +2 -0
- package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts.map +1 -0
- package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts +6 -0
- package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-production.integration.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-production.integration.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts +18 -0
- package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view-reference.d.ts +15 -0
- package/dist/__tests__/gpu-driven-view-reference.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view.browser.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-view.browser.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view.dawn.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-view.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-driven-view.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-driven-view.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts +7 -0
- package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene.dawn.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-scene.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-scene.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts.map +1 -0
- package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts +2 -0
- package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts.map +1 -0
- package/dist/__tests__/injection-kind-owner.test-d.d.ts +2 -0
- package/dist/__tests__/injection-kind-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts +2 -0
- package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/light-buffer-layout.unit.test.d.ts +2 -0
- package/dist/__tests__/light-buffer-layout.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-contract-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/material-contract-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cooked-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cooked-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts +2 -0
- package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts +2 -0
- package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-stride-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/material-stride-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-material-bindings.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-material-bindings.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-stride-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-stride-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts +2 -0
- package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts +2 -0
- package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts.map +1 -0
- package/dist/__tests__/morph-feature-plan.unit.test.d.ts +2 -0
- package/dist/__tests__/morph-feature-plan.unit.test.d.ts.map +1 -0
- package/dist/__tests__/morph-visual-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/morph-visual-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts +2 -0
- package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts +2 -0
- package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-store.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts +2 -0
- package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
- package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
- package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
- package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-phase-token.test.d.ts +2 -0
- package/dist/__tests__/profiler-phase-token.test.d.ts.map +1 -0
- package/dist/__tests__/public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/public-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts +2 -0
- package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/render-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-error-detail-ssot.test-d.d.ts +2 -0
- package/dist/__tests__/render-error-detail-ssot.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts +2 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-assembly.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-context.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-context.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-contract.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-docs.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-docs.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts +2 -0
- package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-plan.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-plan.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-plan.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-plan.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-public-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-retry.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-retry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-scheduling.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-scheduling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-visibility-context.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-visibility-context.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-zero-work.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-zero-work.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-compute-raster-gpu.d.ts +7 -0
- package/dist/__tests__/render-graph-compute-raster-gpu.d.ts.map +1 -0
- package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts +2 -0
- package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-hzb-gpu.d.ts +7 -0
- package/dist/__tests__/render-graph-hzb-gpu.d.ts.map +1 -0
- package/dist/__tests__/render-graph-hzb.browser.test.d.ts +2 -0
- package/dist/__tests__/render-graph-hzb.browser.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-hzb.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-graph-hzb.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-primitives.unit.test.d.ts +2 -0
- package/dist/__tests__/render-graph-primitives.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts +12 -0
- package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts.map +1 -0
- package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts +2 -0
- package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts.map +1 -0
- package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-scene-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/render-scene-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +2 -0
- package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts.map +1 -0
- package/dist/__tests__/shader-stage-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/shader-stage-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-config.unit.test.d.ts +2 -0
- package/dist/__tests__/ssao-config.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts +2 -0
- package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts.map +1 -0
- package/dist/__tests__/standard-descriptors.test-d.d.ts +2 -0
- package/dist/__tests__/standard-descriptors.test-d.d.ts.map +1 -0
- package/dist/__tests__/standard-pipeline.dawn.test.d.ts +2 -0
- package/dist/__tests__/standard-pipeline.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/standard-pipeline.integration.test.d.ts +2 -0
- package/dist/__tests__/standard-pipeline.integration.test.d.ts.map +1 -0
- package/dist/__tests__/standard-profile-oracle.unit.test.d.ts +2 -0
- package/dist/__tests__/standard-profile-oracle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/surface-configuration.unit.test.d.ts +2 -0
- package/dist/__tests__/surface-configuration.unit.test.d.ts.map +1 -0
- package/dist/__tests__/texture-usage-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/texture-usage-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts +2 -0
- package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts.map +1 -0
- package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts +2 -0
- package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts.map +1 -0
- package/dist/__tests__/urp-pipeline.unit.test.d.ts +2 -0
- package/dist/__tests__/urp-pipeline.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts +2 -0
- package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts +2 -0
- package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-diagnostics.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-diagnostics.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-extract.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-extract.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-extract.perf.test.d.ts +2 -0
- package/dist/__tests__/visibility-extract.perf.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-hidden-report.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-hidden-report.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-instances.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-instances.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-multi-world.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-multi-world.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/visibility-public-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/visibility-resolver.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-resolver.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vocabulary.unit.test.d.ts +2 -0
- package/dist/__tests__/vocabulary.unit.test.d.ts.map +1 -0
- package/dist/assembly/backend-contract.d.ts +37 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +207 -0
- package/dist/assembly/factory.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +84 -0
- package/dist/assembly/host-contract.d.ts.map +1 -0
- package/dist/assembly/material/assembly.d.ts +21 -0
- package/dist/assembly/material/assembly.d.ts.map +1 -0
- package/dist/assembly/material/pipeline-projection.d.ts +30 -0
- package/dist/assembly/material/pipeline-projection.d.ts.map +1 -0
- package/dist/assets/asset-decoders.d.ts +5 -0
- package/dist/assets/asset-decoders.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts +17 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -0
- package/dist/assets/resolve-render-asset.d.ts +6 -0
- package/dist/assets/resolve-render-asset.d.ts.map +1 -0
- package/dist/authoring.d.ts +12 -0
- package/dist/authoring.d.ts.map +1 -0
- package/dist/authoring.mjs +27 -0
- package/dist/authoring.mjs.map +1 -0
- package/dist/chunk-CQXZC5XI.mjs +182 -0
- package/dist/chunk-CQXZC5XI.mjs.map +1 -0
- package/dist/chunk-HCOGPHKS.mjs +93 -0
- package/dist/chunk-HCOGPHKS.mjs.map +1 -0
- package/dist/chunk-YEJIUW7D.mjs +1152 -0
- package/dist/chunk-YEJIUW7D.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +152 -0
- package/dist/cluster-binner.d.ts.map +1 -0
- package/dist/components/__tests__/points-lines-admission.unit.test.d.ts +2 -0
- package/dist/components/__tests__/points-lines-admission.unit.test.d.ts.map +1 -0
- package/dist/components/__tests__/points-lines-schema.unit.test.d.ts +2 -0
- package/dist/components/__tests__/points-lines-schema.unit.test.d.ts.map +1 -0
- package/dist/components/__tests__/schema-contract.unit.test.d.ts +2 -0
- package/dist/components/__tests__/schema-contract.unit.test.d.ts.map +1 -0
- package/dist/components/__tests__/schema-parity.unit.test.d.ts +2 -0
- package/dist/components/__tests__/schema-parity.unit.test.d.ts.map +1 -0
- package/dist/components/__tests__/visibility-schema.unit.test.d.ts +2 -0
- package/dist/components/__tests__/visibility-schema.unit.test.d.ts.map +1 -0
- package/dist/components/camera.d.ts +311 -0
- package/dist/components/camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +76 -0
- package/dist/components/directional-light.d.ts.map +1 -0
- package/dist/components/glyph-text.d.ts +26 -0
- package/dist/components/glyph-text.d.ts.map +1 -0
- package/dist/components/index.d.ts +24 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/instances.d.ts +41 -0
- package/dist/components/instances.d.ts.map +1 -0
- package/dist/components/layer.d.ts +34 -0
- package/dist/components/layer.d.ts.map +1 -0
- package/dist/components/light-helpers.d.ts +53 -0
- package/dist/components/light-helpers.d.ts.map +1 -0
- package/dist/components/lines.d.ts +5 -0
- package/dist/components/lines.d.ts.map +1 -0
- package/dist/components/mesh-filter.d.ts +24 -0
- package/dist/components/mesh-filter.d.ts.map +1 -0
- package/dist/components/mesh-renderer.d.ts +33 -0
- package/dist/components/mesh-renderer.d.ts.map +1 -0
- package/dist/components/point-light-shadow.d.ts +27 -0
- package/dist/components/point-light-shadow.d.ts.map +1 -0
- package/dist/components/point-light.d.ts +35 -0
- package/dist/components/point-light.d.ts.map +1 -0
- package/dist/components/points.d.ts +15 -0
- package/dist/components/points.d.ts.map +1 -0
- package/dist/components/post-process-params.d.ts +5 -0
- package/dist/components/post-process-params.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +140 -0
- package/dist/components/scene-instance.d.ts.map +1 -0
- package/dist/components/skybox-background.d.ts +56 -0
- package/dist/components/skybox-background.d.ts.map +1 -0
- package/dist/components/skylight.d.ts +55 -0
- package/dist/components/skylight.d.ts.map +1 -0
- package/dist/components/sort-key.d.ts +44 -0
- package/dist/components/sort-key.d.ts.map +1 -0
- package/dist/components/spot-light.d.ts +81 -0
- package/dist/components/spot-light.d.ts.map +1 -0
- package/dist/components/sprite-animation.d.ts +74 -0
- package/dist/components/sprite-animation.d.ts.map +1 -0
- package/dist/components/sprite-instances.d.ts +51 -0
- package/dist/components/sprite-instances.d.ts.map +1 -0
- package/dist/components/sprite-playback-mode.d.ts +42 -0
- package/dist/components/sprite-playback-mode.d.ts.map +1 -0
- package/dist/components/sprite-region-override.d.ts +46 -0
- package/dist/components/sprite-region-override.d.ts.map +1 -0
- package/dist/components/tile-layer.d.ts +106 -0
- package/dist/components/tile-layer.d.ts.map +1 -0
- package/dist/components/tilemap.d.ts +25 -0
- package/dist/components/tilemap.d.ts.map +1 -0
- package/dist/components/visibility.d.ts +22 -0
- package/dist/components/visibility.d.ts.map +1 -0
- package/dist/construct-renderer.d.ts +19 -0
- package/dist/construct-renderer.d.ts.map +1 -0
- package/dist/construct-renderer.mjs +27462 -0
- package/dist/construct-renderer.mjs.map +1 -0
- package/dist/debug-draw-glue.d.ts +5 -0
- package/dist/debug-draw-glue.d.ts.map +1 -0
- package/dist/device/device-scope.d.ts +83 -0
- package/dist/device/device-scope.d.ts.map +1 -0
- package/dist/device/gpu-residency.d.ts +337 -0
- package/dist/device/gpu-residency.d.ts.map +1 -0
- package/dist/device/resource-types.d.ts +8 -0
- package/dist/device/resource-types.d.ts.map +1 -0
- package/dist/engine-metrics.d.ts +23 -0
- package/dist/engine-metrics.d.ts.map +1 -0
- package/dist/errors/ecs-validation.d.ts +3 -0
- package/dist/errors/ecs-validation.d.ts.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/recover.d.ts +105 -0
- package/dist/errors/recover.d.ts.map +1 -0
- package/dist/errors/render.d.ts +705 -0
- package/dist/errors/render.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts +26 -0
- package/dist/extract/visibility.d.ts.map +1 -0
- package/dist/features/fullscreen.d.ts +19 -0
- package/dist/features/fullscreen.d.ts.map +1 -0
- package/dist/features/host.d.ts +106 -0
- package/dist/features/host.d.ts.map +1 -0
- package/dist/features/morph/morph-feature.d.ts +79 -0
- package/dist/features/morph/morph-feature.d.ts.map +1 -0
- package/dist/features/plan.d.ts +182 -0
- package/dist/features/plan.d.ts.map +1 -0
- package/dist/features/prepared-gpu-work.d.ts +117 -0
- package/dist/features/prepared-gpu-work.d.ts.map +1 -0
- package/dist/features/prepared-graphics-store.d.ts +66 -0
- package/dist/features/prepared-graphics-store.d.ts.map +1 -0
- package/dist/features/prepared-graphics.d.ts +215 -0
- package/dist/features/prepared-graphics.d.ts.map +1 -0
- package/dist/features/render-graph-compute.d.ts +13 -0
- package/dist/features/render-graph-compute.d.ts.map +1 -0
- package/dist/features/render-graph-contribution.d.ts +12 -0
- package/dist/features/render-graph-contribution.d.ts.map +1 -0
- package/dist/features/render-graph-raster.d.ts +38 -0
- package/dist/features/render-graph-raster.d.ts.map +1 -0
- package/dist/features/render-graph-resources.d.ts +11 -0
- package/dist/features/render-graph-resources.d.ts.map +1 -0
- package/dist/features/targets.d.ts +32 -0
- package/dist/features/targets.d.ts.map +1 -0
- package/dist/features/types.d.ts +68 -0
- package/dist/features/types.d.ts.map +1 -0
- package/dist/features/vocabulary.d.ts +14 -0
- package/dist/features/vocabulary.d.ts.map +1 -0
- package/dist/fullscreen-post-process-pass.d.ts +157 -0
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -0
- package/dist/glyph-text-layout-system.d.ts +19 -0
- package/dist/glyph-text-layout-system.d.ts.map +1 -0
- package/dist/gpu-driven/batch-topology.d.ts +55 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +51 -0
- package/dist/gpu-driven/production-raster.d.ts.map +1 -0
- package/dist/gpu-driven/view-gpu.d.ts +85 -0
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -0
- package/dist/gpu-resource.d.ts +73 -0
- package/dist/gpu-resource.d.ts.map +1 -0
- package/dist/gpu-scene-schema.d.ts +78 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -0
- package/dist/gpu-scene.d.ts +55 -0
- package/dist/gpu-scene.d.ts.map +1 -0
- package/dist/gpu-stage.d.ts +4 -0
- package/dist/gpu-stage.d.ts.map +1 -0
- package/dist/gpu-texture-usage.d.ts +6 -0
- package/dist/gpu-texture-usage.d.ts.map +1 -0
- package/dist/gpu-usage.d.ts +9 -0
- package/dist/gpu-usage.d.ts.map +1 -0
- package/dist/hdrp-buffers.d.ts +163 -0
- package/dist/hdrp-buffers.d.ts.map +1 -0
- package/dist/ibl/IblPipelineCache.d.ts +168 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -0
- package/dist/ibl/skylight-bind-group.d.ts +130 -0
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +169 -0
- package/dist/index.mjs.map +1 -0
- package/dist/inspection-types.d.ts +72 -0
- package/dist/inspection-types.d.ts.map +1 -0
- package/dist/instance-buffer-cache.d.ts +56 -0
- package/dist/instance-buffer-cache.d.ts.map +1 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.mjs +3 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/lifecycle.d.ts +62 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/light-buffer-layout.d.ts +164 -0
- package/dist/light-buffer-layout.d.ts.map +1 -0
- package/dist/materials.d.ts +47 -0
- package/dist/materials.d.ts.map +1 -0
- package/dist/mesh-material-bindings.d.ts +43 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -0
- package/dist/pbr-pipeline.d.ts +301 -0
- package/dist/pbr-pipeline.d.ts.map +1 -0
- package/dist/pipeline/standard-forward-lane.d.ts +5 -0
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +31 -0
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -0
- package/dist/pipeline/standard-profile.d.ts +40 -0
- package/dist/pipeline/standard-profile.d.ts.map +1 -0
- package/dist/pipeline-builder.d.ts +122 -0
- package/dist/pipeline-builder.d.ts.map +1 -0
- package/dist/pipeline-spec-types.d.ts +24 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -0
- package/dist/pipeline-spec.d.ts +367 -0
- package/dist/pipeline-spec.d.ts.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/points-lines/__tests__/bounds.unit.test.d.ts +2 -0
- package/dist/points-lines/__tests__/bounds.unit.test.d.ts.map +1 -0
- package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts +2 -0
- package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts.map +1 -0
- package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts +2 -0
- package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts.map +1 -0
- package/dist/points-lines/__tests__/inspection.unit.test.d.ts +2 -0
- package/dist/points-lines/__tests__/inspection.unit.test.d.ts.map +1 -0
- package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts +2 -0
- package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts.map +1 -0
- package/dist/points-lines/__tests__/points-lines.bench.d.ts +2 -0
- package/dist/points-lines/__tests__/points-lines.bench.d.ts.map +1 -0
- package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts +2 -0
- package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts.map +1 -0
- package/dist/points-lines/__tests__/snapshot.unit.test.d.ts +2 -0
- package/dist/points-lines/__tests__/snapshot.unit.test.d.ts.map +1 -0
- package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts +2 -0
- package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts.map +1 -0
- package/dist/points-lines/admission.d.ts +36 -0
- package/dist/points-lines/admission.d.ts.map +1 -0
- package/dist/points-lines/bounds.d.ts +8 -0
- package/dist/points-lines/bounds.d.ts.map +1 -0
- package/dist/points-lines/expansion-cache.d.ts +28 -0
- package/dist/points-lines/expansion-cache.d.ts.map +1 -0
- package/dist/points-lines/inspection.d.ts +53 -0
- package/dist/points-lines/inspection.d.ts.map +1 -0
- package/dist/points-lines/prepare.d.ts +67 -0
- package/dist/points-lines/prepare.d.ts.map +1 -0
- package/dist/points-lines/record.d.ts +60 -0
- package/dist/points-lines/record.d.ts.map +1 -0
- package/dist/points-lines/snapshot.d.ts +49 -0
- package/dist/points-lines/snapshot.d.ts.map +1 -0
- package/dist/post-process-errors.d.ts +182 -0
- package/dist/post-process-errors.d.ts.map +1 -0
- package/dist/prepare/prepared-graphics-resolver.d.ts +59 -0
- package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -0
- package/dist/record/__tests__/material-bg-cache.unit.test.d.ts +2 -0
- package/dist/record/__tests__/material-bg-cache.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/material-slot-plan.unit.test.d.ts +2 -0
- package/dist/record/__tests__/material-slot-plan.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts +2 -0
- package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts.map +1 -0
- package/dist/record/__tests__/video-texture-view.unit.test.d.ts +2 -0
- package/dist/record/__tests__/video-texture-view.unit.test.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +121 -0
- package/dist/record/frame-lighting.d.ts.map +1 -0
- package/dist/record/frame-snapshot.d.ts +429 -0
- package/dist/record/frame-snapshot.d.ts.map +1 -0
- package/dist/record/frame-targets.d.ts +20 -0
- package/dist/record/frame-targets.d.ts.map +1 -0
- package/dist/record/frame.d.ts +71 -0
- package/dist/record/frame.d.ts.map +1 -0
- package/dist/record/helpers.d.ts +102 -0
- package/dist/record/helpers.d.ts.map +1 -0
- package/dist/record/main-pass-geometry.d.ts +28 -0
- package/dist/record/main-pass-geometry.d.ts.map +1 -0
- package/dist/record/main-pass-material.d.ts +204 -0
- package/dist/record/main-pass-material.d.ts.map +1 -0
- package/dist/record/main-pass-sprite-draws.d.ts +43 -0
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -0
- package/dist/record/main-pass.d.ts +20 -0
- package/dist/record/main-pass.d.ts.map +1 -0
- package/dist/record/mesh-ssbo.d.ts +446 -0
- package/dist/record/mesh-ssbo.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +252 -0
- package/dist/record/render-context.d.ts.map +1 -0
- package/dist/record/shadow-pass.d.ts +52 -0
- package/dist/record/shadow-pass.d.ts.map +1 -0
- package/dist/record/skybox-post-pass.d.ts +33 -0
- package/dist/record/skybox-post-pass.d.ts.map +1 -0
- package/dist/record/typed-frame-graph.d.ts +35 -0
- package/dist/record/typed-frame-graph.d.ts.map +1 -0
- package/dist/record/view-ubo.d.ts +45 -0
- package/dist/record/view-ubo.d.ts.map +1 -0
- package/dist/render-contract.d.ts +424 -0
- package/dist/render-contract.d.ts.map +1 -0
- package/dist/render-data.d.ts +143 -0
- package/dist/render-data.d.ts.map +1 -0
- package/dist/render-graph-primitives.d.ts +76 -0
- package/dist/render-graph-primitives.d.ts.map +1 -0
- package/dist/render-pipeline.d.ts +110 -0
- package/dist/render-pipeline.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +868 -0
- package/dist/render-system-extract.d.ts.map +1 -0
- package/dist/render-system.d.ts +125 -0
- package/dist/render-system.d.ts.map +1 -0
- package/dist/scene/render-scene-types.d.ts +61 -0
- package/dist/scene/render-scene-types.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts +112 -0
- package/dist/scene/render-scene.d.ts.map +1 -0
- package/dist/scene-instances/post-spawn-resolve-joints.d.ts +30 -0
- package/dist/scene-instances/post-spawn-resolve-joints.d.ts.map +1 -0
- package/dist/shadow-atlas.d.ts +117 -0
- package/dist/shadow-atlas.d.ts.map +1 -0
- package/dist/ssao-buffers.d.ts +50 -0
- package/dist/ssao-buffers.d.ts.map +1 -0
- package/dist/ssao-config.d.ts +28 -0
- package/dist/ssao-config.d.ts.map +1 -0
- package/dist/systems/active-camera.d.ts +53 -0
- package/dist/systems/active-camera.d.ts.map +1 -0
- package/dist/systems/pass-selector.d.ts +24 -0
- package/dist/systems/pass-selector.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +49 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -0
- package/dist/systems/skin-palette-types.d.ts +14 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -0
- package/dist/systems/transparent-sort-config.d.ts +112 -0
- package/dist/systems/transparent-sort-config.d.ts.map +1 -0
- package/dist/tilemap-chunk-extract-system.d.ts +143 -0
- package/dist/tilemap-chunk-extract-system.d.ts.map +1 -0
- package/dist/typed-render-graph-primitives.d.ts +57 -0
- package/dist/typed-render-graph-primitives.d.ts.map +1 -0
- package/dist/typed-shadow-passes.d.ts +11 -0
- package/dist/typed-shadow-passes.d.ts.map +1 -0
- package/package.json +94 -0
- package/src/__tests__/authoring-path.unit.test.ts +23 -0
- package/src/__tests__/backend-selection-rhi-capture.test-d.ts +21 -0
- package/src/__tests__/callback-inference.test-d.ts +8 -0
- package/src/__tests__/cluster-binner.test.ts +272 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +20 -0
- package/src/__tests__/construct-renderer-identity.unit.test.ts +23 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +151 -0
- package/src/__tests__/device-scope-stale-matrix.unit.test.ts +42 -0
- package/src/__tests__/direct-extract-consumer-contract.test-d.ts +23 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +61 -0
- package/src/__tests__/factory-contract.integration.test.ts +280 -0
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +15 -0
- package/src/__tests__/frame-observation.contract.test.ts +112 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +32 -0
- package/src/__tests__/glyph-text-layout-recovery.unit.test.ts +167 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +350 -0
- package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +12 -0
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +16 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +436 -0
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +87 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +271 -0
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +290 -0
- package/src/__tests__/gpu-driven-view-reference.ts +111 -0
- package/src/__tests__/gpu-driven-view.browser.test.ts +30 -0
- package/src/__tests__/gpu-driven-view.dawn.test.ts +28 -0
- package/src/__tests__/gpu-driven-view.unit.test.ts +199 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +59 -0
- package/src/__tests__/gpu-scene-render-graph-gpu.ts +308 -0
- package/src/__tests__/gpu-scene-render-graph.browser.test.ts +19 -0
- package/src/__tests__/gpu-scene-render-graph.dawn.test.ts +19 -0
- package/src/__tests__/gpu-scene-schema.unit.test.ts +16 -0
- package/src/__tests__/gpu-scene.dawn.test.ts +205 -0
- package/src/__tests__/gpu-scene.unit.test.ts +147 -0
- package/src/__tests__/ibl/face-uniforms.unit.test.ts +46 -0
- package/src/__tests__/infinite-grid-no-vertex-probe.unit.test.ts +92 -0
- package/src/__tests__/injection-kind-owner.test-d.ts +14 -0
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +728 -0
- package/src/__tests__/light-buffer-layout.unit.test.ts +40 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +24 -0
- package/src/__tests__/material-cooked-projection.unit.test.ts +150 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +53 -0
- package/src/__tests__/material-static-dynamic-route.unit.test.ts +94 -0
- package/src/__tests__/material-stride-surface.unit.test.ts +32 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +106 -0
- package/src/__tests__/mesh-material-bindings.unit.test.ts +89 -0
- package/src/__tests__/mesh-stride-surface.unit.test.ts +16 -0
- package/src/__tests__/morph-culling-reentry.dawn.test.ts +157 -0
- package/src/__tests__/morph-feature-gpu-work.integration.test.ts +80 -0
- package/src/__tests__/morph-feature-plan.unit.test.ts +53 -0
- package/src/__tests__/morph-visual-evidence.integration.test.ts +80 -0
- package/src/__tests__/nineslice-handle-surface.unit.test.ts +19 -0
- package/src/__tests__/post-process-error-policy-owner.unit.test.ts +201 -0
- package/src/__tests__/prepared-buffer-kind-owner.test-d.ts +29 -0
- package/src/__tests__/prepared-graphics-capability.unit.test.ts +65 -0
- package/src/__tests__/prepared-graphics-generation.unit.test.ts +161 -0
- package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +138 -0
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +29 -0
- package/src/__tests__/prepared-graphics-resolver.unit.test.ts +285 -0
- package/src/__tests__/prepared-graphics-snapshot.unit.test.ts +105 -0
- package/src/__tests__/prepared-graphics-store.unit.test.ts +154 -0
- package/src/__tests__/prepared-graphics-validation.unit.test.ts +176 -0
- package/src/__tests__/profiler-default-off.test.ts +22 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +66 -0
- package/src/__tests__/profiler-phase-integration.test.ts +62 -0
- package/src/__tests__/profiler-phase-token.test.ts +79 -0
- package/src/__tests__/public-surface.test-d.ts +94 -0
- package/src/__tests__/recover-error-policy-owner.unit.test.ts +107 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +116 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +139 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +110 -0
- package/src/__tests__/render-feature-assembly.test-d.ts +43 -0
- package/src/__tests__/render-feature-context.test-d.ts +57 -0
- package/src/__tests__/render-feature-contract.test-d.ts +49 -0
- package/src/__tests__/render-feature-diagnostics.unit.test.ts +149 -0
- package/src/__tests__/render-feature-docs.unit.test.ts +89 -0
- package/src/__tests__/render-feature-errors.unit.test.ts +47 -0
- package/src/__tests__/render-feature-gpu-work.integration.test.ts +316 -0
- package/src/__tests__/render-feature-plan.test-d.ts +161 -0
- package/src/__tests__/render-feature-plan.unit.test.ts +170 -0
- package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +90 -0
- package/src/__tests__/render-feature-prepared-errors.test-d.ts +79 -0
- package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +80 -0
- package/src/__tests__/render-feature-prepared-graphics-isolation.unit.test.ts +72 -0
- package/src/__tests__/render-feature-prepared-graphics-negative.test-d.ts +36 -0
- package/src/__tests__/render-feature-prepared-graphics.test-d.ts +74 -0
- package/src/__tests__/render-feature-prepared-graphics.unit.test.ts +121 -0
- package/src/__tests__/render-feature-public-surface.test-d.ts +57 -0
- package/src/__tests__/render-feature-retry.unit.test.ts +87 -0
- package/src/__tests__/render-feature-scheduling.unit.test.ts +110 -0
- package/src/__tests__/render-feature-stage-failed-policy-owner.unit.test.ts +137 -0
- package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +146 -0
- package/src/__tests__/render-feature-visibility-context.test-d.ts +33 -0
- package/src/__tests__/render-feature-visibility-context.unit.test.ts +37 -0
- package/src/__tests__/render-feature-zero-work.unit.test.ts +76 -0
- package/src/__tests__/render-graph-compute-raster-gpu.ts +342 -0
- package/src/__tests__/render-graph-compute-raster.browser.test.ts +16 -0
- package/src/__tests__/render-graph-compute-raster.dawn.test.ts +22 -0
- package/src/__tests__/render-graph-hzb-gpu.ts +239 -0
- package/src/__tests__/render-graph-hzb.browser.test.ts +16 -0
- package/src/__tests__/render-graph-hzb.dawn.test.ts +23 -0
- package/src/__tests__/render-graph-primitives.unit.test.ts +22 -0
- package/src/__tests__/render-graph-storage-indirect-gpu.ts +162 -0
- package/src/__tests__/render-graph-storage-indirect.browser.test.ts +21 -0
- package/src/__tests__/render-graph-storage-indirect.dawn.test.ts +28 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +168 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +121 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +58 -0
- package/src/__tests__/rhi-backend-package-boundary.unit.test.ts +11 -0
- package/src/__tests__/scriptable-pack-consumer.test-d.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +43 -0
- package/src/__tests__/ssao-config.unit.test.ts +36 -0
- package/src/__tests__/ssao-kernel-surface.unit.test.ts +14 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +28 -0
- package/src/__tests__/standard-descriptors.test-d.ts +22 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +61 -0
- package/src/__tests__/standard-pipeline.integration.test.ts +124 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +62 -0
- package/src/__tests__/surface-configuration.unit.test.ts +91 -0
- package/src/__tests__/texture-usage-surface.unit.test.ts +58 -0
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +223 -0
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +42 -0
- package/src/__tests__/urp-pipeline.unit.test.ts +14 -0
- package/src/__tests__/vertex-color-pipeline.unit.test.ts +132 -0
- package/src/__tests__/vertex-layout-consumer.unit.test.ts +25 -0
- package/src/__tests__/visibility-diagnostics.integration.test.ts +62 -0
- package/src/__tests__/visibility-extract.integration.test.ts +98 -0
- package/src/__tests__/visibility-extract.perf.test.ts +130 -0
- package/src/__tests__/visibility-feature-dedup.integration.test.ts +55 -0
- package/src/__tests__/visibility-hidden-report.unit.test.ts +36 -0
- package/src/__tests__/visibility-instances.integration.test.ts +46 -0
- package/src/__tests__/visibility-multi-world.integration.test.ts +86 -0
- package/src/__tests__/visibility-producer-matrix.integration.test.ts +168 -0
- package/src/__tests__/visibility-producer-matrix.unit.test.ts +96 -0
- package/src/__tests__/visibility-public-surface.test-d.ts +27 -0
- package/src/__tests__/visibility-resolver.unit.test.ts +85 -0
- package/src/__tests__/visibility-resource-short-circuit.unit.test.ts +80 -0
- package/src/__tests__/visibility-snapshot-contract.unit.test.ts +60 -0
- package/src/__tests__/vocabulary.unit.test.ts +21 -0
- package/src/assembly/backend-contract.ts +57 -0
- package/src/assembly/factory.ts +8130 -0
- package/src/assembly/host-contract.ts +101 -0
- package/src/assembly/material/assembly.ts +48 -0
- package/src/assembly/material/pipeline-projection.ts +63 -0
- package/src/assets/asset-decoders.ts +158 -0
- package/src/assets/render-asset-port.ts +44 -0
- package/src/assets/resolve-render-asset.ts +13 -0
- package/src/authoring.ts +17 -0
- package/src/cluster-binner.ts +700 -0
- package/src/components/__tests__/points-lines-admission.unit.test.ts +219 -0
- package/src/components/__tests__/points-lines-schema.unit.test.ts +56 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +10 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +25 -0
- package/src/components/__tests__/visibility-schema.unit.test.ts +35 -0
- package/src/components/camera.ts +512 -0
- package/src/components/directional-light.ts +120 -0
- package/src/components/glyph-text.ts +64 -0
- package/src/components/index.ts +54 -0
- package/src/components/instances.ts +206 -0
- package/src/components/layer.ts +69 -0
- package/src/components/light-helpers.ts +188 -0
- package/src/components/lines.ts +18 -0
- package/src/components/mesh-filter.ts +60 -0
- package/src/components/mesh-renderer.ts +61 -0
- package/src/components/point-light-shadow.ts +43 -0
- package/src/components/point-light.ts +65 -0
- package/src/components/points.ts +40 -0
- package/src/components/post-process-params.ts +35 -0
- package/src/components/scene-instance.ts +193 -0
- package/src/components/skybox-background.ts +103 -0
- package/src/components/skylight.ts +97 -0
- package/src/components/sort-key.ts +75 -0
- package/src/components/spot-light.ts +124 -0
- package/src/components/sprite-animation.ts +168 -0
- package/src/components/sprite-instances.ts +179 -0
- package/src/components/sprite-playback-mode.ts +79 -0
- package/src/components/sprite-region-override.ts +105 -0
- package/src/components/tile-layer.ts +167 -0
- package/src/components/tilemap.ts +48 -0
- package/src/components/visibility.ts +53 -0
- package/src/construct-renderer.ts +67 -0
- package/src/debug-draw-glue.ts +48 -0
- package/src/device/device-scope.ts +306 -0
- package/src/device/gpu-residency.ts +1822 -0
- package/src/device/resource-types.ts +20 -0
- package/src/engine-metrics.ts +82 -0
- package/src/errors/ecs-validation.ts +16 -0
- package/src/errors/index.ts +3 -0
- package/src/errors/recover.ts +161 -0
- package/src/errors/render.ts +1284 -0
- package/src/extract/visibility.ts +120 -0
- package/src/features/fullscreen.ts +46 -0
- package/src/features/host.ts +1363 -0
- package/src/features/morph/morph-feature.ts +377 -0
- package/src/features/plan.ts +447 -0
- package/src/features/prepared-gpu-work.ts +959 -0
- package/src/features/prepared-graphics-store.ts +449 -0
- package/src/features/prepared-graphics.ts +420 -0
- package/src/features/render-graph-compute.ts +69 -0
- package/src/features/render-graph-contribution.ts +75 -0
- package/src/features/render-graph-raster.ts +332 -0
- package/src/features/render-graph-resources.ts +38 -0
- package/src/features/targets.ts +70 -0
- package/src/features/types.ts +103 -0
- package/src/features/vocabulary.ts +24 -0
- package/src/fullscreen-post-process-pass.ts +363 -0
- package/src/glyph-text-layout-system.ts +421 -0
- package/src/gpu-driven/batch-topology.ts +245 -0
- package/src/gpu-driven/production-raster.ts +695 -0
- package/src/gpu-driven/view-gpu.ts +703 -0
- package/src/gpu-resource.ts +159 -0
- package/src/gpu-scene-schema.ts +136 -0
- package/src/gpu-scene.ts +722 -0
- package/src/gpu-stage.ts +9 -0
- package/src/gpu-texture-usage.ts +12 -0
- package/src/gpu-usage.ts +14 -0
- package/src/hdrp-buffers.ts +480 -0
- package/src/ibl/IblPipelineCache.ts +1006 -0
- package/src/ibl/skylight-bind-group.ts +513 -0
- package/src/index.ts +129 -0
- package/src/inspection-types.ts +80 -0
- package/src/instance-buffer-cache.ts +95 -0
- package/src/internal.ts +9 -0
- package/src/lifecycle.ts +168 -0
- package/src/light-buffer-layout.ts +346 -0
- package/src/materials.ts +214 -0
- package/src/mesh-material-bindings.ts +103 -0
- package/src/pbr-pipeline.ts +1137 -0
- package/src/pipeline/standard-forward-lane.ts +229 -0
- package/src/pipeline/standard-pipeline.ts +555 -0
- package/src/pipeline/standard-profile.ts +65 -0
- package/src/pipeline-builder.ts +382 -0
- package/src/pipeline-spec-types.ts +28 -0
- package/src/pipeline-spec.ts +1231 -0
- package/src/plugin.ts +69 -0
- package/src/points-lines/__tests__/bounds.unit.test.ts +21 -0
- package/src/points-lines/__tests__/cache-recovery.unit.test.ts +157 -0
- package/src/points-lines/__tests__/inspection-lanes.integration.test.ts +146 -0
- package/src/points-lines/__tests__/inspection.unit.test.ts +173 -0
- package/src/points-lines/__tests__/lane-structural.integration.test.ts +122 -0
- package/src/points-lines/__tests__/points-lines.bench.ts +400 -0
- package/src/points-lines/__tests__/raster-lane.integration.test.ts +44 -0
- package/src/points-lines/__tests__/snapshot.unit.test.ts +101 -0
- package/src/points-lines/__tests__/view-ubo.unit.test.ts +73 -0
- package/src/points-lines/admission.ts +238 -0
- package/src/points-lines/bounds.ts +23 -0
- package/src/points-lines/expansion-cache.ts +215 -0
- package/src/points-lines/inspection.ts +97 -0
- package/src/points-lines/prepare.ts +177 -0
- package/src/points-lines/record.ts +125 -0
- package/src/points-lines/snapshot.ts +130 -0
- package/src/post-process-errors.ts +302 -0
- package/src/prepare/prepared-graphics-resolver.ts +456 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +41 -0
- package/src/record/__tests__/material-slot-plan.unit.test.ts +44 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +41 -0
- package/src/record/__tests__/video-texture-view.unit.test.ts +90 -0
- package/src/record/frame-lighting.ts +1074 -0
- package/src/record/frame-snapshot.ts +500 -0
- package/src/record/frame-targets.ts +137 -0
- package/src/record/frame.ts +1598 -0
- package/src/record/helpers.ts +284 -0
- package/src/record/main-pass-geometry.ts +1065 -0
- package/src/record/main-pass-material.ts +1043 -0
- package/src/record/main-pass-sprite-draws.ts +1137 -0
- package/src/record/main-pass.ts +610 -0
- package/src/record/mesh-ssbo.ts +885 -0
- package/src/record/render-context.ts +384 -0
- package/src/record/shadow-pass.ts +698 -0
- package/src/record/skybox-post-pass.ts +593 -0
- package/src/record/typed-frame-graph.ts +461 -0
- package/src/record/view-ubo.ts +222 -0
- package/src/render-contract.ts +582 -0
- package/src/render-data.ts +385 -0
- package/src/render-graph-primitives.ts +811 -0
- package/src/render-pipeline.ts +259 -0
- package/src/render-system-extract.ts +4585 -0
- package/src/render-system.ts +1933 -0
- package/src/scene/render-scene-types.ts +67 -0
- package/src/scene/render-scene.ts +1087 -0
- package/src/scene-instances/post-spawn-resolve-joints.ts +165 -0
- package/src/shadow-atlas.ts +244 -0
- package/src/ssao-buffers.ts +334 -0
- package/src/ssao-config.ts +61 -0
- package/src/systems/active-camera.ts +116 -0
- package/src/systems/pass-selector.ts +49 -0
- package/src/systems/skin-palette-allocator.ts +251 -0
- package/src/systems/skin-palette-types.ts +14 -0
- package/src/systems/transparent-sort-config.ts +233 -0
- package/src/tilemap-chunk-extract-system.ts +1290 -0
- package/src/typed-render-graph-primitives.ts +541 -0
- package/src/typed-shadow-passes.ts +165 -0
|
@@ -0,0 +1,1822 @@
|
|
|
1
|
+
// @forgeax/engine-runtime - GpuResidencyCache
|
|
2
|
+
// (feat-20260601-device/gpu-residency-extraction M1; M2 added the deriveRenderData
|
|
3
|
+
// projection seam — see render-data.ts).
|
|
4
|
+
//
|
|
5
|
+
// Owns the GPU residency layer extracted from AssetRegistry: per-handle GPU
|
|
6
|
+
// texture / cubemap / mesh buffer caches plus the upload primitives that
|
|
7
|
+
// build them. The store is engine-agnostic by default (no @webgpu/types
|
|
8
|
+
// imports) and holds ZERO reference to AssetRegistry (D-2): every upload
|
|
9
|
+
// primitive receives its source POD from the caller, never reaches back into
|
|
10
|
+
// a registry. The cubemap path mints its EquirectAsset POD handle through
|
|
11
|
+
// a wire-injected `registerCube` callback (D-3) so CPU cataloguing stays the
|
|
12
|
+
// registry's job while the store keeps the single-call upload contract. The
|
|
13
|
+
// cubemap projection (`_uploadCubemapFromEquirect`) is internal (@internal,
|
|
14
|
+
// feat-20260630): AI users declare `Skylight{equirect}` and the render-system
|
|
15
|
+
// record arm drives the projection; the method is never reached from user code.
|
|
16
|
+
//
|
|
17
|
+
// Residency model (D-2 pull): consumers call `ensureResident(handle, pod)`
|
|
18
|
+
// at first draw-time access; the store builds the GPU resource on a miss and
|
|
19
|
+
// returns the cached handles on subsequent O(1) hits. There is no global
|
|
20
|
+
// replay queue -- the pull model is purely lazy (user ruling). Builtin
|
|
21
|
+
// meshes are NOT routed through `ensureResident`; createRenderer seeds them
|
|
22
|
+
// via its step-3 direct upload + pipelineState.meshes (D-1).
|
|
23
|
+
//
|
|
24
|
+
// Texture residency is SYNCHRONOUS (D-9): `uploadTexture`'s only async source
|
|
25
|
+
// was the one-time per-device mipmap shader-module build, which is hoisted to
|
|
26
|
+
// `prewarmMipmapPipeline` (called from `createRenderer.initialization`, already async).
|
|
27
|
+
// After prewarm the per-texture mipmap blit is pure synchronous encoder work,
|
|
28
|
+
// so the sync `draw(world)` frame contract is preserved. A format that was
|
|
29
|
+
// not prewarmed surfaces a structured RhiError on the sync path -- it is never
|
|
30
|
+
// lazily awaited (that would break the sync draw contract).
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
blitMipmapsSync,
|
|
34
|
+
getOrCreateMipmapPipeline,
|
|
35
|
+
type MipmapShaderModuleFactory,
|
|
36
|
+
} from '@forgeax/engine-assets-runtime';
|
|
37
|
+
import type { World } from '@forgeax/engine-ecs';
|
|
38
|
+
import {
|
|
39
|
+
PROCEDURAL_FLOATS_PER_VERTEX,
|
|
40
|
+
type VertexLayoutProjection,
|
|
41
|
+
} from '@forgeax/engine-geometry';
|
|
42
|
+
import { halfFloat } from '@forgeax/engine-math';
|
|
43
|
+
import {
|
|
44
|
+
type Buffer,
|
|
45
|
+
type BufferDescriptor,
|
|
46
|
+
err,
|
|
47
|
+
ok,
|
|
48
|
+
type Result,
|
|
49
|
+
type RhiCaps,
|
|
50
|
+
type RhiDevice,
|
|
51
|
+
RhiError,
|
|
52
|
+
type RhiQueue,
|
|
53
|
+
type Sampler,
|
|
54
|
+
type Texture,
|
|
55
|
+
type TextureFormat,
|
|
56
|
+
type TextureView,
|
|
57
|
+
} from '@forgeax/engine-rhi';
|
|
58
|
+
import {
|
|
59
|
+
ASSET_ERROR_HINTS,
|
|
60
|
+
type AssetError,
|
|
61
|
+
countExtraUvSets,
|
|
62
|
+
type DecodedImage,
|
|
63
|
+
type EquirectAsset,
|
|
64
|
+
type Handle,
|
|
65
|
+
handleSlot,
|
|
66
|
+
IMAGE_ERROR_HINTS,
|
|
67
|
+
type ImageError,
|
|
68
|
+
type ImageErrorCode,
|
|
69
|
+
type ImageErrorDetailFor,
|
|
70
|
+
type ImageErrorFor,
|
|
71
|
+
type PrimitiveTopology,
|
|
72
|
+
type SamplerAsset,
|
|
73
|
+
type Submesh,
|
|
74
|
+
type TextureAsset,
|
|
75
|
+
type MeshAsset as TypesMeshAsset,
|
|
76
|
+
toShared,
|
|
77
|
+
} from '@forgeax/engine-types';
|
|
78
|
+
import { GpuBuffer, GpuTexture } from '../gpu-resource';
|
|
79
|
+
import {
|
|
80
|
+
GPU_TEXTURE_USAGE_COPY_DST,
|
|
81
|
+
GPU_TEXTURE_USAGE_TEXTURE_BINDING,
|
|
82
|
+
} from '../gpu-texture-usage';
|
|
83
|
+
import {
|
|
84
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
85
|
+
GPU_BUFFER_USAGE_INDEX,
|
|
86
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
87
|
+
GPU_BUFFER_USAGE_VERTEX,
|
|
88
|
+
} from '../gpu-usage';
|
|
89
|
+
import {
|
|
90
|
+
CAPTURE_VIEW_PROJS,
|
|
91
|
+
CUBEMAP_FACE_VERTICES,
|
|
92
|
+
createIblPipelines,
|
|
93
|
+
type IblShaderModuleFactory,
|
|
94
|
+
PREFILTER_MIP_LEVELS,
|
|
95
|
+
PREFILTER_SIZE,
|
|
96
|
+
runIblPrecompute,
|
|
97
|
+
} from '../ibl/IblPipelineCache';
|
|
98
|
+
import type { RhiErrorListenerRegistry } from '../lifecycle';
|
|
99
|
+
import {
|
|
100
|
+
type CubeRenderData,
|
|
101
|
+
deriveMipUploadLayout,
|
|
102
|
+
deriveRenderDataCubemap,
|
|
103
|
+
deriveRenderDataMesh,
|
|
104
|
+
deriveRenderDataTexture,
|
|
105
|
+
type MeshRenderData,
|
|
106
|
+
type TextureRenderData,
|
|
107
|
+
} from '../render-data';
|
|
108
|
+
import type { DeviceScope } from './device-scope';
|
|
109
|
+
|
|
110
|
+
const DYNAMIC_OFFSET_STRIDE = 256;
|
|
111
|
+
const FACE_COUNT = 6;
|
|
112
|
+
const PREFILTER_SUBPASS_COUNT = PREFILTER_MIP_LEVELS * FACE_COUNT;
|
|
113
|
+
|
|
114
|
+
export type FaceUniformsDevice = Pick<RhiDevice, 'createBuffer'> & {
|
|
115
|
+
readonly queue: Pick<RhiQueue, 'writeBuffer'>;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function invalidUniformInput(expected: string, hint: string): Result<never, RhiError> {
|
|
119
|
+
return err(new RhiError({ code: 'webgpu-runtime-error', expected, hint }));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function createFaceUniformsBuffer(device: FaceUniformsDevice): Result<Buffer, RhiError> {
|
|
123
|
+
const descriptor: BufferDescriptor = {
|
|
124
|
+
label: 'ibl-face-uniforms',
|
|
125
|
+
size: FACE_COUNT * DYNAMIC_OFFSET_STRIDE,
|
|
126
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
127
|
+
mappedAtCreation: false,
|
|
128
|
+
};
|
|
129
|
+
return device.createBuffer(descriptor);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function createPrefilterUniformsBuffer(
|
|
133
|
+
device: FaceUniformsDevice,
|
|
134
|
+
): Result<Buffer, RhiError> {
|
|
135
|
+
const descriptor: BufferDescriptor = {
|
|
136
|
+
label: 'ibl-prefilter-uniforms',
|
|
137
|
+
size: PREFILTER_SUBPASS_COUNT * DYNAMIC_OFFSET_STRIDE,
|
|
138
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
139
|
+
mappedAtCreation: false,
|
|
140
|
+
};
|
|
141
|
+
return device.createBuffer(descriptor);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function writeFaceUniforms(
|
|
145
|
+
device: FaceUniformsDevice,
|
|
146
|
+
buffer: Buffer,
|
|
147
|
+
faceIdx: number,
|
|
148
|
+
viewProj: Float32Array,
|
|
149
|
+
): Result<void, RhiError> {
|
|
150
|
+
if (faceIdx < 0 || faceIdx >= FACE_COUNT) {
|
|
151
|
+
return invalidUniformInput(
|
|
152
|
+
'faceIdx is an integer in the inclusive range [0, 5]',
|
|
153
|
+
`writeFaceUniforms received faceIdx=${faceIdx}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (viewProj.length !== 16) {
|
|
157
|
+
return invalidUniformInput(
|
|
158
|
+
'viewProj contains exactly 16 floats',
|
|
159
|
+
`writeFaceUniforms received ${viewProj.length} floats`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return device.queue.writeBuffer(buffer, faceIdx * DYNAMIC_OFFSET_STRIDE, viewProj);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function writeAllFaceUniforms(
|
|
166
|
+
device: FaceUniformsDevice,
|
|
167
|
+
buffer: Buffer,
|
|
168
|
+
): Result<void, RhiError> {
|
|
169
|
+
for (let face = 0; face < FACE_COUNT; face++) {
|
|
170
|
+
const viewProj = CAPTURE_VIEW_PROJS[face];
|
|
171
|
+
if (viewProj === undefined) continue;
|
|
172
|
+
const result = writeFaceUniforms(device, buffer, face, viewProj);
|
|
173
|
+
if (!result.ok) return result;
|
|
174
|
+
}
|
|
175
|
+
return ok(undefined);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function writePrefilterUniforms(
|
|
179
|
+
device: FaceUniformsDevice,
|
|
180
|
+
buffer: Buffer,
|
|
181
|
+
subPassIdx: number,
|
|
182
|
+
roughness: number,
|
|
183
|
+
mipFaceSize: number,
|
|
184
|
+
): Result<void, RhiError> {
|
|
185
|
+
if (subPassIdx < 0 || subPassIdx >= PREFILTER_SUBPASS_COUNT) {
|
|
186
|
+
return invalidUniformInput(
|
|
187
|
+
`subPassIdx is an integer in the inclusive range [0, ${PREFILTER_SUBPASS_COUNT - 1}]`,
|
|
188
|
+
`writePrefilterUniforms received subPassIdx=${subPassIdx}`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const payload = new Float32Array([roughness, mipFaceSize, 0, 0]);
|
|
192
|
+
return device.queue.writeBuffer(buffer, subPassIdx * DYNAMIC_OFFSET_STRIDE, payload);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function writeAllPrefilterUniforms(
|
|
196
|
+
device: FaceUniformsDevice,
|
|
197
|
+
buffer: Buffer,
|
|
198
|
+
): Result<void, RhiError> {
|
|
199
|
+
for (let mip = 0; mip < PREFILTER_MIP_LEVELS; mip++) {
|
|
200
|
+
const roughness = mip / (PREFILTER_MIP_LEVELS - 1);
|
|
201
|
+
const mipFaceSize = PREFILTER_SIZE / 2 ** mip;
|
|
202
|
+
for (let face = 0; face < FACE_COUNT; face++) {
|
|
203
|
+
const result = writePrefilterUniforms(
|
|
204
|
+
device,
|
|
205
|
+
buffer,
|
|
206
|
+
mip * FACE_COUNT + face,
|
|
207
|
+
roughness,
|
|
208
|
+
mipFaceSize,
|
|
209
|
+
);
|
|
210
|
+
if (!result.ok) return result;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return ok(undefined);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// AssetError is constructed without importing the AssetRegistry's class; the
|
|
217
|
+
// store builds the 4-field surface (.code / .expected / .hint / .detail)
|
|
218
|
+
// directly against the @forgeax/engine-types SSOT (charter P5 producer /
|
|
219
|
+
// consumer split; mirrors AssetRegistry's local RuntimeImageError).
|
|
220
|
+
class RuntimeAssetError extends Error implements AssetError {
|
|
221
|
+
readonly code: AssetError['code'];
|
|
222
|
+
readonly expected: string;
|
|
223
|
+
readonly hint: string;
|
|
224
|
+
constructor(fields: { code: AssetError['code']; expected: string; hint: string }) {
|
|
225
|
+
super(`[AssetError ${fields.code}] expected: ${fields.expected}; hint: ${fields.hint}`);
|
|
226
|
+
this.name = 'AssetError';
|
|
227
|
+
this.code = fields.code;
|
|
228
|
+
this.expected = fields.expected;
|
|
229
|
+
this.hint = fields.hint;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function makeAssetError(fields: {
|
|
234
|
+
code: AssetError['code'];
|
|
235
|
+
expected: string;
|
|
236
|
+
hint: string;
|
|
237
|
+
}): AssetError {
|
|
238
|
+
return new RuntimeAssetError(fields);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const IMAGE_ERROR_EXPECTED_LOCAL: Readonly<Record<string, string>> = {
|
|
242
|
+
'image-decode-failed': 'PNG / JPG byte stream decodes successfully',
|
|
243
|
+
'image-format-unsupported': "format ends in '-srgb' iff colorSpace is 'srgb' (linear otherwise)",
|
|
244
|
+
'image-dimension-out-of-bounds':
|
|
245
|
+
'width and height fall under device caps maxTextureDimension2D (or 16384 hard cap)',
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
class RuntimeImageError<C extends ImageErrorCode> extends Error implements ImageErrorFor<C> {
|
|
249
|
+
readonly code: C;
|
|
250
|
+
readonly expected: string;
|
|
251
|
+
readonly hint: string;
|
|
252
|
+
readonly detail: ImageErrorDetailFor<C>;
|
|
253
|
+
constructor(detail: ImageErrorDetailFor<C>) {
|
|
254
|
+
const code = detail.code;
|
|
255
|
+
const expected = IMAGE_ERROR_EXPECTED_LOCAL[code] ?? '';
|
|
256
|
+
const hint = IMAGE_ERROR_HINTS[code];
|
|
257
|
+
super(`[ImageError ${code}] expected: ${expected}; hint: ${hint}`);
|
|
258
|
+
this.name = 'ImageError';
|
|
259
|
+
this.code = code;
|
|
260
|
+
this.expected = expected;
|
|
261
|
+
this.hint = hint;
|
|
262
|
+
this.detail = detail;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function makeImageError<C extends ImageErrorCode>(
|
|
267
|
+
detail: ImageErrorDetailFor<C>,
|
|
268
|
+
): ImageErrorFor<C> {
|
|
269
|
+
return new RuntimeImageError<C>(detail);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Cube-POD register-relay injected by the wire layer (D-3). */
|
|
273
|
+
type RegisterCube = (
|
|
274
|
+
world: World,
|
|
275
|
+
pod: EquirectAsset,
|
|
276
|
+
) => Result<Handle<'EquirectAsset', 'shared'>, AssetError>;
|
|
277
|
+
|
|
278
|
+
// feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M-3 / w11:
|
|
279
|
+
// the three handle map value types now hold GpuTexture / GpuBuffer wrappers
|
|
280
|
+
// (plan-strategy D-9). Views (TextureView) stay raw RHI handles -- they are
|
|
281
|
+
// not destroyable on their own; their lifetime is bound to the parent
|
|
282
|
+
// GpuTexture, which the wrapper owns. destroyAll() walks the GpuResource
|
|
283
|
+
// fields and forwards .destroy() to the RHI shim.
|
|
284
|
+
interface TextureGpuEntry {
|
|
285
|
+
readonly texture: GpuTexture;
|
|
286
|
+
/** Opaque RHI view handle; lifetime is bound to the parent texture. */
|
|
287
|
+
readonly view: TextureView;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M2 / w11
|
|
291
|
+
// (D-3): the projection-status truth lives here (the store's cubemap map is the
|
|
292
|
+
// single authority — the SkylightSnapshot carries no status, D-3). `status`:
|
|
293
|
+
// - 'pending' — projection launched (fire-and-forget) but not complete; the
|
|
294
|
+
// record arm binds the white-cube fallback for this frame.
|
|
295
|
+
// - 'ready' — texture + cube view + 6 face views are live; bind the real
|
|
296
|
+
// IBL cube.
|
|
297
|
+
// - 'failed' — projection errored; recorded EXPLICITLY so the record arm
|
|
298
|
+
// stops and does NOT retry every frame (R-2 / AC-09). A failed entry has
|
|
299
|
+
// no GPU resources (texture/view are placeholders never bound).
|
|
300
|
+
// `texture`/`view`/`faceViews` are non-readonly so a 'pending' entry can be
|
|
301
|
+
// promoted to 'ready' in place (rebuilds the same slot). A 'failed' entry holds
|
|
302
|
+
// no GPU resources (`texture:null` / `view:undefined`): it exists only to mark
|
|
303
|
+
// the source as terminally failed so the record arm stops retrying (R-2).
|
|
304
|
+
interface CubemapGpuEntry {
|
|
305
|
+
status: 'pending' | 'ready' | 'failed';
|
|
306
|
+
texture: GpuTexture | null;
|
|
307
|
+
view: TextureView | undefined;
|
|
308
|
+
faceViews: readonly TextureView[];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export interface MeshGpuHandles {
|
|
312
|
+
readonly vertexBuffer: GpuBuffer;
|
|
313
|
+
/**
|
|
314
|
+
* Index buffer wrapper, or `null` for a vertex-only mesh
|
|
315
|
+
* (no `MeshAsset.indices`). When `null` the record stage takes the
|
|
316
|
+
* non-indexed `pass.draw(vertexCount)` path and never calls
|
|
317
|
+
* `setIndexBuffer`. Gated on `indexed` below.
|
|
318
|
+
*/
|
|
319
|
+
readonly indexBuffer: GpuBuffer | null;
|
|
320
|
+
/**
|
|
321
|
+
* Allocation byte sizes for the vbo / ibo (mirrors GPUBuffer.size). Used
|
|
322
|
+
* by `updateMeshById` to decide reuse vs reallocation; tracked here so
|
|
323
|
+
* the runtime never reads `.size` off the opaque RHI handle (which is
|
|
324
|
+
* not on the spec-aligned RHI Buffer interface; charter §RHI form rules).
|
|
325
|
+
*/
|
|
326
|
+
readonly vboBytes: number;
|
|
327
|
+
/** Allocation byte size for the ibo, or 0 when indexBuffer is null. */
|
|
328
|
+
readonly iboBytes: number;
|
|
329
|
+
readonly indexCount: number;
|
|
330
|
+
readonly indexFormat: 'uint16' | 'uint32';
|
|
331
|
+
/**
|
|
332
|
+
* Vertex stride discriminator (feat-20260611). `'12F'` = 48 B
|
|
333
|
+
* (position+normal+uv+tangent); `'18F'` = 72 B (12F + skinIndex(uint16x4) +
|
|
334
|
+
* skinWeight(float32x4)). Mirrors `MeshRenderData.layout` in render-data.ts
|
|
335
|
+
* and `MeshRenderData.layout` -- the residency and render-data layout
|
|
336
|
+
* fields are the same union and move together.
|
|
337
|
+
*/
|
|
338
|
+
readonly layout: '12F' | '18F';
|
|
339
|
+
/** Geometry-owned vertex layout projection used by every GPU consumer. */
|
|
340
|
+
readonly layoutProjection: VertexLayoutProjection;
|
|
341
|
+
/**
|
|
342
|
+
* Number of UV sets the interleaved buffer carries (1 = single `uv`, +1 per
|
|
343
|
+
* `uv1..uv7`). feat-20260629-multi-uv-set-support: threaded to the record
|
|
344
|
+
* stage so the forward PSO's vertex layout stride matches the buffer for
|
|
345
|
+
* meshes with a real extra UV set. Mirrors MeshRenderData.uvSetCount /
|
|
346
|
+
* MeshRenderData.uvSetCount.
|
|
347
|
+
*/
|
|
348
|
+
readonly uvSetCount: number;
|
|
349
|
+
/** Vertex count uses 18F for skin or the geometry-owned canonical base stride. */
|
|
350
|
+
readonly vertexCount: number;
|
|
351
|
+
/** True when `MeshAsset.indices` is present (indexed draw path). */
|
|
352
|
+
readonly indexed: boolean;
|
|
353
|
+
/** Primitive topology (default 'triangle-list'). */
|
|
354
|
+
readonly topology: PrimitiveTopology;
|
|
355
|
+
/**
|
|
356
|
+
* Submeshes from MeshAsset.submeshes, carried through to the record stage
|
|
357
|
+
* so per-submesh drawIndexed can iterate independently (feat-20260608 M4 / w16).
|
|
358
|
+
* For single-submesh meshes this is a 1-element array (byte-identical to pre-M4).
|
|
359
|
+
*/
|
|
360
|
+
readonly submeshes: readonly import('@forgeax/engine-types').Submesh[];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* GPU residency store. Owns the per-handle GPU caches and the upload
|
|
365
|
+
* primitives that build them. Constructed once per renderer; wired with the
|
|
366
|
+
* GPU device via `configureGpuDevice` after `Renderer.initialization` resolves.
|
|
367
|
+
*/
|
|
368
|
+
export class GpuResidencyCache {
|
|
369
|
+
/** Monotonic token for mesh GPU residency or contents changes. */
|
|
370
|
+
meshResidencyEpoch = 0;
|
|
371
|
+
/** Monotonic token for texture/sampler identities used by material bind groups. */
|
|
372
|
+
materialResourceEpoch = 0;
|
|
373
|
+
private gpuDevice: RhiDevice | undefined = undefined;
|
|
374
|
+
/** RHI-only owner for IBL projection; never populated with a raw GPUDevice. */
|
|
375
|
+
private iblDevice: RhiDevice | undefined = undefined;
|
|
376
|
+
/** RHI Result-returning shader factory paired with `iblDevice`. */
|
|
377
|
+
private iblShaderModuleFactory: IblShaderModuleFactory | undefined = undefined;
|
|
378
|
+
private deviceScope: DeviceScope | undefined;
|
|
379
|
+
// Shader-module factory injected at `configureGpuDevice`; threaded into the
|
|
380
|
+
// mipmap-pipeline prewarm + the IBL precompute path. `undefined` until wired.
|
|
381
|
+
private asyncCreateShaderModule: MipmapShaderModuleFactory | undefined = undefined;
|
|
382
|
+
// Cube-POD register relay injected at `configureGpuDevice` (D-3); the store
|
|
383
|
+
// never imports AssetRegistry, so CPU cataloguing flows through this fn.
|
|
384
|
+
private registerCube: RegisterCube | undefined = undefined;
|
|
385
|
+
// Error registry injected by createRenderer; evict / releaseUnreferenced
|
|
386
|
+
// fire structured errors through this channel (feat-20260619 D-1/D-6).
|
|
387
|
+
private errorRegistry: RhiErrorListenerRegistry | undefined = undefined;
|
|
388
|
+
// Hardware-probe caps injected at `configureGpuDevice`; guards the HDR cubemap
|
|
389
|
+
// path (_uploadCubemapFromEquirect) when `rgba16floatRenderable` is false.
|
|
390
|
+
private caps: RhiCaps | undefined = undefined;
|
|
391
|
+
|
|
392
|
+
private readonly textureGpuHandles: Map<string | number, TextureGpuEntry> = new Map();
|
|
393
|
+
private readonly samplerGpuHandles: Map<string | number, Sampler> = new Map();
|
|
394
|
+
private readonly cubemapGpuHandles: Map<number, CubemapGpuEntry> = new Map();
|
|
395
|
+
// Maps source EquirectAsset handle id -> minted cubemap handle so the same
|
|
396
|
+
// equirect source always resolves to the same cubemap (idempotent, A2).
|
|
397
|
+
private readonly cubemapIdempotentMap: Map<number, Handle<'EquirectAsset', 'shared'>> = new Map();
|
|
398
|
+
private readonly meshGpuHandles: Map<string | number, MeshGpuHandles> = new Map();
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Compose a cache key from the raw handle slot and its owning World. The
|
|
402
|
+
* draw-time world index is only a position in one frame's `worlds[]`; it is
|
|
403
|
+
* not an asset identity and can be reused by a later World. World objects
|
|
404
|
+
* therefore receive a renderer-local namespace, while numeric callers keep
|
|
405
|
+
* the legacy namespace for low-level tests and built-in upload paths.
|
|
406
|
+
*/
|
|
407
|
+
private readonly worldNamespaces = new WeakMap<World, number>();
|
|
408
|
+
private nextWorldNamespace = 1;
|
|
409
|
+
|
|
410
|
+
private worldKey(slot: number, world: World | number): string | number {
|
|
411
|
+
if (typeof world === 'number') {
|
|
412
|
+
return world === 0 ? slot : world * 0x1000000 + slot;
|
|
413
|
+
}
|
|
414
|
+
let namespace = this.worldNamespaces.get(world);
|
|
415
|
+
if (namespace === undefined) {
|
|
416
|
+
namespace = this.nextWorldNamespace;
|
|
417
|
+
this.nextWorldNamespace += 1;
|
|
418
|
+
this.worldNamespaces.set(world, namespace);
|
|
419
|
+
}
|
|
420
|
+
return `world:${namespace}:${slot}`;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Wire the GPU device, shader-module factory, and cube-POD register relay.
|
|
425
|
+
* Called once by createRenderer after `Renderer.initialization` resolves. Unlike the
|
|
426
|
+
* pre-extraction AssetRegistry.configureGpuDevice, this performs NO replay:
|
|
427
|
+
* the pull model builds resources lazily at first `ensureResident` access.
|
|
428
|
+
*/
|
|
429
|
+
configureGpuDevice(
|
|
430
|
+
device: RhiDevice,
|
|
431
|
+
asyncCreateShaderModule: MipmapShaderModuleFactory | undefined,
|
|
432
|
+
registerCube: RegisterCube,
|
|
433
|
+
caps: RhiCaps,
|
|
434
|
+
): void {
|
|
435
|
+
this.meshResidencyEpoch += 1;
|
|
436
|
+
this.gpuDevice = device;
|
|
437
|
+
this.asyncCreateShaderModule = asyncCreateShaderModule;
|
|
438
|
+
this.registerCube = registerCube;
|
|
439
|
+
this.caps = caps;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Bind the IBL projection to the same opaque RhiDevice used by assembly.
|
|
444
|
+
* This is deliberately separate from the legacy-compatible mipmap fixture
|
|
445
|
+
* seam: unit fixtures that only exercise ordinary residency do not need an
|
|
446
|
+
* IBL device, while a real IBL projection never accepts a raw GPUDevice.
|
|
447
|
+
*/
|
|
448
|
+
configureIblDevice(
|
|
449
|
+
device: RhiDevice,
|
|
450
|
+
shaderModuleFactory: IblShaderModuleFactory | undefined,
|
|
451
|
+
): void {
|
|
452
|
+
this.iblDevice = device;
|
|
453
|
+
this.iblShaderModuleFactory = shaderModuleFactory;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** Bind residency ownership to the renderer's single DeviceScope tree. */
|
|
457
|
+
bindDeviceScope(scope: DeviceScope): void {
|
|
458
|
+
this.deviceScope = scope;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Wrap a raw RHI Buffer handle into a GpuBuffer. The wrapper holds a
|
|
463
|
+
* reference to the device so `.destroy()` forwards to
|
|
464
|
+
* `device.destroyBuffer(handle)` (M-3 / w11; plan-strategy D-2).
|
|
465
|
+
*
|
|
466
|
+
* Pre: `configureGpuDevice` has been called. Callers that build a buffer
|
|
467
|
+
* via `device.createBuffer` already gate on a wired device, so this private
|
|
468
|
+
* helper trusts the caller; an unwired call is a programmer error and
|
|
469
|
+
* surfaces as a non-null assertion rather than a structured error.
|
|
470
|
+
*/
|
|
471
|
+
private wrapBuf(rawHandle: Buffer): GpuBuffer {
|
|
472
|
+
const device = this.gpuDevice;
|
|
473
|
+
if (device === undefined) {
|
|
474
|
+
throw new Error('GpuResidencyCache.wrapBuf called before configureGpuDevice');
|
|
475
|
+
}
|
|
476
|
+
const resource = new GpuBuffer(device, rawHandle);
|
|
477
|
+
this.deviceScope?._adopt('buffer', resource, (value: GpuBuffer) => {
|
|
478
|
+
if (!value.isDestroyed) value.destroy();
|
|
479
|
+
});
|
|
480
|
+
return resource;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Wrap a raw RHI Texture handle into a GpuTexture (mirror of wrapBuf). */
|
|
484
|
+
private wrapTex(rawHandle: Texture): GpuTexture {
|
|
485
|
+
const device = this.gpuDevice;
|
|
486
|
+
if (device === undefined) {
|
|
487
|
+
throw new Error('GpuResidencyCache.wrapTex called before configureGpuDevice');
|
|
488
|
+
}
|
|
489
|
+
const resource = new GpuTexture(device, rawHandle);
|
|
490
|
+
this.deviceScope?._adopt('texture', resource, (value: GpuTexture) => {
|
|
491
|
+
if (!value.isDestroyed) value.destroy();
|
|
492
|
+
});
|
|
493
|
+
return resource;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Walk the three handle maps and destroy every GpuResource, then clear
|
|
498
|
+
* the maps. Called from `Renderer.dispose()` (M-5) as the first step of
|
|
499
|
+
* the dispose chain (plan-strategy D-2: dispose chain walks
|
|
500
|
+
* `gpuStore.destroyAll()` → `graph.drain()` → `instanceBuffers.clear()`
|
|
501
|
+
* → ...).
|
|
502
|
+
*
|
|
503
|
+
* Idempotent: a second call after the maps were cleared is a no-op
|
|
504
|
+
* (architecture-principles §6 idempotency). Each `.destroy()` call
|
|
505
|
+
* routes through the RHI shim's per-handle bookkeeping, so a stray
|
|
506
|
+
* second-destroy on a handle the runtime did not flip to destroyed
|
|
507
|
+
* surfaces as the structured `'destroy-after-destroy'` error from the
|
|
508
|
+
* RHI shim; that error is *not* re-thrown here -- destroyAll is a
|
|
509
|
+
* sweep that tolerates per-handle failures so the dispose chain can
|
|
510
|
+
* make progress (plan-strategy D-3 / D-8).
|
|
511
|
+
*/
|
|
512
|
+
destroyAll(): void {
|
|
513
|
+
if (this.meshGpuHandles.size > 0) this.meshResidencyEpoch += 1;
|
|
514
|
+
const scopeOwnsResources = this.deviceScope !== undefined;
|
|
515
|
+
// The cubemap path registers two entries (sourceId + cubeId) sharing one
|
|
516
|
+
// GpuTexture wrapper, so destroyAll must dedupe on the wrapper identity
|
|
517
|
+
// before forwarding `.destroy()` -- otherwise the second call surfaces
|
|
518
|
+
// `'destroy-after-destroy'` from the RHI shim. The `isDestroyed` getter
|
|
519
|
+
// on the wrapper is the dedupe gate (architecture-principles §6).
|
|
520
|
+
const destroyTex = (gpuTex: GpuTexture): void => {
|
|
521
|
+
if (!scopeOwnsResources && !gpuTex.isDestroyed) gpuTex.destroy();
|
|
522
|
+
};
|
|
523
|
+
const destroyBuf = (gpuBuf: GpuBuffer): void => {
|
|
524
|
+
if (!scopeOwnsResources && !gpuBuf.isDestroyed) gpuBuf.destroy();
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
for (const entry of this.textureGpuHandles.values()) {
|
|
528
|
+
destroyTex(entry.texture);
|
|
529
|
+
}
|
|
530
|
+
if (this.textureGpuHandles.size > 0 || this.samplerGpuHandles.size > 0) {
|
|
531
|
+
this.materialResourceEpoch += 1;
|
|
532
|
+
}
|
|
533
|
+
this.textureGpuHandles.clear();
|
|
534
|
+
this.samplerGpuHandles.clear();
|
|
535
|
+
|
|
536
|
+
for (const entry of this.cubemapGpuHandles.values()) {
|
|
537
|
+
if (entry.texture !== null) destroyTex(entry.texture);
|
|
538
|
+
}
|
|
539
|
+
this.cubemapGpuHandles.clear();
|
|
540
|
+
this.cubemapIdempotentMap.clear();
|
|
541
|
+
|
|
542
|
+
for (const entry of this.meshGpuHandles.values()) {
|
|
543
|
+
destroyBuf(entry.vertexBuffer);
|
|
544
|
+
if (entry.indexBuffer !== null) destroyBuf(entry.indexBuffer);
|
|
545
|
+
}
|
|
546
|
+
this.meshGpuHandles.clear();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Set the error registry channel (wired by createRenderer). evict /
|
|
551
|
+
* releaseUnreferenced fire structured errors here instead of throwing -
|
|
552
|
+
* sweeps continue past individual failures (feat-20260619 D-1/D-6).
|
|
553
|
+
*/
|
|
554
|
+
setErrorRegistry(registry: RhiErrorListenerRegistry): void {
|
|
555
|
+
this.errorRegistry = registry;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* evictTexture / evictMesh / evictCubemap — per-handle evict primitives.
|
|
560
|
+
*
|
|
561
|
+
* Reuses destroyAll's isDestroyed dedup logic + cubemap wrapper
|
|
562
|
+
* shared-dedup (feat-20260619 D-1). Returns {freed, errors} so callers
|
|
563
|
+
* can consume the aggregate result (D-3). Key not present -> no-op
|
|
564
|
+
* returning {freed:0, errors:[]}.
|
|
565
|
+
*/
|
|
566
|
+
evictTexture(
|
|
567
|
+
handle: Handle<'TextureAsset', 'shared'>,
|
|
568
|
+
worldId: World | number = 0,
|
|
569
|
+
): { freed: number; errors: RhiError[] } {
|
|
570
|
+
const id = this.worldKey(handleSlot(handle), worldId);
|
|
571
|
+
const entry = this.textureGpuHandles.get(id);
|
|
572
|
+
if (entry === undefined) return { freed: 0, errors: [] };
|
|
573
|
+
|
|
574
|
+
let freed = 0;
|
|
575
|
+
const errors: RhiError[] = [];
|
|
576
|
+
|
|
577
|
+
if (!entry.texture.isDestroyed) {
|
|
578
|
+
const r = entry.texture.destroy();
|
|
579
|
+
if (r.ok) {
|
|
580
|
+
freed = 1;
|
|
581
|
+
} else {
|
|
582
|
+
errors.push(r.error);
|
|
583
|
+
if (this.errorRegistry) this.errorRegistry.fire(r.error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
this.textureGpuHandles.delete(id);
|
|
588
|
+
this.materialResourceEpoch += 1;
|
|
589
|
+
return { freed, errors };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
evictMesh(
|
|
593
|
+
handle: Handle<'MeshAsset', 'shared'>,
|
|
594
|
+
worldId: World | number = 0,
|
|
595
|
+
): { freed: number; errors: RhiError[] } {
|
|
596
|
+
const id = this.worldKey(handleSlot(handle), worldId);
|
|
597
|
+
const entry = this.meshGpuHandles.get(id);
|
|
598
|
+
if (entry === undefined) return { freed: 0, errors: [] };
|
|
599
|
+
|
|
600
|
+
let freed = 0;
|
|
601
|
+
const errors: RhiError[] = [];
|
|
602
|
+
|
|
603
|
+
const destroyBuf = (gpuBuf: GpuBuffer): void => {
|
|
604
|
+
if (!gpuBuf.isDestroyed) {
|
|
605
|
+
const r = gpuBuf.destroy();
|
|
606
|
+
if (r.ok) {
|
|
607
|
+
freed += 1;
|
|
608
|
+
} else {
|
|
609
|
+
errors.push(r.error);
|
|
610
|
+
if (this.errorRegistry) this.errorRegistry.fire(r.error);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
destroyBuf(entry.vertexBuffer);
|
|
616
|
+
if (entry.indexBuffer !== null) destroyBuf(entry.indexBuffer);
|
|
617
|
+
|
|
618
|
+
this.meshGpuHandles.delete(id);
|
|
619
|
+
this.meshResidencyEpoch += 1;
|
|
620
|
+
return { freed: freed > 0 ? 1 : 0, errors };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
evictCubemap(id: number): { freed: number; errors: RhiError[] } {
|
|
624
|
+
const entry = this.cubemapGpuHandles.get(id);
|
|
625
|
+
if (entry === undefined) return { freed: 0, errors: [] };
|
|
626
|
+
|
|
627
|
+
let freed = 0;
|
|
628
|
+
const errors: RhiError[] = [];
|
|
629
|
+
|
|
630
|
+
// cubemap wrapper shared-dedup (D-1): sourceId and cubeId may share one
|
|
631
|
+
// GpuTexture wrapper. The isDestroyed gate ensures the underlying RHI
|
|
632
|
+
// texture is destroyed at most once, even when both entries are evicted.
|
|
633
|
+
// A 'failed' entry holds no GPU texture (texture:null) — nothing to free.
|
|
634
|
+
if (entry.texture !== null && !entry.texture.isDestroyed) {
|
|
635
|
+
const r = entry.texture.destroy();
|
|
636
|
+
if (r.ok) {
|
|
637
|
+
freed = 1;
|
|
638
|
+
} else {
|
|
639
|
+
errors.push(r.error);
|
|
640
|
+
if (this.errorRegistry) this.errorRegistry.fire(r.error);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
this.cubemapGpuHandles.delete(id);
|
|
645
|
+
return { freed, errors };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* releaseUnreferenced — iterate the three handle maps and evict entries
|
|
650
|
+
* whose key is NOT in `liveSet`.
|
|
651
|
+
*
|
|
652
|
+
* Iterates Map keys (not liveSet — store has no reverse index, D-8).
|
|
653
|
+
* IDs in liveSet that don't exist in the store are naturally ignored.
|
|
654
|
+
* Empty liveSet -> full release; second call -> no-op (maps empty,
|
|
655
|
+
* evict primitives are key-not-present no-op).
|
|
656
|
+
*/
|
|
657
|
+
releaseUnreferenced(liveSet: Set<number>): { freed: number; errors: RhiError[] } {
|
|
658
|
+
let freed = 0;
|
|
659
|
+
const errors: RhiError[] = [];
|
|
660
|
+
let meshResidencyChanged = false;
|
|
661
|
+
let materialResidencyChanged = false;
|
|
662
|
+
const liveSlots = new Set([...liveSet].map((slot) => String(slot)));
|
|
663
|
+
const isLiveKey = (key: string | number): boolean => {
|
|
664
|
+
const slot =
|
|
665
|
+
typeof key === 'number' ? String(key % 0x1000000) : key.slice(key.lastIndexOf(':') + 1);
|
|
666
|
+
return liveSlots.has(slot);
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
for (const key of this.textureGpuHandles.keys()) {
|
|
670
|
+
if (!isLiveKey(key)) {
|
|
671
|
+
const entry = this.textureGpuHandles.get(key);
|
|
672
|
+
if (entry !== undefined) {
|
|
673
|
+
if (!entry.texture.isDestroyed) {
|
|
674
|
+
const r = entry.texture.destroy();
|
|
675
|
+
if (r.ok) {
|
|
676
|
+
freed += 1;
|
|
677
|
+
} else {
|
|
678
|
+
errors.push(r.error);
|
|
679
|
+
if (this.errorRegistry) this.errorRegistry.fire(r.error);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
this.textureGpuHandles.delete(key);
|
|
683
|
+
materialResidencyChanged = true;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
for (const key of this.samplerGpuHandles.keys()) {
|
|
689
|
+
if (!isLiveKey(key)) {
|
|
690
|
+
this.samplerGpuHandles.delete(key);
|
|
691
|
+
materialResidencyChanged = true;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
for (const key of this.cubemapGpuHandles.keys()) {
|
|
696
|
+
if (!liveSlots.has(String(key))) {
|
|
697
|
+
const entry = this.cubemapGpuHandles.get(key);
|
|
698
|
+
if (entry !== undefined) {
|
|
699
|
+
// A 'failed' entry holds no GPU texture (texture:null) — nothing to free.
|
|
700
|
+
if (entry.texture !== null && !entry.texture.isDestroyed) {
|
|
701
|
+
const r = entry.texture.destroy();
|
|
702
|
+
if (r.ok) {
|
|
703
|
+
freed += 1;
|
|
704
|
+
} else {
|
|
705
|
+
errors.push(r.error);
|
|
706
|
+
if (this.errorRegistry) this.errorRegistry.fire(r.error);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
this.cubemapGpuHandles.delete(key);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
for (const key of this.meshGpuHandles.keys()) {
|
|
715
|
+
if (!isLiveKey(key)) {
|
|
716
|
+
const entry = this.meshGpuHandles.get(key);
|
|
717
|
+
if (entry !== undefined) {
|
|
718
|
+
if (!entry.vertexBuffer.isDestroyed) {
|
|
719
|
+
const r = entry.vertexBuffer.destroy();
|
|
720
|
+
if (r.ok) {
|
|
721
|
+
freed += 1;
|
|
722
|
+
} else {
|
|
723
|
+
errors.push(r.error);
|
|
724
|
+
if (this.errorRegistry) this.errorRegistry.fire(r.error);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (entry.indexBuffer !== null && !entry.indexBuffer.isDestroyed) {
|
|
728
|
+
const r = entry.indexBuffer.destroy();
|
|
729
|
+
if (r.ok) {
|
|
730
|
+
freed += 1;
|
|
731
|
+
} else {
|
|
732
|
+
errors.push(r.error);
|
|
733
|
+
if (this.errorRegistry) this.errorRegistry.fire(r.error);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
this.meshGpuHandles.delete(key);
|
|
737
|
+
meshResidencyChanged = true;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (meshResidencyChanged) this.meshResidencyEpoch += 1;
|
|
743
|
+
if (materialResidencyChanged) this.materialResourceEpoch += 1;
|
|
744
|
+
|
|
745
|
+
return { freed, errors };
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Prewarm the mipmap pipeline cache for the given texture formats (D-9).
|
|
750
|
+
* Called from `createRenderer.initialization` (already async): builds the one-time
|
|
751
|
+
* mipmap shader module + per-format render pipeline into the mipmap-generator
|
|
752
|
+
* deviceCache so the per-texture mipmap blit at record-stage `ensureResident`
|
|
753
|
+
* is pure synchronous encoder work. A format absent from this list will make
|
|
754
|
+
* the sync `ensureResident` texture arm return a structured RhiError rather
|
|
755
|
+
* than lazily await a build (which would break the sync draw contract).
|
|
756
|
+
*/
|
|
757
|
+
async prewarmMipmapPipeline(
|
|
758
|
+
device: RhiDevice,
|
|
759
|
+
formats: readonly TextureFormat[],
|
|
760
|
+
): Promise<Result<void, RhiError>> {
|
|
761
|
+
const factory = this.asyncCreateShaderModule;
|
|
762
|
+
if (factory === undefined) {
|
|
763
|
+
return err(
|
|
764
|
+
new RhiError({
|
|
765
|
+
code: 'rhi-not-available',
|
|
766
|
+
expected: 'asyncCreateShaderModule wired by configureGpuDevice before prewarm',
|
|
767
|
+
hint: 'call gpuStore.configureGpuDevice(device, packShaderFactory, registerCube) before prewarmMipmapPipeline',
|
|
768
|
+
}),
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
for (const format of formats) {
|
|
772
|
+
const res = await getOrCreateMipmapPipeline(device, format, factory);
|
|
773
|
+
if (!res.ok) {
|
|
774
|
+
return err(
|
|
775
|
+
new RhiError({
|
|
776
|
+
code: 'rhi-not-available',
|
|
777
|
+
expected: `mipmap pipeline for format ${format} builds during prewarm`,
|
|
778
|
+
hint: `prewarmMipmapPipeline failed building the mipmap pipeline for ${format}; the format may be unsupported by the device`,
|
|
779
|
+
}),
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return ok(undefined);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Return the GpuTexture wrapper for a `Handle<TextureAsset>` if it has been
|
|
788
|
+
* made resident, else `undefined`.
|
|
789
|
+
*
|
|
790
|
+
* @internal — test-only seam for cow-survivor / AC-02 integration tests; not
|
|
791
|
+
* part of the engine's public API surface.
|
|
792
|
+
*/
|
|
793
|
+
_getTextureGpuTexture(
|
|
794
|
+
handle: Handle<'TextureAsset', 'shared'>,
|
|
795
|
+
worldId: World | number = 0,
|
|
796
|
+
): GpuTexture | undefined {
|
|
797
|
+
return this.textureGpuHandles.get(this.worldKey(handleSlot(handle), worldId))?.texture;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Return the GPU texture-view for a `Handle<TextureAsset>` if it has been
|
|
802
|
+
* made resident, else `undefined`.
|
|
803
|
+
*/
|
|
804
|
+
getTextureGpuView(
|
|
805
|
+
handle: Handle<'TextureAsset', 'shared'>,
|
|
806
|
+
worldId: World | number = 0,
|
|
807
|
+
): TextureView | undefined {
|
|
808
|
+
return this.textureGpuHandles.get(this.worldKey(handleSlot(handle), worldId))?.view;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Lazily materialise and cache a sampler descriptor for a material texture.
|
|
813
|
+
* Samplers are opaque, non-destroyable RHI handles, so disposal only drops
|
|
814
|
+
* this store's cache and lets the device own their lifetime.
|
|
815
|
+
*/
|
|
816
|
+
ensureSamplerResident(
|
|
817
|
+
handle: Handle<'SamplerAsset', 'shared'>,
|
|
818
|
+
pod: SamplerAsset,
|
|
819
|
+
worldId: World | number = 0,
|
|
820
|
+
): Result<Sampler, RhiError> {
|
|
821
|
+
const key = this.worldKey(handleSlot(handle), worldId);
|
|
822
|
+
const existing = this.samplerGpuHandles.get(key);
|
|
823
|
+
if (existing !== undefined) return ok(existing);
|
|
824
|
+
const device = this.gpuDevice;
|
|
825
|
+
if (device === undefined) {
|
|
826
|
+
return err(
|
|
827
|
+
new RhiError({
|
|
828
|
+
code: 'rhi-not-available',
|
|
829
|
+
expected: 'GPU device configured before SamplerAsset residency',
|
|
830
|
+
hint: 'call gpuStore.configureGpuDevice before drawing a material with a sampler',
|
|
831
|
+
}),
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
const { kind: _kind, ...descriptor } = pod;
|
|
835
|
+
const created = device.createSampler(descriptor);
|
|
836
|
+
if (!created.ok) return created;
|
|
837
|
+
this.samplerGpuHandles.set(key, created.value);
|
|
838
|
+
this.materialResourceEpoch += 1;
|
|
839
|
+
return created;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Return the GpuTexture wrapper for the cubemap, or `undefined` if not
|
|
844
|
+
* uploaded yet. Consumers that need the raw RHI Texture handle (e.g.
|
|
845
|
+
* `device.createTextureView` arguments) read `.handle` on the wrapper;
|
|
846
|
+
* `.destroy()` routes through the destroy chain (M-3 / w11).
|
|
847
|
+
*/
|
|
848
|
+
getCubemapGpuTexture(handle: Handle<'EquirectAsset', 'shared'>): GpuTexture | undefined {
|
|
849
|
+
// A 'failed' entry has texture:null; normalise to undefined (not resident).
|
|
850
|
+
return this.cubemapGpuHandles.get(handleSlot(handle))?.texture ?? undefined;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/** Return the full-cube texture view, or `undefined` if not uploaded yet. */
|
|
854
|
+
getCubemapGpuView(handle: Handle<'EquirectAsset', 'shared'>): TextureView | undefined {
|
|
855
|
+
return this.cubemapGpuHandles.get(handleSlot(handle))?.view;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** Return per-face 2D views (6 faces), or `undefined` if not uploaded yet. */
|
|
859
|
+
getCubemapFaceViews(
|
|
860
|
+
handle: Handle<'EquirectAsset', 'shared'>,
|
|
861
|
+
): readonly TextureView[] | undefined {
|
|
862
|
+
return this.cubemapGpuHandles.get(handleSlot(handle))?.faceViews;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Query the projection status for an equirect source handle (D-3 SSOT: the
|
|
867
|
+
* store's CubemapGpuEntry is the single authority). Returns `undefined` when
|
|
868
|
+
* no projection has been launched for this source yet (the record arm reads
|
|
869
|
+
* this to decide whether to fire the lazy projection; feat-20260630 M3 / w18):
|
|
870
|
+
* - undefined -> no entry: caller may launch projection (fire-and-forget)
|
|
871
|
+
* - 'pending' -> projection launched, not complete: bind white-cube fallback
|
|
872
|
+
* - 'ready' -> projected cube + IBL views live: bind real IBL
|
|
873
|
+
* - 'failed' -> projection errored: bind white fallback, do NOT retry (R-2)
|
|
874
|
+
*/
|
|
875
|
+
getCubemapStatus(
|
|
876
|
+
handle: Handle<'EquirectAsset', 'shared'>,
|
|
877
|
+
): 'pending' | 'ready' | 'failed' | undefined {
|
|
878
|
+
return this.cubemapGpuHandles.get(handleSlot(handle))?.status;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Return the GPU vertex / index buffer handles + index count for a
|
|
883
|
+
* `Handle<MeshAsset>` if resident, else `undefined`. Consumers treat this
|
|
884
|
+
* as the canonical "mesh asset has GPU residency" probe.
|
|
885
|
+
*/
|
|
886
|
+
getMeshGpuHandles(
|
|
887
|
+
handle: Handle<'MeshAsset', 'shared'>,
|
|
888
|
+
worldId: World | number = 0,
|
|
889
|
+
): MeshGpuHandles | undefined {
|
|
890
|
+
return this.meshGpuHandles.get(this.worldKey(handleSlot(handle), worldId));
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Drop one mutable shared mesh's GPU copy after ECS publishes `markChanged`.
|
|
895
|
+
* The next record pass resolves the same POD and rebuilds residency through
|
|
896
|
+
* the normal pull path, so ECS remains the payload authority.
|
|
897
|
+
*/
|
|
898
|
+
invalidateMesh(handle: number, worldId: World | number = 0): void {
|
|
899
|
+
// SharedRefMutation publishes the encoded handle value, while the cache
|
|
900
|
+
// key is derived from the decoded slot. Keep the invalidation path aligned
|
|
901
|
+
// with getMeshGpuHandles/ensureResident so an in-place ECS payload change
|
|
902
|
+
// cannot leave the old VBO resident.
|
|
903
|
+
const key = this.worldKey(handleSlot(toShared<'MeshAsset'>(handle)), worldId);
|
|
904
|
+
const entry = this.meshGpuHandles.get(key);
|
|
905
|
+
if (entry === undefined) return;
|
|
906
|
+
entry.vertexBuffer.destroy();
|
|
907
|
+
if (entry.indexBuffer !== null) entry.indexBuffer.destroy();
|
|
908
|
+
this.meshGpuHandles.delete(key);
|
|
909
|
+
this.meshResidencyEpoch += 1;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Pull-model residency: build the GPU resource for `handle` from the
|
|
914
|
+
* caller-provided POD on a miss, return the cached handles on a hit (D-2).
|
|
915
|
+
*
|
|
916
|
+
* - mesh POD -> synchronous vertex / index buffer upload (mirrors the
|
|
917
|
+
* pre-extraction `uploadMeshById`).
|
|
918
|
+
* - texture POD -> synchronous texture upload + mipmap blit (D-9). The
|
|
919
|
+
* mipmap pipeline MUST have been prewarmed via `prewarmMipmapPipeline`;
|
|
920
|
+
* an un-prewarmed format returns a structured RhiError (never awaits).
|
|
921
|
+
*
|
|
922
|
+
* Cubemap is NOT routed here -- it is an eager user call
|
|
923
|
+
* (`_uploadCubemapFromEquirect`). Builtin meshes are NOT routed here either
|
|
924
|
+
* (createRenderer step-3 owns them; D-1).
|
|
925
|
+
*/
|
|
926
|
+
ensureResident(
|
|
927
|
+
handle: Handle<'MeshAsset', 'shared'>,
|
|
928
|
+
pod: TypesMeshAsset,
|
|
929
|
+
worldId?: World | number,
|
|
930
|
+
): Result<MeshGpuHandles, RhiError | AssetError | ImageError>;
|
|
931
|
+
ensureResident(
|
|
932
|
+
handle: Handle<'TextureAsset', 'shared'>,
|
|
933
|
+
pod: TextureAsset,
|
|
934
|
+
worldId?: World | number,
|
|
935
|
+
): Result<TextureGpuEntry, RhiError | AssetError | ImageError>;
|
|
936
|
+
ensureResident(
|
|
937
|
+
handle: Handle<'MeshAsset', 'shared'> | Handle<'TextureAsset', 'shared'>,
|
|
938
|
+
pod: TypesMeshAsset | TextureAsset,
|
|
939
|
+
worldId: World | number = 0,
|
|
940
|
+
): Result<TextureGpuEntry | MeshGpuHandles, RhiError | AssetError | ImageError> {
|
|
941
|
+
const id = handleSlot(handle);
|
|
942
|
+
// Hit: O(1) cache lookup, never re-projects (AC-09 -- deriveRenderData*
|
|
943
|
+
// runs only on a miss). The miss arms below dispatch on `pod.kind` with NO
|
|
944
|
+
// default: only the two GPU-resource kinds reachable here (mesh / texture)
|
|
945
|
+
// have arms, so a third reachable kind would surface as a `tsc -b`
|
|
946
|
+
// exhaustiveness error at the switch rather than a silent fallthrough
|
|
947
|
+
// (AC-06; the cube-texture kind is the eager `_uploadCubemapFromEquirect`
|
|
948
|
+
// path, not routed here).
|
|
949
|
+
switch (pod.kind) {
|
|
950
|
+
case 'mesh': {
|
|
951
|
+
const key = this.worldKey(id, worldId);
|
|
952
|
+
const existing = this.meshGpuHandles.get(key);
|
|
953
|
+
if (existing !== undefined) return ok(existing);
|
|
954
|
+
const projected = deriveRenderDataMesh(pod);
|
|
955
|
+
if (!projected.ok) return projected;
|
|
956
|
+
return this.uploadMeshById(key, pod, projected.value);
|
|
957
|
+
}
|
|
958
|
+
case 'texture': {
|
|
959
|
+
const texKey = this.worldKey(id, worldId);
|
|
960
|
+
const existing = this.textureGpuHandles.get(texKey);
|
|
961
|
+
if (existing !== undefined) return ok(existing);
|
|
962
|
+
const projected = deriveRenderDataTexture(pod);
|
|
963
|
+
if (!projected.ok) return projected;
|
|
964
|
+
const decoded: DecodedImage = decodedFromTexture(pod);
|
|
965
|
+
return this.uploadTextureSync(
|
|
966
|
+
handle as Handle<'TextureAsset', 'shared'>,
|
|
967
|
+
pod,
|
|
968
|
+
decoded,
|
|
969
|
+
projected.value,
|
|
970
|
+
worldId,
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Eager public surface: upload decoded image bytes into a GPU texture and
|
|
978
|
+
* cache (texture + view) under the handle. The TextureAsset POD supplies the
|
|
979
|
+
* GPU format (D-2: caller provides POD, store never reaches a registry); the
|
|
980
|
+
* DecodedImage supplies the pixel bytes + colorSpace. Retains the async
|
|
981
|
+
* signature so callers that have not prewarmed can drive a mipmap build here
|
|
982
|
+
* (the one async source).
|
|
983
|
+
*/
|
|
984
|
+
async uploadTexture(
|
|
985
|
+
handle: Handle<'TextureAsset', 'shared'>,
|
|
986
|
+
pod: TextureAsset,
|
|
987
|
+
decoded: DecodedImage,
|
|
988
|
+
worldId: World | number = 0,
|
|
989
|
+
): Promise<Result<void, AssetError | ImageError | RhiError>> {
|
|
990
|
+
const device = this.gpuDevice;
|
|
991
|
+
const projected = deriveRenderDataTexture(pod);
|
|
992
|
+
if (!projected.ok) return projected;
|
|
993
|
+
const prepared = this.prepareTextureUpload(handle, pod, decoded, projected.value);
|
|
994
|
+
if (!prepared.ok) return prepared;
|
|
995
|
+
if (device === undefined) return ok(undefined);
|
|
996
|
+
const { tex, levels, gpuTexture } = prepared.value;
|
|
997
|
+
const cacheKey = this.worldKey(handleSlot(handle), worldId);
|
|
998
|
+
if (gpuTexture === undefined) return ok(undefined);
|
|
999
|
+
|
|
1000
|
+
if (!projected.value.compressed && decoded.mipmap === true && levels > 1) {
|
|
1001
|
+
const factory = this.asyncCreateShaderModule;
|
|
1002
|
+
if (factory === undefined) {
|
|
1003
|
+
return err(
|
|
1004
|
+
new RhiError({
|
|
1005
|
+
code: 'rhi-not-available',
|
|
1006
|
+
expected: 'asyncCreateShaderModule wired by configureGpuDevice',
|
|
1007
|
+
hint: 'GpuResidencyCache.uploadTexture mipmap branch needs a shader-module factory; rhi-webgpu / rhi-wgpu shims expose it via pack.createShaderModule. Explicit-rhi instances must surface the factory on the RhiInstance.',
|
|
1008
|
+
}),
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
const pipelineRes = await getOrCreateMipmapPipeline(device, tex.format, factory);
|
|
1012
|
+
if (!pipelineRes.ok) {
|
|
1013
|
+
return err(
|
|
1014
|
+
new RhiError({
|
|
1015
|
+
code: 'rhi-not-available',
|
|
1016
|
+
expected: `mipmap pipeline for format ${tex.format} builds`,
|
|
1017
|
+
hint: 'GpuResidencyCache.uploadTexture could not build the mipmap pipeline',
|
|
1018
|
+
}),
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
const blitRes = blitMipmapsSync(device, gpuTexture, {
|
|
1022
|
+
format: tex.format,
|
|
1023
|
+
width: tex.width,
|
|
1024
|
+
height: tex.height,
|
|
1025
|
+
levels,
|
|
1026
|
+
});
|
|
1027
|
+
if (!blitRes.ok) return blitRes as Result<void, RhiError>;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const viewRes = device.createTextureView(gpuTexture, {
|
|
1031
|
+
label: `texture-view-${cacheKey}`,
|
|
1032
|
+
dimension: '2d',
|
|
1033
|
+
});
|
|
1034
|
+
if (!viewRes.ok) return viewRes;
|
|
1035
|
+
const previous = this.textureGpuHandles.get(cacheKey);
|
|
1036
|
+
const next = {
|
|
1037
|
+
texture: this.wrapTex(gpuTexture),
|
|
1038
|
+
view: viewRes.value,
|
|
1039
|
+
};
|
|
1040
|
+
this.textureGpuHandles.set(cacheKey, next);
|
|
1041
|
+
this.materialResourceEpoch += 1;
|
|
1042
|
+
if (
|
|
1043
|
+
previous !== undefined &&
|
|
1044
|
+
previous.texture !== next.texture &&
|
|
1045
|
+
!previous.texture.isDestroyed
|
|
1046
|
+
) {
|
|
1047
|
+
previous.texture.destroy();
|
|
1048
|
+
}
|
|
1049
|
+
return ok(undefined);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Synchronous texture upload used by `ensureResident` (D-9). Identical to
|
|
1054
|
+
* `uploadTexture` except the mipmap pipeline is read from the prewarmed
|
|
1055
|
+
* cache via the synchronous `blitMipmapsSync` -- an un-prewarmed format
|
|
1056
|
+
* returns a structured RhiError instead of awaiting a build.
|
|
1057
|
+
*/
|
|
1058
|
+
private uploadTextureSync(
|
|
1059
|
+
handle: Handle<'TextureAsset', 'shared'>,
|
|
1060
|
+
pod: TextureAsset,
|
|
1061
|
+
decoded: DecodedImage,
|
|
1062
|
+
renderData: TextureRenderData,
|
|
1063
|
+
worldId: World | number = 0,
|
|
1064
|
+
): Result<TextureGpuEntry, AssetError | ImageError | RhiError> {
|
|
1065
|
+
const device = this.gpuDevice;
|
|
1066
|
+
const prepared = this.prepareTextureUpload(handle, pod, decoded, renderData);
|
|
1067
|
+
if (!prepared.ok) return prepared;
|
|
1068
|
+
if (device === undefined || prepared.value.gpuTexture === undefined) {
|
|
1069
|
+
return err(
|
|
1070
|
+
new RhiError({
|
|
1071
|
+
code: 'rhi-not-available',
|
|
1072
|
+
expected: 'GpuResidencyCache.configureGpuDevice wired before ensureResident',
|
|
1073
|
+
hint: 'a texture ensureResident ran before the GPU device was wired; call gpuStore.configureGpuDevice in createRenderer',
|
|
1074
|
+
}),
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
const { tex, levels, gpuTexture } = prepared.value;
|
|
1078
|
+
const cacheKey = this.worldKey(handleSlot(handle), worldId);
|
|
1079
|
+
|
|
1080
|
+
if (!renderData.compressed && decoded.mipmap === true && levels > 1) {
|
|
1081
|
+
const blitRes = blitMipmapsSync(device, gpuTexture, {
|
|
1082
|
+
format: tex.format,
|
|
1083
|
+
width: tex.width,
|
|
1084
|
+
height: tex.height,
|
|
1085
|
+
levels,
|
|
1086
|
+
});
|
|
1087
|
+
if (!blitRes.ok) return blitRes as Result<TextureGpuEntry, RhiError>;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const viewRes = device.createTextureView(gpuTexture, {
|
|
1091
|
+
label: `texture-view-${cacheKey}`,
|
|
1092
|
+
dimension: '2d',
|
|
1093
|
+
});
|
|
1094
|
+
if (!viewRes.ok) return viewRes;
|
|
1095
|
+
const entry: TextureGpuEntry = { texture: this.wrapTex(gpuTexture), view: viewRes.value };
|
|
1096
|
+
const previous = this.textureGpuHandles.get(cacheKey);
|
|
1097
|
+
this.textureGpuHandles.set(cacheKey, entry);
|
|
1098
|
+
this.materialResourceEpoch += 1;
|
|
1099
|
+
if (
|
|
1100
|
+
previous !== undefined &&
|
|
1101
|
+
previous.texture !== entry.texture &&
|
|
1102
|
+
!previous.texture.isDestroyed
|
|
1103
|
+
) {
|
|
1104
|
+
previous.texture.destroy();
|
|
1105
|
+
}
|
|
1106
|
+
return ok(entry);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Shared texture-upload prelude: format / colorSpace consistency assertion,
|
|
1111
|
+
* GPU texture allocation + writeTexture. Returns `gpuTexture: undefined`
|
|
1112
|
+
* (with ok) when no device is wired (deferred path).
|
|
1113
|
+
*/
|
|
1114
|
+
private prepareTextureUpload(
|
|
1115
|
+
handle: Handle<'TextureAsset', 'shared'>,
|
|
1116
|
+
pod: TextureAsset,
|
|
1117
|
+
decoded: DecodedImage,
|
|
1118
|
+
renderData: TextureRenderData,
|
|
1119
|
+
): Result<
|
|
1120
|
+
{ id: number; tex: TextureAsset; levels: number; gpuTexture: Texture | undefined },
|
|
1121
|
+
AssetError | ImageError | RhiError
|
|
1122
|
+
> {
|
|
1123
|
+
const id = handleSlot(handle);
|
|
1124
|
+
// The store does not hold the registry (D-2); the caller-provided POD is
|
|
1125
|
+
// the GPU-format SSOT and `renderData` is its projection (format / usage /
|
|
1126
|
+
// mipLevelCount). `decoded` supplies the pixel bytes + colorSpace; the
|
|
1127
|
+
// assertion below guards the actual upload-source colorSpace against the
|
|
1128
|
+
// projected format (the projection already checked the POD's own colorSpace).
|
|
1129
|
+
const tex = pod;
|
|
1130
|
+
const levels = renderData.mipLevelCount;
|
|
1131
|
+
|
|
1132
|
+
const formatExpected: 'srgb' | 'linear' = renderData.format.endsWith('-srgb')
|
|
1133
|
+
? 'srgb'
|
|
1134
|
+
: 'linear';
|
|
1135
|
+
if (decoded.colorSpace !== formatExpected) {
|
|
1136
|
+
const detail: ImageErrorDetailFor<'image-format-unsupported'> = {
|
|
1137
|
+
code: 'image-format-unsupported',
|
|
1138
|
+
actualMime: decoded.mime,
|
|
1139
|
+
formatColorSpaceConflict: {
|
|
1140
|
+
format: renderData.format,
|
|
1141
|
+
colorSpace: decoded.colorSpace,
|
|
1142
|
+
expected: formatExpected,
|
|
1143
|
+
},
|
|
1144
|
+
};
|
|
1145
|
+
return err(makeImageError(detail));
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const device = this.gpuDevice;
|
|
1149
|
+
if (device === undefined) {
|
|
1150
|
+
return ok({ id, tex, levels, gpuTexture: undefined });
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// WebGL2/wgpu exposes texture limits that are lower than many source
|
|
1154
|
+
// images (for example the 2085px LearnOpenGL Mars texture). Reject the
|
|
1155
|
+
// upload before allocating a handle; binding a texture that the backend
|
|
1156
|
+
// accepted syntactically but cannot use later surfaces only as a generic
|
|
1157
|
+
// invalid-bind-group submit error.
|
|
1158
|
+
const maxDimension = device.limits.maxTextureDimension2D;
|
|
1159
|
+
if (tex.width > maxDimension || tex.height > maxDimension) {
|
|
1160
|
+
return err(
|
|
1161
|
+
makeImageError({
|
|
1162
|
+
code: 'image-dimension-out-of-bounds',
|
|
1163
|
+
requested: { width: tex.width, height: tex.height },
|
|
1164
|
+
limit: maxDimension,
|
|
1165
|
+
}),
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
const createTexRes = device.createTexture({
|
|
1170
|
+
label: `texture-${id}`,
|
|
1171
|
+
size: {
|
|
1172
|
+
width: renderData.physicalExtent.width,
|
|
1173
|
+
height: renderData.physicalExtent.height,
|
|
1174
|
+
depthOrArrayLayers: 1,
|
|
1175
|
+
},
|
|
1176
|
+
mipLevelCount: levels,
|
|
1177
|
+
sampleCount: 1,
|
|
1178
|
+
dimension: '2d',
|
|
1179
|
+
format: renderData.format,
|
|
1180
|
+
usage: renderData.usage,
|
|
1181
|
+
viewFormats: [],
|
|
1182
|
+
textureBindingViewDimension: undefined,
|
|
1183
|
+
});
|
|
1184
|
+
if (!createTexRes.ok) return createTexRes;
|
|
1185
|
+
const gpuTexture = createTexRes.value;
|
|
1186
|
+
const queue = device.queue;
|
|
1187
|
+
|
|
1188
|
+
if (renderData.compressed) {
|
|
1189
|
+
// feat-20260707 M5 / w36 (AC-08): block-compressed textures carry their
|
|
1190
|
+
// mip chain in `data` (offline-baked -- the GPU cannot generate compressed
|
|
1191
|
+
// mips). Upload each level with its own block-padded bytesPerRow /
|
|
1192
|
+
// rowsPerImage and mip-major byte offset (deriveMipUploadLayout, SSOT
|
|
1193
|
+
// block math). Full-subresource copies retain their logical size, including
|
|
1194
|
+
// non-block-aligned dimensions. queue.writeTexture avoids the 256 B
|
|
1195
|
+
// copyBufferToTexture alignment trap.
|
|
1196
|
+
const layout = deriveMipUploadLayout(renderData.format, tex.width, tex.height, levels);
|
|
1197
|
+
for (const lvl of layout) {
|
|
1198
|
+
const slice = decoded.bytes.subarray(lvl.byteOffset, lvl.byteOffset + lvl.byteLength);
|
|
1199
|
+
const writeRes = queue.writeTexture(
|
|
1200
|
+
{ texture: gpuTexture, mipLevel: lvl.level, origin: { x: 0, y: 0, z: 0 } },
|
|
1201
|
+
slice,
|
|
1202
|
+
{ offset: 0, bytesPerRow: lvl.bytesPerRow, rowsPerImage: lvl.rowsPerImage },
|
|
1203
|
+
{ width: lvl.copyWidth, height: lvl.copyHeight, depthOrArrayLayers: 1 },
|
|
1204
|
+
);
|
|
1205
|
+
if (!writeRes.ok) return writeRes;
|
|
1206
|
+
}
|
|
1207
|
+
return ok({ id, tex, levels, gpuTexture });
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Uncompressed base-mip write; higher mips (when mipmap) are GPU-generated by
|
|
1211
|
+
// the caller's blit pass. rowsPerImage is the pixel height for linear RGBA.
|
|
1212
|
+
const bytesPerRow = renderData.bytesPerRow;
|
|
1213
|
+
const writeTextureRes = queue.writeTexture(
|
|
1214
|
+
{ texture: gpuTexture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
|
|
1215
|
+
decoded.bytes,
|
|
1216
|
+
{ offset: 0, bytesPerRow, rowsPerImage: tex.height },
|
|
1217
|
+
{ width: tex.width, height: tex.height, depthOrArrayLayers: 1 },
|
|
1218
|
+
);
|
|
1219
|
+
if (!writeTextureRes.ok) return writeTextureRes;
|
|
1220
|
+
|
|
1221
|
+
return ok({ id, tex, levels, gpuTexture });
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Project an equirectangular HDR `EquirectAsset` into a GPU cubemap + IBL
|
|
1226
|
+
* precompute. NOT part of the `ensureResident` pull path.
|
|
1227
|
+
*
|
|
1228
|
+
* @internal — feat-20260630 D-3 / F-9: the cubemap projection is engine
|
|
1229
|
+
* internals, not a user surface. AI users declare `Skylight{equirect}` and
|
|
1230
|
+
* the render-system record arm (same package) drives this method per-frame;
|
|
1231
|
+
* the `_` prefix + `@internal` mark it package-internal (lint:internal gate)
|
|
1232
|
+
* so it never appears as a user-facing call. (A cross-file `private` is not
|
|
1233
|
+
* reachable by the record arm; package-internal is the correct visibility.)
|
|
1234
|
+
*
|
|
1235
|
+
* Idempotent: a second call with the same source handle returns the cached
|
|
1236
|
+
* cubemap handle (no second GPU texture). The minted cubemap handle is an
|
|
1237
|
+
* `EquirectAsset` shared ref catalogued via the injected `registerCube` relay
|
|
1238
|
+
* (D-3); the store never imports AssetRegistry.
|
|
1239
|
+
*
|
|
1240
|
+
* Status (D-3): every entry written carries `status`. A failed projection
|
|
1241
|
+
* records `status:'failed'` EXPLICITLY (R-2 / AC-09) so the caller never
|
|
1242
|
+
* retries by inferring "no entry => try again".
|
|
1243
|
+
*/
|
|
1244
|
+
async _uploadCubemapFromEquirect(
|
|
1245
|
+
world: World,
|
|
1246
|
+
sourceHandle: Handle<'EquirectAsset', 'shared'>,
|
|
1247
|
+
sourcePod: EquirectAsset,
|
|
1248
|
+
): Promise<Result<Handle<'EquirectAsset', 'shared'>, AssetError | RhiError>> {
|
|
1249
|
+
const sourceId = handleSlot(sourceHandle);
|
|
1250
|
+
|
|
1251
|
+
const existing = this.cubemapIdempotentMap.get(sourceId);
|
|
1252
|
+
if (existing !== undefined) {
|
|
1253
|
+
return ok(existing);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// R-2 / AC-09: a previously-failed projection is recorded EXPLICITLY as a
|
|
1257
|
+
// 'failed' entry. Short-circuit here so the record arm never retries the
|
|
1258
|
+
// upload every frame (the missing-key inference would loop forever).
|
|
1259
|
+
if (this.cubemapGpuHandles.get(sourceId)?.status === 'failed') {
|
|
1260
|
+
return err(
|
|
1261
|
+
new RhiError({
|
|
1262
|
+
code: 'feature-not-enabled',
|
|
1263
|
+
expected: 'a prior cubemap projection for this equirect source did not fail',
|
|
1264
|
+
hint: 'this equirect source already failed projection; the record arm must not retry (R-2). Inspect the original failure on the error channel',
|
|
1265
|
+
}),
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// feat-20260630 M3 / w18: a projection already in flight is marked
|
|
1270
|
+
// 'pending' synchronously below (before the first await). A re-entry while
|
|
1271
|
+
// pending short-circuits so the fire-and-forget record arm launches the
|
|
1272
|
+
// async projection exactly once per source (D-4 idempotent; the record arm
|
|
1273
|
+
// calls this every frame until status flips to ready/failed). The pending
|
|
1274
|
+
// entry holds no live GPU resources yet; the record arm binds the white
|
|
1275
|
+
// fallback while pending.
|
|
1276
|
+
if (this.cubemapGpuHandles.get(sourceId)?.status === 'pending') {
|
|
1277
|
+
return ok(sourceHandle);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Record a failed projection explicitly so a re-query short-circuits and the
|
|
1281
|
+
// record arm never retries every frame (R-2 / AC-09). The placeholder
|
|
1282
|
+
// texture wrapper is never bound (a 'failed' entry has no live GPU view).
|
|
1283
|
+
const recordFailed = <E>(error: E): Result<never, E> => {
|
|
1284
|
+
this.cubemapGpuHandles.set(sourceId, {
|
|
1285
|
+
status: 'failed',
|
|
1286
|
+
texture: null,
|
|
1287
|
+
view: undefined,
|
|
1288
|
+
faceViews: [],
|
|
1289
|
+
});
|
|
1290
|
+
return err(error);
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
const registerCube = this.registerCube;
|
|
1294
|
+
if (registerCube === undefined) {
|
|
1295
|
+
return recordFailed(
|
|
1296
|
+
makeAssetError({
|
|
1297
|
+
code: 'asset-not-found',
|
|
1298
|
+
expected: 'GpuResidencyCache.configureGpuDevice wired with registerCube',
|
|
1299
|
+
hint: 'call gpuStore.configureGpuDevice(device, factory, registerCube) before _uploadCubemapFromEquirect',
|
|
1300
|
+
}),
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (sourcePod.kind !== 'equirect') {
|
|
1305
|
+
return recordFailed(
|
|
1306
|
+
makeAssetError({
|
|
1307
|
+
code: 'asset-not-found',
|
|
1308
|
+
expected: `source POD for handle id ${sourceId} is an EquirectAsset`,
|
|
1309
|
+
hint: ASSET_ERROR_HINTS['asset-not-found'],
|
|
1310
|
+
}),
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// feat-20260630 M3 / w18: mark the source 'pending' SYNCHRONOUSLY (this runs
|
|
1315
|
+
// before the first `await` below, so the fire-and-forget record arm that
|
|
1316
|
+
// never awaits still observes the pending entry on the next frame's
|
|
1317
|
+
// getCubemapStatus). All sync fail-fast gates (idempotent / failed / pending
|
|
1318
|
+
// / registerCube / caps / kind) have passed, so the projection is committed;
|
|
1319
|
+
// a re-entry while async work is in flight short-circuits on the 'pending'
|
|
1320
|
+
// guard above. The entry holds no live GPU resources yet (record binds the
|
|
1321
|
+
// white fallback while pending); the texture/views are filled in below when
|
|
1322
|
+
// the projection completes (status flips to 'ready'), or replaced by a
|
|
1323
|
+
// 'failed' entry via recordFailed if a later step errors.
|
|
1324
|
+
this.cubemapGpuHandles.set(sourceId, {
|
|
1325
|
+
status: 'pending',
|
|
1326
|
+
texture: null,
|
|
1327
|
+
view: undefined,
|
|
1328
|
+
faceViews: [],
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
// The cubemap projection + IBL precompute consume a 2D-image view of the
|
|
1332
|
+
// source. EquirectAsset mirrors the TextureAsset 2D surface (width / height
|
|
1333
|
+
// / format / data / colorSpace) but has no CPU mip chain, so the derived
|
|
1334
|
+
// texture view declares `mipmap:false` (the IBL prefilter mip chain is a
|
|
1335
|
+
// GPU-side pass, not a CPU-authored level set).
|
|
1336
|
+
const sourceTex: TextureAsset = {
|
|
1337
|
+
kind: 'texture',
|
|
1338
|
+
width: sourcePod.width,
|
|
1339
|
+
height: sourcePod.height,
|
|
1340
|
+
format: sourcePod.format,
|
|
1341
|
+
data: sourcePod.data,
|
|
1342
|
+
colorSpace: sourcePod.colorSpace,
|
|
1343
|
+
mipmap: false,
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1346
|
+
// Project the equirect source POD into the cubemap descriptor (D-5): the
|
|
1347
|
+
// asset-semantic decisions (format / colorSpace validation, square cube
|
|
1348
|
+
// face size, rgba32f->rgba16f narrowing) live in the pure projection; the
|
|
1349
|
+
// resource build (texture / views / IBL precompute) + the byte conversion
|
|
1350
|
+
// stay here in the store.
|
|
1351
|
+
const projected: CubeRenderData | undefined = (() => {
|
|
1352
|
+
const p = deriveRenderDataCubemap(sourceTex);
|
|
1353
|
+
return p.ok ? p.value : undefined;
|
|
1354
|
+
})();
|
|
1355
|
+
if (projected === undefined) {
|
|
1356
|
+
return recordFailed(
|
|
1357
|
+
makeAssetError({
|
|
1358
|
+
code: 'invalid-source-format',
|
|
1359
|
+
expected: "format 'rgba16float' or 'rgba32float' with colorSpace 'linear'",
|
|
1360
|
+
hint: ASSET_ERROR_HINTS['invalid-source-format'],
|
|
1361
|
+
}),
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const tex: TextureAsset = projected.needsHalfConversion
|
|
1366
|
+
? {
|
|
1367
|
+
kind: 'texture',
|
|
1368
|
+
width: sourceTex.width,
|
|
1369
|
+
height: sourceTex.height,
|
|
1370
|
+
format: projected.outputFormat,
|
|
1371
|
+
data: halfFloat.f32ToF16Bytes(
|
|
1372
|
+
sourceTex.data instanceof Uint8ClampedArray
|
|
1373
|
+
? new Uint8Array(
|
|
1374
|
+
sourceTex.data.buffer,
|
|
1375
|
+
sourceTex.data.byteOffset,
|
|
1376
|
+
sourceTex.data.byteLength,
|
|
1377
|
+
)
|
|
1378
|
+
: sourceTex.data,
|
|
1379
|
+
),
|
|
1380
|
+
colorSpace: 'linear',
|
|
1381
|
+
mipmap: false,
|
|
1382
|
+
}
|
|
1383
|
+
: sourceTex;
|
|
1384
|
+
|
|
1385
|
+
const cubeFaceSize = projected.cubeFaceSize;
|
|
1386
|
+
// The source equirect stays float for filtering, but WebGL2 cannot render
|
|
1387
|
+
// rgba16float attachments. Downlevel only the precompute outputs to the
|
|
1388
|
+
// universally renderable rgba8 path; the same format is threaded through
|
|
1389
|
+
// the cubemap and all three IBL side textures by IblPipelineCache.
|
|
1390
|
+
const outputFormat = this.caps?.rgba16floatRenderable === false ? 'rgba8unorm' : tex.format;
|
|
1391
|
+
|
|
1392
|
+
// Lazy projection runs from the record arm, where the RHI device is always
|
|
1393
|
+
// wired (research R-5). The IBL path is deliberately independent from the
|
|
1394
|
+
// ordinary mipmap fixture seam: it accepts only the opaque RhiDevice and
|
|
1395
|
+
// its Result-returning shader factory, never a raw GPUDevice or unwrap
|
|
1396
|
+
// fallback.
|
|
1397
|
+
const device = this.iblDevice;
|
|
1398
|
+
const shaderModuleFactory = this.iblShaderModuleFactory;
|
|
1399
|
+
const scope = this.deviceScope;
|
|
1400
|
+
if (device === undefined || shaderModuleFactory === undefined || scope === undefined) {
|
|
1401
|
+
return recordFailed(
|
|
1402
|
+
new RhiError({
|
|
1403
|
+
code: 'rhi-not-available',
|
|
1404
|
+
expected:
|
|
1405
|
+
'GpuResidencyCache is configured with an RhiDevice, shader factory, and DeviceScope',
|
|
1406
|
+
hint: 'bind the renderer DeviceScope before a cubemap projection; raw GPU fallback is not supported',
|
|
1407
|
+
}),
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// Usage flags for the small equirect helper texture (sampled source for the
|
|
1412
|
+
// IBL precompute pass); the cube texture's usage comes from the projection.
|
|
1413
|
+
const gpuTextureResult = device.createTexture({
|
|
1414
|
+
label: `cubemap-${sourceId}`,
|
|
1415
|
+
size: { width: cubeFaceSize, height: cubeFaceSize, depthOrArrayLayers: 6 },
|
|
1416
|
+
mipLevelCount: 1,
|
|
1417
|
+
sampleCount: 1,
|
|
1418
|
+
dimension: '2d',
|
|
1419
|
+
format: outputFormat,
|
|
1420
|
+
usage: projected.cubeUsage,
|
|
1421
|
+
viewFormats: [],
|
|
1422
|
+
textureBindingViewDimension: undefined,
|
|
1423
|
+
});
|
|
1424
|
+
if (!gpuTextureResult.ok) return recordFailed(gpuTextureResult.error);
|
|
1425
|
+
const gpuTexture = gpuTextureResult.value;
|
|
1426
|
+
|
|
1427
|
+
const cubeViewResult = device.createTextureView(gpuTexture, {
|
|
1428
|
+
label: `cubemap-view-${sourceId}`,
|
|
1429
|
+
dimension: 'cube',
|
|
1430
|
+
arrayLayerCount: 6,
|
|
1431
|
+
});
|
|
1432
|
+
if (!cubeViewResult.ok) return recordFailed(cubeViewResult.error);
|
|
1433
|
+
const cubeView = cubeViewResult.value;
|
|
1434
|
+
|
|
1435
|
+
const faceViews: TextureView[] = [];
|
|
1436
|
+
for (let face = 0; face < 6; face++) {
|
|
1437
|
+
const faceViewResult = device.createTextureView(gpuTexture, {
|
|
1438
|
+
label: `cubemap-face-${sourceId}-${face}`,
|
|
1439
|
+
dimension: '2d',
|
|
1440
|
+
baseArrayLayer: face,
|
|
1441
|
+
arrayLayerCount: 1,
|
|
1442
|
+
});
|
|
1443
|
+
if (!faceViewResult.ok) return recordFailed(faceViewResult.error);
|
|
1444
|
+
faceViews.push(faceViewResult.value);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// M-3 / w11: a single GpuTexture wrapper is shared between sourceId
|
|
1448
|
+
// and cubeId entries (the underlying RHI handle is the same physical
|
|
1449
|
+
// texture). destroyAll() walks each entry, so attempting to destroy
|
|
1450
|
+
// twice via two wrappers would surface 'destroy-after-destroy' on
|
|
1451
|
+
// the second pass; sharing one wrapper keeps the destroy chain
|
|
1452
|
+
// surfacing exactly one destroy call per physical resource.
|
|
1453
|
+
const gpuTextureWrapper = this.wrapTex(gpuTexture);
|
|
1454
|
+
|
|
1455
|
+
this.cubemapGpuHandles.set(sourceId, {
|
|
1456
|
+
status: 'ready',
|
|
1457
|
+
texture: gpuTextureWrapper,
|
|
1458
|
+
view: cubeView,
|
|
1459
|
+
faceViews,
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
// The minted cubemap handle is a synthetic shared ref (identity token for
|
|
1463
|
+
// the GPU cube residency); its POD is an EquirectAsset placeholder matching
|
|
1464
|
+
// the projected dimensions/format (D-3: the retired cube-texture asset kind
|
|
1465
|
+
// is gone; the relay mints an EquirectAsset shared ref instead).
|
|
1466
|
+
const cubeAsset: EquirectAsset = {
|
|
1467
|
+
kind: 'equirect',
|
|
1468
|
+
width: cubeFaceSize,
|
|
1469
|
+
height: cubeFaceSize,
|
|
1470
|
+
format: tex.format,
|
|
1471
|
+
data: tex.data,
|
|
1472
|
+
colorSpace: 'linear',
|
|
1473
|
+
};
|
|
1474
|
+
const regResult = registerCube(world, cubeAsset);
|
|
1475
|
+
if (!regResult.ok) return recordFailed(regResult.error);
|
|
1476
|
+
const cubeHandle = regResult.value;
|
|
1477
|
+
const cubeId = handleSlot(cubeHandle);
|
|
1478
|
+
|
|
1479
|
+
this.cubemapGpuHandles.set(cubeId, {
|
|
1480
|
+
status: 'ready',
|
|
1481
|
+
texture: gpuTextureWrapper,
|
|
1482
|
+
view: cubeView,
|
|
1483
|
+
faceViews,
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
this.cubemapIdempotentMap.set(sourceId, cubeHandle);
|
|
1487
|
+
|
|
1488
|
+
const pipelinesResult = await createIblPipelines(
|
|
1489
|
+
scope,
|
|
1490
|
+
device,
|
|
1491
|
+
shaderModuleFactory,
|
|
1492
|
+
outputFormat,
|
|
1493
|
+
);
|
|
1494
|
+
if (!pipelinesResult.ok) {
|
|
1495
|
+
return recordFailed(
|
|
1496
|
+
makeAssetError({
|
|
1497
|
+
code: 'ibl-precompute-not-dispatched',
|
|
1498
|
+
expected: pipelinesResult.error.expected,
|
|
1499
|
+
hint: pipelinesResult.error.hint,
|
|
1500
|
+
}),
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const equirectTextureResult = device.createTexture({
|
|
1505
|
+
label: `equirect-${sourceId}`,
|
|
1506
|
+
size: { width: tex.width, height: tex.height, depthOrArrayLayers: 1 },
|
|
1507
|
+
mipLevelCount: 1,
|
|
1508
|
+
sampleCount: 1,
|
|
1509
|
+
dimension: '2d',
|
|
1510
|
+
format: tex.format,
|
|
1511
|
+
usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_DST,
|
|
1512
|
+
viewFormats: [],
|
|
1513
|
+
textureBindingViewDimension: undefined,
|
|
1514
|
+
});
|
|
1515
|
+
if (!equirectTextureResult.ok) return recordFailed(equirectTextureResult.error);
|
|
1516
|
+
const equirectGpuTex = equirectTextureResult.value;
|
|
1517
|
+
|
|
1518
|
+
const equirectViewResult = device.createTextureView(equirectGpuTex, {
|
|
1519
|
+
label: `equirect-view-${sourceId}`,
|
|
1520
|
+
dimension: '2d',
|
|
1521
|
+
});
|
|
1522
|
+
if (!equirectViewResult.ok) return recordFailed(equirectViewResult.error);
|
|
1523
|
+
const equirectView = equirectViewResult.value;
|
|
1524
|
+
|
|
1525
|
+
const bytesPerPixel = tex.format === 'rgba32float' ? 16 : tex.format === 'rgba16float' ? 8 : 4;
|
|
1526
|
+
const equirectWriteResult = device.queue.writeTexture(
|
|
1527
|
+
{ texture: equirectGpuTex },
|
|
1528
|
+
tex.data,
|
|
1529
|
+
{ bytesPerRow: tex.width * bytesPerPixel, rowsPerImage: tex.height },
|
|
1530
|
+
{ width: tex.width, height: tex.height, depthOrArrayLayers: 1 },
|
|
1531
|
+
);
|
|
1532
|
+
if (!equirectWriteResult.ok) return recordFailed(equirectWriteResult.error);
|
|
1533
|
+
|
|
1534
|
+
const faceBufferResult = createFaceUniformsBuffer(device);
|
|
1535
|
+
if (!faceBufferResult.ok) return recordFailed(faceBufferResult.error);
|
|
1536
|
+
const faceBuf = faceBufferResult.value;
|
|
1537
|
+
const prefilterBufferResult = createPrefilterUniformsBuffer(device);
|
|
1538
|
+
if (!prefilterBufferResult.ok) return recordFailed(prefilterBufferResult.error);
|
|
1539
|
+
const prefBuf = prefilterBufferResult.value;
|
|
1540
|
+
|
|
1541
|
+
const cubeVertexResult = device.createBuffer({
|
|
1542
|
+
label: 'ibl-cube-verts',
|
|
1543
|
+
size: CUBEMAP_FACE_VERTICES.byteLength,
|
|
1544
|
+
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
1545
|
+
mappedAtCreation: false,
|
|
1546
|
+
});
|
|
1547
|
+
if (!cubeVertexResult.ok) return recordFailed(cubeVertexResult.error);
|
|
1548
|
+
const cubeVertex = cubeVertexResult.value;
|
|
1549
|
+
|
|
1550
|
+
const cubeVertexWriteResult = device.queue.writeBuffer(cubeVertex, 0, CUBEMAP_FACE_VERTICES);
|
|
1551
|
+
if (!cubeVertexWriteResult.ok) return recordFailed(cubeVertexWriteResult.error);
|
|
1552
|
+
const faceUniformsResult = writeAllFaceUniforms(device, faceBuf);
|
|
1553
|
+
if (!faceUniformsResult.ok) return recordFailed(faceUniformsResult.error);
|
|
1554
|
+
const prefilterUniformsResult = writeAllPrefilterUniforms(device, prefBuf);
|
|
1555
|
+
if (!prefilterUniformsResult.ok) return recordFailed(prefilterUniformsResult.error);
|
|
1556
|
+
|
|
1557
|
+
const runResult = runIblPrecompute({
|
|
1558
|
+
scope,
|
|
1559
|
+
device,
|
|
1560
|
+
equirectGpuTex,
|
|
1561
|
+
equirectView,
|
|
1562
|
+
cubeGpuTex: gpuTexture,
|
|
1563
|
+
cubeView,
|
|
1564
|
+
cubeFaceViews: faceViews,
|
|
1565
|
+
faceUniformsBuffer: faceBuf,
|
|
1566
|
+
prefilterUniformsBuffer: prefBuf,
|
|
1567
|
+
cubeVertexBuffer: cubeVertex,
|
|
1568
|
+
});
|
|
1569
|
+
if (!runResult.ok) {
|
|
1570
|
+
return recordFailed(
|
|
1571
|
+
makeAssetError({
|
|
1572
|
+
code: 'ibl-precompute-not-dispatched',
|
|
1573
|
+
expected: runResult.error.expected,
|
|
1574
|
+
hint: runResult.error.hint,
|
|
1575
|
+
}),
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
return ok(cubeHandle);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* Upload `MeshAsset.vertices` + `MeshAsset.indices` to GPU buffers and cache
|
|
1584
|
+
* the handles under the asset id. Synchronous; mirrors the pre-extraction
|
|
1585
|
+
* `uploadMeshById` byte-for-byte. Returns the cached entry on success.
|
|
1586
|
+
*/
|
|
1587
|
+
private uploadMeshById(
|
|
1588
|
+
id: string | number,
|
|
1589
|
+
mesh: TypesMeshAsset,
|
|
1590
|
+
renderData: MeshRenderData,
|
|
1591
|
+
): Result<MeshGpuHandles, RhiError> {
|
|
1592
|
+
const device = this.gpuDevice;
|
|
1593
|
+
if (device === undefined) {
|
|
1594
|
+
return err(
|
|
1595
|
+
new RhiError({
|
|
1596
|
+
code: 'rhi-not-available',
|
|
1597
|
+
expected: 'GpuResidencyCache.configureGpuDevice wired before mesh ensureResident',
|
|
1598
|
+
hint: 'a mesh ensureResident ran before the GPU device was wired; call gpuStore.configureGpuDevice in createRenderer',
|
|
1599
|
+
}),
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
const indices = mesh.indices;
|
|
1604
|
+
const indexBytesUnpadded = indices === undefined ? 0 : indices.byteLength;
|
|
1605
|
+
const indexBytes = renderData.indexByteLength;
|
|
1606
|
+
|
|
1607
|
+
const vboResult = device.createBuffer({
|
|
1608
|
+
label: `mesh-${id}-vbo`,
|
|
1609
|
+
size: renderData.vertexByteLength,
|
|
1610
|
+
usage: renderData.vertexUsage,
|
|
1611
|
+
mappedAtCreation: false,
|
|
1612
|
+
});
|
|
1613
|
+
if (!vboResult.ok) return vboResult;
|
|
1614
|
+
const vbo = vboResult.value;
|
|
1615
|
+
// Vertex-only mesh: skip the index buffer entirely. The indexed path below
|
|
1616
|
+
// is unchanged byte-for-byte when `indices` is present.
|
|
1617
|
+
let ibo: Buffer | null = null;
|
|
1618
|
+
if (indices !== undefined) {
|
|
1619
|
+
const iboResult = device.createBuffer({
|
|
1620
|
+
label: `mesh-${id}-ibo`,
|
|
1621
|
+
size: indexBytes,
|
|
1622
|
+
usage: renderData.indexUsage,
|
|
1623
|
+
mappedAtCreation: false,
|
|
1624
|
+
});
|
|
1625
|
+
if (!iboResult.ok) return iboResult;
|
|
1626
|
+
ibo = iboResult.value;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
const vboWriteResult = device.queue.writeBuffer(vbo, 0, mesh.vertices);
|
|
1630
|
+
if (!vboWriteResult.ok) return err(vboWriteResult.error);
|
|
1631
|
+
|
|
1632
|
+
if (indices !== undefined) {
|
|
1633
|
+
const indexSrc = new Uint8Array(indexBytes);
|
|
1634
|
+
indexSrc.set(new Uint8Array(indices.buffer, indices.byteOffset, indexBytesUnpadded));
|
|
1635
|
+
if (ibo === null)
|
|
1636
|
+
return err(
|
|
1637
|
+
new RhiError({
|
|
1638
|
+
code: 'webgpu-runtime-error',
|
|
1639
|
+
expected: `mesh-${id} index buffer to be allocated before its upload`,
|
|
1640
|
+
hint: 'the indexed mesh upload lost its RHI index-buffer owner',
|
|
1641
|
+
}),
|
|
1642
|
+
);
|
|
1643
|
+
const iboWriteResult = device.queue.writeBuffer(ibo, 0, indexSrc);
|
|
1644
|
+
if (!iboWriteResult.ok) return err(iboWriteResult.error);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// The geometry projection already contains every packed attribute,
|
|
1648
|
+
// including skin and additional UV sets. Dividing by a second
|
|
1649
|
+
// attribute-derived count would double-count those fields and produce a
|
|
1650
|
+
// fractional vertex count for otherwise valid meshes.
|
|
1651
|
+
const extraUvSets = countExtraUvSets(mesh.attributes);
|
|
1652
|
+
const entry: MeshGpuHandles = {
|
|
1653
|
+
vertexBuffer: this.wrapBuf(vbo),
|
|
1654
|
+
indexBuffer: ibo === null ? null : this.wrapBuf(ibo),
|
|
1655
|
+
vboBytes: renderData.vertexByteLength,
|
|
1656
|
+
iboBytes: indices === undefined ? 0 : indexBytes,
|
|
1657
|
+
indexCount: renderData.indexCount,
|
|
1658
|
+
indexFormat: renderData.indexFormat,
|
|
1659
|
+
layout: renderData.layoutProjection.attributes.some(
|
|
1660
|
+
(attribute) => attribute.key === 'skinIndex' || attribute.key === 'skinWeight',
|
|
1661
|
+
)
|
|
1662
|
+
? '18F'
|
|
1663
|
+
: '12F',
|
|
1664
|
+
layoutProjection: renderData.layoutProjection,
|
|
1665
|
+
uvSetCount: 1 + extraUvSets,
|
|
1666
|
+
vertexCount: mesh.vertices.byteLength / renderData.layoutProjection.arrayStride,
|
|
1667
|
+
indexed: indices !== undefined,
|
|
1668
|
+
topology: renderData.submeshes[0]?.topology ?? 'triangle-list',
|
|
1669
|
+
submeshes: renderData.submeshes,
|
|
1670
|
+
};
|
|
1671
|
+
this.meshGpuHandles.set(id, entry);
|
|
1672
|
+
this.meshResidencyEpoch += 1;
|
|
1673
|
+
return ok(entry);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/**
|
|
1677
|
+
* Update an existing mesh's GPU buffer data in-place (or expand). The handle
|
|
1678
|
+
* must already be resident. Mirrors the pre-extraction `updateMeshById`.
|
|
1679
|
+
*/
|
|
1680
|
+
private updateMeshById(
|
|
1681
|
+
id: string | number,
|
|
1682
|
+
newVertices: Float32Array,
|
|
1683
|
+
newIndices: Uint16Array,
|
|
1684
|
+
submeshes?: readonly Submesh[],
|
|
1685
|
+
): void {
|
|
1686
|
+
const device = this.gpuDevice;
|
|
1687
|
+
if (device === undefined) return;
|
|
1688
|
+
const entry = this.meshGpuHandles.get(id);
|
|
1689
|
+
if (entry === undefined) return;
|
|
1690
|
+
|
|
1691
|
+
const newVertexBytes = newVertices.byteLength;
|
|
1692
|
+
const indexBytesUnpadded = newIndices.byteLength;
|
|
1693
|
+
const newIndexBytes = ((indexBytesUnpadded + 3) >> 2) << 2;
|
|
1694
|
+
|
|
1695
|
+
// M-3 / w11: read allocation bytes from the entry's vboBytes / iboBytes
|
|
1696
|
+
// (tracked at upload time) instead of reaching into the opaque RHI Buffer
|
|
1697
|
+
// for a `.size` property -- the RHI Buffer interface is spec-aligned and
|
|
1698
|
+
// does NOT expose `.size`; allocation bytes remain derived cache facts.
|
|
1699
|
+
const existingIbo = entry.indexBuffer;
|
|
1700
|
+
const nextSubmeshes = submeshes ?? entry.submeshes;
|
|
1701
|
+
if (
|
|
1702
|
+
existingIbo !== null &&
|
|
1703
|
+
newVertexBytes <= entry.vboBytes &&
|
|
1704
|
+
newIndexBytes <= entry.iboBytes
|
|
1705
|
+
) {
|
|
1706
|
+
const vboWriteRet = device.queue.writeBuffer(entry.vertexBuffer.handle, 0, newVertices);
|
|
1707
|
+
if (!vboWriteRet.ok) return;
|
|
1708
|
+
|
|
1709
|
+
const indexSrc = new Uint8Array(newIndexBytes);
|
|
1710
|
+
indexSrc.set(new Uint8Array(newIndices.buffer, newIndices.byteOffset, indexBytesUnpadded));
|
|
1711
|
+
const iboWriteRet = device.queue.writeBuffer(existingIbo.handle, 0, indexSrc);
|
|
1712
|
+
if (!iboWriteRet.ok) return;
|
|
1713
|
+
|
|
1714
|
+
this.meshGpuHandles.set(id, {
|
|
1715
|
+
vertexBuffer: entry.vertexBuffer,
|
|
1716
|
+
indexBuffer: existingIbo,
|
|
1717
|
+
vboBytes: entry.vboBytes,
|
|
1718
|
+
iboBytes: entry.iboBytes,
|
|
1719
|
+
indexCount: newIndices.length,
|
|
1720
|
+
indexFormat: 'uint16',
|
|
1721
|
+
layout: '12F',
|
|
1722
|
+
layoutProjection: entry.layoutProjection,
|
|
1723
|
+
uvSetCount: entry.uvSetCount,
|
|
1724
|
+
vertexCount: newVertices.length / PROCEDURAL_FLOATS_PER_VERTEX,
|
|
1725
|
+
indexed: true,
|
|
1726
|
+
topology: entry.topology,
|
|
1727
|
+
submeshes: nextSubmeshes,
|
|
1728
|
+
});
|
|
1729
|
+
this.meshResidencyEpoch += 1;
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
const vboResult = device.createBuffer({
|
|
1734
|
+
label: `mesh-${id}-vbo`,
|
|
1735
|
+
size: newVertexBytes,
|
|
1736
|
+
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
1737
|
+
mappedAtCreation: false,
|
|
1738
|
+
});
|
|
1739
|
+
if (!vboResult.ok) return;
|
|
1740
|
+
const vbo = vboResult.value;
|
|
1741
|
+
const iboResult = device.createBuffer({
|
|
1742
|
+
label: `mesh-${id}-ibo`,
|
|
1743
|
+
size: newIndexBytes,
|
|
1744
|
+
usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
1745
|
+
mappedAtCreation: false,
|
|
1746
|
+
});
|
|
1747
|
+
if (!iboResult.ok) return;
|
|
1748
|
+
const ibo = iboResult.value;
|
|
1749
|
+
|
|
1750
|
+
const vboWriteResult = device.queue.writeBuffer(vbo, 0, newVertices);
|
|
1751
|
+
if (!vboWriteResult.ok) return;
|
|
1752
|
+
|
|
1753
|
+
const indexSrc = new Uint8Array(newIndexBytes);
|
|
1754
|
+
indexSrc.set(new Uint8Array(newIndices.buffer, newIndices.byteOffset, indexBytesUnpadded));
|
|
1755
|
+
const iboWriteResult = device.queue.writeBuffer(ibo, 0, indexSrc);
|
|
1756
|
+
if (!iboWriteResult.ok) return;
|
|
1757
|
+
|
|
1758
|
+
this.meshGpuHandles.set(id, {
|
|
1759
|
+
vertexBuffer: this.wrapBuf(vbo),
|
|
1760
|
+
indexBuffer: this.wrapBuf(ibo),
|
|
1761
|
+
vboBytes: newVertexBytes,
|
|
1762
|
+
iboBytes: newIndexBytes,
|
|
1763
|
+
indexCount: newIndices.length,
|
|
1764
|
+
indexFormat: 'uint16',
|
|
1765
|
+
layout: '12F',
|
|
1766
|
+
layoutProjection: entry.layoutProjection,
|
|
1767
|
+
uvSetCount: entry.uvSetCount,
|
|
1768
|
+
vertexCount: newVertices.length / PROCEDURAL_FLOATS_PER_VERTEX,
|
|
1769
|
+
indexed: true,
|
|
1770
|
+
topology: entry.topology,
|
|
1771
|
+
submeshes: nextSubmeshes,
|
|
1772
|
+
});
|
|
1773
|
+
this.meshResidencyEpoch += 1;
|
|
1774
|
+
|
|
1775
|
+
// M-3 / w11: release the replaced buffers through their structured
|
|
1776
|
+
// GpuBuffer owner rather than reaching into opaque handles with a
|
|
1777
|
+
// backend-specific property.
|
|
1778
|
+
// `GpuBuffer.destroy()` routes through
|
|
1779
|
+
// `device.destroyBuffer(handle)` (RHI shim is the lifecycle SSOT;
|
|
1780
|
+
// architecture-principles §1 / charter §F1). Errors are swallowed here:
|
|
1781
|
+
// updateMeshById is the in-place reallocation path; the structured fail
|
|
1782
|
+
// surfaces on subsequent buffer use rather than blocking the resize.
|
|
1783
|
+
entry.vertexBuffer.destroy();
|
|
1784
|
+
if (existingIbo !== null) existingIbo.destroy();
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
/**
|
|
1788
|
+
* Public surface for updating an existing unmanaged mesh handle's GPU buffer
|
|
1789
|
+
* data in-place. The handle must have been made resident.
|
|
1790
|
+
*/
|
|
1791
|
+
updateMesh(
|
|
1792
|
+
handle: Handle<'MeshAsset', 'shared'>,
|
|
1793
|
+
newVertices: Float32Array,
|
|
1794
|
+
newIndices: Uint16Array,
|
|
1795
|
+
worldId: World | number = 0,
|
|
1796
|
+
submeshes?: readonly Submesh[],
|
|
1797
|
+
): void {
|
|
1798
|
+
const id = this.worldKey(handleSlot(handle), worldId);
|
|
1799
|
+
this.updateMeshById(id, newVertices, newIndices, submeshes);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* Synthesize the `DecodedImage` view the texture-upload prelude consumes from
|
|
1805
|
+
* a `TextureAsset` POD (the record-stage pull path holds only the POD). The
|
|
1806
|
+
* POD's `data` is the pixel bytes; `colorSpace` + `mipmap` drive the
|
|
1807
|
+
* consistency assertion + mip count.
|
|
1808
|
+
*/
|
|
1809
|
+
function decodedFromTexture(tex: TextureAsset): DecodedImage {
|
|
1810
|
+
const bytes =
|
|
1811
|
+
tex.data instanceof Uint8ClampedArray
|
|
1812
|
+
? new Uint8Array(tex.data.buffer, tex.data.byteOffset, tex.data.byteLength)
|
|
1813
|
+
: tex.data;
|
|
1814
|
+
return {
|
|
1815
|
+
bytes,
|
|
1816
|
+
width: tex.width,
|
|
1817
|
+
height: tex.height,
|
|
1818
|
+
mime: 'image/png',
|
|
1819
|
+
colorSpace: tex.colorSpace,
|
|
1820
|
+
mipmap: tex.mipmap,
|
|
1821
|
+
};
|
|
1822
|
+
}
|