@forgeax/engine-render 0.1.19 → 0.1.21
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 +215 -0
- package/dist/.tsbuildinfo +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 +34 -20
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +9 -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 +3 -0
- 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-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
- package/dist/chunk-5YADUCA7.mjs.map +1 -0
- package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
- package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
- package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
- package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
- package/dist/chunk-FS2KNUQH.mjs +1294 -0
- package/dist/chunk-FS2KNUQH.mjs.map +1 -0
- 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 +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/reflection-probe.d.ts +17 -0
- package/dist/components/reflection-probe.d.ts.map +1 -0
- package/dist/components/spot-light.d.ts +12 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +8552 -1612
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -2
- package/dist/device/gpu-residency.d.ts.map +1 -1
- 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/render.d.ts +209 -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/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.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +0 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +2 -0
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +19 -17
- 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 +35 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +51 -0
- 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.mjs +1 -1
- package/dist/light-buffer-layout.d.ts +30 -13
- package/dist/light-buffer-layout.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 +20 -7
- 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 +12 -20
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +40 -7
- package/dist/pipeline-spec.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/record/frame-lighting.d.ts +29 -32
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +65 -15
- 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 +12 -5
- 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 +37 -0
- package/dist/record/gpu-timing.d.ts.map +1 -0
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +23 -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/render-context.d.ts +48 -3
- 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 +81 -5
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts +7 -4
- 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/projection.d.ts +91 -0
- package/dist/reflection/projection.d.ts.map +1 -0
- package/dist/render-contract.d.ts +114 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-data.d.ts +10 -6
- package/dist/render-data.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 +78 -2
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +113 -20
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +24 -3
- package/dist/render-system.d.ts.map +1 -1
- package/dist/renderer-inspect.d.ts +29 -0
- package/dist/renderer-inspect.d.ts.map +1 -0
- package/dist/renderer.d.ts +43 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts +14 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- 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/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.d.ts +44 -0
- package/dist/temporal/frame.d.ts.map +1 -0
- package/dist/temporal/index.mjs +3 -3
- package/dist/typed-render-graph-primitives.d.ts +6 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-atlas.d.ts +10 -0
- package/dist/typed-shadow-atlas.d.ts.map +1 -0
- package/dist/typed-shadow-passes.d.ts +1 -1
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/capability.d.ts +57 -0
- package/dist/volume/capability.d.ts.map +1 -0
- package/dist/volume/component.d.ts +61 -0
- package/dist/volume/component.d.ts.map +1 -0
- package/dist/volume/extract.d.ts +11 -0
- package/dist/volume/extract.d.ts.map +1 -0
- package/dist/volume/inspection.d.ts +38 -0
- package/dist/volume/inspection.d.ts.map +1 -0
- package/dist/volume/passes.d.ts +38 -0
- package/dist/volume/passes.d.ts.map +1 -0
- package/dist/volume/recovery.d.ts +38 -0
- package/dist/volume/recovery.d.ts.map +1 -0
- package/dist/volume/resources.d.ts +57 -0
- package/dist/volume/resources.d.ts.map +1 -0
- package/dist/volume/temporal.d.ts +35 -0
- package/dist/volume/temporal.d.ts.map +1 -0
- 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 +7 -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__/device-feature-admission.unit.test.ts +73 -0
- package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
- package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
- package/src/__tests__/factory-contract.integration.test.ts +255 -29
- package/src/__tests__/fog-component.unit.test.ts +67 -0
- package/src/__tests__/fog.integration.test.ts +86 -0
- package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
- package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
- package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
- 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__/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__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
- package/src/__tests__/material-observation.integration.test.ts +101 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- 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__/public-surface.test-d.ts +10 -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-pass-encode.integration.test.ts +101 -0
- package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -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 +23 -3
- package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
- 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 +76 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
- 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 +132 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
- 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 +232 -0
- package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -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 +377 -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 -36
- 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-resolver.unit.test.ts +15 -0
- package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
- package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
- package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
- package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
- package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
- package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
- package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
- package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
- package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
- package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
- package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
- package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
- package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
- package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
- package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
- package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
- package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
- package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
- package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
- package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
- package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
- package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
- package/src/assembly/device-feature-admission.ts +73 -0
- package/src/assembly/directional-shadow-inspection.ts +122 -0
- package/src/assembly/factory.ts +1093 -246
- package/src/assembly/host-contract.ts +25 -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/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 +2 -0
- package/src/components/light-helpers.ts +85 -7
- package/src/components/reflection-probe.ts +49 -0
- package/src/components/spot-light.ts +22 -3
- package/src/device/gpu-residency.ts +168 -58
- package/src/environment/frame.ts +6 -24
- package/src/environment/lifecycle.ts +12 -0
- package/src/errors/render.ts +405 -143
- package/src/extract/directional-shadow-projection.ts +61 -0
- package/src/extract/environment.ts +57 -0
- package/src/extract/visibility.ts +25 -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 +1 -0
- package/src/gpu-driven/production-raster.ts +1 -3
- package/src/gpu-scene.ts +8 -1
- package/src/gpu-usage.ts +2 -0
- package/src/hdrp-buffers.ts +165 -59
- package/src/ibl/IblPipelineCache.ts +11 -0
- package/src/ibl/kernel-cache.ts +88 -0
- package/src/index.ts +186 -1
- package/src/inspection-types.ts +56 -0
- package/src/instances-derived-bounds.ts +171 -0
- package/src/light-buffer-layout.ts +74 -26
- package/src/mesh-material-bindings.ts +132 -0
- package/src/pbr-pipeline.ts +139 -27
- package/src/pipeline/standard-forward-lane.ts +65 -15
- package/src/pipeline/standard-lighting/errors.ts +6 -0
- package/src/pipeline/standard-lighting/graph.ts +157 -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 +34 -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 +77 -0
- package/src/pipeline/standard-pipeline.ts +141 -192
- package/src/pipeline/standard-post.ts +26 -7
- package/src/pipeline/standard-profile.ts +23 -30
- package/src/pipeline-spec.ts +84 -21
- package/src/point-shadow-inspection.ts +55 -0
- package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
- package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
- package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
- package/src/record/frame-lighting.ts +470 -516
- package/src/record/frame-snapshot.ts +76 -28
- package/src/record/frame-targets.ts +23 -2
- package/src/record/frame.ts +1169 -79
- 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 +233 -0
- package/src/record/main-pass-geometry.ts +120 -80
- package/src/record/main-pass-material.ts +101 -11
- package/src/record/main-pass-sprite-draws.ts +54 -25
- package/src/record/main-pass.ts +71 -15
- package/src/record/render-context.ts +77 -0
- package/src/record/shadow-pass.ts +104 -66
- package/src/record/skybox-post-pass.ts +20 -4
- package/src/record/typed-frame-graph.ts +1016 -32
- package/src/record/view-ubo.ts +63 -28
- package/src/reflection/filter.ts +81 -0
- package/src/reflection/gpu-table.ts +44 -0
- package/src/reflection/projection.ts +281 -0
- package/src/render-contract.ts +147 -4
- package/src/render-data.ts +50 -22
- package/src/render-graph-primitives.ts +9 -4
- package/src/render-pipeline.ts +161 -2
- package/src/render-system-extract.ts +886 -103
- package/src/render-system.ts +1174 -66
- package/src/renderer-inspect.ts +66 -0
- package/src/renderer.ts +222 -0
- package/src/scene/render-scene.ts +117 -9
- package/src/shadow-atlas.ts +5 -5
- package/src/ssao-buffers.ts +9 -2
- 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.ts +128 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
- package/src/typed-render-graph-primitives.ts +34 -2
- package/src/typed-shadow-atlas.ts +17 -0
- package/src/typed-shadow-passes.ts +47 -40
- package/src/volume/capability.ts +173 -0
- package/src/volume/component.ts +110 -0
- package/src/volume/extract.ts +21 -0
- package/src/volume/inspection.ts +78 -0
- package/src/volume/passes.ts +686 -0
- package/src/volume/recovery.ts +86 -0
- package/src/volume/resources.ts +236 -0
- package/src/volume/temporal.ts +89 -0
- package/dist/chunk-IPXCABEP.mjs.map +0 -1
- package/dist/chunk-LOSO4CTV.mjs.map +0 -1
- package/dist/chunk-RJRUS55L.mjs.map +0 -1
- package/dist/chunk-YTQ5C7YQ.mjs +0 -584
- package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { ok } from '@forgeax/engine-types';
|
|
12
12
|
import { describe, expect, it } from 'vitest';
|
|
13
13
|
import { createRenderer as constructRenderer } from '../assembly/factory';
|
|
14
|
-
import { Camera, MeshFilter, MeshRenderer } from '../components';
|
|
14
|
+
import { Camera, MeshFilter, MeshRenderer, PointLight } from '../components';
|
|
15
15
|
import type { MeshGpuHandles } from '../device/gpu-residency';
|
|
16
16
|
import { BatchTopology } from '../gpu-driven/batch-topology';
|
|
17
17
|
import { GpuDrivenProduction } from '../gpu-driven/production-raster';
|
|
@@ -29,6 +29,11 @@ import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extr
|
|
|
29
29
|
import { RenderScene } from '../scene/render-scene';
|
|
30
30
|
|
|
31
31
|
const EMPTY_RESOURCE_CLASS = JSON.stringify({ textures: [], samplers: [], video: [] });
|
|
32
|
+
const OUTPUT_TRANSFORM_WGSL =
|
|
33
|
+
'@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { ' +
|
|
34
|
+
'var p = array<vec2<f32>, 3>(vec2<f32>(-1.0, -1.0), vec2<f32>(3.0, -1.0), vec2<f32>(-1.0, 3.0)); ' +
|
|
35
|
+
'return vec4<f32>(p[i], 0.0, 1.0); } ' +
|
|
36
|
+
'@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(0.0); }';
|
|
32
37
|
|
|
33
38
|
const STANDARD_PBR_URP_VARIANT_KEY =
|
|
34
39
|
'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
|
|
@@ -38,7 +43,7 @@ const STANDARD_PBR_TRANSMISSION_VARIANT_KEY =
|
|
|
38
43
|
function manifestDataUrl(): string {
|
|
39
44
|
const pbrWgsl = '/* pbr stub - calls f_schlick( */';
|
|
40
45
|
const unlitWgsl = '/* unlit stub */';
|
|
41
|
-
const tonemapWgsl =
|
|
46
|
+
const tonemapWgsl = OUTPUT_TRANSFORM_WGSL;
|
|
42
47
|
const standardPbrVariant = (transmissionAvailable: boolean, definesKey: string) => ({
|
|
43
48
|
definesKey,
|
|
44
49
|
defines: {
|
|
@@ -247,7 +252,6 @@ describe('GPU-driven production projection', () => {
|
|
|
247
252
|
meshes,
|
|
248
253
|
viewBindGroupLayout: viewLayout,
|
|
249
254
|
meshResidencyEpoch: 1,
|
|
250
|
-
hdrp: false,
|
|
251
255
|
})
|
|
252
256
|
.unwrap();
|
|
253
257
|
expect(prepared).toBeDefined();
|
|
@@ -272,7 +276,6 @@ describe('GPU-driven production projection', () => {
|
|
|
272
276
|
meshes,
|
|
273
277
|
viewBindGroupLayout: viewLayout,
|
|
274
278
|
meshResidencyEpoch: 1,
|
|
275
|
-
hdrp: false,
|
|
276
279
|
})
|
|
277
280
|
.unwrap();
|
|
278
281
|
expect(stable?.topologySignature).toBe(prepared.topologySignature);
|
|
@@ -341,7 +344,6 @@ describe('GPU-driven production projection', () => {
|
|
|
341
344
|
meshes,
|
|
342
345
|
viewBindGroupLayout: viewLayout,
|
|
343
346
|
meshResidencyEpoch: 1,
|
|
344
|
-
hdrp: false,
|
|
345
347
|
})
|
|
346
348
|
.unwrap();
|
|
347
349
|
expect(replacementPrepared?.topologySignature).not.toBe(prepared.topologySignature);
|
|
@@ -351,7 +353,7 @@ describe('GPU-driven production projection', () => {
|
|
|
351
353
|
availability.scene.dispose();
|
|
352
354
|
});
|
|
353
355
|
|
|
354
|
-
it('activates inside the renderer-owned
|
|
356
|
+
it('activates inside the renderer-owned clustered Standard graph and removes the CPU draw', async () => {
|
|
355
357
|
const canvas = {
|
|
356
358
|
width: 1,
|
|
357
359
|
height: 1,
|
|
@@ -380,6 +382,12 @@ describe('GPU-driven production projection', () => {
|
|
|
380
382
|
{ component: MeshRenderer, data: {} },
|
|
381
383
|
)
|
|
382
384
|
.unwrap();
|
|
385
|
+
world
|
|
386
|
+
.spawn(
|
|
387
|
+
{ component: Transform, data: { pos: [0, 0, 1] } },
|
|
388
|
+
{ component: PointLight, data: {} },
|
|
389
|
+
)
|
|
390
|
+
.unwrap();
|
|
383
391
|
|
|
384
392
|
for (let frame = 0; frame < 4; frame += 1) {
|
|
385
393
|
world.update(1 / 60).unwrap();
|
|
@@ -391,18 +399,9 @@ describe('GPU-driven production projection', () => {
|
|
|
391
399
|
world.update(1 / 60).unwrap();
|
|
392
400
|
expect(renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 }).ok).toBe(true);
|
|
393
401
|
expect(renderer.perFramePassNames).toEqual(
|
|
394
|
-
expect.arrayContaining([
|
|
395
|
-
'gpu-driven.view-reset',
|
|
396
|
-
'gpu-driven.frustum-compact',
|
|
397
|
-
'gpu-driven.finalize-indirect',
|
|
398
|
-
'main',
|
|
399
|
-
]),
|
|
402
|
+
expect.arrayContaining(['cluster-membership-producer', 'main', 'output-transform']),
|
|
400
403
|
);
|
|
401
|
-
expect(
|
|
402
|
-
// Storage-buffer LDR frames keep the scene linear until the one output
|
|
403
|
-
// transform writes the encoded surface; the GPU-driven indirect raster
|
|
404
|
-
// draw and that fullscreen pass are the only draws in this topology.
|
|
405
|
-
expect(device.totalDrawCount).toBe(2);
|
|
404
|
+
expect(device.totalDrawCount).toBeGreaterThan(0);
|
|
406
405
|
expect(renderer.renderScene).toMatchObject({
|
|
407
406
|
worldEntitiesScanned: 0,
|
|
408
407
|
fullRebuilds: 1,
|
|
@@ -192,6 +192,7 @@ describe('GpuDrivenView graph projection', () => {
|
|
|
192
192
|
const frameState = {
|
|
193
193
|
compiledFrameGraph: null,
|
|
194
194
|
compiledFrameGraphTopologyKey: null,
|
|
195
|
+
compiledFrameGraphGeneration: 0,
|
|
195
196
|
retiredCompiledFrameGraphs: new Set(),
|
|
196
197
|
activePipeline,
|
|
197
198
|
installedPipelineConfig: undefined,
|
|
@@ -209,14 +210,14 @@ describe('GpuDrivenView graph projection', () => {
|
|
|
209
210
|
pointShadow: [],
|
|
210
211
|
spot: [],
|
|
211
212
|
} as unknown as ExtractedLights;
|
|
212
|
-
const compile = (prepared: PreparedGpuDrivenFrame) =>
|
|
213
|
+
const compile = (prepared: PreparedGpuDrivenFrame, width = 1) =>
|
|
213
214
|
ensureCompiledFrameGraph(
|
|
214
215
|
internals,
|
|
215
216
|
frameState,
|
|
216
217
|
pipelineState,
|
|
217
218
|
makeZeroCameraFallbackSnapshot(),
|
|
218
219
|
lights,
|
|
219
|
-
|
|
220
|
+
width,
|
|
220
221
|
1,
|
|
221
222
|
undefined,
|
|
222
223
|
prepared,
|
|
@@ -243,6 +244,7 @@ describe('GpuDrivenView graph projection', () => {
|
|
|
243
244
|
const compiledPrevious = compile(preparedFrame(view));
|
|
244
245
|
expect(compiledPrevious).not.toBeNull();
|
|
245
246
|
if (compiledPrevious === null) return;
|
|
247
|
+
expect(frameState.compiledFrameGraphGeneration).toBe(1);
|
|
246
248
|
|
|
247
249
|
Object.defineProperty(device, 'surfaceViewFormats', {
|
|
248
250
|
configurable: true,
|
|
@@ -269,7 +271,8 @@ describe('GpuDrivenView graph projection', () => {
|
|
|
269
271
|
|
|
270
272
|
expect(view.inspect()).toMatchObject({ candidateCount: 2, candidateCapacity: 2 });
|
|
271
273
|
forceCompileFailure = true;
|
|
272
|
-
expect(compile(preparedFrame(view))).toBe(compiledPrevious);
|
|
274
|
+
expect(compile(preparedFrame(view), 2)).toBe(compiledPrevious);
|
|
275
|
+
expect(frameState.compiledFrameGraphGeneration).toBe(1);
|
|
273
276
|
expect(errors).toHaveLength(1);
|
|
274
277
|
expect(destroyBuffer.mock.calls.some(([buffer]) => buffer === previousVisible)).toBe(false);
|
|
275
278
|
const encoder = device.createCommandEncoder({ label: 'gpu-driven-view-lkg' }).unwrap();
|
|
@@ -277,13 +280,18 @@ describe('GpuDrivenView graph projection', () => {
|
|
|
277
280
|
|
|
278
281
|
forceCompileFailure = false;
|
|
279
282
|
const retirePrevious = vi.spyOn(compiledPrevious, 'retire');
|
|
280
|
-
const acceptedGraph = compile(preparedFrame(view));
|
|
283
|
+
const acceptedGraph = compile(preparedFrame(view), 2);
|
|
281
284
|
expect(acceptedGraph).not.toBe(compiledPrevious);
|
|
285
|
+
expect(frameState.compiledFrameGraphGeneration).toBe(2);
|
|
282
286
|
expect(retirePrevious).toHaveBeenCalledOnce();
|
|
283
287
|
await device.queue.onSubmittedWorkDone();
|
|
284
288
|
await Promise.resolve();
|
|
285
289
|
expect(destroyBuffer.mock.calls.some(([buffer]) => buffer === previousVisible)).toBe(true);
|
|
286
290
|
|
|
291
|
+
const returnedToPreviousTopology = compile(preparedFrame(view), 1);
|
|
292
|
+
expect(returnedToPreviousTopology).not.toBe(acceptedGraph);
|
|
293
|
+
expect(frameState.compiledFrameGraphGeneration).toBe(3);
|
|
294
|
+
|
|
287
295
|
view.dispose();
|
|
288
296
|
sceneAvailability.scene.dispose();
|
|
289
297
|
});
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { World } from '@forgeax/engine-ecs';
|
|
2
|
+
import { rhi } from '@forgeax/engine-rhi-webgpu';
|
|
3
|
+
import { Transform } from '@forgeax/engine-scene';
|
|
4
|
+
import { createRenderer, exposeRenderer } from '../assembly/factory.js';
|
|
5
|
+
import { Camera } from '../components/camera.js';
|
|
6
|
+
import type { GpuPassTimingFrame } from '../record/gpu-pass-timing/contract.js';
|
|
7
|
+
import type { FrameReceipt, Renderer, RenderWorldLease } from '../render-contract.js';
|
|
8
|
+
|
|
9
|
+
export interface BrowserGpuPassTimingBenchDraw {
|
|
10
|
+
readonly token: number;
|
|
11
|
+
readonly frameDurationMicroseconds: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BrowserGpuPassTimingBenchObservation {
|
|
15
|
+
readonly status: 'complete' | 'partial' | 'failed' | 'unavailable';
|
|
16
|
+
readonly frame?: GpuPassTimingFrame | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BrowserGpuPassTimingBenchController {
|
|
20
|
+
readonly supported: boolean;
|
|
21
|
+
readonly realGpu: boolean;
|
|
22
|
+
readonly backend: string;
|
|
23
|
+
readonly frameGeneration: string;
|
|
24
|
+
readonly draw: (timingEnabled: boolean) => BrowserGpuPassTimingBenchDraw;
|
|
25
|
+
readonly observe: (token: number) => Promise<BrowserGpuPassTimingBenchObservation>;
|
|
26
|
+
readonly dispose: () => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface BrowserTimingFixture {
|
|
30
|
+
readonly renderer: Renderer;
|
|
31
|
+
readonly world: World;
|
|
32
|
+
readonly lease: RenderWorldLease;
|
|
33
|
+
readonly canvas: HTMLCanvasElement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface BrowserPendingFrame {
|
|
37
|
+
readonly fixture: BrowserTimingFixture;
|
|
38
|
+
readonly receipt: FrameReceipt;
|
|
39
|
+
readonly timingEnabled: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createTimingCamera(world: World): void {
|
|
43
|
+
world
|
|
44
|
+
.spawn(
|
|
45
|
+
{ component: Transform, data: { pos: [0, 0, 5], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
|
|
46
|
+
{
|
|
47
|
+
component: Camera,
|
|
48
|
+
data: { fov: Math.PI / 4, aspect: 1, near: 0.1, far: 100 },
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
.unwrap();
|
|
52
|
+
world.update(1 / 60).unwrap();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function createBrowserTimingFixture(timingEnabled: boolean): Promise<BrowserTimingFixture> {
|
|
56
|
+
const canvas = document.createElement('canvas');
|
|
57
|
+
canvas.width = 32;
|
|
58
|
+
canvas.height = 32;
|
|
59
|
+
canvas.style.width = '32px';
|
|
60
|
+
canvas.style.height = '32px';
|
|
61
|
+
document.body.append(canvas);
|
|
62
|
+
let renderer: Renderer | undefined;
|
|
63
|
+
try {
|
|
64
|
+
const implementation = await createRenderer(
|
|
65
|
+
canvas,
|
|
66
|
+
timingEnabled
|
|
67
|
+
? {
|
|
68
|
+
rhi,
|
|
69
|
+
gpuPassTiming: { maxPassesPerFrame: 64, maxFramesInFlight: 2, retentionFrames: 8 },
|
|
70
|
+
}
|
|
71
|
+
: { rhi },
|
|
72
|
+
{ shaderManifestUrl: '/shaders/manifest.json' },
|
|
73
|
+
);
|
|
74
|
+
const ready = await implementation.initialization;
|
|
75
|
+
if (!ready.ok) throw new Error(`${ready.error.code}: ${ready.error.hint}`);
|
|
76
|
+
renderer = exposeRenderer(implementation);
|
|
77
|
+
const world = new World();
|
|
78
|
+
const attached = renderer.attach(world);
|
|
79
|
+
if (!attached.ok) throw new Error(attached.error.hint);
|
|
80
|
+
createTimingCamera(world);
|
|
81
|
+
return { renderer, world, lease: attached.value, canvas };
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (renderer !== undefined) await renderer.dispose();
|
|
84
|
+
canvas.remove();
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function drawTimingFrame(fixture: BrowserTimingFixture): {
|
|
90
|
+
readonly receipt: FrameReceipt;
|
|
91
|
+
readonly frameDurationMicroseconds: number;
|
|
92
|
+
} {
|
|
93
|
+
const startedAt = performance.now();
|
|
94
|
+
fixture.world.update(1 / 60).unwrap();
|
|
95
|
+
const drawn = fixture.renderer.draw({
|
|
96
|
+
leases: [fixture.lease],
|
|
97
|
+
camera: { lease: fixture.lease },
|
|
98
|
+
environment: { lease: fixture.lease },
|
|
99
|
+
});
|
|
100
|
+
if (!drawn.ok) throw new Error(drawn.error.hint);
|
|
101
|
+
return {
|
|
102
|
+
receipt: drawn.value,
|
|
103
|
+
frameDurationMicroseconds: Math.max((performance.now() - startedAt) * 1000, 0.001),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function adapterString(value: unknown): string | undefined {
|
|
108
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function createBrowserGpuPassTimingBenchController(): Promise<BrowserGpuPassTimingBenchController> {
|
|
112
|
+
const offFixture = await createBrowserTimingFixture(false);
|
|
113
|
+
let onFixture: BrowserTimingFixture | undefined;
|
|
114
|
+
try {
|
|
115
|
+
const timingFixture = await createBrowserTimingFixture(true);
|
|
116
|
+
onFixture = timingFixture;
|
|
117
|
+
const adapter = await navigator.gpu?.requestAdapter();
|
|
118
|
+
const adapterInfo = adapter?.info as Record<string, unknown> | undefined;
|
|
119
|
+
const adapterParts = [
|
|
120
|
+
adapterInfo?.vendor,
|
|
121
|
+
adapterInfo?.architecture,
|
|
122
|
+
adapterInfo?.device,
|
|
123
|
+
adapterInfo?.description,
|
|
124
|
+
]
|
|
125
|
+
.map(adapterString)
|
|
126
|
+
.filter((part): part is string => part !== undefined);
|
|
127
|
+
const backend = adapterParts.join('/') || 'browser-adapter-unknown';
|
|
128
|
+
const realGpu =
|
|
129
|
+
adapter !== null &&
|
|
130
|
+
adapter !== undefined &&
|
|
131
|
+
!/swiftshader|llvmpipe|software|basic renderer/i.test(backend);
|
|
132
|
+
const inspection = timingFixture.renderer.inspect();
|
|
133
|
+
const pending = new Map<number, BrowserPendingFrame>();
|
|
134
|
+
let nextToken = 1;
|
|
135
|
+
let disposed = false;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
supported: inspection.capabilities.timestampQuery,
|
|
139
|
+
realGpu,
|
|
140
|
+
backend,
|
|
141
|
+
frameGeneration: `device-${inspection.frame.deviceGeneration}:graph-standard`,
|
|
142
|
+
draw: (timingEnabled) => {
|
|
143
|
+
if (disposed) throw new Error('browser GPU pass timing controller is disposed');
|
|
144
|
+
const fixture = timingEnabled ? timingFixture : offFixture;
|
|
145
|
+
const drawn = drawTimingFrame(fixture);
|
|
146
|
+
const token = nextToken;
|
|
147
|
+
nextToken += 1;
|
|
148
|
+
pending.set(token, {
|
|
149
|
+
fixture,
|
|
150
|
+
receipt: drawn.receipt,
|
|
151
|
+
timingEnabled,
|
|
152
|
+
});
|
|
153
|
+
return { token, frameDurationMicroseconds: drawn.frameDurationMicroseconds };
|
|
154
|
+
},
|
|
155
|
+
observe: async (token) => {
|
|
156
|
+
const frame = pending.get(token);
|
|
157
|
+
if (frame === undefined) throw new Error(`unknown browser timing frame token: ${token}`);
|
|
158
|
+
pending.delete(token);
|
|
159
|
+
const observed = await frame.fixture.renderer.observe(frame.receipt, {
|
|
160
|
+
include: frame.timingEnabled ? ['timings'] : [],
|
|
161
|
+
});
|
|
162
|
+
if (!observed.ok) return { status: 'failed' };
|
|
163
|
+
if (!frame.timingEnabled) return { status: 'complete' };
|
|
164
|
+
switch (observed.value.timings?.status) {
|
|
165
|
+
case 'complete':
|
|
166
|
+
return { status: 'complete', frame: observed.value.timings.frame };
|
|
167
|
+
case 'partial':
|
|
168
|
+
return { status: 'partial', frame: observed.value.timings.frame };
|
|
169
|
+
case 'unavailable':
|
|
170
|
+
return { status: 'unavailable' };
|
|
171
|
+
case 'failed':
|
|
172
|
+
case undefined:
|
|
173
|
+
return { status: 'failed' };
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
dispose: async () => {
|
|
177
|
+
if (disposed) return;
|
|
178
|
+
disposed = true;
|
|
179
|
+
pending.clear();
|
|
180
|
+
let firstError: unknown;
|
|
181
|
+
for (const fixture of [offFixture, timingFixture]) {
|
|
182
|
+
const disposedRenderer = await fixture.renderer.dispose();
|
|
183
|
+
fixture.canvas.remove();
|
|
184
|
+
if (!disposedRenderer.ok && firstError === undefined) firstError = disposedRenderer.error;
|
|
185
|
+
}
|
|
186
|
+
if (firstError !== undefined)
|
|
187
|
+
throw new Error(`browser benchmark dispose failed: ${String(firstError)}`);
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
} catch (error) {
|
|
191
|
+
await offFixture.renderer.dispose();
|
|
192
|
+
offFixture.canvas.remove();
|
|
193
|
+
if (onFixture !== undefined) {
|
|
194
|
+
await onFixture.renderer.dispose();
|
|
195
|
+
onFixture.canvas.remove();
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function runBrowserGpuPassTiming(
|
|
202
|
+
options: { readonly suppressRaster?: boolean } = {},
|
|
203
|
+
): Promise<{
|
|
204
|
+
readonly supported: boolean;
|
|
205
|
+
readonly frames: number;
|
|
206
|
+
readonly unavailable: boolean;
|
|
207
|
+
readonly measuredPasses: number;
|
|
208
|
+
readonly visible: boolean;
|
|
209
|
+
readonly rasterSuppressed: boolean;
|
|
210
|
+
}> {
|
|
211
|
+
const canvas = document.createElement('canvas');
|
|
212
|
+
canvas.width = 64;
|
|
213
|
+
canvas.height = 64;
|
|
214
|
+
document.body.append(canvas);
|
|
215
|
+
if (options.suppressRaster === true) canvas.style.visibility = 'hidden';
|
|
216
|
+
const implementation = await createRenderer(
|
|
217
|
+
canvas,
|
|
218
|
+
{ rhi, gpuPassTiming: { maxPassesPerFrame: 64, maxFramesInFlight: 2, retentionFrames: 8 } },
|
|
219
|
+
{ shaderManifestUrl: '/shaders/manifest.json' },
|
|
220
|
+
);
|
|
221
|
+
const ready = await implementation.initialization;
|
|
222
|
+
if (!ready.ok) throw new Error(`${ready.error.code}: ${ready.error.hint}`);
|
|
223
|
+
const renderer = exposeRenderer(implementation);
|
|
224
|
+
const world = new World();
|
|
225
|
+
const attached = renderer.attach(world);
|
|
226
|
+
if (!attached.ok) throw new Error(attached.error.hint);
|
|
227
|
+
world
|
|
228
|
+
.spawn(
|
|
229
|
+
{ component: Transform, data: { pos: [0, 0, 5], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
|
|
230
|
+
{
|
|
231
|
+
component: Camera,
|
|
232
|
+
data: { fov: Math.PI / 4, aspect: 1, near: 0.1, far: 100 },
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
.unwrap();
|
|
236
|
+
world.update(1 / 60).unwrap();
|
|
237
|
+
const supported = renderer.inspect().capabilities.timestampQuery;
|
|
238
|
+
let frames = 0;
|
|
239
|
+
let unavailable = false;
|
|
240
|
+
let measuredPasses = 0;
|
|
241
|
+
try {
|
|
242
|
+
for (let index = 0; index < 3; index += 1) {
|
|
243
|
+
world.update(1 / 60).unwrap();
|
|
244
|
+
const drawn = renderer.draw({
|
|
245
|
+
leases: [attached.value],
|
|
246
|
+
camera: { lease: attached.value },
|
|
247
|
+
environment: { lease: attached.value },
|
|
248
|
+
});
|
|
249
|
+
if (!drawn.ok) continue;
|
|
250
|
+
const observed = await renderer.observe(drawn.value, { include: ['timings'] });
|
|
251
|
+
if (!observed.ok) continue;
|
|
252
|
+
frames += 1;
|
|
253
|
+
if (observed.value.timings?.status === 'unavailable') {
|
|
254
|
+
unavailable = true;
|
|
255
|
+
if ('tick' in observed.value.timings || 'durationNanoseconds' in observed.value.timings) {
|
|
256
|
+
throw new Error('unavailable timing observation exposed numeric GPU facts');
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (
|
|
261
|
+
observed.value.timings?.status === 'complete' ||
|
|
262
|
+
observed.value.timings?.status === 'partial'
|
|
263
|
+
) {
|
|
264
|
+
measuredPasses += observed.value.timings.frame.passes.filter(
|
|
265
|
+
(pass) => pass.status === 'measured',
|
|
266
|
+
).length;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} finally {
|
|
270
|
+
await renderer.dispose();
|
|
271
|
+
canvas.remove();
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
supported,
|
|
275
|
+
frames,
|
|
276
|
+
unavailable,
|
|
277
|
+
measuredPasses,
|
|
278
|
+
visible: !document.body.contains(canvas),
|
|
279
|
+
rasterSuppressed: options.suppressRaster === true,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Buffer, QuerySet, RhiCommandEncoder, RhiDevice } from '@forgeax/engine-rhi';
|
|
2
|
+
import { ok } from '@forgeax/engine-types';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
createGpuPassTimingSession,
|
|
6
|
+
type GpuPassTimingFrameIdentity,
|
|
7
|
+
} from '../record/gpu-pass-timing/session.js';
|
|
8
|
+
|
|
9
|
+
function makeDevice() {
|
|
10
|
+
const device = {
|
|
11
|
+
caps: {
|
|
12
|
+
backendKind: 'webgpu',
|
|
13
|
+
timestampQuery: true,
|
|
14
|
+
timestampPeriodNanoseconds: 1,
|
|
15
|
+
},
|
|
16
|
+
createQuerySet: vi.fn(() => ok({} as QuerySet)),
|
|
17
|
+
createBuffer: vi.fn(() => ok({} as Buffer)),
|
|
18
|
+
destroyQuerySet: vi.fn(() => ok(undefined)),
|
|
19
|
+
destroyBuffer: vi.fn(() => ok(undefined)),
|
|
20
|
+
queue: { onSubmittedWorkDone: vi.fn(async () => undefined) },
|
|
21
|
+
} as unknown as RhiDevice;
|
|
22
|
+
return device;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const identity: GpuPassTimingFrameIdentity = {
|
|
26
|
+
frameId: 3,
|
|
27
|
+
deviceGeneration: 1,
|
|
28
|
+
graphGeneration: 22,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe('GPU pass timing coexistence with producer-owned timestamps', () => {
|
|
32
|
+
it('keeps the producer timestamp owner and reports generic timing as unmeasured', async () => {
|
|
33
|
+
const device = makeDevice();
|
|
34
|
+
const session = createGpuPassTimingSession(device, {
|
|
35
|
+
maxPassesPerFrame: 2,
|
|
36
|
+
maxFramesInFlight: 1,
|
|
37
|
+
retentionFrames: 8,
|
|
38
|
+
}).unwrap();
|
|
39
|
+
const capture = session.beginFrame(identity).unwrap();
|
|
40
|
+
const producerPass = {
|
|
41
|
+
passName: 'cluster-membership-producer',
|
|
42
|
+
passKind: 'compute' as const,
|
|
43
|
+
executionIndex: 0,
|
|
44
|
+
};
|
|
45
|
+
capture.recordPass(producerPass);
|
|
46
|
+
capture.markOwnerConflict(producerPass);
|
|
47
|
+
|
|
48
|
+
expect(capture.timestampWrites(producerPass)).toBeUndefined();
|
|
49
|
+
expect(capture.snapshot()).toMatchObject({
|
|
50
|
+
measuredPassCount: 0,
|
|
51
|
+
passes: [
|
|
52
|
+
{
|
|
53
|
+
passName: 'cluster-membership-producer',
|
|
54
|
+
status: 'unmeasured',
|
|
55
|
+
reason: { code: 'timestamp-owner-conflict' },
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
const commandEncoder = {
|
|
60
|
+
encodeEmptyComputePass: vi.fn(),
|
|
61
|
+
resolveQuerySet: vi.fn(() => ok(undefined)),
|
|
62
|
+
copyBufferToBuffer: vi.fn(),
|
|
63
|
+
} as unknown as RhiCommandEncoder;
|
|
64
|
+
expect(capture.encodeTail(commandEncoder)).toMatchObject({ ok: true });
|
|
65
|
+
expect(commandEncoder.resolveQuerySet).not.toHaveBeenCalled();
|
|
66
|
+
expect(commandEncoder.copyBufferToBuffer).not.toHaveBeenCalled();
|
|
67
|
+
capture.markSubmitted();
|
|
68
|
+
expect(device.queue.onSubmittedWorkDone).not.toHaveBeenCalled();
|
|
69
|
+
await expect(capture.observe()).resolves.toMatchObject({
|
|
70
|
+
ok: true,
|
|
71
|
+
value: {
|
|
72
|
+
frameId: 3,
|
|
73
|
+
deviceGeneration: 1,
|
|
74
|
+
graphGeneration: 22,
|
|
75
|
+
passes: [{ passName: 'cluster-membership-producer', executionIndex: 0 }],
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
session.dispose();
|
|
79
|
+
});
|
|
80
|
+
});
|