@forgeax/engine-render 0.1.20 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +365 -29
- package/dist/assembly/backend-contract.d.ts +13 -0
- package/dist/assembly/backend-contract.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts +18 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -0
- package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
- package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +30 -19
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +19 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +46 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -0
- package/dist/assets/render-asset-port.d.ts.map +1 -1
- package/dist/authoring.mjs +4 -1
- package/dist/authoring.mjs.map +1 -1
- package/dist/capture/cube-views.d.ts +21 -0
- package/dist/capture/cube-views.d.ts.map +1 -0
- package/dist/capture/scheduler.d.ts +54 -0
- package/dist/capture/scheduler.d.ts.map +1 -0
- package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
- package/dist/chunk-4L3P6DT5.mjs.map +1 -0
- package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
- package/dist/chunk-4NPUASZR.mjs.map +1 -0
- package/dist/chunk-DIYW3M5D.mjs +5085 -0
- package/dist/chunk-DIYW3M5D.mjs.map +1 -0
- package/dist/chunk-FBC4R44J.mjs +1003 -0
- package/dist/chunk-FBC4R44J.mjs.map +1 -0
- package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
- package/dist/chunk-FF7K3FSO.mjs.map +1 -0
- package/dist/chunk-HZUPSYW7.mjs +1104 -0
- package/dist/chunk-HZUPSYW7.mjs.map +1 -0
- package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
- package/dist/chunk-QODA5UKU.mjs.map +1 -0
- package/dist/chunk-YALPS44A.mjs +296 -0
- package/dist/chunk-YALPS44A.mjs.map +1 -0
- package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
- package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
- package/dist/cluster-binner.d.ts +5 -0
- package/dist/cluster-binner.d.ts.map +1 -1
- package/dist/components/camera.d.ts +1 -0
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/cube-camera.d.ts +20 -0
- package/dist/components/cube-camera.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +11 -2
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/directional-shadow-filter.d.ts +25 -0
- package/dist/components/directional-shadow-filter.d.ts.map +1 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts +4 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/light-probe.d.ts +9 -0
- package/dist/components/light-probe.d.ts.map +1 -0
- package/dist/components/rect-area-light.d.ts +12 -0
- package/dist/components/rect-area-light.d.ts.map +1 -0
- package/dist/components/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/components/scene-instance.d.ts +4 -0
- package/dist/components/scene-instance.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +3 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +14836 -9620
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +8 -1
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/lifecycle-transaction.d.ts +2 -0
- package/dist/device/lifecycle-transaction.d.ts.map +1 -0
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +5 -0
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/gpu-driven.d.ts +26 -0
- package/dist/errors/gpu-driven.d.ts.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +9 -1
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +166 -93
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/directional-shadow-projection.d.ts +23 -0
- package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
- package/dist/extract/environment.d.ts +14 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +50 -0
- package/dist/extract/gpu-driven.d.ts.map +1 -0
- package/dist/extract/visibility.d.ts.map +1 -1
- package/dist/features/fullscreen.d.ts.map +1 -1
- package/dist/features/targets.d.ts.map +1 -1
- package/dist/features/types.d.ts +10 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/fullscreen-post-process-pass.d.ts +13 -2
- package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts +34 -0
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +63 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
- package/dist/gpu-driven/production-raster.d.ts +97 -2
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +64 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts +14 -0
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +8 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +0 -1
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +21 -19
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +4 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/kernel-cache.d.ts +36 -0
- package/dist/ibl/kernel-cache.d.ts.map +1 -0
- package/dist/index.d.ts +39 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +91 -6
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +214 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances-derived-bounds.d.ts +50 -0
- package/dist/instances-derived-bounds.d.ts.map +1 -0
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +23 -2
- package/dist/internal.mjs.map +1 -1
- package/dist/light-buffer-layout.d.ts +58 -140
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +54 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +38 -14
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts +2 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
- package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
- package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
- package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
- package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
- package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
- package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
- package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
- package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
- package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
- package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
- package/dist/pipeline/standard-pipeline.d.ts +4 -13
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +5 -26
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +2 -0
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/point-shadow-inspection.d.ts +21 -0
- package/dist/point-shadow-inspection.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
- package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/resources.d.ts +72 -0
- package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
- package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
- package/dist/prepare/extended-lighting/state.d.ts +24 -0
- package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
- package/dist/record/frame-lighting.d.ts +33 -36
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +78 -24
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts +5 -0
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +16 -6
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
- package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
- package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/index.d.ts +8 -0
- package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
- package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
- package/dist/record/gpu-pass-timing/session.d.ts +43 -0
- package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
- package/dist/record/gpu-timing.d.ts +20 -3
- package/dist/record/gpu-timing.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +0 -20
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +6 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +25 -2
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +98 -6
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +119 -4
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -5
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/reflection/filter.d.ts +22 -0
- package/dist/reflection/filter.d.ts.map +1 -0
- package/dist/reflection/gpu-table.d.ts +25 -0
- package/dist/reflection/gpu-table.d.ts.map +1 -0
- package/dist/reflection/inspection.d.ts +10 -0
- package/dist/reflection/inspection.d.ts.map +1 -0
- package/dist/reflection/projection.d.ts +140 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/reflection/record-owner.d.ts +60 -0
- package/dist/reflection/record-owner.d.ts.map +1 -0
- package/dist/render-contract.d.ts +62 -14
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +1 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +49 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +160 -31
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +28 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/probe-blend-record.d.ts +22 -0
- package/dist/scene/probe-blend-record.d.ts.map +1 -0
- package/dist/scene/probe-blend.d.ts +142 -0
- package/dist/scene/probe-blend.d.ts.map +1 -0
- package/dist/scene/render-scene-types.d.ts +0 -6
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +117 -9
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/visibility/bounds.d.ts +20 -0
- package/dist/scene/visibility/bounds.d.ts.map +1 -0
- package/dist/scene/visibility/budget.d.ts +16 -0
- package/dist/scene/visibility/budget.d.ts.map +1 -0
- package/dist/scene/visibility/facet.d.ts +73 -0
- package/dist/scene/visibility/facet.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
- package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
- package/dist/scene/visibility/gpu-lod.d.ts +46 -0
- package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
- package/dist/scene/visibility/inspection.d.ts +132 -0
- package/dist/scene/visibility/inspection.d.ts.map +1 -0
- package/dist/scene/visibility/lod-selector.d.ts +25 -0
- package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
- package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
- package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
- package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
- package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
- package/dist/scene/visibility/types.d.ts +34 -0
- package/dist/scene/visibility/types.d.ts.map +1 -0
- package/dist/shadow-atlas.d.ts +3 -3
- package/dist/shadow-atlas.d.ts.map +1 -1
- package/dist/ssao-buffers.d.ts +3 -0
- package/dist/ssao-buffers.d.ts.map +1 -1
- package/dist/ssr/admission.d.ts +122 -0
- package/dist/ssr/admission.d.ts.map +1 -0
- package/dist/ssr/composition.d.ts +32 -0
- package/dist/ssr/composition.d.ts.map +1 -0
- package/dist/ssr/errors.d.ts +39 -0
- package/dist/ssr/errors.d.ts.map +1 -0
- package/dist/ssr/identity.d.ts +13 -0
- package/dist/ssr/identity.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts +1 -1
- package/dist/targets/contracts.d.ts +66 -0
- package/dist/targets/contracts.d.ts.map +1 -0
- package/dist/targets/graph-projection.d.ts +29 -0
- package/dist/targets/graph-projection.d.ts.map +1 -0
- package/dist/targets/material-source.d.ts +34 -0
- package/dist/targets/material-source.d.ts.map +1 -0
- package/dist/targets/owner.d.ts +32 -0
- package/dist/targets/owner.d.ts.map +1 -0
- package/dist/targets/physical.d.ts +18 -0
- package/dist/targets/physical.d.ts.map +1 -0
- package/dist/targets/readback.d.ts +39 -0
- package/dist/targets/readback.d.ts.map +1 -0
- package/dist/temporal/frame-coordinator.d.ts +13 -0
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +2 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -5
- package/dist/temporal/view.d.ts +7 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/tilemap-chunk-extract-system.d.ts +1 -1
- package/dist/typed-render-graph-primitives.d.ts +5 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/passes.d.ts +9 -5
- package/dist/volume/passes.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
- package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
- package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
- package/src/__tests__/cluster-binner.unit.test.ts +35 -0
- package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
- package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
- package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
- package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
- package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
- package/src/__tests__/cube-camera.integration.test.ts +42 -0
- package/src/__tests__/detached-inspection.integration.test.ts +6 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/environment-generation.integration.test.ts +16 -0
- package/src/__tests__/evidence-identity.ts +13 -0
- package/src/__tests__/evidence-identity.unit.test.ts +24 -0
- package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
- package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
- package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
- package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
- package/src/__tests__/factory-contract.integration.test.ts +288 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/frame-observation.contract.test.ts +15 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
- package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
- package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
- package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
- package/src/__tests__/gpu-lod-evidence.ts +188 -0
- package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
- package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
- package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
- package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
- package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
- package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
- package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
- package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
- package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
- package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
- package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
- package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
- package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
- package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
- package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
- package/src/__tests__/gpu-scene.unit.test.ts +49 -3
- package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
- package/src/__tests__/instances-culling.integration.test.ts +94 -0
- package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
- package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
- package/src/__tests__/light-inspection.unit.test.ts +55 -0
- package/src/__tests__/light-resource-error.unit.test.ts +43 -0
- package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
- package/src/__tests__/lod-bounds.unit.test.ts +42 -0
- package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
- package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
- package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
- package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
- package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
- package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
- package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
- package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
- package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
- package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
- package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
- package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
- package/src/__tests__/lod-selector.profile.test.ts +35 -0
- package/src/__tests__/lod-selector.unit.test.ts +68 -0
- package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
- package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
- package/src/__tests__/ltc-resources.unit.test.ts +32 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
- package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
- package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
- package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
- package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
- package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
- package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
- package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
- package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
- package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
- package/src/__tests__/occlusion-query.evidence.ts +178 -0
- package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
- package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
- package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
- package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
- package/src/__tests__/probe-bindings.unit.test.ts +200 -0
- package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
- package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
- package/src/__tests__/probe-capacity.integration.test.ts +97 -0
- package/src/__tests__/probe-recovery.integration.test.ts +104 -0
- package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
- package/src/__tests__/profiler-phase-token.test.ts +1 -0
- package/src/__tests__/public-surface.test-d.ts +10 -0
- package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
- package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
- package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
- package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
- package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
- package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
- package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
- package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
- package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
- package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
- package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
- package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
- package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
- package/src/__tests__/render-target-admission.unit.test.ts +80 -0
- package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
- package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
- package/src/__tests__/render-target-material-source.test-d.ts +30 -0
- package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
- package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
- package/src/__tests__/render-target-owner.unit.test.ts +122 -0
- package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
- package/src/__tests__/render-target-public-schema.json +107 -0
- package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
- package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
- package/src/__tests__/render-target-readback.integration.test.ts +90 -0
- package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
- package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
- package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
- package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
- package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
- package/src/__tests__/ssr-composition.unit.test.ts +46 -0
- package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
- package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
- package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
- package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
- package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
- package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
- package/src/__tests__/standard-lighting.integration.test.ts +67 -0
- package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
- package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
- package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
- package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
- package/src/__tests__/temporal-view.unit.test.ts +3 -3
- package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
- package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
- package/src/__tests__/visibility-budget.integration.test.ts +140 -0
- package/src/__tests__/visibility-budget.unit.test.ts +70 -0
- package/src/__tests__/visibility-extract.integration.test.ts +11 -6
- package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
- package/src/assembly/backend-contract.ts +13 -0
- package/src/assembly/device-feature-admission.ts +80 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +1411 -366
- package/src/assembly/host-contract.ts +35 -0
- package/src/assembly/render-target-host.ts +847 -0
- package/src/assets/asset-decoders.ts +1 -1
- package/src/assets/render-asset-port.ts +7 -1
- package/src/capture/cube-views.ts +69 -0
- package/src/capture/scheduler.ts +265 -0
- package/src/cluster-binner.ts +31 -0
- package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
- package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
- package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
- package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
- package/src/components/camera.ts +4 -0
- package/src/components/cube-camera.ts +52 -0
- package/src/components/directional-light.ts +22 -2
- package/src/components/directional-shadow-filter.ts +57 -0
- package/src/components/index.ts +5 -0
- package/src/components/light-helpers.ts +133 -8
- package/src/components/light-probe.ts +13 -0
- package/src/components/rect-area-light.ts +15 -0
- package/src/components/reflection-probe.ts +49 -0
- package/src/components/scene-instance.ts +6 -0
- package/src/components/spot-light.ts +3 -0
- package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
- package/src/device/gpu-residency.ts +140 -9
- package/src/device/lifecycle-transaction.ts +12 -0
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +20 -1
- package/src/errors/gpu-driven.ts +92 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recover.ts +32 -0
- package/src/errors/render.ts +341 -143
- package/src/extract/camera.ts +3 -3
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/gpu-driven.ts +108 -0
- package/src/extract/visibility.ts +2 -1
- package/src/features/fullscreen.ts +3 -0
- package/src/features/targets.ts +4 -1
- package/src/features/types.ts +10 -0
- package/src/fullscreen-post-process-pass.ts +43 -2
- package/src/gpu-driven/batch-topology.ts +207 -28
- package/src/gpu-driven/prepared-draw.ts +208 -0
- package/src/gpu-driven/production-raster.ts +953 -43
- package/src/gpu-driven/view-gpu.ts +411 -29
- package/src/gpu-scene-schema.ts +13 -0
- package/src/gpu-scene.ts +140 -64
- package/src/gpu-usage.ts +0 -1
- package/src/hdrp-buffers.ts +169 -63
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +222 -0
- package/src/inspection-types.ts +276 -1
- package/src/instances-derived-bounds.ts +171 -0
- package/src/internal.ts +38 -0
- package/src/light-buffer-layout.ts +155 -310
- package/src/materials.ts +16 -2
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +255 -60
- package/src/pipeline/standard-forward-lane.ts +93 -16
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +158 -0
- package/src/pipeline/standard-lighting/inspection.ts +88 -0
- package/src/pipeline/standard-lighting/layout.ts +31 -0
- package/src/pipeline/standard-lighting/light-frame.ts +35 -0
- package/src/pipeline/standard-lighting/membership.ts +43 -0
- package/src/pipeline/standard-lighting/prepare.ts +37 -0
- package/src/pipeline/standard-lighting/topology.ts +120 -0
- package/src/pipeline/standard-lighting/transport.ts +78 -0
- package/src/pipeline/standard-pipeline.ts +124 -193
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +17 -47
- package/src/pipeline-builder.ts +5 -2
- package/src/pipeline-spec.ts +95 -21
- package/src/plugin.ts +4 -0
- package/src/point-shadow-inspection.ts +55 -0
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
- package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
- package/src/prepare/extended-lighting/resources.ts +389 -0
- package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
- package/src/prepare/extended-lighting/state.ts +92 -0
- package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +591 -452
- package/src/record/frame-snapshot.ts +92 -39
- package/src/record/frame-targets.ts +17 -0
- package/src/record/frame.ts +662 -158
- package/src/record/gpu-pass-timing/contract.ts +180 -0
- package/src/record/gpu-pass-timing/errors.ts +34 -0
- package/src/record/gpu-pass-timing/index.ts +37 -0
- package/src/record/gpu-pass-timing/parser.ts +97 -0
- package/src/record/gpu-pass-timing/session.ts +938 -0
- package/src/record/gpu-timing.ts +112 -39
- package/src/record/helpers.ts +0 -58
- package/src/record/main-pass-geometry.ts +324 -119
- package/src/record/main-pass-material.ts +103 -10
- package/src/record/main-pass-sprite-draws.ts +99 -103
- package/src/record/main-pass.ts +71 -15
- package/src/record/mesh-ssbo.ts +47 -36
- package/src/record/render-context.ts +136 -3
- package/src/record/shadow-pass.ts +83 -68
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +1034 -43
- package/src/record/view-ubo.ts +72 -47
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/inspection.ts +67 -0
- package/src/reflection/projection.ts +476 -0
- package/src/reflection/record-owner.ts +1283 -0
- package/src/render-contract.ts +93 -10
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +122 -2
- package/src/render-system-extract.ts +1041 -225
- package/src/render-system.ts +1310 -85
- package/src/renderer.ts +222 -0
- package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
- package/src/scene/probe-blend-record.ts +54 -0
- package/src/scene/probe-blend.ts +813 -0
- package/src/scene/render-scene-types.ts +0 -7
- package/src/scene/render-scene.ts +942 -171
- package/src/scene/visibility/bounds.ts +57 -0
- package/src/scene/visibility/budget.ts +40 -0
- package/src/scene/visibility/facet.ts +400 -0
- package/src/scene/visibility/gpu-frame-timing.ts +101 -0
- package/src/scene/visibility/gpu-lod.ts +119 -0
- package/src/scene/visibility/inspection.ts +313 -0
- package/src/scene/visibility/lod-selector.ts +84 -0
- package/src/scene/visibility/occlusion-confidence.ts +478 -0
- package/src/scene/visibility/occlusion-pass.ts +165 -0
- package/src/scene/visibility/occlusion-query-pool.ts +178 -0
- package/src/scene/visibility/occlusion-runtime.ts +672 -0
- package/src/scene/visibility/types.ts +50 -0
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- package/src/ssr/admission.ts +451 -0
- package/src/ssr/composition.ts +73 -0
- package/src/ssr/errors.ts +76 -0
- package/src/ssr/identity.ts +12 -0
- package/src/systems/skin-palette-allocator.ts +1 -1
- package/src/targets/contracts.ts +182 -0
- package/src/targets/graph-projection.ts +169 -0
- package/src/targets/material-source.ts +143 -0
- package/src/targets/owner.ts +214 -0
- package/src/targets/physical.ts +18 -0
- package/src/targets/readback.ts +153 -0
- package/src/temporal/frame-coordinator.ts +26 -0
- package/src/temporal/frame.ts +14 -1
- package/src/temporal/view.ts +11 -0
- package/src/tilemap-chunk-extract-system.ts +4 -4
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
- package/src/typed-render-graph-primitives.ts +53 -3
- package/src/typed-shadow-passes.ts +46 -40
- package/src/volume/passes.ts +61 -58
- package/dist/.tsbuildinfo +0 -1
- package/dist/chunk-4XINDLAY.mjs.map +0 -1
- package/dist/chunk-CDTWTHZR.mjs.map +0 -1
- package/dist/chunk-M2MP4ZOU.mjs +0 -73
- package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
- package/dist/chunk-PCHBPYML.mjs.map +0 -1
- package/dist/chunk-TBQJOK2L.mjs.map +0 -1
- package/dist/chunk-VFPLTKCU.mjs.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deriveVertexBufferLayoutFromProjection } from '@forgeax/engine-geometry';
|
|
2
|
+
import { frustum, mat3, mat4 } from '@forgeax/engine-math';
|
|
2
3
|
import type {
|
|
3
4
|
GraphAccess,
|
|
4
5
|
GraphBuffer,
|
|
@@ -9,6 +10,7 @@ import type {
|
|
|
9
10
|
import type {
|
|
10
11
|
BindGroup,
|
|
11
12
|
BindGroupLayout,
|
|
13
|
+
Buffer,
|
|
12
14
|
RenderPipeline,
|
|
13
15
|
Result,
|
|
14
16
|
RhiDevice,
|
|
@@ -16,29 +18,165 @@ import type {
|
|
|
16
18
|
TextureFormat,
|
|
17
19
|
} from '@forgeax/engine-rhi';
|
|
18
20
|
import { err, ok, RhiError } from '@forgeax/engine-rhi';
|
|
21
|
+
import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
|
|
19
22
|
import type { MaterialRenderState } from '@forgeax/engine-types';
|
|
20
23
|
import type { MeshGpuHandles } from '../device/gpu-residency';
|
|
21
24
|
import { GPU_SCENE_WGSL } from '../gpu-scene-schema';
|
|
22
25
|
import {
|
|
23
26
|
GPU_BUFFER_USAGE_COPY_DST,
|
|
24
27
|
GPU_BUFFER_USAGE_INDEX,
|
|
28
|
+
GPU_BUFFER_USAGE_STORAGE,
|
|
29
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
25
30
|
GPU_BUFFER_USAGE_VERTEX,
|
|
26
31
|
} from '../gpu-usage';
|
|
32
|
+
import { assembleMaterialWithSkylightEntries } from '../ibl/skylight-bind-group';
|
|
27
33
|
import type { GpuDrivenProductionInspection } from '../inspection-types';
|
|
28
34
|
import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
|
|
29
35
|
import { worldEntityKey } from '../record/frame-snapshot';
|
|
30
36
|
import { geometryRenderStateForTopology } from '../record/main-pass-material';
|
|
37
|
+
import { MESH_SSBO_BYTES } from '../record/mesh-ssbo';
|
|
38
|
+
import type { PipelineState } from '../record/render-context';
|
|
31
39
|
import type { CameraSnapshot } from '../render-contract';
|
|
32
40
|
import type { RenderPipelineFrame, RenderPipelineGpuDrivenProjection } from '../render-pipeline';
|
|
33
41
|
import type { RenderableSnapshot } from '../render-system-extract';
|
|
34
42
|
import type { PersistentGpuDrivenState } from '../scene/render-scene';
|
|
35
|
-
import
|
|
36
|
-
import {
|
|
43
|
+
import { selectLod } from '../scene/visibility/lod-selector';
|
|
44
|
+
import type { OcclusionFrameProjection } from '../scene/visibility/occlusion-runtime';
|
|
45
|
+
import {
|
|
46
|
+
type GpuDrivenBatch,
|
|
47
|
+
type GpuDrivenCandidate,
|
|
48
|
+
projectedHeightForCandidate,
|
|
49
|
+
type SubmissionPlan,
|
|
50
|
+
} from './batch-topology';
|
|
51
|
+
import { type GpuDrivenLodSelectionInspection, GpuDrivenView, selectGpuLodLane } from './view-gpu';
|
|
37
52
|
|
|
38
53
|
const VERTEX_STAGE = 0x1;
|
|
39
54
|
const FRAGMENT_STAGE = 0x2;
|
|
40
55
|
const EMPTY_RESOURCE_CLASS = JSON.stringify({ textures: [], samplers: [], video: [] });
|
|
41
56
|
|
|
57
|
+
export interface StandardPbrSceneCandidate {
|
|
58
|
+
readonly primitiveIndex: number;
|
|
59
|
+
readonly world: Float32Array;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface StandardPbrSceneProjection {
|
|
63
|
+
readonly entryPoint: string;
|
|
64
|
+
readonly layoutIdentity: string;
|
|
65
|
+
readonly receiptGeneration: number;
|
|
66
|
+
/** Packed Mesh rows: mat4 + padded mat3 + previous mat4 + temporal vec4. */
|
|
67
|
+
readonly rows: Float32Array;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build the producer-owned Standard PBR scene-index projection. The receipt is
|
|
72
|
+
* the only source of the vertex entry and layout identity; callers cannot
|
|
73
|
+
* silently fall back to the legacy GPU-driven shader ABI.
|
|
74
|
+
*/
|
|
75
|
+
export function projectStandardPbrScene(input: {
|
|
76
|
+
readonly artifact: MaterialShaderArtifact;
|
|
77
|
+
readonly candidates: readonly StandardPbrSceneCandidate[];
|
|
78
|
+
}):
|
|
79
|
+
| { readonly ok: true; readonly value: StandardPbrSceneProjection }
|
|
80
|
+
| {
|
|
81
|
+
readonly ok: false;
|
|
82
|
+
readonly error: { readonly code: 'missing-material-receipt' | 'invalid-material-receipt' };
|
|
83
|
+
} {
|
|
84
|
+
const receipt = input.artifact.receipt;
|
|
85
|
+
if (receipt === undefined) return { ok: false, error: { code: 'missing-material-receipt' } };
|
|
86
|
+
if (receipt.reflection.layoutIdentity !== input.artifact.layoutIdentity) {
|
|
87
|
+
return { ok: false, error: { code: 'invalid-material-receipt' } };
|
|
88
|
+
}
|
|
89
|
+
const rows = new Float32Array(input.candidates.length * 48);
|
|
90
|
+
const normal = mat3.create();
|
|
91
|
+
for (let index = 0; index < input.candidates.length; index += 1) {
|
|
92
|
+
const candidate = input.candidates[index];
|
|
93
|
+
if (candidate === undefined) continue;
|
|
94
|
+
const base = index * 48;
|
|
95
|
+
for (let value = 0; value < 16; value += 1) {
|
|
96
|
+
rows[base + value] = candidate.world[value] ?? 0;
|
|
97
|
+
rows[base + 28 + value] = candidate.world[value] ?? 0;
|
|
98
|
+
}
|
|
99
|
+
const normalMatrix = mat3.normalMatrix(normal, candidate.world);
|
|
100
|
+
for (let column = 0; column < 3; column += 1) {
|
|
101
|
+
const source = column * 3;
|
|
102
|
+
const target = base + 16 + column * 4;
|
|
103
|
+
rows[target] = normalMatrix[source] ?? 0;
|
|
104
|
+
rows[target + 1] = normalMatrix[source + 1] ?? 0;
|
|
105
|
+
rows[target + 2] = normalMatrix[source + 2] ?? 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
ok: true,
|
|
110
|
+
value: {
|
|
111
|
+
entryPoint: receipt.sceneIndexEntry,
|
|
112
|
+
layoutIdentity: input.artifact.layoutIdentity,
|
|
113
|
+
receiptGeneration: receipt.generation,
|
|
114
|
+
rows,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface StandardPbrFrameResourceBinding {
|
|
120
|
+
readonly entryPoint: string;
|
|
121
|
+
readonly layoutIdentity: string;
|
|
122
|
+
readonly receiptGeneration: number;
|
|
123
|
+
readonly bindGroups: readonly [BindGroup, BindGroup, BindGroup, BindGroup];
|
|
124
|
+
readonly resourceSlots: NonNullable<MaterialShaderArtifact['receipt']>['resourceSlots'];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Adapt the frame's already-created Standard PBR groups to the GPU-driven
|
|
129
|
+
* scene-index draw. This deliberately accepts group handles rather than
|
|
130
|
+
* rebuilding layouts or shader artifacts; the receipt remains the binding
|
|
131
|
+
* contract and the regular frame producers remain the resource owners.
|
|
132
|
+
*/
|
|
133
|
+
export function adaptStandardPbrFrameResources(input: {
|
|
134
|
+
readonly artifact: MaterialShaderArtifact;
|
|
135
|
+
readonly view: BindGroup;
|
|
136
|
+
readonly material: BindGroup;
|
|
137
|
+
readonly mesh: BindGroup;
|
|
138
|
+
readonly instances: BindGroup;
|
|
139
|
+
}):
|
|
140
|
+
| { readonly ok: true; readonly value: StandardPbrFrameResourceBinding }
|
|
141
|
+
| {
|
|
142
|
+
readonly ok: false;
|
|
143
|
+
readonly error: { readonly code: 'missing-material-receipt' | 'invalid-material-receipt' };
|
|
144
|
+
} {
|
|
145
|
+
const receipt = input.artifact.receipt;
|
|
146
|
+
if (receipt === undefined) return { ok: false, error: { code: 'missing-material-receipt' } };
|
|
147
|
+
const reflected = receipt.reflection;
|
|
148
|
+
const sameSlots =
|
|
149
|
+
reflected.resourceSlots.length === receipt.resourceSlots.length &&
|
|
150
|
+
reflected.resourceSlots.every((slot, index) => {
|
|
151
|
+
const expected = receipt.resourceSlots[index];
|
|
152
|
+
return (
|
|
153
|
+
expected !== undefined &&
|
|
154
|
+
slot.name === expected.name &&
|
|
155
|
+
slot.parameter === expected.parameter &&
|
|
156
|
+
slot.kind === expected.kind &&
|
|
157
|
+
slot.group === expected.group &&
|
|
158
|
+
slot.binding === expected.binding
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
if (
|
|
162
|
+
reflected.layoutIdentity !== input.artifact.layoutIdentity ||
|
|
163
|
+
!sameSlots ||
|
|
164
|
+
receipt.resourceSlots.some((slot) => slot.group !== 1)
|
|
165
|
+
) {
|
|
166
|
+
return { ok: false, error: { code: 'invalid-material-receipt' } };
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
value: {
|
|
171
|
+
entryPoint: receipt.sceneIndexEntry,
|
|
172
|
+
layoutIdentity: input.artifact.layoutIdentity,
|
|
173
|
+
receiptGeneration: receipt.generation,
|
|
174
|
+
bindGroups: [input.view, input.material, input.mesh, input.instances],
|
|
175
|
+
resourceSlots: receipt.resourceSlots,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
42
180
|
export const GPU_DRIVEN_RIGID_UNLIT_WGSL = /* wgsl */ `
|
|
43
181
|
${GPU_SCENE_WGSL}
|
|
44
182
|
|
|
@@ -87,6 +225,11 @@ interface PreparedBatch {
|
|
|
87
225
|
readonly batch: GpuDrivenBatch;
|
|
88
226
|
readonly mesh: MeshGpuHandles;
|
|
89
227
|
readonly renderState: MaterialRenderState | undefined;
|
|
228
|
+
/** Stable World attachment key used to join same-submit GPU counters. */
|
|
229
|
+
readonly worldKey: number;
|
|
230
|
+
/** Stable representative slot identity for this homogeneous batch. */
|
|
231
|
+
readonly primitiveSlot: number;
|
|
232
|
+
readonly slotGeneration: number;
|
|
90
233
|
}
|
|
91
234
|
|
|
92
235
|
interface ProjectedBatch extends PreparedBatch {
|
|
@@ -133,6 +276,22 @@ function activeFrustum(camera: CameraSnapshot): Float32Array {
|
|
|
133
276
|
);
|
|
134
277
|
}
|
|
135
278
|
|
|
279
|
+
/**
|
|
280
|
+
* LOD projected height is a view fact, not a scene-topology fact. Keep the
|
|
281
|
+
* filtered plan cache scene-stable while still rebuilding its candidate
|
|
282
|
+
* heights whenever a camera input used by the selector changes.
|
|
283
|
+
*/
|
|
284
|
+
function lodProjectionFingerprint(camera: CameraSnapshot): string {
|
|
285
|
+
return JSON.stringify({
|
|
286
|
+
position: Array.from(camera.position),
|
|
287
|
+
world: Array.from(camera.world),
|
|
288
|
+
projection: camera.projection,
|
|
289
|
+
fov: camera.fov,
|
|
290
|
+
aspect: camera.aspect,
|
|
291
|
+
orthoHeight: camera.orthoTop - camera.orthoBottom,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
136
295
|
function productionEligible(snapshot: RenderableSnapshot, batch: GpuDrivenBatch): boolean {
|
|
137
296
|
const material = snapshot.materials[batch.key.materialSlot] ?? snapshot.material;
|
|
138
297
|
const shader = material.materialShaderId ?? 'forgeax::default-unlit';
|
|
@@ -144,44 +303,142 @@ function productionEligible(snapshot: RenderableSnapshot, batch: GpuDrivenBatch)
|
|
|
144
303
|
);
|
|
145
304
|
}
|
|
146
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Prepared Standard PBR draws have an explicit receipt identity on their
|
|
308
|
+
* batch key. Unprepared batches stay on the rigid production shader; the
|
|
309
|
+
* presence of a frame-wide PBR artifact is not permission to reinterpret an
|
|
310
|
+
* unlit batch with the PBR bind-group layout.
|
|
311
|
+
*/
|
|
312
|
+
function isStandardPbrBatch(batch: GpuDrivenBatch): boolean {
|
|
313
|
+
return batch.key.preparedIdentity !== undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
147
316
|
function meshSupportsBatch(mesh: MeshGpuHandles, batch: GpuDrivenBatch): boolean {
|
|
148
|
-
if (
|
|
317
|
+
if (
|
|
318
|
+
batch.candidates.some((candidate) => {
|
|
319
|
+
const lodCount = candidate.lodCoverages?.length ?? 0;
|
|
320
|
+
return (
|
|
321
|
+
lodCount > 1 &&
|
|
322
|
+
(mesh.lodRanges === undefined ||
|
|
323
|
+
mesh.lodRanges.length < lodCount ||
|
|
324
|
+
mesh.lodRanges.some((ranges) => ranges[candidate.drawItemIndex] === undefined))
|
|
325
|
+
);
|
|
326
|
+
})
|
|
327
|
+
) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
149
330
|
return batch.key.drawKind === 'indexed'
|
|
150
331
|
? mesh.indexed && mesh.indexBuffer !== null
|
|
151
332
|
: !mesh.indexed;
|
|
152
333
|
}
|
|
153
334
|
|
|
335
|
+
function selectedLodLevel(candidate: GpuDrivenCandidate): number {
|
|
336
|
+
const coverages = candidate.lodCoverages;
|
|
337
|
+
if (coverages === undefined || coverages.length <= 1) return 0;
|
|
338
|
+
return selectLod({
|
|
339
|
+
levels: coverages.slice(1).map((screenCoverage) => ({ screenCoverage })),
|
|
340
|
+
projectedHeight: candidate.projectedHeight ?? Number.NaN,
|
|
341
|
+
previousLevel: 0,
|
|
342
|
+
hysteresis: candidate.lodHysteresis ?? 0.08,
|
|
343
|
+
ready: coverages.map(() => true),
|
|
344
|
+
historyValid: false,
|
|
345
|
+
}).level;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function selectedLodRange(
|
|
349
|
+
batch: GpuDrivenBatch,
|
|
350
|
+
candidate: GpuDrivenCandidate,
|
|
351
|
+
): { readonly first: number; readonly count: number; readonly baseVertex: number } {
|
|
352
|
+
const level = selectedLodLevel(candidate);
|
|
353
|
+
return (
|
|
354
|
+
(level === 0 ? undefined : candidate.lodRanges?.[level - 1]) ?? {
|
|
355
|
+
first: batch.key.first,
|
|
356
|
+
count: batch.key.count,
|
|
357
|
+
baseVertex: batch.key.baseVertex,
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* One raster indirect command is emitted for each selector batch that has at
|
|
364
|
+
* least one admitted candidate. Selector-only batches remain in the compute
|
|
365
|
+
* and readback plan, but they must not inflate the raster command count.
|
|
366
|
+
*/
|
|
367
|
+
function admittedRasterBatchCount(plan: SubmissionPlan): number {
|
|
368
|
+
return plan.batches.reduce((count, batch) => count + (batch.visibleCapacity > 0 ? 1 : 0), 0);
|
|
369
|
+
}
|
|
370
|
+
|
|
154
371
|
function filteredPlan(
|
|
155
372
|
source: SubmissionPlan,
|
|
156
373
|
slots: PersistentGpuDrivenState['slots'],
|
|
157
374
|
meshes: ReadonlyMap<number, MeshGpuHandles>,
|
|
375
|
+
camera: CameraSnapshot,
|
|
376
|
+
activeEntityKeys?: ReadonlySet<number>,
|
|
377
|
+
worldKeys?: readonly number[],
|
|
158
378
|
): {
|
|
159
379
|
readonly plan: SubmissionPlan;
|
|
160
380
|
readonly batches: readonly PreparedBatch[];
|
|
161
381
|
readonly entityKeys: ReadonlySet<number>;
|
|
382
|
+
/** No source primitive can enter this lane without a topology/material change. */
|
|
383
|
+
readonly topologyInvariantEmpty: boolean;
|
|
162
384
|
} {
|
|
163
385
|
const slotByPrimitive = new Map(slots.map((slot) => [slot.slot, slot] as const));
|
|
386
|
+
const participatingByPrimitive = new Map<number, Set<GpuDrivenBatch>>();
|
|
387
|
+
for (const batch of source.batches) {
|
|
388
|
+
for (const candidate of batch.candidates) {
|
|
389
|
+
const participating = participatingByPrimitive.get(candidate.primitiveIndex);
|
|
390
|
+
if (participating === undefined) {
|
|
391
|
+
participatingByPrimitive.set(candidate.primitiveIndex, new Set([batch]));
|
|
392
|
+
} else {
|
|
393
|
+
participating.add(batch);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
164
397
|
const entityKeys = new Set<number>();
|
|
398
|
+
const worldKeyFor = (slot: PersistentGpuDrivenState['slots'][number]): number =>
|
|
399
|
+
worldKeys?.[slot.snapshot.worldId] ?? slot.snapshot.worldId;
|
|
400
|
+
const activeKey = (slot: PersistentGpuDrivenState['slots'][number]): number =>
|
|
401
|
+
worldEntityKey(worldKeyFor(slot), slot.snapshot.entityKey);
|
|
165
402
|
const eligiblePrimitives = new Set<number>();
|
|
403
|
+
let topologyEligiblePrimitiveCount = 0;
|
|
166
404
|
for (const slot of slots) {
|
|
167
405
|
const mesh = meshes.get(slot.snapshot.assetHandle);
|
|
168
|
-
const participating =
|
|
169
|
-
|
|
170
|
-
|
|
406
|
+
const participating = participatingByPrimitive.get(slot.slot);
|
|
407
|
+
const participatingBatches = participating === undefined ? [] : [...participating];
|
|
408
|
+
const topologyEligible =
|
|
409
|
+
participatingBatches.length > 0 &&
|
|
410
|
+
participatingBatches.length === (slot.snapshot.gpuDrivenDraws?.length ?? 0) &&
|
|
411
|
+
participatingBatches.every((batch) => productionEligible(slot.snapshot, batch));
|
|
412
|
+
if (topologyEligible) topologyEligiblePrimitiveCount += 1;
|
|
171
413
|
if (
|
|
172
414
|
mesh !== undefined &&
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
(batch) => productionEligible(slot.snapshot, batch) && meshSupportsBatch(mesh, batch),
|
|
176
|
-
)
|
|
415
|
+
topologyEligible &&
|
|
416
|
+
participatingBatches.every((batch) => meshSupportsBatch(mesh, batch))
|
|
177
417
|
) {
|
|
178
418
|
eligiblePrimitives.add(slot.slot);
|
|
179
|
-
|
|
419
|
+
// Record validation still keys extracted renderables by their current
|
|
420
|
+
// stable world/entity key. Suppressed candidates remain in the GPU
|
|
421
|
+
// selector, but only admitted entities are owned by the compacted draw.
|
|
422
|
+
if (activeEntityKeys === undefined || activeEntityKeys.has(activeKey(slot))) {
|
|
423
|
+
entityKeys.add(activeKey(slot));
|
|
424
|
+
}
|
|
180
425
|
}
|
|
181
426
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
427
|
+
// BatchTopology deliberately keeps LOD candidates individually addressable
|
|
428
|
+
// so topology updates never alias a candidate's selector state. Once the
|
|
429
|
+
// camera has projected the candidates, the renderer can safely coalesce
|
|
430
|
+
// candidates that have identical immutable geometry/LOD facts and the same
|
|
431
|
+
// selected range. The GPU selector uses the same no-history inputs below,
|
|
432
|
+
// so one indirect range remains correct for every candidate in a group.
|
|
433
|
+
const groups = new Map<
|
|
434
|
+
string,
|
|
435
|
+
{
|
|
436
|
+
readonly source: GpuDrivenBatch;
|
|
437
|
+
readonly mesh: MeshGpuHandles;
|
|
438
|
+
readonly renderState: MaterialRenderState | undefined;
|
|
439
|
+
readonly candidates: GpuDrivenCandidate[];
|
|
440
|
+
}
|
|
441
|
+
>();
|
|
185
442
|
for (const batch of source.batches) {
|
|
186
443
|
const candidates = batch.candidates.filter((candidate) => {
|
|
187
444
|
const slot = slotByPrimitive.get(candidate.primitiveIndex);
|
|
@@ -190,24 +447,84 @@ function filteredPlan(
|
|
|
190
447
|
return mesh !== undefined && meshSupportsBatch(mesh, batch);
|
|
191
448
|
});
|
|
192
449
|
if (candidates.length === 0) continue;
|
|
450
|
+
for (const candidate of candidates) {
|
|
451
|
+
const slot = slotByPrimitive.get(candidate.primitiveIndex);
|
|
452
|
+
if (slot === undefined) continue;
|
|
453
|
+
const mesh = meshes.get(slot.snapshot.assetHandle);
|
|
454
|
+
if (mesh === undefined) continue;
|
|
455
|
+
const physicalLodRanges = mesh.lodRanges
|
|
456
|
+
?.slice(1)
|
|
457
|
+
.map((ranges) => ranges[candidate.drawItemIndex]);
|
|
458
|
+
const projected = Object.freeze({
|
|
459
|
+
...candidate,
|
|
460
|
+
submitAdmission: activeEntityKeys === undefined || activeEntityKeys.has(activeKey(slot)),
|
|
461
|
+
projectedHeight: projectedHeightForCandidate(slot, camera),
|
|
462
|
+
...(physicalLodRanges?.every((range) => range !== undefined)
|
|
463
|
+
? { lodRanges: physicalLodRanges as NonNullable<typeof candidate.lodRanges> }
|
|
464
|
+
: {}),
|
|
465
|
+
});
|
|
466
|
+
const selectedRange = selectedLodRange(batch, projected);
|
|
467
|
+
const groupKey = JSON.stringify({
|
|
468
|
+
key: batch.key,
|
|
469
|
+
worldKey: worldKeyFor(slot),
|
|
470
|
+
lodCoverages: projected.lodCoverages ?? null,
|
|
471
|
+
lodRanges: projected.lodRanges ?? null,
|
|
472
|
+
selectedRange,
|
|
473
|
+
selectedLevel: selectedLodLevel(projected),
|
|
474
|
+
});
|
|
475
|
+
const existing = groups.get(groupKey);
|
|
476
|
+
if (existing === undefined) {
|
|
477
|
+
const material = slot.snapshot.materials[batch.key.materialSlot] ?? slot.snapshot.material;
|
|
478
|
+
groups.set(groupKey, {
|
|
479
|
+
source: batch,
|
|
480
|
+
mesh,
|
|
481
|
+
renderState: geometryRenderStateForTopology(batch.key.topology, material.renderState),
|
|
482
|
+
candidates: [projected],
|
|
483
|
+
});
|
|
484
|
+
} else {
|
|
485
|
+
existing.candidates.push(projected);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const batches: PreparedBatch[] = [];
|
|
491
|
+
let visibleBase = 0;
|
|
492
|
+
const filteredBatches: GpuDrivenBatch[] = [];
|
|
493
|
+
let batchId = 0;
|
|
494
|
+
for (const group of groups.values()) {
|
|
193
495
|
visibleBase = Math.ceil(visibleBase / 64) * 64;
|
|
496
|
+
const admittedCount = group.candidates.reduce(
|
|
497
|
+
(count, candidate) =>
|
|
498
|
+
count +
|
|
499
|
+
((candidate as GpuDrivenCandidate & { readonly submitAdmission?: boolean })
|
|
500
|
+
.submitAdmission === false
|
|
501
|
+
? 0
|
|
502
|
+
: 1),
|
|
503
|
+
0,
|
|
504
|
+
);
|
|
194
505
|
const filtered: GpuDrivenBatch = Object.freeze({
|
|
195
|
-
...
|
|
196
|
-
|
|
506
|
+
...group.source,
|
|
507
|
+
batchId,
|
|
508
|
+
candidates: Object.freeze(group.candidates),
|
|
197
509
|
visibleBase,
|
|
198
|
-
visibleCapacity:
|
|
510
|
+
visibleCapacity: admittedCount,
|
|
511
|
+
indirectOffset: batchId * 20,
|
|
199
512
|
});
|
|
200
|
-
visibleBase +=
|
|
201
|
-
const first = slotByPrimitive.get(candidates[0]?.primitiveIndex ?? -1);
|
|
202
|
-
const mesh = first === undefined ? undefined : meshes.get(first.snapshot.assetHandle);
|
|
203
|
-
if (mesh === undefined || first === undefined) continue;
|
|
513
|
+
visibleBase += admittedCount;
|
|
204
514
|
filteredBatches.push(filtered);
|
|
205
|
-
const
|
|
515
|
+
const firstCandidate = group.candidates[0];
|
|
516
|
+
const firstSlot =
|
|
517
|
+
firstCandidate === undefined ? undefined : slotByPrimitive.get(firstCandidate.primitiveIndex);
|
|
518
|
+
if (firstCandidate === undefined || firstSlot === undefined) continue;
|
|
206
519
|
batches.push({
|
|
207
520
|
batch: filtered,
|
|
208
|
-
mesh,
|
|
209
|
-
renderState:
|
|
521
|
+
mesh: group.mesh,
|
|
522
|
+
renderState: group.renderState,
|
|
523
|
+
worldKey: worldKeyFor(firstSlot),
|
|
524
|
+
primitiveSlot: firstSlot.slot,
|
|
525
|
+
slotGeneration: firstSlot.generation,
|
|
210
526
|
});
|
|
527
|
+
batchId += 1;
|
|
211
528
|
}
|
|
212
529
|
return {
|
|
213
530
|
plan: Object.freeze({
|
|
@@ -218,11 +535,32 @@ function filteredPlan(
|
|
|
218
535
|
}),
|
|
219
536
|
batches: Object.freeze(batches),
|
|
220
537
|
entityKeys,
|
|
538
|
+
topologyInvariantEmpty: topologyEligiblePrimitiveCount === 0,
|
|
221
539
|
};
|
|
222
540
|
}
|
|
223
541
|
|
|
224
542
|
type FilteredProductionPlan = ReturnType<typeof filteredPlan>;
|
|
225
543
|
|
|
544
|
+
export interface GpuDrivenWorldLodSelectionInspection {
|
|
545
|
+
readonly worldKey: number;
|
|
546
|
+
readonly primitiveSlot: number;
|
|
547
|
+
readonly slotGeneration: number;
|
|
548
|
+
readonly candidateCount: number;
|
|
549
|
+
readonly visible: number;
|
|
550
|
+
readonly occluded: number;
|
|
551
|
+
readonly lodHistogram: readonly { readonly level: number; readonly count: number }[];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function sameEntityKeySet(
|
|
555
|
+
left: ReadonlySet<number> | undefined,
|
|
556
|
+
right: ReadonlySet<number> | undefined,
|
|
557
|
+
): boolean {
|
|
558
|
+
if (left === right) return true;
|
|
559
|
+
if (left === undefined || right === undefined || left.size !== right.size) return false;
|
|
560
|
+
for (const key of left) if (!right.has(key)) return false;
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
|
|
226
564
|
class GpuDrivenRigidRaster {
|
|
227
565
|
private readonly pipelines = new Map<string, RenderPipeline>();
|
|
228
566
|
|
|
@@ -375,10 +713,212 @@ class GpuDrivenRigidRaster {
|
|
|
375
713
|
}
|
|
376
714
|
}
|
|
377
715
|
|
|
716
|
+
/** The GPU-driven bridge to the existing four-group Standard PBR pipeline. */
|
|
717
|
+
class StandardPbrRasterAdapter {
|
|
718
|
+
private readonly pipelines = new Map<string, RenderPipeline>();
|
|
719
|
+
private readonly projectionBuffers: Buffer[] = [];
|
|
720
|
+
|
|
721
|
+
private constructor(
|
|
722
|
+
private readonly device: RhiDevice,
|
|
723
|
+
private readonly pipelineLayout: import('@forgeax/engine-rhi').PipelineLayout,
|
|
724
|
+
private readonly meshLayout: BindGroupLayout,
|
|
725
|
+
private readonly module: ShaderModule,
|
|
726
|
+
private readonly materialBindGroup: BindGroup,
|
|
727
|
+
private readonly instancesBindGroup: BindGroup,
|
|
728
|
+
private readonly artifact: MaterialShaderArtifact,
|
|
729
|
+
) {}
|
|
730
|
+
|
|
731
|
+
static create(input: {
|
|
732
|
+
readonly device: RhiDevice;
|
|
733
|
+
readonly shaderModuleFactory: PipelineBuilderShaderModuleFactory;
|
|
734
|
+
readonly pipelineState: PipelineState;
|
|
735
|
+
readonly artifact: MaterialShaderArtifact;
|
|
736
|
+
}): Result<StandardPbrRasterAdapter, RhiError> {
|
|
737
|
+
const pipelineLayout = input.pipelineState.pbrPipelineLayout;
|
|
738
|
+
const fallback = input.pipelineState.skylightFallback;
|
|
739
|
+
if (pipelineLayout === null || fallback === null) {
|
|
740
|
+
return err(
|
|
741
|
+
new RhiError({
|
|
742
|
+
code: 'rhi-not-available',
|
|
743
|
+
expected: 'Standard PBR pipeline layout and Skylight fallback',
|
|
744
|
+
hint: 'initialize the Standard PBR pipeline before enabling GPU-driven raster',
|
|
745
|
+
}),
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
const module = input.shaderModuleFactory.createShaderModule({
|
|
749
|
+
label: 'standard-pbr-gpu-driven-scene-index',
|
|
750
|
+
code: input.artifact.wgsl,
|
|
751
|
+
});
|
|
752
|
+
if (!module.ok) return module;
|
|
753
|
+
const materialBuffer = input.device.createBuffer({
|
|
754
|
+
label: 'standard-pbr-gpu-driven-material-row',
|
|
755
|
+
size: 384,
|
|
756
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
757
|
+
});
|
|
758
|
+
if (!materialBuffer.ok) return materialBuffer;
|
|
759
|
+
const materialPayload = new Float32Array(96);
|
|
760
|
+
materialPayload[0] = 1;
|
|
761
|
+
materialPayload[1] = 1;
|
|
762
|
+
materialPayload[2] = 1;
|
|
763
|
+
materialPayload[3] = 1;
|
|
764
|
+
materialPayload[5] = 1;
|
|
765
|
+
materialPayload[6] = 1;
|
|
766
|
+
const materialWrite = input.device.queue.writeBuffer(materialBuffer.value, 0, materialPayload);
|
|
767
|
+
if (!materialWrite.ok) return materialWrite;
|
|
768
|
+
const baseEntries = [
|
|
769
|
+
{ binding: 0, resource: bufferBinding(materialBuffer.value, 0, 384) },
|
|
770
|
+
...Array.from({ length: 8 }, (_, index) => [
|
|
771
|
+
{
|
|
772
|
+
binding: index * 2 + 1,
|
|
773
|
+
resource: { kind: 'sampler' as const, value: fallback.sampler },
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
binding: index * 2 + 2,
|
|
777
|
+
resource: {
|
|
778
|
+
kind: 'textureView' as const,
|
|
779
|
+
value:
|
|
780
|
+
index === 2
|
|
781
|
+
? input.pipelineState.defaultNormalTextureView
|
|
782
|
+
: input.pipelineState.defaultWhiteTextureView,
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
]).flat(),
|
|
786
|
+
];
|
|
787
|
+
const materialEntries = assembleMaterialWithSkylightEntries(
|
|
788
|
+
baseEntries,
|
|
789
|
+
{
|
|
790
|
+
irradianceView: fallback.irradianceView,
|
|
791
|
+
irradianceSampler: fallback.sampler,
|
|
792
|
+
prefilterView: fallback.prefilterView,
|
|
793
|
+
prefilterSampler: fallback.sampler,
|
|
794
|
+
brdfLutView: fallback.brdfLutView,
|
|
795
|
+
brdfLutSampler: fallback.sampler,
|
|
796
|
+
intensityBuffer: fallback.intensityBuffer,
|
|
797
|
+
},
|
|
798
|
+
{ sampler: fallback.sampler, backdropView: input.pipelineState.defaultWhiteTextureView },
|
|
799
|
+
);
|
|
800
|
+
const materialBindGroup = input.device.createBindGroup({
|
|
801
|
+
label: 'standard-pbr-gpu-driven-material-bg',
|
|
802
|
+
layout: input.pipelineState.materialBindGroupLayout,
|
|
803
|
+
entries: materialEntries,
|
|
804
|
+
});
|
|
805
|
+
if (!materialBindGroup.ok) return materialBindGroup;
|
|
806
|
+
const instancesBindGroup = input.device.createBindGroup({
|
|
807
|
+
label: 'standard-pbr-gpu-driven-identity-instances-bg',
|
|
808
|
+
layout: input.pipelineState.instancesBindGroupLayout,
|
|
809
|
+
entries: [
|
|
810
|
+
{
|
|
811
|
+
binding: 0,
|
|
812
|
+
resource: bufferBinding(input.pipelineState.identityInstanceBuffer),
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
});
|
|
816
|
+
if (!instancesBindGroup.ok) return instancesBindGroup;
|
|
817
|
+
return ok(
|
|
818
|
+
new StandardPbrRasterAdapter(
|
|
819
|
+
input.device,
|
|
820
|
+
pipelineLayout,
|
|
821
|
+
input.pipelineState.meshBindGroupLayout,
|
|
822
|
+
module.value,
|
|
823
|
+
materialBindGroup.value,
|
|
824
|
+
instancesBindGroup.value,
|
|
825
|
+
input.artifact,
|
|
826
|
+
),
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
pipeline(
|
|
831
|
+
format: TextureFormat,
|
|
832
|
+
sampleCount: 1 | 4,
|
|
833
|
+
layoutProjection: MeshGpuHandles['layoutProjection'],
|
|
834
|
+
topology: GpuDrivenBatch['key']['topology'],
|
|
835
|
+
stripIndexFormat: 'uint16' | 'uint32' | undefined,
|
|
836
|
+
renderState: MaterialRenderState | undefined,
|
|
837
|
+
): Result<RenderPipeline, RhiError> {
|
|
838
|
+
const projectedLayout = deriveVertexBufferLayoutFromProjection(
|
|
839
|
+
layoutProjection,
|
|
840
|
+
this.artifact.uvSetCount === undefined
|
|
841
|
+
? undefined
|
|
842
|
+
: { shaderUvSetCount: this.artifact.uvSetCount },
|
|
843
|
+
)[0];
|
|
844
|
+
const vertexStride = projectedLayout?.arrayStride ?? layoutProjection.arrayStride;
|
|
845
|
+
const attributes = projectedLayout?.attributes ?? layoutProjection.attributes;
|
|
846
|
+
const key = `${format}|${sampleCount}|${vertexStride}|${topology}|${stripIndexFormat ?? ''}|${JSON.stringify(renderState ?? null)}|${this.artifact.layoutIdentity}`;
|
|
847
|
+
const cached = this.pipelines.get(key);
|
|
848
|
+
if (cached !== undefined) return ok(cached);
|
|
849
|
+
const pipeline = this.device.createRenderPipeline({
|
|
850
|
+
label: `standard-pbr-gpu-driven.${key}`,
|
|
851
|
+
layout: this.pipelineLayout,
|
|
852
|
+
vertex: {
|
|
853
|
+
module: this.module,
|
|
854
|
+
entryPoint: this.artifact.receipt?.sceneIndexEntry ?? 'vs_scene_index',
|
|
855
|
+
buffers: [
|
|
856
|
+
{
|
|
857
|
+
arrayStride: vertexStride,
|
|
858
|
+
stepMode: 'vertex',
|
|
859
|
+
attributes: attributes.map(({ shaderLocation, offset, format }) => ({
|
|
860
|
+
shaderLocation,
|
|
861
|
+
offset,
|
|
862
|
+
format: format as GPUVertexFormat,
|
|
863
|
+
})),
|
|
864
|
+
},
|
|
865
|
+
],
|
|
866
|
+
},
|
|
867
|
+
fragment: {
|
|
868
|
+
module: this.module,
|
|
869
|
+
entryPoint: 'fs_main',
|
|
870
|
+
targets: [{ format }],
|
|
871
|
+
},
|
|
872
|
+
primitive: {
|
|
873
|
+
topology,
|
|
874
|
+
cullMode: renderState?.cullMode ?? 'back',
|
|
875
|
+
frontFace: renderState?.frontFace ?? 'ccw',
|
|
876
|
+
...(stripIndexFormat === undefined ? {} : { stripIndexFormat }),
|
|
877
|
+
},
|
|
878
|
+
depthStencil: {
|
|
879
|
+
format: 'depth24plus-stencil8',
|
|
880
|
+
depthWriteEnabled: renderState?.depthWriteEnabled ?? true,
|
|
881
|
+
depthCompare: renderState?.depthCompare ?? 'less',
|
|
882
|
+
},
|
|
883
|
+
...(sampleCount === 4 ? { multisample: { count: 4 } } : {}),
|
|
884
|
+
});
|
|
885
|
+
if (!pipeline.ok) return pipeline;
|
|
886
|
+
this.pipelines.set(key, pipeline.value);
|
|
887
|
+
return pipeline;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
projectionBindGroup(rows: Float32Array): Result<BindGroup, RhiError> {
|
|
891
|
+
const buffer = this.device.createBuffer({
|
|
892
|
+
label: 'standard-pbr-gpu-driven-scene-meshes',
|
|
893
|
+
size: Math.max(MESH_SSBO_BYTES, rows.byteLength),
|
|
894
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
895
|
+
});
|
|
896
|
+
if (!buffer.ok) return buffer;
|
|
897
|
+
const write = this.device.queue.writeBuffer(buffer.value, 0, rows);
|
|
898
|
+
if (!write.ok) return write;
|
|
899
|
+
this.projectionBuffers.push(buffer.value);
|
|
900
|
+
return this.device.createBindGroup({
|
|
901
|
+
label: 'standard-pbr-gpu-driven-scene-mesh-bg',
|
|
902
|
+
layout: this.meshLayout,
|
|
903
|
+
entries: [{ binding: 0, resource: bufferBinding(buffer.value, 0, rows.byteLength) }],
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
dispose(): void {
|
|
908
|
+
for (const buffer of this.projectionBuffers) this.device.destroyBuffer(buffer);
|
|
909
|
+
this.projectionBuffers.length = 0;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
get frameGroups(): readonly [BindGroup, BindGroup] {
|
|
913
|
+
return [this.materialBindGroup, this.instancesBindGroup];
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
378
917
|
export interface PreparedGpuDrivenFrame {
|
|
379
918
|
readonly topologySignature: string;
|
|
380
919
|
readonly entityKeys: ReadonlySet<number>;
|
|
381
920
|
readonly ownsAllRenderables: boolean;
|
|
921
|
+
readonly occlusion?: OcclusionFrameProjection;
|
|
382
922
|
/** @internal Commits a replacement generation only after graph promotion. */
|
|
383
923
|
readonly _commitResourceReplacement: () => void;
|
|
384
924
|
project(
|
|
@@ -391,12 +931,16 @@ export interface PreparedGpuDrivenFrame {
|
|
|
391
931
|
export class GpuDrivenProduction {
|
|
392
932
|
private view: GpuDrivenView | undefined;
|
|
393
933
|
private raster: GpuDrivenRigidRaster | undefined;
|
|
934
|
+
private standardRaster: StandardPbrRasterAdapter | undefined;
|
|
394
935
|
private readonly sceneIdentities = new WeakMap<object, number>();
|
|
395
936
|
private nextSceneIdentity = 1;
|
|
396
937
|
private filteredCache:
|
|
397
938
|
| {
|
|
398
939
|
readonly source: SubmissionPlan;
|
|
399
940
|
readonly meshResidencyEpoch: number;
|
|
941
|
+
readonly lodProjectionFingerprint: string;
|
|
942
|
+
readonly activeEntityRevision?: number;
|
|
943
|
+
readonly activeEntityKeys?: ReadonlySet<number>;
|
|
400
944
|
readonly value: FilteredProductionPlan;
|
|
401
945
|
}
|
|
402
946
|
| undefined;
|
|
@@ -406,6 +950,13 @@ export class GpuDrivenProduction {
|
|
|
406
950
|
private batchBindGroupCreates = 0;
|
|
407
951
|
private validatedGpuOwnedRows = 0;
|
|
408
952
|
private cpuFallbackDrawItems = 0;
|
|
953
|
+
private telemetryPrepared = false;
|
|
954
|
+
private telemetryCandidateCount = 0;
|
|
955
|
+
private telemetryGeometryWork = 0;
|
|
956
|
+
private telemetryRootGeometryWork = 0;
|
|
957
|
+
private indirectDrawCount = 0;
|
|
958
|
+
private sceneRowsRequired = true;
|
|
959
|
+
private standardPbrProjection: StandardPbrSceneProjection | undefined;
|
|
409
960
|
private signatureCache:
|
|
410
961
|
| {
|
|
411
962
|
readonly filtered: FilteredProductionPlan;
|
|
@@ -428,40 +979,130 @@ export class GpuDrivenProduction {
|
|
|
428
979
|
readonly viewBindGroupLayout: BindGroupLayout;
|
|
429
980
|
readonly meshResidencyEpoch: number;
|
|
430
981
|
readonly hdrp: boolean;
|
|
982
|
+
/** Entity keys admitted by the persistent visibility facet for this view. */
|
|
983
|
+
readonly activeEntityKeys?: ReadonlySet<number>;
|
|
984
|
+
/** Collision-free renderer-owned revision for the active visibility set. */
|
|
985
|
+
readonly activeEntityRevision?: number;
|
|
986
|
+
/** Total extracted candidates, including facet-suppressed rows. */
|
|
987
|
+
readonly telemetryCandidateCount?: number;
|
|
988
|
+
readonly occlusion?: OcclusionFrameProjection;
|
|
989
|
+
/** Cooked producer artifact selected from the frame material catalog. */
|
|
990
|
+
readonly standardPbrArtifact?: MaterialShaderArtifact;
|
|
991
|
+
readonly standardPbrPipelineState?: PipelineState;
|
|
431
992
|
}): Result<PreparedGpuDrivenFrame | undefined, RhiError> {
|
|
432
993
|
this.gpuOwnedSnapshotsMaterialized = 0;
|
|
433
994
|
this.filteredPlanBuilds = 0;
|
|
434
995
|
this.batchBindGroupCreates = 0;
|
|
435
996
|
this.validatedGpuOwnedRows = 0;
|
|
436
997
|
this.cpuFallbackDrawItems = 0;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
) {
|
|
998
|
+
this.telemetryPrepared = false;
|
|
999
|
+
this.telemetryCandidateCount = input.telemetryCandidateCount ?? 0;
|
|
1000
|
+
this.telemetryGeometryWork = 0;
|
|
1001
|
+
this.telemetryRootGeometryWork = 0;
|
|
1002
|
+
this.indirectDrawCount = 0;
|
|
1003
|
+
if (input.hdrp || input.scene === undefined || selectGpuLodLane(this.device.caps) === 'cpu') {
|
|
444
1004
|
this.gpuOwnedEntityCount = 0;
|
|
1005
|
+
this.sceneRowsRequired = false;
|
|
445
1006
|
return ok(undefined);
|
|
446
1007
|
}
|
|
447
1008
|
const scene = input.scene;
|
|
1009
|
+
const projectionFingerprint = lodProjectionFingerprint(input.camera);
|
|
1010
|
+
// An empty filtered plan means this topology/mesh-residency pair has no
|
|
1011
|
+
// renderables that this production lane can own. Camera and visibility
|
|
1012
|
+
// inputs only affect admitted candidates and LOD selection, so they cannot
|
|
1013
|
+
// turn that empty topology into an eligible one. Keep the negative result
|
|
1014
|
+
// resident instead of rescanning every scene slot as the camera moves.
|
|
1015
|
+
const cachedPlanIsTopologyInvariant = this.filteredCache?.value.topologyInvariantEmpty === true;
|
|
1016
|
+
const cachedPlanIsCameraInvariant = cachedPlanIsTopologyInvariant;
|
|
1017
|
+
const cachedVisibilityMatches =
|
|
1018
|
+
cachedPlanIsCameraInvariant || this.filteredCache === undefined
|
|
1019
|
+
? true
|
|
1020
|
+
: input.activeEntityRevision !== undefined &&
|
|
1021
|
+
this.filteredCache.activeEntityRevision !== undefined
|
|
1022
|
+
? input.activeEntityRevision === this.filteredCache.activeEntityRevision
|
|
1023
|
+
: sameEntityKeySet(this.filteredCache.activeEntityKeys, input.activeEntityKeys);
|
|
448
1024
|
let filtered = this.filteredCache?.value;
|
|
449
1025
|
if (
|
|
450
1026
|
filtered === undefined ||
|
|
451
1027
|
this.filteredCache?.source !== scene.plan ||
|
|
452
|
-
|
|
1028
|
+
(!cachedPlanIsTopologyInvariant &&
|
|
1029
|
+
this.filteredCache.meshResidencyEpoch !== input.meshResidencyEpoch) ||
|
|
1030
|
+
(!cachedPlanIsCameraInvariant &&
|
|
1031
|
+
this.filteredCache.lodProjectionFingerprint !== projectionFingerprint) ||
|
|
1032
|
+
!cachedVisibilityMatches
|
|
453
1033
|
) {
|
|
454
|
-
filtered = filteredPlan(
|
|
1034
|
+
filtered = filteredPlan(
|
|
1035
|
+
scene.plan,
|
|
1036
|
+
scene.slots,
|
|
1037
|
+
input.meshes,
|
|
1038
|
+
input.camera,
|
|
1039
|
+
input.activeEntityKeys,
|
|
1040
|
+
scene.worldKeys,
|
|
1041
|
+
);
|
|
455
1042
|
this.filteredCache = {
|
|
456
1043
|
source: scene.plan,
|
|
457
1044
|
meshResidencyEpoch: input.meshResidencyEpoch,
|
|
1045
|
+
lodProjectionFingerprint: projectionFingerprint,
|
|
1046
|
+
...(input.activeEntityRevision === undefined
|
|
1047
|
+
? {}
|
|
1048
|
+
: { activeEntityRevision: input.activeEntityRevision }),
|
|
1049
|
+
...(input.activeEntityKeys === undefined
|
|
1050
|
+
? {}
|
|
1051
|
+
: { activeEntityKeys: input.activeEntityKeys }),
|
|
458
1052
|
value: filtered,
|
|
459
1053
|
};
|
|
460
1054
|
this.gpuOwnedSnapshotsMaterialized = scene.slots.length;
|
|
461
1055
|
this.filteredPlanBuilds = 1;
|
|
462
1056
|
}
|
|
463
1057
|
this.gpuOwnedEntityCount = filtered.entityKeys.size;
|
|
1058
|
+
this.sceneRowsRequired = !filtered.topologyInvariantEmpty;
|
|
1059
|
+
// Publish the command count from the current filtered plan on every
|
|
1060
|
+
// prepare call. A cached graph does not call project() again on stable
|
|
1061
|
+
// frames, so deriving this only while compiling the raster pass would
|
|
1062
|
+
// transiently report zero after prepare() reset the per-frame counters.
|
|
1063
|
+
this.indirectDrawCount = admittedRasterBatchCount(filtered.plan);
|
|
464
1064
|
if (filtered.batches.length === 0) return ok(undefined);
|
|
1065
|
+
this.standardPbrProjection = undefined;
|
|
1066
|
+
const usesStandardPbrRaster = filtered.batches.every(({ batch }) => isStandardPbrBatch(batch));
|
|
1067
|
+
if (usesStandardPbrRaster && input.standardPbrArtifact !== undefined) {
|
|
1068
|
+
const slotsByPrimitive = new Map(scene.slots.map((slot) => [slot.slot, slot] as const));
|
|
1069
|
+
const projection = projectStandardPbrScene({
|
|
1070
|
+
artifact: input.standardPbrArtifact,
|
|
1071
|
+
candidates: filtered.batches.flatMap(({ batch }) =>
|
|
1072
|
+
batch.candidates.flatMap((candidate) => {
|
|
1073
|
+
const slot = slotsByPrimitive.get(candidate.primitiveIndex);
|
|
1074
|
+
return slot === undefined
|
|
1075
|
+
? []
|
|
1076
|
+
: [
|
|
1077
|
+
{
|
|
1078
|
+
primitiveIndex: candidate.primitiveIndex,
|
|
1079
|
+
world: slot.snapshot.transform.world,
|
|
1080
|
+
},
|
|
1081
|
+
];
|
|
1082
|
+
}),
|
|
1083
|
+
),
|
|
1084
|
+
});
|
|
1085
|
+
if (!projection.ok) {
|
|
1086
|
+
return err(
|
|
1087
|
+
new RhiError({
|
|
1088
|
+
code: 'shader-compile-failed',
|
|
1089
|
+
expected: 'receipt-derived Standard PBR scene projection',
|
|
1090
|
+
hint: 'publish one validated MaterialShaderArtifact receipt before entering the GPU lane',
|
|
1091
|
+
}),
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
this.standardPbrProjection = projection.value;
|
|
1095
|
+
if (input.standardPbrPipelineState !== undefined && this.standardRaster === undefined) {
|
|
1096
|
+
const created = StandardPbrRasterAdapter.create({
|
|
1097
|
+
device: this.device,
|
|
1098
|
+
shaderModuleFactory: this.shaderModuleFactory,
|
|
1099
|
+
pipelineState: input.standardPbrPipelineState,
|
|
1100
|
+
artifact: input.standardPbrArtifact,
|
|
1101
|
+
});
|
|
1102
|
+
if (!created.ok) return created;
|
|
1103
|
+
this.standardRaster = created.value;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
465
1106
|
if (this.view === undefined) {
|
|
466
1107
|
const created = GpuDrivenView.create({
|
|
467
1108
|
device: this.device,
|
|
@@ -481,8 +1122,11 @@ export class GpuDrivenProduction {
|
|
|
481
1122
|
}
|
|
482
1123
|
const updated = this.view.update(filtered.plan, scene.scene, activeFrustum(input.camera));
|
|
483
1124
|
if (!updated.ok) return updated;
|
|
1125
|
+
this.telemetryPrepared = true;
|
|
484
1126
|
const view = this.view;
|
|
485
1127
|
const raster = this.raster;
|
|
1128
|
+
const standardRaster = this.standardRaster;
|
|
1129
|
+
const standardArtifact = input.standardPbrArtifact;
|
|
486
1130
|
const sceneIdentity = this.sceneIdentity(scene.scene);
|
|
487
1131
|
const viewInspection = view.inspect();
|
|
488
1132
|
let signature = this.signatureCache?.value;
|
|
@@ -497,10 +1141,12 @@ export class GpuDrivenProduction {
|
|
|
497
1141
|
sceneIdentity,
|
|
498
1142
|
revision: filtered.plan.revision,
|
|
499
1143
|
meshResidencyEpoch: input.meshResidencyEpoch,
|
|
1144
|
+
standardPbrLayoutIdentity: this.standardPbrProjection?.layoutIdentity,
|
|
500
1145
|
viewResourceGeneration: viewInspection.resourceGeneration,
|
|
501
1146
|
batches: filtered.batches.map(({ batch, mesh }) => [
|
|
502
1147
|
batch.batchId,
|
|
503
1148
|
batch.generation,
|
|
1149
|
+
batch.visibleCapacity,
|
|
504
1150
|
batch.visibleBase,
|
|
505
1151
|
batch.candidates.map((candidate) => [
|
|
506
1152
|
candidate.primitiveIndex,
|
|
@@ -514,6 +1160,7 @@ export class GpuDrivenProduction {
|
|
|
514
1160
|
]),
|
|
515
1161
|
capacities: [
|
|
516
1162
|
viewInspection.candidateCapacity,
|
|
1163
|
+
viewInspection.visibleBufferCapacity,
|
|
517
1164
|
viewInspection.batchCapacity,
|
|
518
1165
|
viewInspection.indirectCapacity,
|
|
519
1166
|
],
|
|
@@ -529,11 +1176,165 @@ export class GpuDrivenProduction {
|
|
|
529
1176
|
return ok({
|
|
530
1177
|
topologySignature: signature,
|
|
531
1178
|
entityKeys: filtered.entityKeys,
|
|
532
|
-
ownsAllRenderables:
|
|
1179
|
+
ownsAllRenderables:
|
|
1180
|
+
input.activeEntityKeys === undefined
|
|
1181
|
+
? filtered.entityKeys.size === scene.slots.length
|
|
1182
|
+
: filtered.entityKeys.size === input.activeEntityKeys.size,
|
|
1183
|
+
...(input.occlusion === undefined ? {} : { occlusion: input.occlusion }),
|
|
533
1184
|
_commitResourceReplacement: () => view._commitResourceReplacement(),
|
|
534
1185
|
project: (graph, format, sampleCount) => {
|
|
1186
|
+
this.indirectDrawCount = admittedRasterBatchCount(filtered.plan);
|
|
535
1187
|
const outputs = view.addPasses(graph);
|
|
536
1188
|
if (!outputs.ok) return outputs;
|
|
1189
|
+
if (
|
|
1190
|
+
usesStandardPbrRaster &&
|
|
1191
|
+
standardRaster !== undefined &&
|
|
1192
|
+
standardArtifact !== undefined
|
|
1193
|
+
) {
|
|
1194
|
+
const standardProjected: Array<{
|
|
1195
|
+
readonly batch: GpuDrivenBatch;
|
|
1196
|
+
readonly mesh: MeshGpuHandles;
|
|
1197
|
+
readonly vertex: GraphBuffer;
|
|
1198
|
+
readonly index?: GraphBuffer;
|
|
1199
|
+
readonly pipeline: RenderPipeline;
|
|
1200
|
+
readonly meshBindGroup: BindGroup;
|
|
1201
|
+
}> = [];
|
|
1202
|
+
const standardAccesses: GraphAccess[] = [
|
|
1203
|
+
{ resource: outputs.value.primitive, usage: 'storage-read' },
|
|
1204
|
+
{ resource: outputs.value.instance, usage: 'storage-read' },
|
|
1205
|
+
{ resource: outputs.value.material, usage: 'storage-read' },
|
|
1206
|
+
{ resource: outputs.value.visible, usage: 'storage-read' },
|
|
1207
|
+
{ resource: outputs.value.transform, usage: 'storage-read' },
|
|
1208
|
+
{ resource: outputs.value.indirect, usage: 'indirect-read' },
|
|
1209
|
+
];
|
|
1210
|
+
const slotsByPrimitive = new Map(scene.slots.map((slot) => [slot.slot, slot] as const));
|
|
1211
|
+
const meshBuffers = new Map<
|
|
1212
|
+
number,
|
|
1213
|
+
{ readonly vertex: GraphBuffer; readonly index?: GraphBuffer }
|
|
1214
|
+
>();
|
|
1215
|
+
for (const prepared of filtered.batches) {
|
|
1216
|
+
const candidates = prepared.batch.candidates.flatMap((candidate) => {
|
|
1217
|
+
const slot = slotsByPrimitive.get(candidate.primitiveIndex);
|
|
1218
|
+
return slot === undefined
|
|
1219
|
+
? []
|
|
1220
|
+
: [
|
|
1221
|
+
{
|
|
1222
|
+
primitiveIndex: candidate.primitiveIndex,
|
|
1223
|
+
world: slot.snapshot.transform.world,
|
|
1224
|
+
},
|
|
1225
|
+
];
|
|
1226
|
+
});
|
|
1227
|
+
const projection = projectStandardPbrScene({
|
|
1228
|
+
artifact: standardArtifact,
|
|
1229
|
+
candidates,
|
|
1230
|
+
});
|
|
1231
|
+
if (!projection.ok) {
|
|
1232
|
+
return err(
|
|
1233
|
+
new RhiError({
|
|
1234
|
+
code: 'shader-compile-failed',
|
|
1235
|
+
expected: 'receipt-derived Standard PBR scene projection',
|
|
1236
|
+
hint: 'keep the frame artifact receipt and projected rows on one identity',
|
|
1237
|
+
}),
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
const meshBindGroup = standardRaster.projectionBindGroup(projection.value.rows);
|
|
1241
|
+
if (!meshBindGroup.ok) return meshBindGroup;
|
|
1242
|
+
let buffers = meshBuffers.get(prepared.batch.key.assetHandle);
|
|
1243
|
+
if (buffers === undefined) {
|
|
1244
|
+
const vertex = graph.importBuffer(
|
|
1245
|
+
`gpu-driven.standard.${prepared.batch.key.assetHandle}.vertex`,
|
|
1246
|
+
{
|
|
1247
|
+
size: prepared.mesh.vboBytes,
|
|
1248
|
+
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
1249
|
+
},
|
|
1250
|
+
() => prepared.mesh.vertexBuffer.handle,
|
|
1251
|
+
);
|
|
1252
|
+
if (!vertex.ok) return vertex;
|
|
1253
|
+
let index: GraphBuffer | undefined;
|
|
1254
|
+
if (prepared.mesh.indexBuffer !== null) {
|
|
1255
|
+
const indexHandle = prepared.mesh.indexBuffer;
|
|
1256
|
+
const imported = graph.importBuffer(
|
|
1257
|
+
`gpu-driven.standard.${prepared.batch.key.assetHandle}.index`,
|
|
1258
|
+
{
|
|
1259
|
+
size: prepared.mesh.iboBytes,
|
|
1260
|
+
usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
1261
|
+
},
|
|
1262
|
+
() => indexHandle.handle,
|
|
1263
|
+
);
|
|
1264
|
+
if (!imported.ok) return imported;
|
|
1265
|
+
index = imported.value;
|
|
1266
|
+
}
|
|
1267
|
+
buffers = { vertex: vertex.value, ...(index === undefined ? {} : { index }) };
|
|
1268
|
+
meshBuffers.set(prepared.batch.key.assetHandle, buffers);
|
|
1269
|
+
standardAccesses.push({ resource: buffers.vertex, usage: 'vertex-read' });
|
|
1270
|
+
if (buffers.index !== undefined) {
|
|
1271
|
+
standardAccesses.push({ resource: buffers.index, usage: 'index-read' });
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
const pipeline = standardRaster.pipeline(
|
|
1275
|
+
format,
|
|
1276
|
+
sampleCount,
|
|
1277
|
+
prepared.mesh.layoutProjection,
|
|
1278
|
+
prepared.batch.key.topology,
|
|
1279
|
+
prepared.batch.key.drawKind === 'indexed' &&
|
|
1280
|
+
(prepared.batch.key.topology === 'line-strip' ||
|
|
1281
|
+
prepared.batch.key.topology === 'triangle-strip')
|
|
1282
|
+
? prepared.mesh.indexFormat
|
|
1283
|
+
: undefined,
|
|
1284
|
+
prepared.renderState,
|
|
1285
|
+
);
|
|
1286
|
+
if (!pipeline.ok) return pipeline;
|
|
1287
|
+
standardProjected.push({
|
|
1288
|
+
batch: prepared.batch,
|
|
1289
|
+
mesh: prepared.mesh,
|
|
1290
|
+
...buffers,
|
|
1291
|
+
pipeline: pipeline.value,
|
|
1292
|
+
meshBindGroup: meshBindGroup.value,
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
const [materialBindGroup, instancesBindGroup] = standardRaster.frameGroups;
|
|
1296
|
+
return ok({
|
|
1297
|
+
accesses: standardAccesses,
|
|
1298
|
+
encode: (viewBindGroup, pass, resources: GraphResourceResolver) => {
|
|
1299
|
+
// pbr-view-bgl has two dynamic uniform bindings: the frame view
|
|
1300
|
+
// slot and the optional Points/Lines style slot. Standard
|
|
1301
|
+
// draws do not use the latter, but WebGPU still requires one
|
|
1302
|
+
// offset per dynamic binding.
|
|
1303
|
+
pass.setBindGroup(0, viewBindGroup, [0, 0]);
|
|
1304
|
+
pass.setBindGroup(1, materialBindGroup, [0]);
|
|
1305
|
+
pass.setBindGroup(3, instancesBindGroup);
|
|
1306
|
+
let currentPipeline: RenderPipeline | undefined;
|
|
1307
|
+
let currentVertex: GraphBuffer | undefined;
|
|
1308
|
+
let currentIndex: GraphBuffer | undefined;
|
|
1309
|
+
for (const batch of standardProjected) {
|
|
1310
|
+
if (currentPipeline !== batch.pipeline) {
|
|
1311
|
+
pass.setPipeline(batch.pipeline);
|
|
1312
|
+
currentPipeline = batch.pipeline;
|
|
1313
|
+
}
|
|
1314
|
+
pass.setBindGroup(2, batch.meshBindGroup, [0]);
|
|
1315
|
+
if (currentVertex !== batch.vertex) {
|
|
1316
|
+
const vertex = resources.buffer(batch.vertex);
|
|
1317
|
+
if (!vertex.ok) throw vertex.error;
|
|
1318
|
+
pass.setVertexBuffer(0, vertex.value);
|
|
1319
|
+
currentVertex = batch.vertex;
|
|
1320
|
+
}
|
|
1321
|
+
if (batch.index !== undefined && currentIndex !== batch.index) {
|
|
1322
|
+
const index = resources.buffer(batch.index);
|
|
1323
|
+
if (!index.ok) throw index.error;
|
|
1324
|
+
pass.setIndexBuffer(index.value, batch.mesh.indexFormat);
|
|
1325
|
+
currentIndex = batch.index;
|
|
1326
|
+
}
|
|
1327
|
+
const indirect = resources.buffer(outputs.value.indirect);
|
|
1328
|
+
if (!indirect.ok) throw indirect.error;
|
|
1329
|
+
if (batch.batch.key.drawKind === 'indexed') {
|
|
1330
|
+
pass.drawIndexedIndirect(indirect.value, batch.batch.indirectOffset);
|
|
1331
|
+
} else {
|
|
1332
|
+
pass.drawIndirect(indirect.value, batch.batch.indirectOffset);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
537
1338
|
const projected: ProjectedBatch[] = [];
|
|
538
1339
|
const accesses: GraphAccess[] = [
|
|
539
1340
|
{ resource: outputs.value.primitive, usage: 'storage-read' },
|
|
@@ -548,6 +1349,10 @@ export class GpuDrivenProduction {
|
|
|
548
1349
|
{ readonly vertex: GraphBuffer; readonly index?: GraphBuffer }
|
|
549
1350
|
>();
|
|
550
1351
|
for (const prepared of filtered.batches) {
|
|
1352
|
+
// A selector-only batch can have zero admitted candidates. Keep it
|
|
1353
|
+
// in the GPU selector and readback counters, but do not create a
|
|
1354
|
+
// raster bind group or encode a zero-instance indirect draw.
|
|
1355
|
+
if (prepared.batch.visibleCapacity === 0) continue;
|
|
551
1356
|
let buffers = meshBuffers.get(prepared.batch.key.assetHandle);
|
|
552
1357
|
if (buffers === undefined) {
|
|
553
1358
|
const vertex = graph.importBuffer(
|
|
@@ -583,7 +1388,7 @@ export class GpuDrivenProduction {
|
|
|
583
1388
|
const pipeline = raster.pipeline(
|
|
584
1389
|
format,
|
|
585
1390
|
sampleCount,
|
|
586
|
-
|
|
1391
|
+
prepared.mesh.layoutProjection.arrayStride,
|
|
587
1392
|
prepared.batch.key.topology,
|
|
588
1393
|
prepared.batch.key.drawKind === 'indexed' &&
|
|
589
1394
|
(prepared.batch.key.topology === 'line-strip' ||
|
|
@@ -603,6 +1408,7 @@ export class GpuDrivenProduction {
|
|
|
603
1408
|
bindings: bindings.value,
|
|
604
1409
|
});
|
|
605
1410
|
}
|
|
1411
|
+
this.indirectDrawCount = projected.length;
|
|
606
1412
|
return ok({
|
|
607
1413
|
accesses,
|
|
608
1414
|
encode: (viewBindGroup, pass, resources: GraphResourceResolver) => {
|
|
@@ -611,7 +1417,7 @@ export class GpuDrivenProduction {
|
|
|
611
1417
|
// shared by the GPU-driven raster pipeline, so even rigid draws
|
|
612
1418
|
// must provide the binding-10 offset (zero selects the frame
|
|
613
1419
|
// default slot).
|
|
614
|
-
pass.setBindGroup(0, viewBindGroup, [0]);
|
|
1420
|
+
pass.setBindGroup(0, viewBindGroup, [0, 0]);
|
|
615
1421
|
let currentPipeline: RenderPipeline | undefined;
|
|
616
1422
|
let currentVertex: GraphBuffer | undefined;
|
|
617
1423
|
let currentIndex: GraphBuffer | undefined;
|
|
@@ -635,11 +1441,14 @@ export class GpuDrivenProduction {
|
|
|
635
1441
|
}
|
|
636
1442
|
const indirect = resources.buffer(outputs.value.indirect);
|
|
637
1443
|
if (!indirect.ok) throw indirect.error;
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1444
|
+
const draw = (): void => {
|
|
1445
|
+
if (batch.batch.key.drawKind === 'indexed') {
|
|
1446
|
+
pass.drawIndexedIndirect(indirect.value, batch.batch.indirectOffset);
|
|
1447
|
+
} else {
|
|
1448
|
+
pass.drawIndirect(indirect.value, batch.batch.indirectOffset);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
draw();
|
|
643
1452
|
}
|
|
644
1453
|
},
|
|
645
1454
|
});
|
|
@@ -653,6 +1462,12 @@ export class GpuDrivenProduction {
|
|
|
653
1462
|
this.raster = undefined;
|
|
654
1463
|
this.filteredCache = undefined;
|
|
655
1464
|
this.signatureCache = undefined;
|
|
1465
|
+
this.sceneRowsRequired = true;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/** Whether the current production topology can consume GPU Scene row updates. */
|
|
1469
|
+
requiresSceneRows(): boolean {
|
|
1470
|
+
return this.sceneRowsRequired;
|
|
656
1471
|
}
|
|
657
1472
|
|
|
658
1473
|
inspect(): GpuDrivenProductionInspection {
|
|
@@ -669,17 +1484,112 @@ export class GpuDrivenProduction {
|
|
|
669
1484
|
topologyRevision: view?.topologyRevision,
|
|
670
1485
|
validatedGpuOwnedRows: this.validatedGpuOwnedRows,
|
|
671
1486
|
cpuFallbackDrawItems: this.cpuFallbackDrawItems,
|
|
1487
|
+
geometryWork: this.telemetryGeometryWork,
|
|
1488
|
+
rootGeometryWork: this.telemetryRootGeometryWork,
|
|
1489
|
+
geometryWorkReduction:
|
|
1490
|
+
this.telemetryRootGeometryWork > 0
|
|
1491
|
+
? 1 - this.telemetryGeometryWork / this.telemetryRootGeometryWork
|
|
1492
|
+
: 0,
|
|
1493
|
+
batchCount: view?.batchCount ?? 0,
|
|
1494
|
+
indirectDrawCount: this.indirectDrawCount,
|
|
672
1495
|
};
|
|
673
1496
|
}
|
|
674
1497
|
|
|
1498
|
+
/** Read renderer-owned LOD counters copied by the last GPU submission. */
|
|
1499
|
+
readLodSelection(): Promise<GpuDrivenLodSelectionInspection | undefined> {
|
|
1500
|
+
if (!this.telemetryPrepared) return Promise.resolve(undefined);
|
|
1501
|
+
return (this.view?.readLodSelection() ?? Promise.resolve(undefined)).then((selection) => {
|
|
1502
|
+
if (selection !== undefined) {
|
|
1503
|
+
this.telemetryGeometryWork = selection.geometryWork;
|
|
1504
|
+
this.telemetryRootGeometryWork = selection.rootGeometryWork;
|
|
1505
|
+
}
|
|
1506
|
+
if (selection === undefined || this.telemetryCandidateCount <= 0) return selection;
|
|
1507
|
+
const filtered = this.filteredCache?.value;
|
|
1508
|
+
if (filtered === undefined) return selection;
|
|
1509
|
+
const preparedByBatchId = new Map(
|
|
1510
|
+
filtered.batches.map((prepared) => [prepared.batch.batchId, prepared] as const),
|
|
1511
|
+
);
|
|
1512
|
+
const worldSelections = new Map<
|
|
1513
|
+
number,
|
|
1514
|
+
{
|
|
1515
|
+
readonly worldKey: number;
|
|
1516
|
+
readonly primitiveSlot: number;
|
|
1517
|
+
readonly slotGeneration: number;
|
|
1518
|
+
candidateCount: number;
|
|
1519
|
+
visible: number;
|
|
1520
|
+
occluded: number;
|
|
1521
|
+
readonly lodHistogram: Map<number, number>;
|
|
1522
|
+
}
|
|
1523
|
+
>();
|
|
1524
|
+
for (const batchSelection of selection.batches) {
|
|
1525
|
+
const prepared = preparedByBatchId.get(batchSelection.batchId);
|
|
1526
|
+
if (prepared === undefined) return selection;
|
|
1527
|
+
let aggregate = worldSelections.get(prepared.worldKey);
|
|
1528
|
+
if (aggregate === undefined) {
|
|
1529
|
+
aggregate = {
|
|
1530
|
+
worldKey: prepared.worldKey,
|
|
1531
|
+
primitiveSlot: prepared.primitiveSlot,
|
|
1532
|
+
slotGeneration: prepared.slotGeneration,
|
|
1533
|
+
candidateCount: 0,
|
|
1534
|
+
visible: 0,
|
|
1535
|
+
occluded: 0,
|
|
1536
|
+
lodHistogram: new Map(),
|
|
1537
|
+
};
|
|
1538
|
+
worldSelections.set(prepared.worldKey, aggregate);
|
|
1539
|
+
}
|
|
1540
|
+
aggregate.candidateCount += batchSelection.candidateCount;
|
|
1541
|
+
aggregate.visible += batchSelection.visible;
|
|
1542
|
+
aggregate.occluded += batchSelection.occluded;
|
|
1543
|
+
for (const row of batchSelection.lodHistogram) {
|
|
1544
|
+
aggregate.lodHistogram.set(
|
|
1545
|
+
row.level,
|
|
1546
|
+
(aggregate.lodHistogram.get(row.level) ?? 0) + row.count,
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return {
|
|
1551
|
+
...selection,
|
|
1552
|
+
candidateCount: this.telemetryCandidateCount,
|
|
1553
|
+
occluded: Math.max(0, this.telemetryCandidateCount - selection.visible),
|
|
1554
|
+
worldSelections: Object.freeze(
|
|
1555
|
+
[...worldSelections.values()].map((aggregate) =>
|
|
1556
|
+
Object.freeze({
|
|
1557
|
+
worldKey: aggregate.worldKey,
|
|
1558
|
+
primitiveSlot: aggregate.primitiveSlot,
|
|
1559
|
+
slotGeneration: aggregate.slotGeneration,
|
|
1560
|
+
candidateCount: aggregate.candidateCount,
|
|
1561
|
+
visible: aggregate.visible,
|
|
1562
|
+
occluded: aggregate.occluded,
|
|
1563
|
+
lodHistogram: Object.freeze(
|
|
1564
|
+
[...aggregate.lodHistogram.entries()]
|
|
1565
|
+
.sort(([left], [right]) => left - right)
|
|
1566
|
+
.map(([level, count]) => Object.freeze({ level, count })),
|
|
1567
|
+
),
|
|
1568
|
+
} satisfies GpuDrivenWorldLodSelectionInspection),
|
|
1569
|
+
),
|
|
1570
|
+
),
|
|
1571
|
+
};
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
|
|
675
1575
|
recordCpuValidation(
|
|
676
1576
|
validated: readonly { readonly source: RenderableSnapshot }[],
|
|
677
1577
|
gpuOwned: ReadonlySet<number>,
|
|
1578
|
+
worldKeys?: readonly number[],
|
|
678
1579
|
): void {
|
|
679
1580
|
this.cpuFallbackDrawItems = validated.length;
|
|
680
1581
|
let gpuOwnedRows = 0;
|
|
681
1582
|
for (const row of validated) {
|
|
682
|
-
if (
|
|
1583
|
+
if (
|
|
1584
|
+
gpuOwned.has(
|
|
1585
|
+
worldEntityKey(
|
|
1586
|
+
worldKeys?.[row.source.worldId] ?? row.source.worldId,
|
|
1587
|
+
row.source.entityKey,
|
|
1588
|
+
),
|
|
1589
|
+
)
|
|
1590
|
+
) {
|
|
1591
|
+
gpuOwnedRows += 1;
|
|
1592
|
+
}
|
|
683
1593
|
}
|
|
684
1594
|
this.validatedGpuOwnedRows = gpuOwnedRows;
|
|
685
1595
|
}
|