@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,1933 @@
|
|
|
1
|
+
// @forgeax/engine-runtime - RenderSystem main entry (D-S2 three-stage Extract ->
|
|
2
|
+
// Prepare -> Record + 4-tier error fan-out).
|
|
3
|
+
//
|
|
4
|
+
// Engine-internal phase: NOT registered to World schedule (AC-09);
|
|
5
|
+
// `Renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 })` invokes once per frame. See `Renderer` JSDoc in
|
|
6
|
+
// `./render-contract.ts` for the public error/frame contract and AGENTS.md
|
|
7
|
+
// "ECS render bridge" section for the AI-user-facing contract.
|
|
8
|
+
//
|
|
9
|
+
// Stage carve-out (review round 1 finding #3 - 505 line cap fallback):
|
|
10
|
+
// - render-system.ts (this file) types + orchestration
|
|
11
|
+
// - render-system-extract.ts Extract ECS query phase + snapshot helpers
|
|
12
|
+
// - render-system-record.ts Prepare + Record GPU phase + matrix helpers
|
|
13
|
+
//
|
|
14
|
+
// @forgeax/engine-math is referenced through render-system-record.ts which builds
|
|
15
|
+
// view/proj matrices and worldFromLocal via `mat4.compose / .multiply /
|
|
16
|
+
// .invert` (charter proposition 5: no math reinvention; render-system.test.ts
|
|
17
|
+
// asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
|
|
18
|
+
|
|
19
|
+
import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
|
|
20
|
+
import type { World } from '@forgeax/engine-ecs';
|
|
21
|
+
import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
22
|
+
import type { RecorderSession } from '@forgeax/engine-profiler';
|
|
23
|
+
import type { BindGroupEntry, BindGroupLayout, Buffer, RenderPipeline } from '@forgeax/engine-rhi';
|
|
24
|
+
import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
|
|
25
|
+
import {
|
|
26
|
+
derive,
|
|
27
|
+
type Handle,
|
|
28
|
+
type MaterialAsset,
|
|
29
|
+
type MaterialTextureValue,
|
|
30
|
+
type MeshAsset,
|
|
31
|
+
type RenderPipelineAsset,
|
|
32
|
+
RenderQueue,
|
|
33
|
+
type SamplerAsset,
|
|
34
|
+
toShared,
|
|
35
|
+
} from '@forgeax/engine-types';
|
|
36
|
+
import { createClusterBinScratch } from './cluster-binner';
|
|
37
|
+
import {
|
|
38
|
+
type ObservationUnavailableError,
|
|
39
|
+
PointsLinesMaterialUnsupportedError,
|
|
40
|
+
type RenderError,
|
|
41
|
+
} from './errors/render';
|
|
42
|
+
import {
|
|
43
|
+
type RenderFeaturePreparedGraphicsResolverInput,
|
|
44
|
+
runRenderFeatureFrame,
|
|
45
|
+
settlePreparedGraphicsCompletion,
|
|
46
|
+
} from './features/host';
|
|
47
|
+
import {
|
|
48
|
+
createRenderFeatureGpuWorkOwner,
|
|
49
|
+
type RenderFeatureGpuWorkOwner,
|
|
50
|
+
} from './features/prepared-gpu-work';
|
|
51
|
+
import { resolveStandardRenderFeatureTargets } from './features/targets';
|
|
52
|
+
import {
|
|
53
|
+
buildFullscreenPostProcessPass,
|
|
54
|
+
DEPTH_MIN_PARAMS_BYTE_SIZE,
|
|
55
|
+
entryHasDepthRead,
|
|
56
|
+
type PostProcessShaderEntry,
|
|
57
|
+
} from './fullscreen-post-process-pass';
|
|
58
|
+
import { GpuDrivenProduction } from './gpu-driven/production-raster';
|
|
59
|
+
import type { RenderSceneInspection } from './inspection-types';
|
|
60
|
+
import { admitPointsLines } from './points-lines/admission';
|
|
61
|
+
import { PointsLinesExpansionCache } from './points-lines/expansion-cache';
|
|
62
|
+
import {
|
|
63
|
+
inspectPointsLines,
|
|
64
|
+
type PointsLinesInspection,
|
|
65
|
+
type PointsLinesSourceError,
|
|
66
|
+
} from './points-lines/inspection';
|
|
67
|
+
import {
|
|
68
|
+
createPointsLinesLanePreparationAdapter,
|
|
69
|
+
type PointsLinesLanePreparationAdapter,
|
|
70
|
+
} from './points-lines/prepare';
|
|
71
|
+
import { createPointsLinesLaneAdapter, type PointsLinesBackend } from './points-lines/record';
|
|
72
|
+
import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
|
|
73
|
+
import type { PointsLinesRecordOwner, PointsLinesRecordSubmission } from './record/render-context';
|
|
74
|
+
|
|
75
|
+
export type { RenderSceneInspection } from './inspection-types';
|
|
76
|
+
|
|
77
|
+
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
78
|
+
import {
|
|
79
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
80
|
+
GPU_BUFFER_USAGE_INDEX,
|
|
81
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
82
|
+
GPU_BUFFER_USAGE_VERTEX,
|
|
83
|
+
} from './gpu-usage';
|
|
84
|
+
import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
|
|
85
|
+
import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
|
|
86
|
+
import type { MeshMaterialBindingObservation } from './mesh-material-bindings';
|
|
87
|
+
import { validateClusterGrid } from './pipeline/standard-pipeline';
|
|
88
|
+
import { DEFAULT_CLUSTER_GRID } from './pipeline/standard-profile';
|
|
89
|
+
import { PostProcessError } from './post-process-errors';
|
|
90
|
+
import {
|
|
91
|
+
createPreparedGraphicsResolver,
|
|
92
|
+
type PreparedGraphicsResolver,
|
|
93
|
+
} from './prepare/prepared-graphics-resolver';
|
|
94
|
+
import {
|
|
95
|
+
type FrameObservation,
|
|
96
|
+
type FrameObservationOptions,
|
|
97
|
+
observeCurrentFrame,
|
|
98
|
+
type RecordProfileRunner,
|
|
99
|
+
recordFrame,
|
|
100
|
+
} from './record/frame';
|
|
101
|
+
import { buildPerFrameBindGroups } from './record/frame-lighting';
|
|
102
|
+
import type { RenderFrameState } from './record/frame-snapshot';
|
|
103
|
+
import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
|
|
104
|
+
import type { RenderSystemInternals } from './record/render-context';
|
|
105
|
+
import {
|
|
106
|
+
type RenderFeatureGraphCandidate,
|
|
107
|
+
resetRenderFeatureGraphState,
|
|
108
|
+
} from './record/typed-frame-graph';
|
|
109
|
+
import {
|
|
110
|
+
type CameraSnapshot,
|
|
111
|
+
type DrawOwnerOptions,
|
|
112
|
+
RENDER_PHASE_CATALOG,
|
|
113
|
+
type RenderPhase,
|
|
114
|
+
type RenderPhaseSkipReason,
|
|
115
|
+
type RenderRecordPhase,
|
|
116
|
+
} from './render-contract';
|
|
117
|
+
import type { DispatchEntry, ExtractedFrame, RenderableSnapshot } from './render-system-extract';
|
|
118
|
+
import { extractFrames } from './render-system-extract';
|
|
119
|
+
import { PersistentRenderScene } from './scene/render-scene';
|
|
120
|
+
import { resolveSsaoParameters } from './ssao-config';
|
|
121
|
+
import {
|
|
122
|
+
getTransparentSortConfig,
|
|
123
|
+
TRANSPARENT_SORT_MODE_DISTANCE,
|
|
124
|
+
TRANSPARENT_SORT_MODE_LAYER_Y,
|
|
125
|
+
TRANSPARENT_SORT_MODE_LAYER_YZ,
|
|
126
|
+
TRANSPARENT_SORT_MODE_LAYER_Z,
|
|
127
|
+
} from './systems/transparent-sort-config';
|
|
128
|
+
|
|
129
|
+
export type {
|
|
130
|
+
_InternalRenderPipelineContext,
|
|
131
|
+
_StandardForwardSceneView,
|
|
132
|
+
PerPassResources,
|
|
133
|
+
PipelineState,
|
|
134
|
+
RenderSystemInternals,
|
|
135
|
+
RenderSystemRuntime,
|
|
136
|
+
SurfaceBackendKind,
|
|
137
|
+
SwapChainFormatPair,
|
|
138
|
+
} from './record/render-context';
|
|
139
|
+
export {
|
|
140
|
+
configureSurface,
|
|
141
|
+
MATERIAL_PER_ENTITY_STRIDE,
|
|
142
|
+
resolveSurfaceFormatPair,
|
|
143
|
+
STANDARD_PBR_UBO_SIZE,
|
|
144
|
+
selectSwapChainFormat,
|
|
145
|
+
} from './record/render-context';
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Unified transparent-queue sub-sort covering all four
|
|
149
|
+
* {@link TransparentSortConfig} modes. Reorders the
|
|
150
|
+
* `queue === RenderQueue.Transparent` segment of the dispatch list;
|
|
151
|
+
* all other queue segments keep their relative order.
|
|
152
|
+
*
|
|
153
|
+
* | mode | primary key | secondary key | tertiary key |
|
|
154
|
+
* |:--:|:--|:--|:--|
|
|
155
|
+
* | 0 (LAYER_Z) | `layer` ASC | `posZ` ASC | `materialHandle` ASC |
|
|
156
|
+
* | 1 (LAYER_Y) | `layer` ASC | `-(posY - pivotY * sizeY)` ASC | `materialHandle` ASC |
|
|
157
|
+
* | 2 (LAYER_YZ) | `layer` ASC | `(posY - pivotY * sizeY) + yzAlpha * posZ` ASC | `materialHandle` ASC |
|
|
158
|
+
* | 3 (DISTANCE) | `-(dist² from camera)` ASC (back-to-front, layer ignored) | — |
|
|
159
|
+
*
|
|
160
|
+
* The `materialHandle` tertiary key for modes 0/1/2 groups same-material
|
|
161
|
+
* entries together whenever the primary+secondary sort values are equal
|
|
162
|
+
* (e.g. tilemap tiles in the same row/layer share `posY` in LAYER_Y mode).
|
|
163
|
+
* Consecutive same-material groups then collapse into fold buckets in the
|
|
164
|
+
* record-stage fold operator, significantly reducing draw call count for
|
|
165
|
+
* tilemap-heavy scenes.
|
|
166
|
+
*
|
|
167
|
+
* `posX/Y/Z` = translation column of the entity's world mat4 (indices 12/13/14).
|
|
168
|
+
* `pivotY` = `RenderableSnapshot.material.paramSnapshot.pivotAndSize[1]` (default 0.5).
|
|
169
|
+
* `sizeY` = length of the Y-axis column of the world mat4 (indices 4/5/6).
|
|
170
|
+
*/
|
|
171
|
+
function sortTransparentDispatch(
|
|
172
|
+
dispatch: DispatchEntry[],
|
|
173
|
+
world: World,
|
|
174
|
+
cameras: readonly CameraSnapshot[],
|
|
175
|
+
renderables: readonly RenderableSnapshot[],
|
|
176
|
+
): DispatchEntry[] {
|
|
177
|
+
const cfg = getTransparentSortConfig(world);
|
|
178
|
+
const mode = cfg.mode;
|
|
179
|
+
|
|
180
|
+
// Indices of Transparent-queue entries within the dispatch list.
|
|
181
|
+
const transparentSlots: number[] = [];
|
|
182
|
+
for (let i = 0; i < dispatch.length; i++) {
|
|
183
|
+
if (dispatch[i]?.queue === RenderQueue.Transparent) transparentSlots.push(i);
|
|
184
|
+
}
|
|
185
|
+
if (transparentSlots.length <= 1) return dispatch;
|
|
186
|
+
|
|
187
|
+
let sortedSlotOrder: number[];
|
|
188
|
+
|
|
189
|
+
if (mode === TRANSPARENT_SORT_MODE_DISTANCE) {
|
|
190
|
+
const camera = cameras[0];
|
|
191
|
+
if (camera === undefined) return dispatch;
|
|
192
|
+
const camPos = camera.position;
|
|
193
|
+
|
|
194
|
+
// Squared camera distance per entry (negated → ascending = back-to-front).
|
|
195
|
+
// D-3: world position = translation column of Transform.world (m[12,13,14]).
|
|
196
|
+
const negDistSq = (entry: DispatchEntry): number => {
|
|
197
|
+
const tx = renderables[entry.renderableIndex]?.transform;
|
|
198
|
+
if (tx === undefined) return 0;
|
|
199
|
+
const w = tx.world;
|
|
200
|
+
const dx = (w[12] ?? 0) - (camPos[0] ?? 0);
|
|
201
|
+
const dy = (w[13] ?? 0) - (camPos[1] ?? 0);
|
|
202
|
+
const dz = (w[14] ?? 0) - (camPos[2] ?? 0);
|
|
203
|
+
return -(dx * dx + dy * dy + dz * dz);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
sortedSlotOrder = transparentSlots.slice().sort((a, b) => {
|
|
207
|
+
const da = negDistSq(dispatch[a] as DispatchEntry);
|
|
208
|
+
const db = negDistSq(dispatch[b] as DispatchEntry);
|
|
209
|
+
if (da < db) return -1;
|
|
210
|
+
if (da > db) return 1;
|
|
211
|
+
return 0;
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
// Modes 0/1/2: primary = layer ASC, secondary = mode-formula ASC.
|
|
215
|
+
// sizeY = length of the Y-axis column (col1 = indices 4,5,6) of the
|
|
216
|
+
// world mat4; rotation-invariant and handles flipV sign correctly.
|
|
217
|
+
const sortVal = (entry: DispatchEntry): number => {
|
|
218
|
+
const tx = renderables[entry.renderableIndex]?.transform;
|
|
219
|
+
const w = tx?.world;
|
|
220
|
+
const posY = (w?.[13] ?? 0) as number;
|
|
221
|
+
const posZ = (w?.[14] ?? 0) as number;
|
|
222
|
+
if (mode === TRANSPARENT_SORT_MODE_LAYER_Z) return posZ;
|
|
223
|
+
const mat = renderables[entry.renderableIndex]?.material;
|
|
224
|
+
// feat-20260625-refactor-sprite-as-transparent-mesh M3 / w15: post
|
|
225
|
+
// SpriteFieldsSnapshot ablation, pivot lives in paramSnapshot
|
|
226
|
+
// .pivotAndSize[0..1] (UBO-aligned vec4 slot 2, plan D-6). Non-sprite
|
|
227
|
+
// materials carry paramSnapshot=undefined and fall back to 0.5.
|
|
228
|
+
const pivotAndSize = mat?.paramSnapshot?.pivotAndSize as readonly number[] | undefined;
|
|
229
|
+
const pivotY = (pivotAndSize?.[1] ?? 0.5) as number;
|
|
230
|
+
const wy4 = (w?.[4] ?? 0) as number;
|
|
231
|
+
const wy5 = (w?.[5] ?? 1) as number;
|
|
232
|
+
const wy6 = (w?.[6] ?? 0) as number;
|
|
233
|
+
const sizeY = Math.sqrt(wy4 * wy4 + wy5 * wy5 + wy6 * wy6);
|
|
234
|
+
const footY = posY - pivotY * sizeY;
|
|
235
|
+
if (mode === TRANSPARENT_SORT_MODE_LAYER_Y) return -footY;
|
|
236
|
+
if (mode === TRANSPARENT_SORT_MODE_LAYER_YZ) return footY + cfg.yzAlpha * posZ;
|
|
237
|
+
// Defensive fallback for an unknown mode that slips past setTransparentSortConfig.
|
|
238
|
+
return posZ;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
sortedSlotOrder = transparentSlots.slice().sort((a, b) => {
|
|
242
|
+
const da = dispatch[a] as DispatchEntry;
|
|
243
|
+
const db = dispatch[b] as DispatchEntry;
|
|
244
|
+
const la = da.layer;
|
|
245
|
+
const lb = db.layer;
|
|
246
|
+
if (la !== lb) return la - lb;
|
|
247
|
+
const va = sortVal(da);
|
|
248
|
+
const vb = sortVal(db);
|
|
249
|
+
if (va < vb) return -1;
|
|
250
|
+
if (va > vb) return 1;
|
|
251
|
+
// Tertiary tiebreaker: group same-materialHandle entries together so
|
|
252
|
+
// fold-eligible consecutive runs form. Entries at equal (layer,
|
|
253
|
+
// sortVal) — e.g. tilemap tiles in the same row under LAYER_Y — are
|
|
254
|
+
// depth-equivalent; reordering them by material does not change the
|
|
255
|
+
// visual result but maximises fold-bucket width.
|
|
256
|
+
return da.materialHandle - db.materialHandle;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Scatter the reordered Transparent entries back into their original slots.
|
|
261
|
+
const result = dispatch.slice();
|
|
262
|
+
for (let k = 0; k < transparentSlots.length; k++) {
|
|
263
|
+
const targetSlot = transparentSlots[k] as number;
|
|
264
|
+
const sourceSlot = sortedSlotOrder[k] as number;
|
|
265
|
+
result[targetSlot] = dispatch[sourceSlot] as DispatchEntry;
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Engine-internal Extract / Prepare / Record driver; constructed by createRenderer.
|
|
272
|
+
*
|
|
273
|
+
* w15 M5 dual-pipeline dispatch: `pipelineDispatchCounts` surfaces per-frame
|
|
274
|
+
* counters of how many entities were routed to each pipeline (plan-strategy
|
|
275
|
+
* D-P4 / requirements AC-07). The counts roll over monotonically — test
|
|
276
|
+
* callers read them after `draw([world], { cameraOwner: 0, resourceOwner: 0 })` to assert each tag saw >= 1 draw.
|
|
277
|
+
* Reset is intentional on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry so per-frame assertions
|
|
278
|
+
* stay local (charter proposition 4 explicit failure: test code sees exact
|
|
279
|
+
* per-draw counts, not stale cross-frame totals).
|
|
280
|
+
*
|
|
281
|
+
* bug-20260519: BUILTIN cube migrated to 12F so the legacy `unlitBuiltin`
|
|
282
|
+
* counter is gone; the surface collapses to `unlit` (every entity whose
|
|
283
|
+
* shader identity is `forgeax::default-unlit`) + `standard` (every entity
|
|
284
|
+
* whose shader identity is `forgeax::default-standard-pbr`).
|
|
285
|
+
*/
|
|
286
|
+
export interface RenderSystem {
|
|
287
|
+
/** Returns true only when this invocation reached queue submission. */
|
|
288
|
+
draw(
|
|
289
|
+
worlds: readonly World[],
|
|
290
|
+
opts: DrawOwnerOptions,
|
|
291
|
+
renderReadLeases?: readonly RenderReadLease[],
|
|
292
|
+
): boolean;
|
|
293
|
+
/** Release renderer-owned persistent state for one detached World. */
|
|
294
|
+
detachScene(world: World): void;
|
|
295
|
+
/** Release the profiler catalog contribution owned by this RenderSystem. */
|
|
296
|
+
releaseProfilerCatalog(): void;
|
|
297
|
+
observeCurrentFrame(
|
|
298
|
+
options: FrameObservationOptions,
|
|
299
|
+
): Promise<Result<FrameObservation, ObservationUnavailableError>>;
|
|
300
|
+
readonly pipelineDispatchCounts: {
|
|
301
|
+
readonly unlit: number;
|
|
302
|
+
};
|
|
303
|
+
/**
|
|
304
|
+
* feat-20260528-frustum-culling M5 / w14: per-frame frustum-culling counters.
|
|
305
|
+
* Updated by `draw([world], { cameraOwner: 0, resourceOwner: 0 })` on every call from the Extract stage.
|
|
306
|
+
*/
|
|
307
|
+
readonly frustumStats: { culled: number; total: number };
|
|
308
|
+
/** Per-frame candidate entities rejected by author visibility. */
|
|
309
|
+
readonly visibilityStats: { explicitlyHidden: number };
|
|
310
|
+
/** Persistent scene maintenance evidence from the ordinary single-World path. */
|
|
311
|
+
readonly renderScene: RenderSceneInspection;
|
|
312
|
+
/** Retained Points/Lines authoring facts from the single scene projection. */
|
|
313
|
+
readonly pointsLinesSnapshots: readonly PointsLinesRetainedSnapshot[];
|
|
314
|
+
/** Current mesh-slot provenance and active diagnostics from the last frame. */
|
|
315
|
+
readonly meshMaterialBindings: readonly MeshMaterialBindingObservation[];
|
|
316
|
+
/**
|
|
317
|
+
* feat-20260531-bloom-first-declarative-render-graph-pass M4 fix-up w19:
|
|
318
|
+
* per-frame render-graph pass names in declaration order. Empty array
|
|
319
|
+
* before the first `draw([world], { cameraOwner: 0, resourceOwner: 0 })` call; populated after the per-frame
|
|
320
|
+
* graph is built (lazily on first draw). Read-only introspection surface
|
|
321
|
+
* so smoke tests can assert the declarative pass chain is wired without
|
|
322
|
+
* reaching into engine internals.
|
|
323
|
+
*/
|
|
324
|
+
readonly perFramePassNames: readonly string[];
|
|
325
|
+
/**
|
|
326
|
+
* feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
|
|
327
|
+
* createBindGroup counter. Reset to 0 on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry,
|
|
328
|
+
* bumped on each cache-miss createBindGroup call in the record stage.
|
|
329
|
+
* Aligns with pipelineDispatchCounts precedent: closure-mutable object
|
|
330
|
+
* + draw-entry reset + readonly getter. Stable-frame AC-03 asserts
|
|
331
|
+
* createBindGroup == 0 when all bind groups are cache-resident.
|
|
332
|
+
*
|
|
333
|
+
* M5 / w19 type-safe finalization: the return type is purposely the
|
|
334
|
+
* narrowest inline object literal `{ readonly createBindGroup: number }`
|
|
335
|
+
* rather than a wider Record/alias — this ensures TS language service
|
|
336
|
+
* hover shows the exact field name + type, and AC-09 consumption sites
|
|
337
|
+
* infer `number` without `as` casts (plan-strategy D-7 + sec.8
|
|
338
|
+
* discoverability).
|
|
339
|
+
*/
|
|
340
|
+
readonly bindGroupCounts: {
|
|
341
|
+
readonly createBindGroup: number;
|
|
342
|
+
readonly keys: readonly string[];
|
|
343
|
+
};
|
|
344
|
+
/** Configure the sole Standard graph owner before the first frame. */
|
|
345
|
+
configureStandard(config: RenderPipelineAsset['config']): void;
|
|
346
|
+
/** Register one engine-owned post-process shader used by the Standard lane. */
|
|
347
|
+
registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void;
|
|
348
|
+
/**
|
|
349
|
+
* feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M5 / w21:
|
|
350
|
+
* release the per-RenderSystem frame-state GPU bookkeeping during
|
|
351
|
+
* `Renderer.dispose()`. Retires the compiled graph and clears the instance-buffer cache.
|
|
352
|
+
*
|
|
353
|
+
* Idempotent (architecture-principles §6): a second call after both
|
|
354
|
+
* structures are cleared is a no-op. Per-step failures inside drain /
|
|
355
|
+
* disposeInstanceBuffers fall through silently -- the Renderer.dispose
|
|
356
|
+
* cascade owns the try/catch + errorRegistry.fire fan-out (D-3).
|
|
357
|
+
*/
|
|
358
|
+
disposeFrameState(): void;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* feat-20260622-s5 M3 / B-2 / w18: drop the device-bound state the recover()
|
|
362
|
+
* rebuild must shed before re-running the pipeline build against a fresh
|
|
363
|
+
* device. Two effects, both keyed to the lost device:
|
|
364
|
+
* 1. the per-frame render-graph's pendingDestroy queue (PooledTextures
|
|
365
|
+
* minted by the lost device — clearPendingDestroy skips destroyTexture);
|
|
366
|
+
* 2. post-process declarations + their eager param UBOs. Declarations are
|
|
367
|
+
* CPU-owned author intent and survive; UBOs are stale lost-device
|
|
368
|
+
* handles and are rebuilt after the replacement device is live.
|
|
369
|
+
* Idempotent and graph-optional (no-op when nothing has compiled yet).
|
|
370
|
+
*/
|
|
371
|
+
resetForRecover(): void;
|
|
372
|
+
/** Recreate post-process GPU parameter resources after a device rebuild. */
|
|
373
|
+
restorePostProcessResources(): void;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function isStructuredRendererError(error: unknown): error is RhiError | RenderError {
|
|
377
|
+
if (!(error instanceof Error)) return false;
|
|
378
|
+
const structured = error as {
|
|
379
|
+
readonly code?: unknown;
|
|
380
|
+
readonly expected?: unknown;
|
|
381
|
+
readonly hint?: unknown;
|
|
382
|
+
readonly detail?: unknown;
|
|
383
|
+
};
|
|
384
|
+
return (
|
|
385
|
+
typeof structured.code === 'string' &&
|
|
386
|
+
typeof structured.expected === 'string' &&
|
|
387
|
+
typeof structured.hint === 'string' &&
|
|
388
|
+
structured.detail !== undefined
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function reportPreparedGraphicsCompletionError(
|
|
393
|
+
internals: RenderSystemInternals,
|
|
394
|
+
error: unknown,
|
|
395
|
+
): void {
|
|
396
|
+
if (isStructuredRendererError(error)) {
|
|
397
|
+
internals.errorRegistry.fire(error);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const innerError =
|
|
401
|
+
error instanceof RhiError
|
|
402
|
+
? error
|
|
403
|
+
: { code: 'unknown' as const, message: String(error), name: (error as Error)?.name };
|
|
404
|
+
internals.errorRegistry.fire(
|
|
405
|
+
new RhiError({
|
|
406
|
+
code: 'webgpu-runtime-error',
|
|
407
|
+
expected: 'prepared graphics completion cleanup reports its errors',
|
|
408
|
+
hint: 'inspect detail.error for the underlying retirement or recovery failure',
|
|
409
|
+
detail: { error: innerError },
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function isPendingRenderFeaturePreparation(error: RenderError): boolean {
|
|
415
|
+
return (
|
|
416
|
+
error.code === 'render-feature-preparation-failed' &&
|
|
417
|
+
error.detail.reason.startsWith('rhi-not-available:')
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function makePreparedPipelinePendingError(): RhiError {
|
|
422
|
+
return new RhiError({
|
|
423
|
+
code: 'rhi-not-available',
|
|
424
|
+
expected: 'prepared pipeline shader module warm-up to finish asynchronously',
|
|
425
|
+
hint: 'retry the prepared graphics pass on the next frame',
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
type PointsLinesGpuResources = {
|
|
430
|
+
readonly vertexBuffer: Buffer;
|
|
431
|
+
readonly indexBuffer: Buffer;
|
|
432
|
+
};
|
|
433
|
+
type PointsLinesPreparationAdapter = PointsLinesLanePreparationAdapter<PointsLinesGpuResources>;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* The Standard renderer's single Points/Lines prepare owner.
|
|
437
|
+
*
|
|
438
|
+
* The retained snapshot is the identity boundary; this owner only resolves
|
|
439
|
+
* the source assets, runs admission, and publishes the existing preparation
|
|
440
|
+
* and record contracts to the main geometry loop. The expansion cache is
|
|
441
|
+
* shared across lanes and this owner keeps the one prepared vertex/index
|
|
442
|
+
* resource pair for each retained identity.
|
|
443
|
+
*/
|
|
444
|
+
class StandardPointsLinesOwner implements PointsLinesRecordOwner {
|
|
445
|
+
private readonly cache = new PointsLinesExpansionCache();
|
|
446
|
+
private readonly preparations = new Map<string, PointsLinesPreparationAdapter>();
|
|
447
|
+
private readonly active = new Map<string, PointsLinesInspection>();
|
|
448
|
+
private readonly layoutProjection = deriveVertexLayoutProjection({
|
|
449
|
+
position: new Float32Array(0),
|
|
450
|
+
normal: new Float32Array(0),
|
|
451
|
+
uv: new Float32Array(0),
|
|
452
|
+
tangent: new Float32Array(0),
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
constructor(private readonly internals: RenderSystemInternals) {}
|
|
456
|
+
|
|
457
|
+
beginFrame(): void {
|
|
458
|
+
this.active.clear();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
prepare(
|
|
462
|
+
entry: import('./record/frame-snapshot').ValidatedRenderable,
|
|
463
|
+
clustered: boolean,
|
|
464
|
+
): PointsLinesRecordSubmission | undefined {
|
|
465
|
+
const snapshot = entry.source.pointsLines;
|
|
466
|
+
if (snapshot === undefined || snapshot.component === undefined || entry.world === undefined) {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
const key = `${snapshot.worldId}:${snapshot.entityKey}`;
|
|
470
|
+
const topology = snapshot.component === 'Points' ? 'point-list' : 'line-list';
|
|
471
|
+
const backend = this.backend();
|
|
472
|
+
const lane = backend === 'wgpu-webgl2' ? 'cpu-webgl2' : clustered ? 'clustered' : 'direct';
|
|
473
|
+
const meshResult = resolveAssetHandle<MeshAsset>(
|
|
474
|
+
entry.world,
|
|
475
|
+
toShared<'MeshAsset'>(entry.source.assetHandle),
|
|
476
|
+
);
|
|
477
|
+
const materialResult = resolveAssetHandle<MaterialAsset>(
|
|
478
|
+
entry.world,
|
|
479
|
+
toShared<'MaterialAsset'>(snapshot.materialHandle),
|
|
480
|
+
);
|
|
481
|
+
if (!meshResult.ok || !materialResult.ok) {
|
|
482
|
+
this.publishRefusal(
|
|
483
|
+
snapshot,
|
|
484
|
+
new PointsLinesMaterialUnsupportedError({
|
|
485
|
+
entity: snapshot.entityKey,
|
|
486
|
+
material: 'unresolved',
|
|
487
|
+
pass: 'forward',
|
|
488
|
+
module: 'asset-resolution',
|
|
489
|
+
reason: 'source MeshAsset or MaterialAsset could not be resolved',
|
|
490
|
+
}),
|
|
491
|
+
);
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
const admission = admitPointsLines({
|
|
495
|
+
entity: snapshot.entityKey,
|
|
496
|
+
mesh: meshResult.value,
|
|
497
|
+
material: materialResult.value,
|
|
498
|
+
...(snapshot.style?.kind === 'points'
|
|
499
|
+
? {
|
|
500
|
+
points: {
|
|
501
|
+
sizePx: snapshot.style.sizePx,
|
|
502
|
+
shape: snapshot.style.shape === 'circle' ? 1 : 0,
|
|
503
|
+
},
|
|
504
|
+
}
|
|
505
|
+
: { lines: { widthPx: snapshot.style?.widthPx ?? 1 } }),
|
|
506
|
+
});
|
|
507
|
+
if (!admission.ok) {
|
|
508
|
+
this.publishRefusal(snapshot, admission.error);
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
const geometryKey = this.cache.getOrCreate(snapshot, meshResult.value).key;
|
|
512
|
+
const prepKey = `${geometryKey}:${lane}:${backend}`;
|
|
513
|
+
let preparation = this.preparations.get(prepKey);
|
|
514
|
+
if (preparation === undefined) {
|
|
515
|
+
preparation = createPointsLinesLanePreparationAdapter(lane, backend, {
|
|
516
|
+
cache: this.cache,
|
|
517
|
+
adapter: {
|
|
518
|
+
create: (geometry) => {
|
|
519
|
+
const vertex = this.internals.device.createBuffer({
|
|
520
|
+
label: `points-lines-vertices:${geometryKey}`,
|
|
521
|
+
size: Math.max(4, geometry.vertices.byteLength),
|
|
522
|
+
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
523
|
+
mappedAtCreation: false,
|
|
524
|
+
});
|
|
525
|
+
if (!vertex.ok) return err(vertex.error);
|
|
526
|
+
const index = this.internals.device.createBuffer({
|
|
527
|
+
label: `points-lines-indices:${geometryKey}`,
|
|
528
|
+
size: Math.max(4, geometry.indices.byteLength),
|
|
529
|
+
usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
530
|
+
mappedAtCreation: false,
|
|
531
|
+
});
|
|
532
|
+
if (!index.ok) {
|
|
533
|
+
this.internals.device.destroyBuffer(vertex.value);
|
|
534
|
+
return err(index.error);
|
|
535
|
+
}
|
|
536
|
+
return ok({ vertexBuffer: vertex.value, indexBuffer: index.value });
|
|
537
|
+
},
|
|
538
|
+
upload: (resource, geometry) => {
|
|
539
|
+
if (geometry.vertices.byteLength > 0) {
|
|
540
|
+
const vertexWrite = this.internals.device.queue.writeBuffer(
|
|
541
|
+
resource.vertexBuffer,
|
|
542
|
+
0,
|
|
543
|
+
geometry.vertices,
|
|
544
|
+
);
|
|
545
|
+
if (!vertexWrite.ok) return err(vertexWrite.error);
|
|
546
|
+
}
|
|
547
|
+
if (geometry.indices.byteLength > 0) {
|
|
548
|
+
const indexWrite = this.internals.device.queue.writeBuffer(
|
|
549
|
+
resource.indexBuffer,
|
|
550
|
+
0,
|
|
551
|
+
geometry.indices,
|
|
552
|
+
);
|
|
553
|
+
if (!indexWrite.ok) return err(indexWrite.error);
|
|
554
|
+
}
|
|
555
|
+
return ok(geometry.derivedBytes);
|
|
556
|
+
},
|
|
557
|
+
validate: (_resource, geometry) =>
|
|
558
|
+
geometry.expandedVertexCount > 0 && geometry.expandedIndexCount > 0
|
|
559
|
+
? ok(undefined)
|
|
560
|
+
: err(new Error('Points/Lines expansion contains no drawable triangles')),
|
|
561
|
+
destroy: (resource) => {
|
|
562
|
+
this.internals.device.destroyBuffer(resource.vertexBuffer);
|
|
563
|
+
this.internals.device.destroyBuffer(resource.indexBuffer);
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
this.preparations.set(prepKey, preparation);
|
|
568
|
+
}
|
|
569
|
+
const prepared = preparation.prepare(snapshot, meshResult.value);
|
|
570
|
+
if (!prepared.ok) {
|
|
571
|
+
const lkg = preparation.lastKnownGood();
|
|
572
|
+
if (lkg !== undefined) {
|
|
573
|
+
const lkgPlan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
|
|
574
|
+
lkg.snapshot,
|
|
575
|
+
lkg.geometry,
|
|
576
|
+
);
|
|
577
|
+
const state = preparation.inspect();
|
|
578
|
+
this.active.set(
|
|
579
|
+
key,
|
|
580
|
+
inspectPointsLines({
|
|
581
|
+
snapshot,
|
|
582
|
+
topology,
|
|
583
|
+
lane,
|
|
584
|
+
pointCount: lkg.geometry.pointCount,
|
|
585
|
+
segmentCount: lkg.geometry.segmentCount,
|
|
586
|
+
sourceBytes: lkg.geometry.sourceBytes,
|
|
587
|
+
derivedBytes: lkg.geometry.derivedBytes,
|
|
588
|
+
cache: { hit: true, rebuilds: state.rebuilds, evictions: 0 },
|
|
589
|
+
drawCount: lkgPlan.drawCount,
|
|
590
|
+
uploadBytes: 0,
|
|
591
|
+
lastKnownGood: true,
|
|
592
|
+
refusal: {
|
|
593
|
+
code: prepared.error.code,
|
|
594
|
+
expected: prepared.error.expected,
|
|
595
|
+
hint: prepared.error.hint,
|
|
596
|
+
detail: prepared.error.detail,
|
|
597
|
+
generation: prepared.error.detail.generation,
|
|
598
|
+
lastKnownGood: true,
|
|
599
|
+
},
|
|
600
|
+
}),
|
|
601
|
+
);
|
|
602
|
+
return {
|
|
603
|
+
plan: lkgPlan,
|
|
604
|
+
vertexBuffer: lkg.resource.vertexBuffer,
|
|
605
|
+
indexBuffer: lkg.resource.indexBuffer,
|
|
606
|
+
layoutProjection: this.layoutProjection,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
this.publishRefusal(snapshot, prepared.error);
|
|
610
|
+
return undefined;
|
|
611
|
+
}
|
|
612
|
+
const plan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
|
|
613
|
+
snapshot,
|
|
614
|
+
prepared.value.geometry,
|
|
615
|
+
);
|
|
616
|
+
const state = preparation.inspect();
|
|
617
|
+
this.active.set(
|
|
618
|
+
key,
|
|
619
|
+
inspectPointsLines({
|
|
620
|
+
snapshot,
|
|
621
|
+
topology,
|
|
622
|
+
lane,
|
|
623
|
+
pointCount: prepared.value.geometry.pointCount,
|
|
624
|
+
segmentCount: prepared.value.geometry.segmentCount,
|
|
625
|
+
sourceBytes: prepared.value.geometry.sourceBytes,
|
|
626
|
+
derivedBytes: prepared.value.geometry.derivedBytes,
|
|
627
|
+
cache: {
|
|
628
|
+
hit: prepared.value.uploadedBytes === 0,
|
|
629
|
+
rebuilds: state.rebuilds,
|
|
630
|
+
evictions: 0,
|
|
631
|
+
},
|
|
632
|
+
drawCount: plan.drawCount,
|
|
633
|
+
uploadBytes: prepared.value.uploadedBytes,
|
|
634
|
+
lastKnownGood: prepared.value.lastKnownGood,
|
|
635
|
+
}),
|
|
636
|
+
);
|
|
637
|
+
return {
|
|
638
|
+
plan,
|
|
639
|
+
vertexBuffer: prepared.value.resource.vertexBuffer,
|
|
640
|
+
indexBuffer: prepared.value.resource.indexBuffer,
|
|
641
|
+
layoutProjection: this.layoutProjection,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
resetForDeviceLoss(): void {
|
|
646
|
+
for (const preparation of this.preparations.values()) preparation.resetForDeviceLoss();
|
|
647
|
+
this.active.clear();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
inspections(): readonly PointsLinesInspection[] {
|
|
651
|
+
return [...this.active.values()];
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private backend(): PointsLinesBackend {
|
|
655
|
+
switch (this.internals.device.caps.backendKind) {
|
|
656
|
+
case 'wgpu-webgl2':
|
|
657
|
+
return 'wgpu-webgl2';
|
|
658
|
+
case 'null':
|
|
659
|
+
return 'null';
|
|
660
|
+
default:
|
|
661
|
+
return 'webgpu';
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
private publishRefusal(
|
|
666
|
+
snapshot: PointsLinesRetainedSnapshot,
|
|
667
|
+
error: PointsLinesSourceError,
|
|
668
|
+
): void {
|
|
669
|
+
this.active.set(
|
|
670
|
+
`${snapshot.worldId}:${snapshot.entityKey}`,
|
|
671
|
+
inspectPointsLines({
|
|
672
|
+
snapshot,
|
|
673
|
+
topology: snapshot.component === 'Points' ? 'point-list' : 'line-list',
|
|
674
|
+
lane: 'refused',
|
|
675
|
+
pointCount: 0,
|
|
676
|
+
segmentCount: 0,
|
|
677
|
+
sourceBytes: 0,
|
|
678
|
+
derivedBytes: 0,
|
|
679
|
+
cache: { hit: false, rebuilds: 0, evictions: 0 },
|
|
680
|
+
drawCount: 0,
|
|
681
|
+
uploadBytes: 0,
|
|
682
|
+
lastKnownGood: false,
|
|
683
|
+
refusal: {
|
|
684
|
+
code: error.code,
|
|
685
|
+
expected: error.expected,
|
|
686
|
+
hint: error.hint,
|
|
687
|
+
detail: error.detail,
|
|
688
|
+
generation: snapshot.meshGeneration,
|
|
689
|
+
lastKnownGood: false,
|
|
690
|
+
},
|
|
691
|
+
}),
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
export function createRenderSystem(internals: RenderSystemInternals): RenderSystem {
|
|
697
|
+
const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
|
|
698
|
+
'render',
|
|
699
|
+
RENDER_PHASE_CATALOG,
|
|
700
|
+
);
|
|
701
|
+
let releaseProfilerCatalog =
|
|
702
|
+
phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : undefined;
|
|
703
|
+
let preparedWorlds: readonly World[] = [];
|
|
704
|
+
const pointsLinesOwner = new StandardPointsLinesOwner(internals);
|
|
705
|
+
const persistentRenderScene = new PersistentRenderScene({
|
|
706
|
+
getDevice: () => internals.device,
|
|
707
|
+
onGpuError: (error) => internals.errorRegistry.fire(error),
|
|
708
|
+
onSharedRefMutation: (worldId, handle) => {
|
|
709
|
+
internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
const gpuDrivenShaderFactory =
|
|
713
|
+
internals.shaderModuleFactory ??
|
|
714
|
+
({
|
|
715
|
+
createShaderModule: () =>
|
|
716
|
+
err(
|
|
717
|
+
new RhiError({
|
|
718
|
+
code: 'rhi-not-available',
|
|
719
|
+
expected: 'the renderer backend exposes shader module creation',
|
|
720
|
+
hint: 'construct the renderer through the backend pack before activating GPU-driven rendering',
|
|
721
|
+
}),
|
|
722
|
+
),
|
|
723
|
+
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
|
|
724
|
+
let gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
|
|
725
|
+
const featureGpuWork: RenderFeatureGpuWorkOwner = createRenderFeatureGpuWorkOwner({
|
|
726
|
+
getDevice: () => internals.device,
|
|
727
|
+
getShaderModuleFactory: () =>
|
|
728
|
+
internals.shaderModuleFactory ??
|
|
729
|
+
({
|
|
730
|
+
createShaderModule: () =>
|
|
731
|
+
err(
|
|
732
|
+
new RhiError({
|
|
733
|
+
code: 'rhi-not-available',
|
|
734
|
+
expected: 'the renderer backend exposes shader module creation',
|
|
735
|
+
hint: 'construct the renderer through the backend pack before activating GPU features',
|
|
736
|
+
}),
|
|
737
|
+
),
|
|
738
|
+
} satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory),
|
|
739
|
+
});
|
|
740
|
+
const disposeFeatureGpuWork = (): void => {
|
|
741
|
+
const disposed = featureGpuWork.dispose();
|
|
742
|
+
if (!disposed.ok) internals.errorRegistry.fire(disposed.error);
|
|
743
|
+
};
|
|
744
|
+
// Per-RenderSystem frame state: closure-internal frameNumber + the
|
|
745
|
+
// per-entity instance GPU buffer cache (feat-20260514 M3 / w15: the
|
|
746
|
+
// record stage owns GPU storage buffer allocation for Instances entities;
|
|
747
|
+
// the `instanceBuffers` map is keyed by the packed Entity u32 surfaced
|
|
748
|
+
// through `InstancesSnapshot.cacheKey` and rebuilds buffers on archetype
|
|
749
|
+
// version bump or byte-length change). The legacy
|
|
750
|
+
// `lastFiredLimitExceededFrame` engine-side dedup field was removed in
|
|
751
|
+
// feat-20260513-instanced-mesh M5 (T-M5-1 + T-M5-3); the active
|
|
752
|
+
// `'limit-exceeded'` emit point is now the record stage upload path.
|
|
753
|
+
//
|
|
754
|
+
// feat-20260708 M1 / D-1a #1: positive-half keys are worldEntityKey(worldId,
|
|
755
|
+
// cacheKey) composites. Negative-half fold-bucket keys (sprite fold) stay
|
|
756
|
+
// raw (material-handle-based, cross-world collision semantically correct).
|
|
757
|
+
const frameState: RenderFrameState = {
|
|
758
|
+
frameNumber: 0,
|
|
759
|
+
directionalShadowCache: null,
|
|
760
|
+
directionalShadowCacheRecorded: false,
|
|
761
|
+
compiledFrameGraph: null,
|
|
762
|
+
compiledFrameGraphTopologyKey: null,
|
|
763
|
+
retiredCompiledFrameGraphs: new Set(),
|
|
764
|
+
currentFrameObservationSource: undefined,
|
|
765
|
+
currentDirectionalShadowView: null,
|
|
766
|
+
currentSpotShadowView: null,
|
|
767
|
+
instanceBuffers: new Map(),
|
|
768
|
+
morphBuffers: new Map(),
|
|
769
|
+
hdrpClusterBinScratch: createClusterBinScratch(),
|
|
770
|
+
hdrpClusterGridScratch: null,
|
|
771
|
+
hdrpLightIndexListScratch: null,
|
|
772
|
+
hdrpClusterMembershipBindGroup: null,
|
|
773
|
+
transientInstanceBuffers: [],
|
|
774
|
+
warnedZeroLightStandard: false,
|
|
775
|
+
warnedMultiLightDirectional: false,
|
|
776
|
+
warnedMultiLightPoint: false,
|
|
777
|
+
warnedMultiLightSpot: false,
|
|
778
|
+
// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w19:
|
|
779
|
+
// once-warn latches for >1 Skylight / >1 SkyboxBackground (names winner).
|
|
780
|
+
warnedMultiSkylight: false,
|
|
781
|
+
warnedMultiSkybox: false,
|
|
782
|
+
warnedSkyboxTonemapNone: false,
|
|
783
|
+
// feat-20260520-2d-sprite-layer-mvp M-3 / w25 (AC-18 path 4): per-
|
|
784
|
+
// handle warn-once anchor for the sprite-bucket missing-texture
|
|
785
|
+
// fallback. Set<number> keyed by raw Handle<TextureAsset>; never
|
|
786
|
+
// cleared (charter F1 minimal surface — the per-RenderSystem lifetime
|
|
787
|
+
// is the natural upper bound).
|
|
788
|
+
warnedMissingBaseColorTextureHandles: new Set<number>(),
|
|
789
|
+
// feat-20260527-sprite-nineslice M2 / w11 + M4 / w16 (AC-16): once-per-
|
|
790
|
+
// renderable guard for the runtime `nineslice.scale-too-small` metric
|
|
791
|
+
// counter (`runtime.metrics.increment(...)` in render-system-record.ts).
|
|
792
|
+
warnedNineSliceScaleEntities: new Set<number>(),
|
|
793
|
+
// feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w18:
|
|
794
|
+
// per-handle fire-once anchor for the lazy equirect projection failure
|
|
795
|
+
// (EquirectProjectionFailedError). Set<number> keyed by raw
|
|
796
|
+
// Handle<EquirectAsset>; never cleared (per-RenderSystem lifetime upper bound).
|
|
797
|
+
firedEquirectProjectionFailedHandles: new Set<number>(),
|
|
798
|
+
// feat-20260622-handle-to-id-allocator-elimination M1 / w3: per-frame
|
|
799
|
+
// bind group caches as nested WeakMap chain roots. viewBindGroupCache
|
|
800
|
+
// covers main and shadow variants; meshBindGroupCache keys on inner
|
|
801
|
+
// buffer handles (D-3). Roots are stable between device recoveries.
|
|
802
|
+
viewBindGroupCache: new WeakMap(),
|
|
803
|
+
meshBindGroupCache: new WeakMap(),
|
|
804
|
+
// feat-20260622-handle-to-id-allocator-elimination M1 / w2: per-entity
|
|
805
|
+
// material and instances caches (outer Map<entityKey, WeakMap>).
|
|
806
|
+
materialBgPerEntity: new Map(),
|
|
807
|
+
instancesBgPerEntity: new Map(),
|
|
808
|
+
instancesBgShared: new WeakMap(),
|
|
809
|
+
// cross-entity shared material cache (outer Map<shaderId, WeakMap>).
|
|
810
|
+
materialBgShared: new Map(),
|
|
811
|
+
// cross-frame material assembly cache; entries are only retained when all
|
|
812
|
+
// explicit texture/sampler handles resolved to resident GPU resources.
|
|
813
|
+
materialBgAssemblyCache: new Map(),
|
|
814
|
+
// singleton material cache (flat Map<variant, BindGroup>; D-6).
|
|
815
|
+
singletonMaterialCache: new Map(),
|
|
816
|
+
// post-process bind group cache (bloom / fxaa / ssao): identity-keyed
|
|
817
|
+
// WeakMap chain so resize-retired transient targets rebuild automatically.
|
|
818
|
+
postProcessBgCache: new WeakMap(),
|
|
819
|
+
// feat-20260601-customizable-render-pipeline-seam M1 / w7: installed-pipeline state.
|
|
820
|
+
// 0 = nothing installed yet (createRenderer dogfood installs the default before a
|
|
821
|
+
// draw). activePipeline defaults to the built-in forward pipeline.
|
|
822
|
+
installedPipelineHandle: 0,
|
|
823
|
+
activePipeline: internals.standardPipeline,
|
|
824
|
+
// feat-20260601 verify round 2: the standard forward pipeline installs with no config
|
|
825
|
+
// (its topology is frame-invariant). Standard configuration overwrites this with the
|
|
826
|
+
// resolved asset config on every swap so the active graph reads its current config.
|
|
827
|
+
installedPipelineConfig: undefined,
|
|
828
|
+
// feat-20260608-cluster-lighting M2 / w10 + M5 / w20: HDRP active flag + once-per-frame
|
|
829
|
+
// warn dedup set for hdrp-light-budget-exceeded / hdrp-index-list-overflow.
|
|
830
|
+
isHdrpActive: false,
|
|
831
|
+
hdrpOncePerFrameFired: new Set(),
|
|
832
|
+
// feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-2 (plan-strategy §D-1):
|
|
833
|
+
// cube_array shadow atlas + per-frame snapshot list. Atlas is null until the
|
|
834
|
+
// first frame whose extracted lights.pointShadow is non-empty (zero-shadow
|
|
835
|
+
// scenes never allocate; AC-09); the snapshot list defaults to an empty
|
|
836
|
+
// tuple so the typed point-shadow graph sees zero shadow lights as the
|
|
837
|
+
// initial steady state.
|
|
838
|
+
pointShadowAtlas: null,
|
|
839
|
+
pointShadowSnapshots: [],
|
|
840
|
+
// feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4 (D-1):
|
|
841
|
+
// initial fold-bucket count is 0; recordFrame writes the per-frame
|
|
842
|
+
// value from foldDispatchBuckets(...) before dispatch.
|
|
843
|
+
lastFoldBucketCount: 0,
|
|
844
|
+
// feat-20260625-spot-light-shadow-mapping M2 / w9 (D-2): empty initial spot
|
|
845
|
+
// shadow snapshot list so the spotShadowDepth caster pass renders zero
|
|
846
|
+
// tiles until the first frame with a castShadow spot (AC-03).
|
|
847
|
+
spotShadowSnapshots: [],
|
|
848
|
+
};
|
|
849
|
+
let directFrameId = 0;
|
|
850
|
+
|
|
851
|
+
function beginProfilePhase(session: RecorderSession | undefined, phase: RenderPhase): boolean {
|
|
852
|
+
if (session === undefined) return false;
|
|
853
|
+
try {
|
|
854
|
+
return session.beginPhase('render', phase).ok;
|
|
855
|
+
} catch {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function endProfilePhase(session: RecorderSession | undefined): void {
|
|
861
|
+
if (session === undefined) return;
|
|
862
|
+
try {
|
|
863
|
+
session.endPhase();
|
|
864
|
+
} catch {
|
|
865
|
+
// Profiler failures never alter rendering.
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function recordProfileSkip(
|
|
870
|
+
session: RecorderSession | undefined,
|
|
871
|
+
phase: RenderPhase,
|
|
872
|
+
reason: RenderPhaseSkipReason,
|
|
873
|
+
): void {
|
|
874
|
+
if (session === undefined) return;
|
|
875
|
+
try {
|
|
876
|
+
session.recordSkip({ source: 'render', phase, reason });
|
|
877
|
+
} catch {
|
|
878
|
+
// Profiler failures never alter rendering.
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function runProfiledRenderPhase<T>(
|
|
883
|
+
session: RecorderSession | undefined,
|
|
884
|
+
phase: RenderPhase,
|
|
885
|
+
action: () => T,
|
|
886
|
+
): T {
|
|
887
|
+
const opened = beginProfilePhase(session, phase);
|
|
888
|
+
try {
|
|
889
|
+
return action();
|
|
890
|
+
} finally {
|
|
891
|
+
if (opened) endProfilePhase(session);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
let lastBuiltPipelineHandle = 0;
|
|
895
|
+
// Monotonic configuration epoch: bumped on every Standard configuration change to brand the
|
|
896
|
+
// installed pipeline so `draw` can detect a swap and rebuild the per-frame
|
|
897
|
+
// graph. Replaces the prior raw-handle brand (D-19: the pipeline configuration takes a
|
|
898
|
+
// POD, no handle).
|
|
899
|
+
let installEpoch = 0;
|
|
900
|
+
// CPU post-process declarations are owned by the feature host and remain live
|
|
901
|
+
// across recovery. Only device-bound buffers and pipelines are rebuilt here.
|
|
902
|
+
// D-3 / D-8: per-shader params UBO resource table (id -> GPU Buffer).
|
|
903
|
+
// Eager-created at register time when entry.params is present (byteSize >= 16,
|
|
904
|
+
// defaultValue.length === byteSize); reused frame-to-frame via queue.writeBuffer.
|
|
905
|
+
const postProcessParamsBuffers = new Map<string, Buffer>();
|
|
906
|
+
const builtinPostProcessEntries = new Map<string, PostProcessShaderEntry>();
|
|
907
|
+
let activeFeaturePostProcessEntries: ReadonlyMap<string, PostProcessShaderEntry> = new Map();
|
|
908
|
+
const lookupPostProcess = (id: string): PostProcessShaderEntry | undefined =>
|
|
909
|
+
builtinPostProcessEntries.get(id) ?? activeFeaturePostProcessEntries.get(id);
|
|
910
|
+
// feat-20260621 M-A2 / w8: expose the eager-created per-id params UBO through
|
|
911
|
+
// the narrow runtime surface so dispatchFullscreenPass can writeBuffer the
|
|
912
|
+
// per-frame snapshot + bind it at group(1) binding(2).
|
|
913
|
+
const getPostProcessParamsBuffer = (id: string): Buffer | undefined =>
|
|
914
|
+
postProcessParamsBuffers.get(id);
|
|
915
|
+
const ensurePostProcessParamsResources = (
|
|
916
|
+
featureEntries: ReadonlyMap<string, PostProcessShaderEntry>,
|
|
917
|
+
): void => {
|
|
918
|
+
const declarations = new Map<string, PostProcessShaderEntry>([
|
|
919
|
+
...builtinPostProcessEntries.entries(),
|
|
920
|
+
...featureEntries.entries(),
|
|
921
|
+
]);
|
|
922
|
+
for (const [id, entry] of declarations) {
|
|
923
|
+
const byteSize =
|
|
924
|
+
entry.params?.byteSize ?? (entryHasDepthRead(entry) ? DEPTH_MIN_PARAMS_BYTE_SIZE : 0);
|
|
925
|
+
if (byteSize === 0 || postProcessParamsBuffers.has(id)) continue;
|
|
926
|
+
const created = internals.device.createBuffer({
|
|
927
|
+
label: `post-process-params-${id}`,
|
|
928
|
+
size: byteSize,
|
|
929
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
930
|
+
mappedAtCreation: false,
|
|
931
|
+
});
|
|
932
|
+
if (!created.ok) throw created.error;
|
|
933
|
+
const buffer = created.value;
|
|
934
|
+
if (entry.params !== undefined) {
|
|
935
|
+
const written = internals.device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
|
|
936
|
+
if (!written.ok) {
|
|
937
|
+
internals.device.destroyBuffer(buffer);
|
|
938
|
+
throw written.error;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
postProcessParamsBuffers.set(id, buffer);
|
|
942
|
+
}
|
|
943
|
+
for (const [id, buffer] of postProcessParamsBuffers) {
|
|
944
|
+
if (declarations.has(id)) continue;
|
|
945
|
+
internals.device.destroyBuffer(buffer);
|
|
946
|
+
postProcessParamsBuffers.delete(id);
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
// feat-20260609 M4 / T-10-a: post-process pipeline cache (id|colorFormat -> RhiRenderPipeline).
|
|
950
|
+
// Solves CONCERN-1: dispatcher previously passed `pipeline=null` to
|
|
951
|
+
// built.createHandle because per-frame execute closures cannot await async
|
|
952
|
+
// shader compile. The cache here delegates the actual build to
|
|
953
|
+
// `internals.buildPostProcessPipeline` (sync wrapper over the shared shader
|
|
954
|
+
// adapter; 1-frame warmup), then memoizes by `id|colorFormat`.
|
|
955
|
+
const postProcessPipelineCache = new Map<string, RenderPipeline>();
|
|
956
|
+
const getPostProcessPipeline = (
|
|
957
|
+
id: string,
|
|
958
|
+
bgl: BindGroupLayout,
|
|
959
|
+
colorFormat: GPUTextureFormat,
|
|
960
|
+
): RenderPipeline | null => {
|
|
961
|
+
const key = `${id}|${colorFormat}`;
|
|
962
|
+
const cached = postProcessPipelineCache.get(key);
|
|
963
|
+
if (cached !== undefined) return cached;
|
|
964
|
+
const entry = lookupPostProcess(id);
|
|
965
|
+
if (entry === undefined) return null;
|
|
966
|
+
const factory = internals.buildPostProcessPipeline;
|
|
967
|
+
if (factory === undefined) return null;
|
|
968
|
+
const built = factory(entry, bgl, colorFormat, `post-process-${id}`);
|
|
969
|
+
if (built === null) return null;
|
|
970
|
+
postProcessPipelineCache.set(key, built);
|
|
971
|
+
return built;
|
|
972
|
+
};
|
|
973
|
+
Object.assign(internals, {
|
|
974
|
+
lookupPostProcess,
|
|
975
|
+
getPostProcessParamsBuffer,
|
|
976
|
+
getPostProcessPipeline,
|
|
977
|
+
});
|
|
978
|
+
// w15 M5 (plan-strategy D-P4 / AC-07): per-frame dispatch counters. Reset
|
|
979
|
+
// on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry; bumped once per actual `pass.setPipeline`
|
|
980
|
+
// dispatch in render-system-record.ts. Two-way split mirrors the two
|
|
981
|
+
// render pipelines on PipelineState (bug-20260519: BUILTIN cube migrated
|
|
982
|
+
// to 12F so `unlitBuiltin` retired): `unlit` covers every
|
|
983
|
+
// unlit material, `standard` covers every PBR material.
|
|
984
|
+
const dispatchCounts: { unlit: number } = {
|
|
985
|
+
unlit: 0,
|
|
986
|
+
};
|
|
987
|
+
// feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame
|
|
988
|
+
// createBindGroup counter scaffolding. Reset on every draw([world], { cameraOwner: 0, resourceOwner: 0 }) entry,
|
|
989
|
+
// bumped on cache-miss in render-system-record.ts (M2-M4 bump points).
|
|
990
|
+
// Aligns with dispatchCounts precedent: closure-mutable object.
|
|
991
|
+
const bindGroupCounts: { createBindGroup: number; keys: string[] } = {
|
|
992
|
+
createBindGroup: 0,
|
|
993
|
+
keys: [],
|
|
994
|
+
};
|
|
995
|
+
const preparedPipelineIds = new WeakMap<object, string>();
|
|
996
|
+
const preparedMaterialPipelineShaders = new WeakMap<object, string>();
|
|
997
|
+
const preparedGroup0Pipelines = new WeakSet<object>();
|
|
998
|
+
const preparedViewOnlyPipelines = new WeakSet<object>();
|
|
999
|
+
const preparedRenderMaterialPipelines = new WeakSet<object>();
|
|
1000
|
+
const preparedAssetHandles = new WeakMap<World, Map<string, number>>();
|
|
1001
|
+
const preparedHandle = <Brand extends string>(
|
|
1002
|
+
world: World,
|
|
1003
|
+
guid: string,
|
|
1004
|
+
brand: Brand,
|
|
1005
|
+
): Handle<Brand, 'shared'> | undefined => {
|
|
1006
|
+
let handles = preparedAssetHandles.get(world);
|
|
1007
|
+
if (handles === undefined) {
|
|
1008
|
+
handles = new Map();
|
|
1009
|
+
preparedAssetHandles.set(world, handles);
|
|
1010
|
+
}
|
|
1011
|
+
const key = `${brand}:${guid.toLowerCase()}`;
|
|
1012
|
+
const cached = handles.get(key);
|
|
1013
|
+
if (cached !== undefined) return cached as Handle<Brand, 'shared'>;
|
|
1014
|
+
const asset = internals.assets.lookup(guid);
|
|
1015
|
+
if (asset === undefined) return undefined;
|
|
1016
|
+
const handle = world.allocSharedRef(brand, asset);
|
|
1017
|
+
handles.set(key, handle as number);
|
|
1018
|
+
return handle;
|
|
1019
|
+
};
|
|
1020
|
+
const preparedMaterialBindings = (
|
|
1021
|
+
materialShaderId: string,
|
|
1022
|
+
worldIndex: number,
|
|
1023
|
+
materialGuid: string,
|
|
1024
|
+
layout: BindGroupLayout,
|
|
1025
|
+
) => {
|
|
1026
|
+
const world = preparedWorlds[worldIndex];
|
|
1027
|
+
const material = internals.assets.lookup(materialGuid) as MaterialAsset | undefined;
|
|
1028
|
+
const pipelineState = internals.getPipelineState();
|
|
1029
|
+
if (world === undefined || material?.kind !== 'material' || pipelineState === null) {
|
|
1030
|
+
return err(new Error('prepared material asset is unavailable'));
|
|
1031
|
+
}
|
|
1032
|
+
const schema = internals.getParamSchema?.(materialShaderId) ?? [];
|
|
1033
|
+
const values = material.values ?? {};
|
|
1034
|
+
const derived = derive(schema);
|
|
1035
|
+
const fields = [...derived.textureFieldNames];
|
|
1036
|
+
let buffer: Buffer | undefined;
|
|
1037
|
+
let payloadByteLength = 0;
|
|
1038
|
+
if (derived.uboLayout.totalBytes > 0) {
|
|
1039
|
+
const payload = new Uint8Array(Math.max(16, derived.uboLayout.totalBytes));
|
|
1040
|
+
applyParamSnapshotToUbo(
|
|
1041
|
+
payload,
|
|
1042
|
+
schema,
|
|
1043
|
+
values as Readonly<Record<string, number | readonly number[]>>,
|
|
1044
|
+
);
|
|
1045
|
+
const created = internals.device.createBuffer({
|
|
1046
|
+
label: `prepared-material:${materialGuid}`,
|
|
1047
|
+
size: payload.byteLength,
|
|
1048
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1049
|
+
});
|
|
1050
|
+
if (!created.ok) return created;
|
|
1051
|
+
buffer = created.value;
|
|
1052
|
+
payloadByteLength = payload.byteLength;
|
|
1053
|
+
const written = internals.device.queue.writeBuffer(buffer, 0, payload);
|
|
1054
|
+
if (!written.ok) {
|
|
1055
|
+
internals.device.destroyBuffer(buffer);
|
|
1056
|
+
return written;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const textureResources: Array<{
|
|
1060
|
+
sampler: import('@forgeax/engine-rhi').Sampler;
|
|
1061
|
+
view: import('@forgeax/engine-rhi').TextureView;
|
|
1062
|
+
}> = [];
|
|
1063
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
1064
|
+
const field = fields[index];
|
|
1065
|
+
const value = field === undefined ? undefined : values[field];
|
|
1066
|
+
const textureValue =
|
|
1067
|
+
typeof value === 'object' && value !== null && 'texture' in value
|
|
1068
|
+
? (value as MaterialTextureValue)
|
|
1069
|
+
: undefined;
|
|
1070
|
+
let sampler = pipelineState.defaultSampler;
|
|
1071
|
+
if (textureValue?.sampler !== undefined) {
|
|
1072
|
+
const handle = preparedHandle(world, String(textureValue.sampler), 'SamplerAsset');
|
|
1073
|
+
if (handle !== undefined) {
|
|
1074
|
+
const asset = resolveAssetHandle<SamplerAsset>(world, handle);
|
|
1075
|
+
if (asset.ok) {
|
|
1076
|
+
const resident = internals.gpuStore.ensureSamplerResident(handle, asset.value, world);
|
|
1077
|
+
if (resident.ok) sampler = resident.value;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
let view = pipelineState.defaultWhiteTextureView;
|
|
1082
|
+
if (textureValue !== undefined) {
|
|
1083
|
+
const handle = preparedHandle(world, String(textureValue.texture), 'TextureAsset');
|
|
1084
|
+
if (handle !== undefined) {
|
|
1085
|
+
view = residentTextureView(world, internals.gpuStore, internals, handle) ?? view;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
textureResources.push({ sampler, view });
|
|
1089
|
+
}
|
|
1090
|
+
const entries: BindGroupEntry[] = [];
|
|
1091
|
+
let textureIndex = 0;
|
|
1092
|
+
for (let index = 0; index < derived.bglEntries.length; index += 1) {
|
|
1093
|
+
const expected = derived.bglEntries[index];
|
|
1094
|
+
if (expected?.buffer?.type === 'uniform' && buffer !== undefined) {
|
|
1095
|
+
entries.push({
|
|
1096
|
+
binding: expected.binding,
|
|
1097
|
+
resource: { kind: 'buffer', value: { buffer, size: payloadByteLength } },
|
|
1098
|
+
});
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
const textureResource = textureResources[textureIndex];
|
|
1102
|
+
if (
|
|
1103
|
+
expected?.sampler?.type === 'filtering' &&
|
|
1104
|
+
derived.bglEntries[index + 1]?.texture !== undefined &&
|
|
1105
|
+
textureResource !== undefined
|
|
1106
|
+
) {
|
|
1107
|
+
entries.push({
|
|
1108
|
+
binding: expected.binding,
|
|
1109
|
+
resource: { kind: 'sampler', value: textureResource.sampler },
|
|
1110
|
+
});
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (expected?.texture !== undefined && textureResource !== undefined) {
|
|
1114
|
+
entries.push({
|
|
1115
|
+
binding: expected.binding,
|
|
1116
|
+
resource: { kind: 'textureView', value: textureResource.view },
|
|
1117
|
+
});
|
|
1118
|
+
textureIndex += 1;
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
if (buffer !== undefined) internals.device.destroyBuffer(buffer);
|
|
1122
|
+
return err(new Error('prepared material schema contains unsupported binding kinds'));
|
|
1123
|
+
}
|
|
1124
|
+
for (let index = fields.length; index < 4; index += 1) {
|
|
1125
|
+
const binding = derived.userRegionBindingEnd + (index - fields.length) * 2;
|
|
1126
|
+
entries.push(
|
|
1127
|
+
{
|
|
1128
|
+
binding,
|
|
1129
|
+
resource: { kind: 'sampler', value: pipelineState.defaultSampler },
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
binding: binding + 1,
|
|
1133
|
+
resource: { kind: 'textureView', value: pipelineState.defaultWhiteTextureView },
|
|
1134
|
+
},
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
const fallback = pipelineState.skylightFallback;
|
|
1138
|
+
if (fallback === null) {
|
|
1139
|
+
if (buffer !== undefined) internals.device.destroyBuffer(buffer);
|
|
1140
|
+
return err(new Error('prepared material skylight fallback is unavailable'));
|
|
1141
|
+
}
|
|
1142
|
+
const completeEntries = assembleMaterialWithSkylightEntries(
|
|
1143
|
+
entries,
|
|
1144
|
+
{
|
|
1145
|
+
irradianceView: fallback.irradianceView,
|
|
1146
|
+
irradianceSampler: fallback.sampler,
|
|
1147
|
+
prefilterView: fallback.prefilterView,
|
|
1148
|
+
prefilterSampler: fallback.sampler,
|
|
1149
|
+
brdfLutView: fallback.brdfLutView,
|
|
1150
|
+
brdfLutSampler: fallback.sampler,
|
|
1151
|
+
intensityBuffer: fallback.intensityBuffer,
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
emissiveSampler: pipelineState.defaultSampler,
|
|
1155
|
+
emissiveView: pipelineState.defaultWhiteTextureView,
|
|
1156
|
+
occlusionSampler: pipelineState.defaultSampler,
|
|
1157
|
+
occlusionView: pipelineState.defaultWhiteTextureView,
|
|
1158
|
+
},
|
|
1159
|
+
);
|
|
1160
|
+
const group = internals.device.createBindGroup({ layout, entries: completeEntries });
|
|
1161
|
+
if (!group.ok) {
|
|
1162
|
+
if (buffer !== undefined) internals.device.destroyBuffer(buffer);
|
|
1163
|
+
return group;
|
|
1164
|
+
}
|
|
1165
|
+
if (buffer === undefined) return group;
|
|
1166
|
+
return ok({
|
|
1167
|
+
handle: group.value,
|
|
1168
|
+
dynamicOffsets: [0],
|
|
1169
|
+
release: () => internals.device.destroyBuffer(buffer),
|
|
1170
|
+
});
|
|
1171
|
+
};
|
|
1172
|
+
const lastFrustumStats: { culled: number; total: number } = { culled: 0, total: 0 };
|
|
1173
|
+
const lastVisibilityStats: { explicitlyHidden: number } = { explicitlyHidden: 0 };
|
|
1174
|
+
let lastMeshMaterialBindings: readonly MeshMaterialBindingObservation[] = [];
|
|
1175
|
+
let lastMeshMaterialBindingFrame: ExtractedFrame | undefined;
|
|
1176
|
+
const preparedResolverFactory = (
|
|
1177
|
+
input: RenderFeaturePreparedGraphicsResolverInput,
|
|
1178
|
+
): PreparedGraphicsResolver => {
|
|
1179
|
+
activeFeaturePostProcessEntries = input.fullscreenEffects;
|
|
1180
|
+
ensurePostProcessParamsResources(input.fullscreenEffects);
|
|
1181
|
+
return createPreparedGraphicsResolver({
|
|
1182
|
+
device: internals.device,
|
|
1183
|
+
featureIdentity: input.featureIdentity,
|
|
1184
|
+
generation: input.generation,
|
|
1185
|
+
capabilityAvailable: true,
|
|
1186
|
+
featureOrder: input.order,
|
|
1187
|
+
lookup: input.lookup,
|
|
1188
|
+
resolveGpuBuffer: (reference) =>
|
|
1189
|
+
featureGpuWork.resolveBuffer(input.featureIdentity, reference),
|
|
1190
|
+
resolvePipeline: (descriptor) => {
|
|
1191
|
+
const postProcessEntry =
|
|
1192
|
+
input.fullscreenEffects.get(descriptor.shader) ??
|
|
1193
|
+
builtinPostProcessEntries.get(descriptor.shader);
|
|
1194
|
+
if (postProcessEntry !== undefined) {
|
|
1195
|
+
const fullscreen = buildFullscreenPostProcessPass(
|
|
1196
|
+
{ device: internals.device, errorRegistry: internals.errorRegistry },
|
|
1197
|
+
postProcessEntry,
|
|
1198
|
+
);
|
|
1199
|
+
if (fullscreen === null) return err(new Error('prepared post-process layout failed'));
|
|
1200
|
+
const pipeline = internals.getPostProcessPipeline?.(
|
|
1201
|
+
descriptor.shader,
|
|
1202
|
+
fullscreen.bindGroupLayout,
|
|
1203
|
+
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
1204
|
+
);
|
|
1205
|
+
if (pipeline !== null && pipeline !== undefined) {
|
|
1206
|
+
preparedPipelineIds.set(pipeline as object, descriptor.shader);
|
|
1207
|
+
if (internals.getMaterialShaderBindingContract?.(descriptor.shader) === 'group-0') {
|
|
1208
|
+
preparedGroup0Pipelines.add(pipeline as object);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
return pipeline === null || pipeline === undefined
|
|
1212
|
+
? err(makePreparedPipelinePendingError())
|
|
1213
|
+
: ok(pipeline);
|
|
1214
|
+
}
|
|
1215
|
+
const preparedPipeline = internals.getMaterialShaderPipeline?.(
|
|
1216
|
+
descriptor.shader,
|
|
1217
|
+
descriptor.colorFormats[0] === 'rgba16float',
|
|
1218
|
+
descriptor.renderState,
|
|
1219
|
+
descriptor.topology,
|
|
1220
|
+
descriptor.indexFormat,
|
|
1221
|
+
undefined,
|
|
1222
|
+
'forward',
|
|
1223
|
+
undefined,
|
|
1224
|
+
descriptor.sampleCount ?? 1,
|
|
1225
|
+
// Prepared graphics own their declared attachment format. Passing
|
|
1226
|
+
// it through keeps the PSO compatible with feature-target views
|
|
1227
|
+
// (the ordinary material path intentionally defaults to the
|
|
1228
|
+
// swap-chain view format).
|
|
1229
|
+
descriptor.colorFormats[0] as GPUTextureFormat,
|
|
1230
|
+
undefined,
|
|
1231
|
+
descriptor.depthFormat === undefined
|
|
1232
|
+
? null
|
|
1233
|
+
: (descriptor.depthFormat as GPUTextureFormat),
|
|
1234
|
+
descriptor.vertexLayout,
|
|
1235
|
+
);
|
|
1236
|
+
// A requested material shader is an exact pipeline contract. During
|
|
1237
|
+
// async shader warmup, substituting the generic unlit pipeline can
|
|
1238
|
+
// mismatch vertex layouts and HDR attachment formats, turning a
|
|
1239
|
+
// retryable next-frame prepare into an invalid GPU command buffer.
|
|
1240
|
+
const pipeline = preparedPipeline ?? null;
|
|
1241
|
+
if (pipeline !== null) {
|
|
1242
|
+
preparedMaterialPipelineShaders.set(pipeline as object, descriptor.shader);
|
|
1243
|
+
const bindingContract = internals.getMaterialShaderBindingContract?.(descriptor.shader);
|
|
1244
|
+
if (bindingContract === 'group-0') {
|
|
1245
|
+
preparedGroup0Pipelines.add(pipeline as object);
|
|
1246
|
+
} else if (bindingContract === 'view-only') {
|
|
1247
|
+
preparedViewOnlyPipelines.add(pipeline as object);
|
|
1248
|
+
} else {
|
|
1249
|
+
preparedRenderMaterialPipelines.add(pipeline as object);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
return pipeline === null ? err(makePreparedPipelinePendingError()) : ok(pipeline);
|
|
1253
|
+
},
|
|
1254
|
+
resolveBindings: (descriptor, pipeline) => {
|
|
1255
|
+
const materialShaderId = preparedMaterialPipelineShaders.get(pipeline as object);
|
|
1256
|
+
const bindingContract =
|
|
1257
|
+
materialShaderId === undefined
|
|
1258
|
+
? undefined
|
|
1259
|
+
: internals.getMaterialShaderBindingContract?.(materialShaderId);
|
|
1260
|
+
if (bindingContract === 'group-0-resource') {
|
|
1261
|
+
return descriptor.values.sceneDepth === undefined
|
|
1262
|
+
? err(new Error('prepared group-0 resource pipeline requires a scene target'))
|
|
1263
|
+
: ok(undefined);
|
|
1264
|
+
}
|
|
1265
|
+
if (bindingContract === 'view-and-scene-depth') {
|
|
1266
|
+
return ok(undefined);
|
|
1267
|
+
}
|
|
1268
|
+
if (preparedGroup0Pipelines.has(pipeline as object)) {
|
|
1269
|
+
const layout =
|
|
1270
|
+
(materialShaderId === undefined
|
|
1271
|
+
? undefined
|
|
1272
|
+
: internals.getMaterialBindGroupLayout?.(materialShaderId)) ??
|
|
1273
|
+
(
|
|
1274
|
+
pipeline as RenderPipeline & {
|
|
1275
|
+
getBindGroupLayout?: (index: number) => BindGroupLayout;
|
|
1276
|
+
}
|
|
1277
|
+
).getBindGroupLayout?.(0);
|
|
1278
|
+
return layout === undefined
|
|
1279
|
+
? err(new Error('prepared group-0 pipeline bind group layout is unavailable'))
|
|
1280
|
+
: internals.device.createBindGroup({ layout, entries: [] });
|
|
1281
|
+
}
|
|
1282
|
+
if (
|
|
1283
|
+
preparedViewOnlyPipelines.has(pipeline as object) ||
|
|
1284
|
+
(preparedRenderMaterialPipelines.has(pipeline as object) &&
|
|
1285
|
+
descriptor.values.group === 0) ||
|
|
1286
|
+
pipeline === internals.getPipelineState()?.unlitPipeline
|
|
1287
|
+
) {
|
|
1288
|
+
return ok(undefined);
|
|
1289
|
+
}
|
|
1290
|
+
const group = descriptor.values.group;
|
|
1291
|
+
const layout =
|
|
1292
|
+
(materialShaderId !== undefined
|
|
1293
|
+
? (internals.getMaterialBindGroupLayout?.(materialShaderId) ??
|
|
1294
|
+
(group === 0 ? internals.getPipelineState()?.materialBindGroupLayout : undefined))
|
|
1295
|
+
: undefined) ??
|
|
1296
|
+
(
|
|
1297
|
+
pipeline as RenderPipeline & {
|
|
1298
|
+
getBindGroupLayout?: (index: number) => BindGroupLayout;
|
|
1299
|
+
}
|
|
1300
|
+
).getBindGroupLayout?.(group === 1 ? 1 : 0);
|
|
1301
|
+
const material = descriptor.values.material;
|
|
1302
|
+
if (
|
|
1303
|
+
group === 1 &&
|
|
1304
|
+
layout !== undefined &&
|
|
1305
|
+
materialShaderId !== undefined &&
|
|
1306
|
+
typeof material === 'object' &&
|
|
1307
|
+
material !== null &&
|
|
1308
|
+
'world' in material &&
|
|
1309
|
+
'guid' in material &&
|
|
1310
|
+
typeof material.world === 'number' &&
|
|
1311
|
+
typeof material.guid === 'string'
|
|
1312
|
+
) {
|
|
1313
|
+
return preparedMaterialBindings(materialShaderId, material.world, material.guid, layout);
|
|
1314
|
+
}
|
|
1315
|
+
return layout === undefined
|
|
1316
|
+
? err(new Error('prepared pipeline bind group layout is unavailable'))
|
|
1317
|
+
: internals.device.createBindGroup({
|
|
1318
|
+
layout,
|
|
1319
|
+
entries:
|
|
1320
|
+
group === 1 && preparedPipelineIds.has(pipeline as object)
|
|
1321
|
+
? [
|
|
1322
|
+
{
|
|
1323
|
+
binding: 0,
|
|
1324
|
+
resource: {
|
|
1325
|
+
kind: 'textureView',
|
|
1326
|
+
value: internals.getPipelineState()?.fallbackTextureView as never,
|
|
1327
|
+
},
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
binding: 1,
|
|
1331
|
+
resource: {
|
|
1332
|
+
kind: 'sampler',
|
|
1333
|
+
value: internals.getPipelineState()?.defaultSampler as never,
|
|
1334
|
+
},
|
|
1335
|
+
},
|
|
1336
|
+
...(internals.getPostProcessParamsBuffer?.(
|
|
1337
|
+
preparedPipelineIds.get(pipeline as object) ?? '',
|
|
1338
|
+
) === undefined
|
|
1339
|
+
? []
|
|
1340
|
+
: [
|
|
1341
|
+
{
|
|
1342
|
+
binding: 2,
|
|
1343
|
+
resource: {
|
|
1344
|
+
kind: 'buffer' as const,
|
|
1345
|
+
value: {
|
|
1346
|
+
buffer: internals.getPostProcessParamsBuffer?.(
|
|
1347
|
+
preparedPipelineIds.get(pipeline as object) ?? '',
|
|
1348
|
+
) as never,
|
|
1349
|
+
},
|
|
1350
|
+
},
|
|
1351
|
+
},
|
|
1352
|
+
]),
|
|
1353
|
+
]
|
|
1354
|
+
: [],
|
|
1355
|
+
});
|
|
1356
|
+
},
|
|
1357
|
+
});
|
|
1358
|
+
};
|
|
1359
|
+
return {
|
|
1360
|
+
releaseProfilerCatalog(): void {
|
|
1361
|
+
releaseProfilerCatalog?.();
|
|
1362
|
+
releaseProfilerCatalog = undefined;
|
|
1363
|
+
},
|
|
1364
|
+
get renderScene(): RenderSceneInspection {
|
|
1365
|
+
return {
|
|
1366
|
+
...persistentRenderScene.inspect(),
|
|
1367
|
+
gpuDriven: gpuDrivenProduction.inspect(),
|
|
1368
|
+
};
|
|
1369
|
+
},
|
|
1370
|
+
get pointsLinesSnapshots(): readonly PointsLinesRetainedSnapshot[] {
|
|
1371
|
+
return persistentRenderScene.pointsLinesSnapshots();
|
|
1372
|
+
},
|
|
1373
|
+
detachScene(world: World): void {
|
|
1374
|
+
persistentRenderScene.detach(world);
|
|
1375
|
+
},
|
|
1376
|
+
draw(
|
|
1377
|
+
worlds: readonly World[],
|
|
1378
|
+
opts: DrawOwnerOptions,
|
|
1379
|
+
renderReadLeases?: readonly RenderReadLease[],
|
|
1380
|
+
): boolean {
|
|
1381
|
+
preparedWorlds = worlds;
|
|
1382
|
+
const profileSession = internals.profiler?.activeSession();
|
|
1383
|
+
let ownsProfileFrame = false;
|
|
1384
|
+
let submitted = false;
|
|
1385
|
+
if (profileSession !== undefined && opts.profileFrame === undefined) {
|
|
1386
|
+
try {
|
|
1387
|
+
ownsProfileFrame = profileSession.beginFrame(++directFrameId).ok;
|
|
1388
|
+
} catch {
|
|
1389
|
+
ownsProfileFrame = false;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
try {
|
|
1393
|
+
pointsLinesOwner.beginFrame();
|
|
1394
|
+
// cameraOwner drives the surfaced cameras + frustum
|
|
1395
|
+
// cull; resourceOwner drives skylight/skybox/postProcess + per-world
|
|
1396
|
+
// record config.
|
|
1397
|
+
const { cameraOwner, resourceOwner } = opts;
|
|
1398
|
+
// Keep the compiled graph as last-known-good until the candidate topology
|
|
1399
|
+
// compiles. The failed candidate frame does not execute it; successful
|
|
1400
|
+
// replacement retires it atomically in ensureCompiledFrameGraph.
|
|
1401
|
+
if (frameState.installedPipelineHandle !== lastBuiltPipelineHandle) {
|
|
1402
|
+
// Feature contributions describe the active pipeline graph. Drop the
|
|
1403
|
+
// old graph before the next frame re-runs feature contribution
|
|
1404
|
+
// so a hot-swap cannot reuse passes compiled for the retired pipeline.
|
|
1405
|
+
resetRenderFeatureGraphState(internals);
|
|
1406
|
+
lastBuiltPipelineHandle = frameState.installedPipelineHandle;
|
|
1407
|
+
persistentRenderScene.invalidate();
|
|
1408
|
+
}
|
|
1409
|
+
dispatchCounts.unlit = 0;
|
|
1410
|
+
|
|
1411
|
+
// feat-20260708-composited-multi-world-rendering M3 / D-2 / m3-i2:
|
|
1412
|
+
// extractFrames merges per-world snapshots (renderables + lights from
|
|
1413
|
+
// every world, cameras + singleton resources from the owner world),
|
|
1414
|
+
// runs read-only extractFrame per world over the already-updated World,
|
|
1415
|
+
// resets frame state once, and isolates
|
|
1416
|
+
// per-world errors (AC-09).
|
|
1417
|
+
// Single-world draw([world], { cameraOwner: 0, resourceOwner: 0 }) is the identity path
|
|
1418
|
+
// (worldId=0), byte-for-byte equivalent to the pre-M3 direct
|
|
1419
|
+
// extractFrame path (AC-03 regression guarantee).
|
|
1420
|
+
//
|
|
1421
|
+
// Entry validation (empty worlds / owner out of range) is enforced by
|
|
1422
|
+
// the public renderer.draw facade (createRenderer.ts, D-5) before this
|
|
1423
|
+
// internal method is reached; the owner world is guaranteed present.
|
|
1424
|
+
//
|
|
1425
|
+
// feat-20260709-editor-world-partition M1 / w5+w6 (D-3): per-world
|
|
1426
|
+
// record configuration (transparent-sort mode, fold buckets, skybox /
|
|
1427
|
+
// asset resolution) is read from the resource-owner world — the world
|
|
1428
|
+
// that owns skylight / skybox / postProcessParams. w6 sources it from
|
|
1429
|
+
// the dedicated resourceOwner index. The variable is named
|
|
1430
|
+
// `resourceWorld` so the record-stage reads are self-describing (they
|
|
1431
|
+
// are resource-owner reads, not camera reads).
|
|
1432
|
+
const resourceWorld = worlds[resourceOwner] as World;
|
|
1433
|
+
const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
|
|
1434
|
+
return persistentRenderScene.extractComposition(
|
|
1435
|
+
worlds,
|
|
1436
|
+
{ cameraOwner, resourceOwner },
|
|
1437
|
+
internals.assets.catalogEpoch,
|
|
1438
|
+
() =>
|
|
1439
|
+
extractFrames(
|
|
1440
|
+
worlds,
|
|
1441
|
+
{ cameraOwner, resourceOwner },
|
|
1442
|
+
internals.assets,
|
|
1443
|
+
internals.getPipelineState(),
|
|
1444
|
+
persistentRenderScene.materialSnapshotCacheStore(),
|
|
1445
|
+
{ cull: 'none' },
|
|
1446
|
+
),
|
|
1447
|
+
renderReadLeases,
|
|
1448
|
+
);
|
|
1449
|
+
});
|
|
1450
|
+
const {
|
|
1451
|
+
cameras,
|
|
1452
|
+
lights,
|
|
1453
|
+
renderables,
|
|
1454
|
+
dispatch,
|
|
1455
|
+
skylight,
|
|
1456
|
+
skylightCount,
|
|
1457
|
+
skybox,
|
|
1458
|
+
skyboxCount,
|
|
1459
|
+
frustumStats,
|
|
1460
|
+
visibilityStats,
|
|
1461
|
+
postProcessParams,
|
|
1462
|
+
} = frame;
|
|
1463
|
+
if (lastMeshMaterialBindingFrame !== frame) {
|
|
1464
|
+
lastMeshMaterialBindingFrame = frame;
|
|
1465
|
+
lastMeshMaterialBindings = renderables.map((renderable) => ({
|
|
1466
|
+
worldId: renderable.worldId,
|
|
1467
|
+
entityKey: renderable.entityKey,
|
|
1468
|
+
bindings: renderable.materials.map((material, slotIndex) => ({
|
|
1469
|
+
handle: material.materialHandle ?? 0,
|
|
1470
|
+
source: renderable.materialBindingSources[slotIndex] ?? 'engine-default',
|
|
1471
|
+
})),
|
|
1472
|
+
diagnostics: renderable.materialBindingDiagnostics ?? [],
|
|
1473
|
+
}));
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
bindGroupCounts.createBindGroup = 0;
|
|
1477
|
+
bindGroupCounts.keys = [];
|
|
1478
|
+
const preparedPipelineState = internals.getPipelineState();
|
|
1479
|
+
const bindGroupSkipReason: RenderPhaseSkipReason | undefined =
|
|
1480
|
+
internals.featureHost === undefined
|
|
1481
|
+
? 'feature-host-unavailable'
|
|
1482
|
+
: internals.featureHost.size === 0
|
|
1483
|
+
? 'feature-host-empty'
|
|
1484
|
+
: preparedPipelineState === null
|
|
1485
|
+
? 'pipeline-state-unavailable'
|
|
1486
|
+
: frame.cameras.length === 0
|
|
1487
|
+
? 'camera-unavailable'
|
|
1488
|
+
: undefined;
|
|
1489
|
+
if (bindGroupSkipReason !== undefined || preparedPipelineState === null) {
|
|
1490
|
+
recordProfileSkip(
|
|
1491
|
+
profileSession,
|
|
1492
|
+
'bind-groups',
|
|
1493
|
+
bindGroupSkipReason ?? 'pipeline-state-unavailable',
|
|
1494
|
+
);
|
|
1495
|
+
} else {
|
|
1496
|
+
runProfiledRenderPhase(profileSession, 'bind-groups', () =>
|
|
1497
|
+
buildPerFrameBindGroups(
|
|
1498
|
+
internals,
|
|
1499
|
+
frameState,
|
|
1500
|
+
preparedPipelineState,
|
|
1501
|
+
true,
|
|
1502
|
+
bindGroupCounts,
|
|
1503
|
+
undefined,
|
|
1504
|
+
false,
|
|
1505
|
+
),
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
lastFrustumStats.culled = frustumStats.culled;
|
|
1509
|
+
lastFrustumStats.total = frustumStats.total;
|
|
1510
|
+
lastVisibilityStats.explicitlyHidden = visibilityStats.explicitlyHidden;
|
|
1511
|
+
const featureTargets =
|
|
1512
|
+
preparedPipelineState === null || cameras[0] === undefined
|
|
1513
|
+
? []
|
|
1514
|
+
: resolveStandardRenderFeatureTargets({
|
|
1515
|
+
tonemap: cameras[0].tonemap,
|
|
1516
|
+
antialias: cameras[0].antialias,
|
|
1517
|
+
colorAttachmentFormat: preparedPipelineState.colorAttachmentFormat,
|
|
1518
|
+
storageBuffer: internals.device.caps.storageBuffer,
|
|
1519
|
+
multisample: internals.device.caps.backendKind !== 'wgpu-webgl2',
|
|
1520
|
+
});
|
|
1521
|
+
let featureGraphCandidate: RenderFeatureGraphCandidate | undefined;
|
|
1522
|
+
const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
|
|
1523
|
+
if (internals.featureHost === undefined) return [];
|
|
1524
|
+
const featureFrame = runRenderFeatureFrame(internals.featureHost, {
|
|
1525
|
+
worlds,
|
|
1526
|
+
owner: resourceOwner,
|
|
1527
|
+
frameNumber: frameState.frameNumber,
|
|
1528
|
+
visibilitySnapshots: frame.featureVisibilitySnapshots,
|
|
1529
|
+
hiddenEntityReports: frame.hiddenEntityReports,
|
|
1530
|
+
targets: featureTargets,
|
|
1531
|
+
generation: internals.featureHost?.preparedGeneration ?? 0,
|
|
1532
|
+
caps: internals.device.caps,
|
|
1533
|
+
createPreparedGraphicsResolver: preparedResolverFactory,
|
|
1534
|
+
gpuWork: featureGpuWork,
|
|
1535
|
+
});
|
|
1536
|
+
activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
|
|
1537
|
+
ensurePostProcessParamsResources(featureFrame.fullscreenEffects);
|
|
1538
|
+
lastVisibilityStats.explicitlyHidden = featureFrame.hiddenEntityReports.length;
|
|
1539
|
+
for (const featureError of featureFrame.errors) {
|
|
1540
|
+
if (!isPendingRenderFeaturePreparation(featureError)) {
|
|
1541
|
+
internals.errorRegistry.fire(featureError);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
featureGraphCandidate = {
|
|
1545
|
+
plans: featureFrame.plans,
|
|
1546
|
+
fullscreenEffects: featureFrame.fullscreenEffects,
|
|
1547
|
+
...(featureFrame.preparedResourceBatches.length === 0
|
|
1548
|
+
? {}
|
|
1549
|
+
: { preparedResourceKey: `frame-${frameState.frameNumber}` }),
|
|
1550
|
+
onRejected: () => {
|
|
1551
|
+
for (const batch of featureFrame.preparedResourceBatches) {
|
|
1552
|
+
const released = batch.release();
|
|
1553
|
+
if (!released.ok) {
|
|
1554
|
+
internals.errorRegistry.fire(released.error);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
};
|
|
1559
|
+
return featureFrame.preparedResourceBatches;
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
// Unified transparent-sort: (layer ASC, sortValue ASC) for modes 0/1/2;
|
|
1563
|
+
// distance back-to-front for mode=3. The transparent-sort config is a
|
|
1564
|
+
// per-world resource; it is read from the resource-owner world (the
|
|
1565
|
+
// world that owns skylight / skybox / singleton render state, w5 / D-3).
|
|
1566
|
+
// Only the Transparent segment is reordered; queue ordering between
|
|
1567
|
+
// segments (sortDispatchByQueue, stable) is preserved.
|
|
1568
|
+
const orderedDispatch = runProfiledRenderPhase(profileSession, 'sort', () =>
|
|
1569
|
+
sortTransparentDispatch(dispatch, resourceWorld, cameras, renderables),
|
|
1570
|
+
);
|
|
1571
|
+
|
|
1572
|
+
// M3 / w26: single dispatch list replaces old three-bucket model.
|
|
1573
|
+
// Pass dispatch to recordFrame — the record stage iterates dispatch
|
|
1574
|
+
// entries in queue order per plan-strategy D-3.
|
|
1575
|
+
//
|
|
1576
|
+
// feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2):
|
|
1577
|
+
// the record stage must resolve each renderable's mesh + material
|
|
1578
|
+
// textures against the world it was EXTRACTED from — never the single
|
|
1579
|
+
// resourceWorld. The extract stage already resolves per-world
|
|
1580
|
+
// (extractFrames loops worlds[] and stamps RenderableSnapshot.worldId);
|
|
1581
|
+
// the record stage regressed to resolving everything against
|
|
1582
|
+
// resourceWorld, so a user-tier mesh living in the cameraOwner world
|
|
1583
|
+
// (e.g. an editor gizmo handle) resolves against resourceWorld's
|
|
1584
|
+
// sharedRefs — either a miss (asset-not-registered) or, when that slot
|
|
1585
|
+
// is occupied by an unrelated user-tier payload (equirect), a
|
|
1586
|
+
// wrong-kind resolve that throws in ensureResident. `worlds` is threaded
|
|
1587
|
+
// so the record stage can index worlds[renderable.worldId]. `resourceWorld`
|
|
1588
|
+
// stays the singleton-resource owner (skybox equirect / transparent-sort
|
|
1589
|
+
// config / video provider) — those ARE resource-owner reads.
|
|
1590
|
+
const recordProfilePhase: RecordProfileRunner | undefined =
|
|
1591
|
+
profileSession === undefined || profileSession.detail === 'owner'
|
|
1592
|
+
? undefined
|
|
1593
|
+
: function recordProfilePhase<T>(phase: RenderRecordPhase, action: () => T): T {
|
|
1594
|
+
// `passes` deliberately keeps only the graph-pass boundary.
|
|
1595
|
+
// The same runner is also called by geometry/material helpers;
|
|
1596
|
+
// invoking those wrappers would turn a pass probe into the
|
|
1597
|
+
// high-overhead per-draw `nested` probe.
|
|
1598
|
+
if (
|
|
1599
|
+
profileSession.detail === 'passes' &&
|
|
1600
|
+
(!phase.startsWith('record/graph-execute/') ||
|
|
1601
|
+
(phase.slice('record/graph-execute/'.length).includes('/') &&
|
|
1602
|
+
!phase.endsWith('/geometry-loop')))
|
|
1603
|
+
) {
|
|
1604
|
+
return action();
|
|
1605
|
+
}
|
|
1606
|
+
return runProfiledRenderPhase(profileSession, phase, action);
|
|
1607
|
+
};
|
|
1608
|
+
submitted = runProfiledRenderPhase(profileSession, 'record', () =>
|
|
1609
|
+
recordFrame(
|
|
1610
|
+
internals,
|
|
1611
|
+
resourceWorld,
|
|
1612
|
+
cameras,
|
|
1613
|
+
lights,
|
|
1614
|
+
renderables,
|
|
1615
|
+
orderedDispatch,
|
|
1616
|
+
frameState,
|
|
1617
|
+
dispatchCounts,
|
|
1618
|
+
bindGroupCounts,
|
|
1619
|
+
skylight,
|
|
1620
|
+
skylightCount,
|
|
1621
|
+
skybox,
|
|
1622
|
+
skyboxCount,
|
|
1623
|
+
postProcessParams,
|
|
1624
|
+
worlds,
|
|
1625
|
+
recordProfilePhase,
|
|
1626
|
+
{
|
|
1627
|
+
owner: gpuDrivenProduction,
|
|
1628
|
+
scene: persistentRenderScene.compositionGpuDrivenState(),
|
|
1629
|
+
onCoverage: (ownsAll) => {
|
|
1630
|
+
const key = `${internals.gpuStore.meshResidencyEpoch}:${frameState.isHdrpActive ? 'hdrp' : 'urp'}`;
|
|
1631
|
+
persistentRenderScene.setCompositionGpuDrivenCoverage(key, ownsAll);
|
|
1632
|
+
},
|
|
1633
|
+
},
|
|
1634
|
+
renderReadLeases,
|
|
1635
|
+
featureGraphCandidate,
|
|
1636
|
+
pointsLinesOwner,
|
|
1637
|
+
),
|
|
1638
|
+
);
|
|
1639
|
+
persistentRenderScene.setPointsLinesInspections(pointsLinesOwner.inspections());
|
|
1640
|
+
if (internals.featureHost !== undefined && preparedResourceBatches.length > 0) {
|
|
1641
|
+
const batches = preparedResourceBatches;
|
|
1642
|
+
if (submitted) {
|
|
1643
|
+
internals.featureHost.markPreparedGraphicsSubmitted(batches);
|
|
1644
|
+
settlePreparedGraphicsCompletion(
|
|
1645
|
+
internals.featureHost,
|
|
1646
|
+
batches,
|
|
1647
|
+
internals.device.queue.onSubmittedWorkDone(),
|
|
1648
|
+
(error) => reportPreparedGraphicsCompletionError(internals, error),
|
|
1649
|
+
);
|
|
1650
|
+
} else {
|
|
1651
|
+
const retired = internals.featureHost.retirePreparedGraphics();
|
|
1652
|
+
if (!retired.ok) internals.errorRegistry.fire(retired.error);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
} catch (err) {
|
|
1656
|
+
const innerError =
|
|
1657
|
+
err instanceof RhiError
|
|
1658
|
+
? err
|
|
1659
|
+
: { code: 'unknown' as const, message: String(err), name: (err as Error)?.name };
|
|
1660
|
+
internals.errorRegistry.fire(
|
|
1661
|
+
new RhiError({
|
|
1662
|
+
code: 'webgpu-runtime-error',
|
|
1663
|
+
expected: 'RenderSystem to record one frame without an internal exception',
|
|
1664
|
+
hint: 'inspect detail.error for the underlying cause; next frame will retry',
|
|
1665
|
+
detail: { error: innerError },
|
|
1666
|
+
}),
|
|
1667
|
+
);
|
|
1668
|
+
} finally {
|
|
1669
|
+
if (ownsProfileFrame) {
|
|
1670
|
+
try {
|
|
1671
|
+
profileSession?.endFrame();
|
|
1672
|
+
} catch {
|
|
1673
|
+
// Profiler failures never alter rendering.
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
return submitted;
|
|
1678
|
+
},
|
|
1679
|
+
pipelineDispatchCounts: dispatchCounts,
|
|
1680
|
+
observeCurrentFrame(options: FrameObservationOptions) {
|
|
1681
|
+
const currentFrameId = frameState.frameNumber - 1;
|
|
1682
|
+
return observeCurrentFrame(options, frameState.currentFrameObservationSource, currentFrameId);
|
|
1683
|
+
},
|
|
1684
|
+
bindGroupCounts: bindGroupCounts,
|
|
1685
|
+
frustumStats: lastFrustumStats,
|
|
1686
|
+
visibilityStats: lastVisibilityStats,
|
|
1687
|
+
get meshMaterialBindings(): readonly MeshMaterialBindingObservation[] {
|
|
1688
|
+
return lastMeshMaterialBindings;
|
|
1689
|
+
},
|
|
1690
|
+
get perFramePassNames(): readonly string[] {
|
|
1691
|
+
return frameState.compiledFrameGraph?.inspect().passes.map((pass) => pass.name) ?? [];
|
|
1692
|
+
},
|
|
1693
|
+
configureStandard(config: RenderPipelineAsset['config']): void {
|
|
1694
|
+
const profileConfig = internals.standardProfile;
|
|
1695
|
+
const resolvedConfig =
|
|
1696
|
+
profileConfig === undefined
|
|
1697
|
+
? config
|
|
1698
|
+
: {
|
|
1699
|
+
...(profileConfig.lighting === 'clustered'
|
|
1700
|
+
? { clusterGrid: DEFAULT_CLUSTER_GRID }
|
|
1701
|
+
: {}),
|
|
1702
|
+
...(profileConfig.ssao ? { ssao: { enabled: true } } : {}),
|
|
1703
|
+
...config,
|
|
1704
|
+
};
|
|
1705
|
+
if (resolvedConfig?.clusterGrid !== undefined) {
|
|
1706
|
+
const grid = resolvedConfig.clusterGrid;
|
|
1707
|
+
const gridResult = validateClusterGrid(grid);
|
|
1708
|
+
if (!gridResult.ok) {
|
|
1709
|
+
throw gridResult.error;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
if (resolvedConfig?.ssao?.enabled === true) {
|
|
1713
|
+
const ssaoResult = resolveSsaoParameters(resolvedConfig.ssao);
|
|
1714
|
+
if (!ssaoResult.ok) {
|
|
1715
|
+
throw ssaoResult.error;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
// Standard configuration no longer carries a handle (D-19: RenderPipelineAsset is
|
|
1719
|
+
// supplied as a POD at boot/swap time before a World exists). The
|
|
1720
|
+
// brand-number that `draw` compares to force a per-frame graph rebuild is
|
|
1721
|
+
// now a monotonic epoch bumped on every install -- distinct configs (and
|
|
1722
|
+
// even identical re-installs) trigger the rebuild, which is correct: install
|
|
1723
|
+
// is a rare boot/swap event, never a per-frame cost.
|
|
1724
|
+
installEpoch += 1;
|
|
1725
|
+
frameState.installedPipelineHandle = installEpoch;
|
|
1726
|
+
frameState.isHdrpActive = resolvedConfig?.clusterGrid !== undefined;
|
|
1727
|
+
frameState.installedPipelineConfig = resolvedConfig;
|
|
1728
|
+
},
|
|
1729
|
+
registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void {
|
|
1730
|
+
// D-3: eager-create params UBO at register time + fail-fast
|
|
1731
|
+
// byteSize / defaultValue validation (q5=A).
|
|
1732
|
+
let paramsBuffer: Buffer | undefined;
|
|
1733
|
+
try {
|
|
1734
|
+
if (entry.params !== undefined) {
|
|
1735
|
+
const { byteSize, defaultValue } = entry.params;
|
|
1736
|
+
if (byteSize < 16 || defaultValue.length !== byteSize) {
|
|
1737
|
+
throw new PostProcessError({
|
|
1738
|
+
code: 'params-size-mismatch',
|
|
1739
|
+
detail: { byteSize, actualLength: defaultValue.length },
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
const paramsBufferResult = internals.device.createBuffer({
|
|
1743
|
+
label: `post-process-params-${id}`,
|
|
1744
|
+
size: byteSize,
|
|
1745
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1746
|
+
mappedAtCreation: false,
|
|
1747
|
+
});
|
|
1748
|
+
if (!paramsBufferResult.ok) throw paramsBufferResult.error;
|
|
1749
|
+
paramsBuffer = paramsBufferResult.value;
|
|
1750
|
+
const writeResult = internals.device.queue.writeBuffer(paramsBuffer, 0, defaultValue);
|
|
1751
|
+
if (!writeResult.ok) throw writeResult.error;
|
|
1752
|
+
} else if (entryHasDepthRead(entry)) {
|
|
1753
|
+
const paramsBufferResult = internals.device.createBuffer({
|
|
1754
|
+
label: `post-process-params-${id}`,
|
|
1755
|
+
size: DEPTH_MIN_PARAMS_BYTE_SIZE,
|
|
1756
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1757
|
+
mappedAtCreation: false,
|
|
1758
|
+
});
|
|
1759
|
+
if (!paramsBufferResult.ok) throw paramsBufferResult.error;
|
|
1760
|
+
paramsBuffer = paramsBufferResult.value;
|
|
1761
|
+
}
|
|
1762
|
+
if (builtinPostProcessEntries.has(id)) {
|
|
1763
|
+
throw new PostProcessError({
|
|
1764
|
+
code: 'post-process-already-registered',
|
|
1765
|
+
detail: { id },
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
builtinPostProcessEntries.set(id, entry);
|
|
1769
|
+
if (paramsBuffer !== undefined) postProcessParamsBuffers.set(id, paramsBuffer);
|
|
1770
|
+
return () => {
|
|
1771
|
+
if (builtinPostProcessEntries.get(id) === entry) {
|
|
1772
|
+
builtinPostProcessEntries.delete(id);
|
|
1773
|
+
}
|
|
1774
|
+
const current = postProcessParamsBuffers.get(id);
|
|
1775
|
+
if (current !== undefined) {
|
|
1776
|
+
internals.device.destroyBuffer(current);
|
|
1777
|
+
postProcessParamsBuffers.delete(id);
|
|
1778
|
+
}
|
|
1779
|
+
for (const key of postProcessPipelineCache.keys()) {
|
|
1780
|
+
if (key.startsWith(`${id}|`)) postProcessPipelineCache.delete(key);
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
} catch (cause) {
|
|
1784
|
+
if (paramsBuffer !== undefined) internals.device.destroyBuffer(paramsBuffer);
|
|
1785
|
+
throw cause;
|
|
1786
|
+
}
|
|
1787
|
+
},
|
|
1788
|
+
disposeFrameState(): void {
|
|
1789
|
+
persistentRenderScene.dispose();
|
|
1790
|
+
gpuDrivenProduction.dispose();
|
|
1791
|
+
disposeFeatureGpuWork();
|
|
1792
|
+
// Retire graph-owned resources and dispose instance-buffer caches.
|
|
1793
|
+
// Both calls are idempotent + tolerate per-handle errors silently;
|
|
1794
|
+
// the Renderer.dispose() cascade owns the surrounding try/catch
|
|
1795
|
+
// (D-3 method A: void signature, sub-errors fan out via
|
|
1796
|
+
// errorRegistry.fire at the cascade layer, dispose still walks all
|
|
1797
|
+
// 6 steps).
|
|
1798
|
+
frameState.directionalShadowCache = null;
|
|
1799
|
+
frameState.directionalShadowCacheRecorded = false;
|
|
1800
|
+
frameState.currentFrameObservationSource = undefined;
|
|
1801
|
+
frameState.currentDirectionalShadowView = null;
|
|
1802
|
+
frameState.currentSpotShadowView = null;
|
|
1803
|
+
const compiled = frameState.compiledFrameGraph;
|
|
1804
|
+
frameState.compiledFrameGraph = null;
|
|
1805
|
+
frameState.compiledFrameGraphTopologyKey = null;
|
|
1806
|
+
if (compiled !== null) compiled.retire().catch(() => undefined);
|
|
1807
|
+
for (const retired of frameState.retiredCompiledFrameGraphs) {
|
|
1808
|
+
retired.retire().catch(() => undefined);
|
|
1809
|
+
}
|
|
1810
|
+
frameState.retiredCompiledFrameGraphs.clear();
|
|
1811
|
+
// feat-20260619 M4 (D-6): pass errorRegistry to disposeInstanceBuffers
|
|
1812
|
+
// so destroy failures fire structured errors (unified per-frame +
|
|
1813
|
+
// dispose error strategy).
|
|
1814
|
+
disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
|
|
1815
|
+
disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
|
|
1816
|
+
if (frameState.morphBuffers !== undefined) {
|
|
1817
|
+
for (const entry of frameState.morphBuffers.values()) {
|
|
1818
|
+
if (!entry.buffer.isDestroyed) {
|
|
1819
|
+
const result = entry.buffer.destroy();
|
|
1820
|
+
if (!result.ok) internals.errorRegistry.fire(result.error);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
frameState.morphBuffers.clear();
|
|
1824
|
+
}
|
|
1825
|
+
// feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-2: dispose the
|
|
1826
|
+
// cube_array shadow atlas owned by the RenderSystem closure. The atlas
|
|
1827
|
+
// is per-RenderSystem (= per Renderer) and is shared transparently
|
|
1828
|
+
// between URP and HDRP pipelines.
|
|
1829
|
+
// Idempotent: dispose() on a null / already-disposed atlas is a no-op.
|
|
1830
|
+
if (frameState.pointShadowAtlas !== null) {
|
|
1831
|
+
frameState.pointShadowAtlas.dispose();
|
|
1832
|
+
frameState.pointShadowAtlas = null;
|
|
1833
|
+
}
|
|
1834
|
+
},
|
|
1835
|
+
resetForRecover(): void {
|
|
1836
|
+
persistentRenderScene.resetGpuForRecover();
|
|
1837
|
+
gpuDrivenProduction.dispose();
|
|
1838
|
+
gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
|
|
1839
|
+
disposeFeatureGpuWork();
|
|
1840
|
+
// feat-20260622-s5 M3 / B-2 / w18: recover() rebuild drops device-bound
|
|
1841
|
+
// state minted by the lost device. The active graph and per-entity caches
|
|
1842
|
+
// must be discarded, not merely marked for destruction: their opaque
|
|
1843
|
+
// handles cannot be used on the fresh device and the next draw must
|
|
1844
|
+
// lazily build a new graph from the preserved ECS / asset POD caches.
|
|
1845
|
+
frameState.compiledFrameGraph?.retire().catch(() => undefined);
|
|
1846
|
+
frameState.compiledFrameGraph = null;
|
|
1847
|
+
frameState.compiledFrameGraphTopologyKey = null;
|
|
1848
|
+
frameState.directionalShadowCache = null;
|
|
1849
|
+
frameState.directionalShadowCacheRecorded = false;
|
|
1850
|
+
frameState.currentFrameObservationSource = undefined;
|
|
1851
|
+
frameState.currentDirectionalShadowView = null;
|
|
1852
|
+
frameState.currentSpotShadowView = null;
|
|
1853
|
+
for (const retired of frameState.retiredCompiledFrameGraphs) {
|
|
1854
|
+
retired.retire().catch(() => undefined);
|
|
1855
|
+
}
|
|
1856
|
+
frameState.retiredCompiledFrameGraphs.clear();
|
|
1857
|
+
frameState.instanceBuffers.clear();
|
|
1858
|
+
frameState.morphBuffers?.clear();
|
|
1859
|
+
frameState.transientInstanceBuffers = [];
|
|
1860
|
+
frameState.pointShadowAtlas = null;
|
|
1861
|
+
// Bind groups retain opaque handles from the lost device. WeakMap roots
|
|
1862
|
+
// cannot be cleared, so replace them; the Map-backed caches can be
|
|
1863
|
+
// emptied in place. The next frame recreates every binding from the
|
|
1864
|
+
// rebuilt PipelineState and fresh residency handles.
|
|
1865
|
+
frameState.viewBindGroupCache = new WeakMap();
|
|
1866
|
+
frameState.meshBindGroupCache = new WeakMap();
|
|
1867
|
+
frameState.materialBgPerEntity.clear();
|
|
1868
|
+
frameState.instancesBgPerEntity.clear();
|
|
1869
|
+
frameState.materialBgShared.clear();
|
|
1870
|
+
frameState.materialBgAssemblyCache.clear();
|
|
1871
|
+
frameState.singletonMaterialCache.clear();
|
|
1872
|
+
frameState.postProcessBgCache = new WeakMap();
|
|
1873
|
+
// Fullscreen PSOs are cached outside frameState because the normal
|
|
1874
|
+
// path reuses them across frames. They still carry opaque handles from
|
|
1875
|
+
// the lost device, so recovery must invalidate this cache alongside the
|
|
1876
|
+
// feature-host declarations and UBOs below.
|
|
1877
|
+
postProcessPipelineCache.clear();
|
|
1878
|
+
// Logical declarations stay live in the feature host while a replacement
|
|
1879
|
+
// device is being requested.
|
|
1880
|
+
postProcessParamsBuffers.clear();
|
|
1881
|
+
resetRenderFeatureGraphState(internals);
|
|
1882
|
+
},
|
|
1883
|
+
restorePostProcessResources(): void {
|
|
1884
|
+
const declarations = [
|
|
1885
|
+
...builtinPostProcessEntries.entries(),
|
|
1886
|
+
...activeFeaturePostProcessEntries.entries(),
|
|
1887
|
+
];
|
|
1888
|
+
for (const [id, entry] of declarations) {
|
|
1889
|
+
let buffer: Buffer | undefined;
|
|
1890
|
+
if (entry.params !== undefined) {
|
|
1891
|
+
const created = internals.device.createBuffer({
|
|
1892
|
+
label: `post-process-params-${id}`,
|
|
1893
|
+
size: entry.params.byteSize,
|
|
1894
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1895
|
+
mappedAtCreation: false,
|
|
1896
|
+
});
|
|
1897
|
+
if (!created.ok) throw created.error;
|
|
1898
|
+
buffer = created.value;
|
|
1899
|
+
const written = internals.device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
|
|
1900
|
+
if (!written.ok) {
|
|
1901
|
+
internals.device.destroyBuffer(buffer);
|
|
1902
|
+
throw written.error;
|
|
1903
|
+
}
|
|
1904
|
+
} else if (entryHasDepthRead(entry)) {
|
|
1905
|
+
const created = internals.device.createBuffer({
|
|
1906
|
+
label: `post-process-params-${id}`,
|
|
1907
|
+
size: DEPTH_MIN_PARAMS_BYTE_SIZE,
|
|
1908
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1909
|
+
mappedAtCreation: false,
|
|
1910
|
+
});
|
|
1911
|
+
if (!created.ok) throw created.error;
|
|
1912
|
+
buffer = created.value;
|
|
1913
|
+
}
|
|
1914
|
+
if (buffer !== undefined && lookupPostProcess(id) === entry) {
|
|
1915
|
+
postProcessParamsBuffers.set(id, buffer);
|
|
1916
|
+
} else if (buffer !== undefined) {
|
|
1917
|
+
internals.device.destroyBuffer(buffer);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
},
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// @forgeax/engine-render - built-in record context extension.
|
|
1925
|
+
//
|
|
1926
|
+
// The public RenderPipelineContext is canonical in render-contract.ts. This
|
|
1927
|
+
// owner adds only concrete assembly state for built-in record closures.
|
|
1928
|
+
|
|
1929
|
+
/**
|
|
1930
|
+
* Package-private extension consumed by the built-in record closures only.
|
|
1931
|
+
* Custom pipeline authors see the leaf RenderPipelineContext contract and
|
|
1932
|
+
* never this surface.
|
|
1933
|
+
*/
|