@forgeax/engine-runtime 0.1.23 → 0.1.24
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 +15 -6
- package/dist/__tests__/advance-animation-player.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/advance-animation-player.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-aabb.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-aabb.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-bootstrap.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-bootstrap.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-builtin-nineslice.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-builtin-nineslice.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-guid.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-guid.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-material-validate.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-material-validate.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-scene.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-scene.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-renderer-assembly.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-renderer-assembly.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/bloom-gating.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/bloom-gating.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-guid-ssot.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/builtin-guid-ssot.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-pack.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/builtin-pack.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/camera-antialias.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-antialias.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/camera-bloom.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-bloom.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/camera-clear-schema.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-clear-schema.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/camera-ortho.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-ortho.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-binner.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/cluster-binner.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/create-renderer-uniform-fallback.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-uniform-fallback.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/cubemap-upload.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/cubemap-upload.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/dev-import-transport.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/dev-import-transport.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/engine-metrics.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/engine-metrics.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/equirect-projection-error.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/equirect-projection-error.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/font-asset-load.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/font-asset-load.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fxaa-intermediate-texture.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/fxaa-intermediate-texture.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fxaa-pipeline.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/fxaa-pipeline.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-residency.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-residency.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-resource-release.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-resource-release.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/handle-quad.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/handle-quad.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-bgl-slots.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-bgl-slots.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-grid-invalid.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-grid-invalid.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-index-list-overflow-once.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-index-list-overflow-once.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-light-budget.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-light-budget.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-pipeline-asset-config.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-pipeline-asset-config.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/helpers/renderer-device-loss-dawn.d.ts +25 -0
- package/dist/__tests__/helpers/renderer-device-loss-dawn.d.ts.map +1 -0
- package/dist/__tests__/helpers/standard-material-manifest.d.ts +0 -1
- package/dist/__tests__/helpers/standard-material-manifest.d.ts.map +1 -1
- package/dist/__tests__/ibl-pipeline-cache.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-pipeline-cache.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lazy-catalog.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/lazy-catalog.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-by-guid-hdr.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/load-by-guid-hdr.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-by-guid-prod-material-parent.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/load-by-guid-prod-material-parent.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-by-guid-prod.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/load-by-guid-prod.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-registry.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-registry.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/m7-hdrp-demo-shape.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/m7-hdrp-demo-shape.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mipmap-formula.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/mipmap-formula.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mipmap-pipeline-cache.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/mipmap-pipeline-cache.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parse-asset-payload-material.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/parse-asset-payload-material.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parse-asset-payload-texture.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/parse-asset-payload-texture.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parse-scene-payload-refs.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/parse-scene-payload-refs.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pass-selector.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pass-selector.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-builder.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-builder.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-cache-key-topology.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-cache-key-topology.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-vertex-stride-branch.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-vertex-stride-branch.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/post-process-register.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/post-process-register.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/propagate-transforms.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/propagate-transforms.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/record-all-topology.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/record-all-topology.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/record-draw-branch.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/record-draw-branch.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/record-strip-index-format.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/record-strip-index-format.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/register-with-guid-rgba16float.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/register-with-guid-rgba16float.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-data.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/render-data.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
- package/dist/__tests__/render-query-regression.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/render-query-regression.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-record-submesh.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-record-submesh.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-record-warn-once.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-record-warn-once.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-device-loss-multi-world.integration.test.d.ts +2 -0
- package/dist/__tests__/renderer-device-loss-multi-world.integration.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-device-loss-provider-feasibility.dawn.test.d.ts +2 -0
- package/dist/__tests__/renderer-device-loss-provider-feasibility.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-device-loss-recovery.dawn.test.d.ts +2 -0
- package/dist/__tests__/renderer-device-loss-recovery.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-draw-world.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/renderer-draw-world.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-recovery-consumer.integration.test.d.ts +2 -0
- package/dist/__tests__/renderer-recovery-consumer.integration.test.d.ts.map +1 -0
- package/dist/__tests__/renderstate-pipeline-cache.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/renderstate-pipeline-cache.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-scene-guids.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-scene-guids.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-skip-non-triangle.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/shadow-skip-non-triangle.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-errors-kebab-case.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-errors-kebab-case.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-instances-coexist.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-instances-coexist.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m2-extract-happy.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m2-extract-happy.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m2-extract-joint-error.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m2-extract-joint-error.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m2-extract-null-assets.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m2-extract-null-assets.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m3-record-cache.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m3-record-cache.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-palette-extract.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-palette-extract.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-pipeline-routing.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-pipeline-routing.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skybox-extract.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skybox-extract.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skybox-shader-compile.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skybox-shader-compile.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-bind-group.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-bind-group.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-component.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-component.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-fallback-path.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-fallback-path.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-pipeline-layout.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-pipeline-layout.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-solid-color-extract.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-solid-color-extract.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-boundary.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-boundary.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-clamp.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-clamp.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-loop.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-loop.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-override-probe.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-override-probe.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/storage-buffer-caps.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/storage-buffer-caps.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/systems.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/systems.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tonemap-hdr-target.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/tonemap-hdr-target.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tonemap-pipeline-split.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/tonemap-pipeline-split.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tonemap.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/tonemap.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-sort-config-get.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-sort-config-get.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-sort-config-set.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-sort-config-set.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-sort.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-sort.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/upload-texture-consistency.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/upload-texture-consistency.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/verify-revisions.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/verify-revisions.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/wire-default-loaders.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/wire-default-loaders.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/zero-camera-clear-fallback.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/zero-camera-clear-fallback.cases.unit.test.d.ts.map +1 -0
- package/dist/dev-import-transport.d.ts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +30 -30
- package/src/__tests__/advance-animation-player.cases.unit.test.ts +1666 -0
- package/src/__tests__/asset-registry-aabb.cases.unit.test.ts +676 -0
- package/src/__tests__/asset-registry-bootstrap.cases.unit.test.ts +472 -0
- package/src/__tests__/asset-registry-builtin-nineslice.cases.unit.test.ts +481 -0
- package/src/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.ts +568 -0
- package/src/__tests__/asset-registry-guid.cases.unit.test.ts +500 -0
- package/src/__tests__/asset-registry-material-validate.cases.unit.test.ts +756 -0
- package/src/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.ts +591 -0
- package/src/__tests__/asset-registry-scene.cases.unit.test.ts +501 -0
- package/src/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.ts +536 -0
- package/src/__tests__/asset-renderer-assembly.cases.unit.test.ts +496 -0
- package/src/__tests__/asset.cases.unit.test.ts +439 -0
- package/src/__tests__/bind-group-cache-keying.test.ts +6 -2
- package/src/__tests__/bloom-gating.cases.unit.test.ts +428 -0
- package/src/__tests__/builtin-guid-ssot.cases.unit.test.ts +472 -0
- package/src/__tests__/builtin-pack.cases.unit.test.ts +639 -0
- package/src/__tests__/builtin-shader-register-e2e.test.ts +1 -1
- package/src/__tests__/camera-antialias.cases.unit.test.ts +372 -0
- package/src/__tests__/camera-bloom.cases.unit.test.ts +396 -0
- package/src/__tests__/camera-clear-schema.cases.unit.test.ts +391 -0
- package/src/__tests__/camera-ortho.cases.unit.test.ts +484 -0
- package/src/__tests__/cluster-binner.cases.unit.test.ts +817 -0
- package/src/__tests__/collect-scene-mount-collapse.test.ts +5 -1
- package/src/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.ts +345 -0
- package/src/__tests__/create-renderer-uniform-fallback.cases.unit.test.ts +445 -0
- package/src/__tests__/cubemap-upload.cases.unit.test.ts +593 -0
- package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +184 -17
- package/src/__tests__/dawn/material-alpha.dawn.test.ts +17 -4
- package/src/__tests__/derive-bgl-integration.test.ts +8 -8
- package/src/__tests__/dev-import-transport.cases.unit.test.ts +503 -0
- package/src/__tests__/engine-metrics.cases.unit.test.ts +371 -0
- package/src/__tests__/equirect-projection-error.cases.unit.test.ts +416 -0
- package/src/__tests__/font-asset-load.cases.unit.test.ts +510 -0
- package/src/__tests__/fxaa-intermediate-texture.cases.unit.test.ts +690 -0
- package/src/__tests__/fxaa-pipeline.cases.unit.test.ts +546 -0
- package/src/__tests__/gpu-residency.cases.unit.test.ts +479 -0
- package/src/__tests__/gpu-resource-release.cases.unit.test.ts +929 -0
- package/src/__tests__/handle-quad.cases.unit.test.ts +491 -0
- package/src/__tests__/hdrp-bgl-slots.cases.unit.test.ts +451 -0
- package/src/__tests__/hdrp-grid-invalid.cases.unit.test.ts +408 -0
- package/src/__tests__/hdrp-index-list-overflow-once.cases.unit.test.ts +405 -0
- package/src/__tests__/hdrp-light-budget.cases.unit.test.ts +483 -0
- package/src/__tests__/hdrp-pipeline-asset-config.cases.unit.test.ts +441 -0
- package/src/__tests__/helpers/renderer-device-loss-dawn.ts +69 -0
- package/src/__tests__/helpers/standard-material-manifest.ts +11 -15
- package/src/__tests__/ibl-pipeline-cache.cases.unit.test.ts +415 -0
- package/src/__tests__/lazy-catalog.cases.unit.test.ts +848 -0
- package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +6 -5
- package/src/__tests__/load-by-guid-hdr.cases.unit.test.ts +582 -0
- package/src/__tests__/load-by-guid-prod-material-parent.cases.unit.test.ts +1514 -0
- package/src/__tests__/load-by-guid-prod.cases.unit.test.ts +557 -0
- package/src/__tests__/loader-registry.cases.unit.test.ts +767 -0
- package/src/__tests__/m7-hdrp-demo-shape.cases.unit.test.ts +361 -0
- package/src/__tests__/material-texture-uv-scale.unit.test.ts +2 -2
- package/src/__tests__/materials.unit.test.ts +29 -10
- package/src/__tests__/mipmap-formula.cases.unit.test.ts +434 -0
- package/src/__tests__/mipmap-pipeline-cache.cases.unit.test.ts +490 -0
- package/src/__tests__/parse-asset-payload-material.cases.unit.test.ts +565 -0
- package/src/__tests__/parse-asset-payload-texture.cases.unit.test.ts +720 -0
- package/src/__tests__/parse-scene-payload-refs.cases.unit.test.ts +767 -0
- package/src/__tests__/pass-selector.cases.unit.test.ts +376 -0
- package/src/__tests__/pbr-pipeline.unit.test.ts +23 -5
- package/src/__tests__/pipeline-builder.cases.unit.test.ts +624 -0
- package/src/__tests__/pipeline-cache-key-topology.cases.unit.test.ts +807 -0
- package/src/__tests__/pipeline-vertex-stride-branch.cases.unit.test.ts +379 -0
- package/src/__tests__/pipeline.cases.unit.test.ts +370 -0
- package/src/__tests__/post-process-register.cases.unit.test.ts +405 -0
- package/src/__tests__/propagate-transforms.cases.unit.test.ts +502 -0
- package/src/__tests__/propagate-transforms.dawn.test.ts +19 -2
- package/src/__tests__/record-all-topology.cases.unit.test.ts +608 -0
- package/src/__tests__/record-draw-branch.cases.unit.test.ts +621 -0
- package/src/__tests__/record-strip-index-format.cases.unit.test.ts +419 -0
- package/src/__tests__/register-with-guid-rgba16float.cases.unit.test.ts +469 -0
- package/src/__tests__/render-data.cases.unit.test.ts +553 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +1 -0
- package/src/__tests__/render-query-regression.cases.unit.test.ts +531 -0
- package/src/__tests__/render-system-record-submesh.cases.unit.test.ts +740 -0
- package/src/__tests__/render-system-record-warn-once.cases.unit.test.ts +418 -0
- package/src/__tests__/render-system-record.test.ts +15 -33
- package/src/__tests__/renderer-device-loss-multi-world.integration.test.ts +103 -0
- package/src/__tests__/renderer-device-loss-provider-feasibility.dawn.test.ts +124 -0
- package/src/__tests__/renderer-device-loss-recovery.dawn.test.ts +50 -0
- package/src/__tests__/renderer-draw-world.cases.unit.test.ts +514 -0
- package/src/__tests__/renderer-recovery-consumer.integration.test.ts +96 -0
- package/src/__tests__/renderstate-pipeline-cache.cases.unit.test.ts +631 -0
- package/src/__tests__/resolve-scene-guids.cases.unit.test.ts +1123 -0
- package/src/__tests__/shadow-caster-empty-schema.test.ts +1 -1
- package/src/__tests__/shadow-fields-observable.dawn.test.ts +2 -2
- package/src/__tests__/shadow-m1.dawn.test.ts +1 -1
- package/src/__tests__/shadow-skip-non-triangle.cases.unit.test.ts +681 -0
- package/src/__tests__/skin-errors-kebab-case.cases.unit.test.ts +475 -0
- package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +5 -4
- package/src/__tests__/skin-instances-coexist.cases.unit.test.ts +375 -0
- package/src/__tests__/skin-m2-extract-happy.cases.unit.test.ts +592 -0
- package/src/__tests__/skin-m2-extract-joint-error.cases.unit.test.ts +619 -0
- package/src/__tests__/skin-m2-extract-null-assets.cases.unit.test.ts +569 -0
- package/src/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.ts +585 -0
- package/src/__tests__/skin-m3-record-cache.cases.unit.test.ts +457 -0
- package/src/__tests__/skin-palette-extract.cases.unit.test.ts +635 -0
- package/src/__tests__/skin-pipeline-routing.cases.unit.test.ts +411 -0
- package/src/__tests__/skin.cases.unit.test.ts +421 -0
- package/src/__tests__/skybox-extract.cases.unit.test.ts +511 -0
- package/src/__tests__/skybox-shader-compile.cases.unit.test.ts +437 -0
- package/src/__tests__/skylight-bind-group.cases.unit.test.ts +642 -0
- package/src/__tests__/skylight-component.cases.unit.test.ts +386 -0
- package/src/__tests__/skylight-fallback-path.cases.unit.test.ts +566 -0
- package/src/__tests__/skylight-lazy-projection.test.ts +35 -20
- package/src/__tests__/skylight-pipeline-layout.cases.unit.test.ts +506 -0
- package/src/__tests__/skylight-solid-color-extract.cases.unit.test.ts +399 -0
- package/src/__tests__/sprite-animation-tick-boundary.cases.unit.test.ts +521 -0
- package/src/__tests__/sprite-animation-tick-clamp.cases.unit.test.ts +453 -0
- package/src/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.ts +447 -0
- package/src/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.ts +395 -0
- package/src/__tests__/sprite-animation-tick-loop.cases.unit.test.ts +494 -0
- package/src/__tests__/sprite-animation-tick-override-probe.cases.unit.test.ts +439 -0
- package/src/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.ts +466 -0
- package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +5 -5
- package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +1 -1
- package/src/__tests__/ssao-pipelines.test.ts +1 -1
- package/src/__tests__/storage-buffer-caps.cases.unit.test.ts +373 -0
- package/src/__tests__/systems.cases.unit.test.ts +425 -0
- package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +1 -1
- package/src/__tests__/tonemap-hdr-target.cases.unit.test.ts +670 -0
- package/src/__tests__/tonemap-pipeline-split.cases.unit.test.ts +867 -0
- package/src/__tests__/tonemap.cases.unit.test.ts +446 -0
- package/src/__tests__/transparent-sort-config-get.cases.unit.test.ts +395 -0
- package/src/__tests__/transparent-sort-config-set.cases.unit.test.ts +424 -0
- package/src/__tests__/transparent-sort.cases.unit.test.ts +684 -0
- package/src/__tests__/upload-texture-consistency.cases.unit.test.ts +478 -0
- package/src/__tests__/verify-revisions.cases.unit.test.ts +619 -0
- package/src/__tests__/wire-default-loaders.cases.unit.test.ts +461 -0
- package/src/__tests__/zero-camera-clear-fallback.cases.unit.test.ts +374 -0
- package/src/dev-import-transport.ts +2 -1
- package/dist/__tests__/asset.unit.test.d.ts +0 -2
- package/dist/__tests__/asset.unit.test.d.ts.map +0 -1
- package/dist/__tests__/pipeline.unit.test.d.ts +0 -2
- package/dist/__tests__/pipeline.unit.test.d.ts.map +0 -1
- package/dist/__tests__/systems.unit.test.d.ts +0 -2
- package/dist/__tests__/systems.unit.test.d.ts.map +0 -1
- package/src/__tests__/asset.unit.test.ts +0 -7113
- package/src/__tests__/pipeline.unit.test.ts +0 -5279
- package/src/__tests__/systems.unit.test.ts +0 -9061
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
// @ts-nocheck — merged file: cross-source node:fs/node:path imports outside @types/node coverage in runtime tsconfig
|
|
2
|
+
// Consolidated by feat-20260609-test-pool-startup-reduction-merge-tiny-test-files
|
|
3
|
+
// biome-ignore-all lint/complexity/noUselessLoneBlockStatements: block-scope isolation between merged source files (consolidation paradigm)
|
|
4
|
+
//
|
|
5
|
+
// Source files (N=33):
|
|
6
|
+
// - packages/runtime/__tests__/create-renderer-fallback.test.ts
|
|
7
|
+
// - packages/runtime/src/__tests__/cluster-binner.test.ts
|
|
8
|
+
// - packages/runtime/src/__tests__/create-renderer-fallback-shader-manifest.test.ts
|
|
9
|
+
// - packages/runtime/src/__tests__/create-renderer-uniform-fallback.test.ts
|
|
10
|
+
// - packages/runtime/src/__tests__/createRenderer.test.ts
|
|
11
|
+
// - packages/runtime/src/__tests__/dispatch-sort.test.ts
|
|
12
|
+
// - packages/runtime/src/__tests__/engine-metrics.test.ts
|
|
13
|
+
// - packages/runtime/src/__tests__/device/gpu-residency-caps-guard.test.ts
|
|
14
|
+
// - packages/runtime/src/__tests__/pass-selector.test.ts
|
|
15
|
+
// - packages/runtime/src/__tests__/pipeline-builder.test.ts
|
|
16
|
+
// - packages/runtime/src/__tests__/pipeline-cache-key-topology.test.ts
|
|
17
|
+
// - packages/runtime/src/__tests__/pipeline-rename-grep-gate.test.ts
|
|
18
|
+
// - packages/runtime/src/__tests__/pipeline-vertex-stride-branch.test.ts
|
|
19
|
+
// - packages/runtime/src/__tests__/post-process-register.test.ts
|
|
20
|
+
// - packages/runtime/src/__tests__/record-all-topology.test.ts
|
|
21
|
+
// - packages/runtime/src/__tests__/record-draw-branch.test.ts
|
|
22
|
+
// - packages/runtime/src/__tests__/record-strip-index-format.test.ts
|
|
23
|
+
// - packages/runtime/src/__tests__/render-query-regression.test.ts
|
|
24
|
+
// - packages/runtime/src/__tests__/renderer-draw-world.test.ts
|
|
25
|
+
// - packages/runtime/src/__tests__/renderer-input-snapshot.test.ts
|
|
26
|
+
// - packages/runtime/src/__tests__/renderer-read-pixels.test.ts
|
|
27
|
+
// - packages/runtime/src/__tests__/renderer-ready.test.ts
|
|
28
|
+
// - packages/runtime/src/__tests__/renderstate-pipeline-cache.test.ts
|
|
29
|
+
// - packages/runtime/src/__tests__/storage-buffer-caps.test.ts
|
|
30
|
+
// - packages/runtime/src/__tests__/device/gpu-residency.test.ts
|
|
31
|
+
// - packages/runtime/src/__tests__/render-data.test.ts
|
|
32
|
+
// - packages/runtime/src/__tests__/hdrp-bgl-slots.test.ts
|
|
33
|
+
// - packages/runtime/src/__tests__/hdrp-caps-gate.test.ts
|
|
34
|
+
// - packages/runtime/src/__tests__/hdrp-grid-invalid.test.ts
|
|
35
|
+
// - packages/runtime/src/__tests__/hdrp-index-list-overflow-once.test.ts
|
|
36
|
+
// - packages/runtime/src/__tests__/hdrp-light-budget.test.ts
|
|
37
|
+
// - packages/runtime/src/__tests__/hdrp-pipeline-asset-config.test.ts
|
|
38
|
+
// - packages/runtime/src/__tests__/m7-hdrp-demo-shape.test.ts
|
|
39
|
+
//
|
|
40
|
+
// Paradigm: each block-scope wraps a source file. ancestorTitles[0] is the
|
|
41
|
+
// source-preserved inner describe (NOT the source filename for these 3 files
|
|
42
|
+
// — recovery path: vitest report ancestorTitles -> grep this file -> upstream
|
|
43
|
+
// `// ─── from <name>.test.ts ───` block separator -> source filename).
|
|
44
|
+
// Top-level imports merged + deduped.
|
|
45
|
+
|
|
46
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
47
|
+
import { dirname, resolve } from 'node:path';
|
|
48
|
+
import { fileURLToPath } from 'node:url';
|
|
49
|
+
import { HANDLE_CUBE, HANDLE_TRIANGLE, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
|
|
50
|
+
import type { World as WorldType } from '@forgeax/engine-ecs';
|
|
51
|
+
import { defineComponent, World } from '@forgeax/engine-ecs';
|
|
52
|
+
import {
|
|
53
|
+
buildMeshAttributeMapForUvSets,
|
|
54
|
+
createBoxGeometry,
|
|
55
|
+
createConeGeometry,
|
|
56
|
+
createCylinderGeometry,
|
|
57
|
+
createPlaneGeometry,
|
|
58
|
+
createSphereGeometry,
|
|
59
|
+
createTorusGeometry,
|
|
60
|
+
PROCEDURAL_FLOATS_PER_VERTEX,
|
|
61
|
+
} from '@forgeax/engine-geometry';
|
|
62
|
+
import { type Mat4, mat4, type Vec3, vec3 } from '@forgeax/engine-math';
|
|
63
|
+
import type { Renderer as RendererType } from '@forgeax/engine-render';
|
|
64
|
+
import { Camera, MeshFilter, MeshRenderer } from '@forgeax/engine-render';
|
|
65
|
+
import {
|
|
66
|
+
err,
|
|
67
|
+
ok,
|
|
68
|
+
type PipelineLayout,
|
|
69
|
+
type RenderPipeline,
|
|
70
|
+
type Result,
|
|
71
|
+
type RhiCaps,
|
|
72
|
+
type RhiDevice,
|
|
73
|
+
RhiError,
|
|
74
|
+
ok as rhiOk,
|
|
75
|
+
type ShaderModule,
|
|
76
|
+
} from '@forgeax/engine-rhi';
|
|
77
|
+
import {
|
|
78
|
+
acquireCanvasContext as acquireNullCanvasContext,
|
|
79
|
+
createShaderModule as createNullShaderModule,
|
|
80
|
+
RhiNullAdapter,
|
|
81
|
+
} from '@forgeax/engine-rhi-null';
|
|
82
|
+
import { ChildOf, Transform } from '@forgeax/engine-scene';
|
|
83
|
+
import {
|
|
84
|
+
findVariantByKey,
|
|
85
|
+
type MaterialShaderEntry,
|
|
86
|
+
type MaterialShaderManifestEntry,
|
|
87
|
+
type MaterialShaderManifestVariant,
|
|
88
|
+
} from '@forgeax/engine-shader';
|
|
89
|
+
import {
|
|
90
|
+
type AssetError,
|
|
91
|
+
type EquirectAsset,
|
|
92
|
+
type Handle,
|
|
93
|
+
type MaterialRenderState,
|
|
94
|
+
type MeshAsset,
|
|
95
|
+
type PassSelector,
|
|
96
|
+
type PrimitiveTopology,
|
|
97
|
+
type RenderPipelineAsset,
|
|
98
|
+
type TextureAsset,
|
|
99
|
+
toShared,
|
|
100
|
+
} from '@forgeax/engine-types';
|
|
101
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
102
|
+
import {
|
|
103
|
+
bin,
|
|
104
|
+
type ClusterBinError,
|
|
105
|
+
calculateSphereClusterBounds,
|
|
106
|
+
clusterSpaceObjectAabb,
|
|
107
|
+
deriveCullingRadius,
|
|
108
|
+
ndcPositionToCluster,
|
|
109
|
+
viewZToZSlice,
|
|
110
|
+
} from '../../../render/src/cluster-binner';
|
|
111
|
+
import { GpuResidencyCache } from '../../../render/src/device/gpu-residency';
|
|
112
|
+
import { createEngineMetrics } from '../../../render/src/engine-metrics';
|
|
113
|
+
import { assertStorageBufferCap } from '../../../render/src/light-buffer-layout';
|
|
114
|
+
import {
|
|
115
|
+
buildPbrPipelineLayouts,
|
|
116
|
+
buildPbrViewBglEntries,
|
|
117
|
+
type PbrCaps,
|
|
118
|
+
} from '../../../render/src/pbr-pipeline';
|
|
119
|
+
import {
|
|
120
|
+
HdrpInstallError,
|
|
121
|
+
validateClusterGrid,
|
|
122
|
+
} from '../../../render/src/pipeline/standard-pipeline';
|
|
123
|
+
import {
|
|
124
|
+
buildPipelineForMaterialShader,
|
|
125
|
+
type PipelineBuilderContext,
|
|
126
|
+
type PipelineBuilderShaderModuleFactory,
|
|
127
|
+
} from '../../../render/src/pipeline-builder';
|
|
128
|
+
import {
|
|
129
|
+
cacheKeyOf,
|
|
130
|
+
createHdrpBindGroupLayoutDescriptor,
|
|
131
|
+
type PipelineSpec,
|
|
132
|
+
} from '../../../render/src/pipeline-spec';
|
|
133
|
+
import {
|
|
134
|
+
deriveRenderDataCubemap,
|
|
135
|
+
deriveRenderDataMesh,
|
|
136
|
+
deriveRenderDataTexture,
|
|
137
|
+
} from '../../../render/src/render-data';
|
|
138
|
+
import { extractFrame, prepareExtractContext } from '../../../render/src/render-system-extract';
|
|
139
|
+
import { matchPass } from '../../../render/src/systems/pass-selector';
|
|
140
|
+
import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
141
|
+
import { standardMaterialShaderVariants } from './helpers/standard-material-manifest';
|
|
142
|
+
|
|
143
|
+
function makeExplicitNullRhi(spies: {
|
|
144
|
+
setIndexBuffer: ReturnType<typeof vi.fn>;
|
|
145
|
+
setVertexBuffer: ReturnType<typeof vi.fn>;
|
|
146
|
+
draw: ReturnType<typeof vi.fn>;
|
|
147
|
+
drawIndexed: ReturnType<typeof vi.fn>;
|
|
148
|
+
}): unknown {
|
|
149
|
+
const adapter = new RhiNullAdapter();
|
|
150
|
+
const requestDevice = adapter.requestDevice.bind(adapter);
|
|
151
|
+
adapter.requestDevice = async () => {
|
|
152
|
+
const result = await requestDevice();
|
|
153
|
+
if (result.ok) {
|
|
154
|
+
const device = result.value;
|
|
155
|
+
const createCommandEncoder = device.createCommandEncoder.bind(device);
|
|
156
|
+
device.createCommandEncoder = (desc) => {
|
|
157
|
+
const encoderResult = createCommandEncoder(desc);
|
|
158
|
+
if (!encoderResult.ok) return encoderResult;
|
|
159
|
+
const encoder = encoderResult.value;
|
|
160
|
+
const beginRenderPass = encoder.beginRenderPass.bind(encoder);
|
|
161
|
+
encoder.beginRenderPass = (passDesc) => {
|
|
162
|
+
const pass = beginRenderPass(passDesc);
|
|
163
|
+
const draw = pass.draw.bind(pass);
|
|
164
|
+
const drawIndexed = pass.drawIndexed.bind(pass);
|
|
165
|
+
const setIndexBuffer = pass.setIndexBuffer.bind(pass);
|
|
166
|
+
const setVertexBuffer = pass.setVertexBuffer.bind(pass);
|
|
167
|
+
pass.draw = (...args) => {
|
|
168
|
+
spies.draw(...args);
|
|
169
|
+
draw(...args);
|
|
170
|
+
};
|
|
171
|
+
pass.drawIndexed = (...args) => {
|
|
172
|
+
spies.drawIndexed(...args);
|
|
173
|
+
drawIndexed(...args);
|
|
174
|
+
};
|
|
175
|
+
pass.setIndexBuffer = (...args) => {
|
|
176
|
+
spies.setIndexBuffer(...args);
|
|
177
|
+
setIndexBuffer(...args);
|
|
178
|
+
};
|
|
179
|
+
pass.setVertexBuffer = (...args) => {
|
|
180
|
+
spies.setVertexBuffer(...args);
|
|
181
|
+
setVertexBuffer(...args);
|
|
182
|
+
};
|
|
183
|
+
return pass;
|
|
184
|
+
};
|
|
185
|
+
return { ok: true, value: encoder };
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
requestAdapter: async () => ({ ok: true, value: adapter }),
|
|
192
|
+
acquireCanvasContext: acquireNullCanvasContext,
|
|
193
|
+
createShaderModule: (device: unknown, desc: { code: string; label?: string }) =>
|
|
194
|
+
createNullShaderModule(device as never, desc),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
vi.mock('@forgeax/engine-rhi-wgpu', () => {
|
|
199
|
+
return {
|
|
200
|
+
rhi: {
|
|
201
|
+
requestAdapter: async () => ({
|
|
202
|
+
ok: false,
|
|
203
|
+
error: {
|
|
204
|
+
code: 'adapter-unavailable',
|
|
205
|
+
expected: 'adapter available',
|
|
206
|
+
hint: 'default mock fail',
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
getPreferredCanvasFormat: () => 'bgra8unorm',
|
|
210
|
+
acquireCanvasContext: () => ({
|
|
211
|
+
ok: false,
|
|
212
|
+
error: {
|
|
213
|
+
code: 'webgpu-runtime-error',
|
|
214
|
+
expected: 'context available',
|
|
215
|
+
hint: 'default mock fail',
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
},
|
|
219
|
+
ensureReady: async () => undefined,
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
void [
|
|
224
|
+
Camera,
|
|
225
|
+
ChildOf,
|
|
226
|
+
GpuResidencyCache,
|
|
227
|
+
HANDLE_CUBE,
|
|
228
|
+
HANDLE_TRIANGLE,
|
|
229
|
+
HdrpInstallError,
|
|
230
|
+
MeshFilter,
|
|
231
|
+
MeshRenderer,
|
|
232
|
+
PROCEDURAL_FLOATS_PER_VERTEX,
|
|
233
|
+
RhiError,
|
|
234
|
+
RhiNullAdapter,
|
|
235
|
+
Transform,
|
|
236
|
+
World,
|
|
237
|
+
acquireNullCanvasContext,
|
|
238
|
+
afterEach,
|
|
239
|
+
assertStorageBufferCap,
|
|
240
|
+
beforeEach,
|
|
241
|
+
bin,
|
|
242
|
+
buildMeshAttributeMapForUvSets,
|
|
243
|
+
buildPbrPipelineLayouts,
|
|
244
|
+
buildPbrViewBglEntries,
|
|
245
|
+
buildPipelineForMaterialShader,
|
|
246
|
+
cacheKeyOf,
|
|
247
|
+
calculateSphereClusterBounds,
|
|
248
|
+
clusterSpaceObjectAabb,
|
|
249
|
+
createBoxGeometry,
|
|
250
|
+
createConeGeometry,
|
|
251
|
+
createCylinderGeometry,
|
|
252
|
+
createEngineMetrics,
|
|
253
|
+
createHdrpBindGroupLayoutDescriptor,
|
|
254
|
+
createNullShaderModule,
|
|
255
|
+
createPlaneGeometry,
|
|
256
|
+
createSphereGeometry,
|
|
257
|
+
createTorusGeometry,
|
|
258
|
+
defineComponent,
|
|
259
|
+
deriveCullingRadius,
|
|
260
|
+
deriveRenderDataCubemap,
|
|
261
|
+
deriveRenderDataMesh,
|
|
262
|
+
deriveRenderDataTexture,
|
|
263
|
+
describe,
|
|
264
|
+
dirname,
|
|
265
|
+
err,
|
|
266
|
+
existsSync,
|
|
267
|
+
expect,
|
|
268
|
+
extractFrame,
|
|
269
|
+
fileURLToPath,
|
|
270
|
+
findVariantByKey,
|
|
271
|
+
it,
|
|
272
|
+
makeExplicitNullRhi,
|
|
273
|
+
makeMockShaderRegistry,
|
|
274
|
+
mat4,
|
|
275
|
+
matchPass,
|
|
276
|
+
ndcPositionToCluster,
|
|
277
|
+
ok,
|
|
278
|
+
prepareExtractContext,
|
|
279
|
+
readFileSync,
|
|
280
|
+
resolve,
|
|
281
|
+
resolveAssetHandle,
|
|
282
|
+
rhiOk,
|
|
283
|
+
standardMaterialShaderVariants,
|
|
284
|
+
toShared,
|
|
285
|
+
validateClusterGrid,
|
|
286
|
+
vec3,
|
|
287
|
+
vi,
|
|
288
|
+
viewZToZSlice,
|
|
289
|
+
];
|
|
290
|
+
type __MergedKeep =
|
|
291
|
+
| AssetError
|
|
292
|
+
| ClusterBinError
|
|
293
|
+
| EquirectAsset
|
|
294
|
+
| Handle
|
|
295
|
+
| Mat4
|
|
296
|
+
| MaterialRenderState
|
|
297
|
+
| MaterialShaderEntry
|
|
298
|
+
| MaterialShaderManifestEntry
|
|
299
|
+
| MaterialShaderManifestVariant
|
|
300
|
+
| MeshAsset
|
|
301
|
+
| PassSelector
|
|
302
|
+
| PbrCaps
|
|
303
|
+
| PipelineBuilderContext
|
|
304
|
+
| PipelineBuilderShaderModuleFactory
|
|
305
|
+
| PipelineLayout
|
|
306
|
+
| PipelineSpec
|
|
307
|
+
| PrimitiveTopology
|
|
308
|
+
| RenderPipeline
|
|
309
|
+
| RenderPipelineAsset
|
|
310
|
+
| RendererType
|
|
311
|
+
| Result
|
|
312
|
+
| RhiCaps
|
|
313
|
+
| RhiDevice
|
|
314
|
+
| ShaderModule
|
|
315
|
+
| TextureAsset
|
|
316
|
+
| Vec3
|
|
317
|
+
| WorldType;
|
|
318
|
+
|
|
319
|
+
{
|
|
320
|
+
// --- from cluster-binner.test.ts ---
|
|
321
|
+
function makeIdentityMat4(): Mat4 {
|
|
322
|
+
return mat4.identity(mat4.create());
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function makePerspectiveProj(near = 0.1, far = 100): Mat4 {
|
|
326
|
+
// perspective(fov=PI/2, aspect=1, near, far)
|
|
327
|
+
const fov = Math.PI / 2;
|
|
328
|
+
return mat4.perspective(mat4.create(), fov, 1, near, far);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function makeLookAtView(eyeX: number, eyeY: number, eyeZ: number): Mat4 {
|
|
332
|
+
const eye = vec3.create(eyeX, eyeY, eyeZ);
|
|
333
|
+
const target = vec3.create(0, 0, 0);
|
|
334
|
+
const up = vec3.create(0, 1, 0);
|
|
335
|
+
return mat4.lookAt(mat4.create(), eye, target, up);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ── (a) cluster_space_object_aabb ───────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
describe('clusterSpaceObjectAabb', () => {
|
|
341
|
+
it('returns a finite AABB for a sphere at origin in view space', () => {
|
|
342
|
+
const center = vec3.create(0, 0, -5);
|
|
343
|
+
const radius = 1;
|
|
344
|
+
const view = makeIdentityMat4();
|
|
345
|
+
const proj = makePerspectiveProj();
|
|
346
|
+
const result = clusterSpaceObjectAabb(center, radius, view, proj);
|
|
347
|
+
expect(Number.isFinite(result.min[0])).toBe(true);
|
|
348
|
+
expect(Number.isFinite(result.min[1])).toBe(true);
|
|
349
|
+
expect(Number.isFinite(result.min[2])).toBe(true);
|
|
350
|
+
expect(Number.isFinite(result.max[0])).toBe(true);
|
|
351
|
+
expect(Number.isFinite(result.max[1])).toBe(true);
|
|
352
|
+
expect(Number.isFinite(result.max[2])).toBe(true);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('returns AABB roughly centered around sphere NDC position', () => {
|
|
356
|
+
const center = vec3.create(0, 0, -5);
|
|
357
|
+
const radius = 0.5;
|
|
358
|
+
const view = makeIdentityMat4();
|
|
359
|
+
const proj = makePerspectiveProj();
|
|
360
|
+
const aabb = clusterSpaceObjectAabb(center, radius, view, proj);
|
|
361
|
+
// AABB should contain the center NDC projection
|
|
362
|
+
expect(aabb.min[0]).toBeLessThanOrEqual(0);
|
|
363
|
+
expect(aabb.max[0]).toBeGreaterThanOrEqual(0);
|
|
364
|
+
expect(aabb.min[1]).toBeLessThanOrEqual(0);
|
|
365
|
+
expect(aabb.max[1]).toBeGreaterThanOrEqual(0);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('clamps to NDC [-1,1] for a large radius sphere', () => {
|
|
369
|
+
const center = vec3.create(0, 0, -5);
|
|
370
|
+
const radius = 100; // huge sphere
|
|
371
|
+
const view = makeIdentityMat4();
|
|
372
|
+
const proj = makePerspectiveProj();
|
|
373
|
+
const aabb = clusterSpaceObjectAabb(center, radius, view, proj);
|
|
374
|
+
expect(aabb.min[0]).toBeGreaterThanOrEqual(-1);
|
|
375
|
+
expect(aabb.min[1]).toBeGreaterThanOrEqual(-1);
|
|
376
|
+
expect(aabb.max[0]).toBeLessThanOrEqual(1);
|
|
377
|
+
expect(aabb.max[1]).toBeLessThanOrEqual(1);
|
|
378
|
+
// Z may still extend beyond [-1,1] after projection (perspective projects to [0,1]),
|
|
379
|
+
// but XY must be clamped.
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('handles sphere at very far distance without NaN', () => {
|
|
383
|
+
const center = vec3.create(0, 0, -94);
|
|
384
|
+
const radius = 2;
|
|
385
|
+
const view = makeIdentityMat4();
|
|
386
|
+
const proj = makePerspectiveProj(0.1, 100);
|
|
387
|
+
const aabb = clusterSpaceObjectAabb(center, radius, view, proj);
|
|
388
|
+
expect(aabb.min.every((v: number) => Number.isFinite(v))).toBe(true);
|
|
389
|
+
expect(aabb.max.every((v: number) => Number.isFinite(v))).toBe(true);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('applies view matrix', () => {
|
|
393
|
+
// Camera at (5,0,0) looking at origin -> sphere at origin is at x=-5 in view space
|
|
394
|
+
const center = vec3.create(0, 0, -5);
|
|
395
|
+
const radius = 1;
|
|
396
|
+
const view = makeLookAtView(5, 0, 0);
|
|
397
|
+
const proj = makePerspectiveProj();
|
|
398
|
+
const aabb = clusterSpaceObjectAabb(center, radius, view, proj);
|
|
399
|
+
expect(Number.isFinite(aabb.min[0] ?? 0)).toBe(true);
|
|
400
|
+
expect(Number.isFinite(aabb.max[0] ?? 0)).toBe(true);
|
|
401
|
+
expect(Number.isFinite(aabb.min[1] ?? 0)).toBe(true);
|
|
402
|
+
expect(Number.isFinite(aabb.max[1] ?? 0)).toBe(true);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// ── (b) ndc_position_to_cluster ─────────────────────────────────────────────
|
|
407
|
+
|
|
408
|
+
describe('ndcPositionToCluster', () => {
|
|
409
|
+
const gridX = 16;
|
|
410
|
+
const gridY = 9;
|
|
411
|
+
const gridZ = 24;
|
|
412
|
+
const near = 0.1;
|
|
413
|
+
const far = 100;
|
|
414
|
+
|
|
415
|
+
it('maps NDC center to middle cluster XY', () => {
|
|
416
|
+
const ndc = vec3.create(0, 0, 0.5); // center of screen, mid-depth
|
|
417
|
+
const idx = ndcPositionToCluster(ndc, -5, gridX, gridY, gridZ, near, far);
|
|
418
|
+
expect(idx.x).toBe(Math.floor(gridX / 2));
|
|
419
|
+
expect(idx.y).toBe(Math.floor(gridY / 2));
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('maps NDC top-left to cluster (0, gridY-1)', () => {
|
|
423
|
+
const ndc = vec3.create(-1, 1, 0.5);
|
|
424
|
+
const idx = ndcPositionToCluster(ndc, -5, gridX, gridY, gridZ, near, far);
|
|
425
|
+
expect(idx.x).toBe(0);
|
|
426
|
+
expect(idx.y).toBe(gridY - 1);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('maps NDC bottom-right to cluster (gridX-1, 0)', () => {
|
|
430
|
+
const ndc = vec3.create(1, -1, 0.5);
|
|
431
|
+
const idx = ndcPositionToCluster(ndc, -5, gridX, gridY, gridZ, near, far);
|
|
432
|
+
expect(idx.x).toBe(gridX - 1);
|
|
433
|
+
expect(idx.y).toBe(0);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('clamps out-of-bounds NDC to [0, grid-1]', () => {
|
|
437
|
+
const ndc = vec3.create(-2, 3, 0.5);
|
|
438
|
+
const idx = ndcPositionToCluster(ndc, -5, gridX, gridY, gridZ, near, far);
|
|
439
|
+
expect(idx.x).toBeGreaterThanOrEqual(0);
|
|
440
|
+
expect(idx.x).toBeLessThan(gridX);
|
|
441
|
+
expect(idx.y).toBeGreaterThanOrEqual(0);
|
|
442
|
+
expect(idx.y).toBeLessThan(gridY);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('clamps Z to [0, gridZ-1]', () => {
|
|
446
|
+
const ndc = vec3.create(0, 0, 2); // beyond far plane
|
|
447
|
+
const idx = ndcPositionToCluster(ndc, -1, gridX, gridY, gridZ, near, far);
|
|
448
|
+
expect(idx.z).toBeLessThan(gridZ);
|
|
449
|
+
expect(idx.z).toBeGreaterThanOrEqual(0);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// ── (c) calculate_sphere_cluster_bounds ─────────────────────────────────────
|
|
454
|
+
|
|
455
|
+
describe('calculateSphereClusterBounds', () => {
|
|
456
|
+
const gridX = 16;
|
|
457
|
+
const gridY = 9;
|
|
458
|
+
const gridZ = 24;
|
|
459
|
+
const near = 0.1;
|
|
460
|
+
const far = 100;
|
|
461
|
+
|
|
462
|
+
it('returns valid min/max cluster indices for a sphere in the frustum', () => {
|
|
463
|
+
const center = vec3.create(0, 0, -5);
|
|
464
|
+
const radius = 1;
|
|
465
|
+
const view = makeIdentityMat4();
|
|
466
|
+
const proj = makePerspectiveProj(near, far);
|
|
467
|
+
const aabb = clusterSpaceObjectAabb(center, radius, view, proj);
|
|
468
|
+
const bounds = calculateSphereClusterBounds(aabb, gridX, gridY, gridZ, near, far);
|
|
469
|
+
expect(bounds.min.x).toBeLessThanOrEqual(bounds.max.x);
|
|
470
|
+
expect(bounds.min.y).toBeLessThanOrEqual(bounds.max.y);
|
|
471
|
+
expect(bounds.min.z).toBeLessThanOrEqual(bounds.max.z);
|
|
472
|
+
expect(bounds.max.x).toBeLessThan(gridX);
|
|
473
|
+
expect(bounds.max.y).toBeLessThan(gridY);
|
|
474
|
+
expect(bounds.max.z).toBeLessThan(gridZ);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('returns min > max for sphere completely behind camera (cull signal)', () => {
|
|
478
|
+
// Sphere directly behind the camera: view-space z > 0.
|
|
479
|
+
// In a standard view matrix, world (0,0,5) with identity view is behind the camera.
|
|
480
|
+
const center = vec3.create(0, 0, 5);
|
|
481
|
+
const radius = 1;
|
|
482
|
+
const view = makeIdentityMat4();
|
|
483
|
+
const proj = makePerspectiveProj(near, far);
|
|
484
|
+
const aabb = clusterSpaceObjectAabb(center, radius, view, proj);
|
|
485
|
+
const bounds = calculateSphereClusterBounds(aabb, gridX, gridY, gridZ, near, far);
|
|
486
|
+
// With the sphere fully behind camera, the AABB Z will be empty (minZ > maxZ)
|
|
487
|
+
// or the cluster bounds will have min > max.
|
|
488
|
+
const isCulled =
|
|
489
|
+
bounds.min.x > bounds.max.x || bounds.min.y > bounds.max.y || bounds.min.z > bounds.max.z;
|
|
490
|
+
// Note: clusterSpaceObjectAabb clamps view-z to -1e-5 (near plane),
|
|
491
|
+
// so a sphere behind the near plane may still get valid AABB bounds.
|
|
492
|
+
// This test verifies the function does not throw and returns a valid shape.
|
|
493
|
+
expect(isCulled || !isCulled).toBe(true); // always true — just exercises the branch
|
|
494
|
+
expect('min' in bounds).toBe(true);
|
|
495
|
+
expect('max' in bounds).toBe(true);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('encompasses camera when sphere wraps around it', () => {
|
|
499
|
+
// Large sphere right in front of the camera
|
|
500
|
+
const center = vec3.create(0, 0, -5);
|
|
501
|
+
const radius = 20;
|
|
502
|
+
const view = makeIdentityMat4();
|
|
503
|
+
const proj = makePerspectiveProj(near, far);
|
|
504
|
+
const aabb = clusterSpaceObjectAabb(center, radius, view, proj);
|
|
505
|
+
const bounds = calculateSphereClusterBounds(aabb, gridX, gridY, gridZ, near, far);
|
|
506
|
+
// Should cover a large portion of the grid
|
|
507
|
+
const dx = bounds.max.x - bounds.min.x;
|
|
508
|
+
const dy = bounds.max.y - bounds.min.y;
|
|
509
|
+
expect(dx).toBeGreaterThan(gridX / 4);
|
|
510
|
+
expect(dy).toBeGreaterThan(gridY / 4);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// ── (d) view_z_to_z_slice — log-z formula ───────────────────────────────────
|
|
515
|
+
|
|
516
|
+
describe('viewZToZSlice', () => {
|
|
517
|
+
const gridZ = 24;
|
|
518
|
+
const near = 0.1;
|
|
519
|
+
const far = 100;
|
|
520
|
+
|
|
521
|
+
it('maps view_z=near to z_slice=0', () => {
|
|
522
|
+
const slice = viewZToZSlice(-near, gridZ, near, far);
|
|
523
|
+
expect(slice).toBe(0);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('maps view_z=far to z_slice=gridZ-1 (approximately)', () => {
|
|
527
|
+
const slice = viewZToZSlice(-far, gridZ, near, far);
|
|
528
|
+
expect(slice).toBeGreaterThanOrEqual(gridZ - 2);
|
|
529
|
+
expect(slice).toBeLessThanOrEqual(gridZ - 1);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('monotonically increases', () => {
|
|
533
|
+
const slices: number[] = [];
|
|
534
|
+
for (let i = 0; i <= gridZ; i++) {
|
|
535
|
+
const t = i / gridZ;
|
|
536
|
+
const z = -near * Math.exp(t * Math.log(far / near));
|
|
537
|
+
slices.push(viewZToZSlice(z, gridZ, near, far));
|
|
538
|
+
}
|
|
539
|
+
for (let i = 1; i < slices.length; i++) {
|
|
540
|
+
const cur = slices[i];
|
|
541
|
+
const prev = slices[i - 1];
|
|
542
|
+
if (cur !== undefined && prev !== undefined) {
|
|
543
|
+
expect(cur).toBeGreaterThanOrEqual(prev);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('matches idTech6 inverse formula shape', () => {
|
|
549
|
+
// idTech6: Z_slice = near * (far/near)^(slice/numSlices)
|
|
550
|
+
// inverse: slice = floor(log(-view_z / near) / log(far / near) * numSlices)
|
|
551
|
+
const viewZ = -5;
|
|
552
|
+
const logFarOverNear = Math.log(far / near);
|
|
553
|
+
const expected = Math.floor((Math.log(-viewZ / near) / logFarOverNear) * gridZ);
|
|
554
|
+
expect(viewZToZSlice(viewZ, gridZ, near, far)).toBe(expected);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('clamps to [0, gridZ-1]', () => {
|
|
558
|
+
expect(viewZToZSlice(1, gridZ, near, far)).toBe(0); // positive z -> clamp to 0
|
|
559
|
+
expect(viewZToZSlice(-1e9, gridZ, near, far)).toBe(gridZ - 1); // way distant
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ── (e) bin() overflow ──────────────────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
describe('bin overflow', () => {
|
|
566
|
+
it('returns error when writeCount would exceed 65536', () => {
|
|
567
|
+
// Use a small index list capacity to force overflow.
|
|
568
|
+
const near = 0.1;
|
|
569
|
+
const far = 100;
|
|
570
|
+
const grid = { x: 16, y: 9, z: 24 };
|
|
571
|
+
const view = makeIdentityMat4();
|
|
572
|
+
const proj = makePerspectiveProj(near, far);
|
|
573
|
+
|
|
574
|
+
// 256 lights all at origin with huge ranges — every light intersects
|
|
575
|
+
// every cluster.
|
|
576
|
+
const lights: Array<{ position: Vec3; range: number }> = [];
|
|
577
|
+
for (let i = 0; i < 256; i++) {
|
|
578
|
+
lights.push({
|
|
579
|
+
position: vec3.create(0, 0, -5),
|
|
580
|
+
range: 1000,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const clusterGrid = new Uint32Array(grid.x * grid.y * grid.z * 2);
|
|
585
|
+
// Small capacity to trigger overflow
|
|
586
|
+
const capacity = 1000;
|
|
587
|
+
const lightIndexList = new Uint32Array(capacity);
|
|
588
|
+
|
|
589
|
+
const result = bin(
|
|
590
|
+
lights,
|
|
591
|
+
view,
|
|
592
|
+
proj,
|
|
593
|
+
grid,
|
|
594
|
+
near,
|
|
595
|
+
far,
|
|
596
|
+
clusterGrid,
|
|
597
|
+
lightIndexList,
|
|
598
|
+
capacity,
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
expect(result.ok).toBe(false);
|
|
602
|
+
if (!result.ok) {
|
|
603
|
+
const error = result.error;
|
|
604
|
+
expect(error.code).toBe('index-overflow');
|
|
605
|
+
expect(error.detail.actual).toBeGreaterThan(capacity);
|
|
606
|
+
expect(error.detail.capacity).toBe(capacity);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('succeeds with 65536 capacity for spread configuration', () => {
|
|
611
|
+
const near = 0.1;
|
|
612
|
+
const far = 100;
|
|
613
|
+
const grid = { x: 8, y: 6, z: 12 };
|
|
614
|
+
const view = makeIdentityMat4();
|
|
615
|
+
const proj = makePerspectiveProj(near, far);
|
|
616
|
+
|
|
617
|
+
const lights: Array<{ position: Vec3; range: number }> = [];
|
|
618
|
+
for (let i = 0; i < 128; i++) {
|
|
619
|
+
lights.push({
|
|
620
|
+
position: vec3.create(
|
|
621
|
+
((i % 16) - 8) * 2,
|
|
622
|
+
((Math.floor(i / 16) % 8) - 4) * 2,
|
|
623
|
+
-2 - Math.floor(i / 128) * 20,
|
|
624
|
+
),
|
|
625
|
+
range: 0.5,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const clusterGrid = new Uint32Array(grid.x * grid.y * grid.z * 2);
|
|
630
|
+
const capacity = 65536;
|
|
631
|
+
const lightIndexList = new Uint32Array(capacity);
|
|
632
|
+
|
|
633
|
+
const result = bin(
|
|
634
|
+
lights,
|
|
635
|
+
view,
|
|
636
|
+
proj,
|
|
637
|
+
grid,
|
|
638
|
+
near,
|
|
639
|
+
far,
|
|
640
|
+
clusterGrid,
|
|
641
|
+
lightIndexList,
|
|
642
|
+
capacity,
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
expect(result.ok).toBe(true);
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// ── (f) +Infinity range -> deriveCullingRadius ──────────────────────────────
|
|
650
|
+
|
|
651
|
+
describe('deriveCullingRadius', () => {
|
|
652
|
+
it('returns finite number for +Infinity range light', () => {
|
|
653
|
+
const radius = deriveCullingRadius(Infinity, 10, 1);
|
|
654
|
+
expect(Number.isFinite(radius)).toBe(true);
|
|
655
|
+
expect(radius).toBeGreaterThan(0);
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it('returns the range itself when range is finite', () => {
|
|
659
|
+
expect(deriveCullingRadius(5, 10, 1)).toBe(5);
|
|
660
|
+
expect(deriveCullingRadius(0.1, 100, 1)).toBe(0.1);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it('scales with intensity for +Infinity range', () => {
|
|
664
|
+
const r1 = deriveCullingRadius(Infinity, 1, 1);
|
|
665
|
+
const r2 = deriveCullingRadius(Infinity, 100, 1);
|
|
666
|
+
// Higher intensity => larger visible radius
|
|
667
|
+
expect(r2).toBeGreaterThan(r1);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('never returns +Infinity', () => {
|
|
671
|
+
const radius = deriveCullingRadius(Infinity, 1e6, 1);
|
|
672
|
+
expect(Number.isFinite(radius)).toBe(true);
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// ── bin() basic integration ─────────────────────────────────────────────────
|
|
677
|
+
|
|
678
|
+
describe('bin integration', () => {
|
|
679
|
+
it('writes clusterGrid offsets and light indices for single light', () => {
|
|
680
|
+
const near = 0.1;
|
|
681
|
+
const far = 100;
|
|
682
|
+
const grid = { x: 4, y: 3, z: 4 };
|
|
683
|
+
const view = makeIdentityMat4();
|
|
684
|
+
const proj = makePerspectiveProj(near, far);
|
|
685
|
+
|
|
686
|
+
const lights = [{ position: vec3.create(0, 0, -5), range: 1 }];
|
|
687
|
+
|
|
688
|
+
const clusterGrid = new Uint32Array(grid.x * grid.y * grid.z * 2);
|
|
689
|
+
const capacity = 65536;
|
|
690
|
+
const lightIndexList = new Uint32Array(capacity);
|
|
691
|
+
|
|
692
|
+
const result = bin(
|
|
693
|
+
lights,
|
|
694
|
+
view,
|
|
695
|
+
proj,
|
|
696
|
+
grid,
|
|
697
|
+
near,
|
|
698
|
+
far,
|
|
699
|
+
clusterGrid,
|
|
700
|
+
lightIndexList,
|
|
701
|
+
capacity,
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
expect(result.ok).toBe(true);
|
|
705
|
+
// At least one cluster should have a non-zero light count
|
|
706
|
+
let totalLights = 0;
|
|
707
|
+
for (let i = 0; i < grid.x * grid.y * grid.z; i++) {
|
|
708
|
+
const val = clusterGrid[i * 2 + 1];
|
|
709
|
+
if (val !== undefined) totalLights += val;
|
|
710
|
+
}
|
|
711
|
+
expect(totalLights).toBeGreaterThan(0);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('completes 256 lights under 100ms (AC-16 wall time)', () => {
|
|
715
|
+
// Use a smaller grid to avoid overflow, focus on timing.
|
|
716
|
+
const near = 0.1;
|
|
717
|
+
const far = 100;
|
|
718
|
+
const grid = { x: 8, y: 6, z: 12 };
|
|
719
|
+
const view = makeIdentityMat4();
|
|
720
|
+
const proj = makePerspectiveProj(near, far);
|
|
721
|
+
|
|
722
|
+
const lights: Array<{ position: Vec3; range: number }> = [];
|
|
723
|
+
for (let i = 0; i < 256; i++) {
|
|
724
|
+
lights.push({
|
|
725
|
+
position: vec3.create(
|
|
726
|
+
((i % 16) - 8) * 1.0,
|
|
727
|
+
((Math.floor(i / 16) % 8) - 4) * 1.0,
|
|
728
|
+
-1 - Math.floor(i / 128) * 50,
|
|
729
|
+
),
|
|
730
|
+
range: 1.5,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const clusterGrid = new Uint32Array(grid.x * grid.y * grid.z * 2);
|
|
735
|
+
const capacity = 65536;
|
|
736
|
+
const lightIndexList = new Uint32Array(capacity);
|
|
737
|
+
|
|
738
|
+
const start = performance.now();
|
|
739
|
+
const result = bin(
|
|
740
|
+
lights,
|
|
741
|
+
view,
|
|
742
|
+
proj,
|
|
743
|
+
grid,
|
|
744
|
+
near,
|
|
745
|
+
far,
|
|
746
|
+
clusterGrid,
|
|
747
|
+
lightIndexList,
|
|
748
|
+
capacity,
|
|
749
|
+
);
|
|
750
|
+
const elapsed = performance.now() - start;
|
|
751
|
+
|
|
752
|
+
expect(result.ok).toBe(true);
|
|
753
|
+
// AC-16 quantifies: 256 lights x 6912 clusters (~1.77M intersection tests)
|
|
754
|
+
// should complete on CPU main thread well under 100ms.
|
|
755
|
+
// Extrapolated: 256 lights x {16,9,24} grid = ~1.77M tests,
|
|
756
|
+
// single-threaded JS should be << 100ms (research §8 R2).
|
|
757
|
+
expect(elapsed).toBeLessThan(100);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it('spreads a mid-frustum light across multiple Z slices (M4.5-followup w55)', () => {
|
|
761
|
+
// Regression for the broken clamp `Math.max(vMinZ, -1e-5)` in
|
|
762
|
+
// clusterSpaceObjectAabb. Before the fix, a light at view_z=-3 with
|
|
763
|
+
// radius 3.5 clamped its FAR-edge view_z to ~0, so the cluster Z range
|
|
764
|
+
// collapsed to slice [0,0]. Floor pixels at view_z=-6 (slice 13..17)
|
|
765
|
+
// looked up empty clusters and rendered black -- which is the demo
|
|
766
|
+
// symptom: "front edge of floor lit, back half black".
|
|
767
|
+
//
|
|
768
|
+
// Lock in the correct behaviour: a 3.5 m sphere at view_z=-3 must touch
|
|
769
|
+
// SEVERAL distinct Z slices (the whole [vMinZ, min(vMaxZ, -near)]
|
|
770
|
+
// bracket), not collapse to slice 0.
|
|
771
|
+
const near = 0.1;
|
|
772
|
+
const far = 50;
|
|
773
|
+
const grid = { x: 16, y: 9, z: 24 };
|
|
774
|
+
const view = makeIdentityMat4();
|
|
775
|
+
const proj = makePerspectiveProj(near, far);
|
|
776
|
+
// Light center at view_z=-3, range 3.5 -> view-space z extent
|
|
777
|
+
// [-6.5, +0.5]; clamp near edge to -1e-5 -> [-6.5, -1e-5]; this should
|
|
778
|
+
// span slices 0..15 (log-z), NOT collapse to slice 0.
|
|
779
|
+
const lights = [{ position: vec3.create(0, 0, -3), range: 3.5 }];
|
|
780
|
+
const clusterGrid = new Uint32Array(grid.x * grid.y * grid.z * 2);
|
|
781
|
+
const lightIndexList = new Uint32Array(65536);
|
|
782
|
+
const result = bin(lights, view, proj, grid, near, far, clusterGrid, lightIndexList, 65536);
|
|
783
|
+
expect(result.ok).toBe(true);
|
|
784
|
+
// Collect every distinct Z slice that received this light.
|
|
785
|
+
const occupiedSlices = new Set<number>();
|
|
786
|
+
for (let cz = 0; cz < grid.z; cz++) {
|
|
787
|
+
for (let cy = 0; cy < grid.y; cy++) {
|
|
788
|
+
for (let cx = 0; cx < grid.x; cx++) {
|
|
789
|
+
const ci = cz * grid.y * grid.x + cy * grid.x + cx;
|
|
790
|
+
const count = clusterGrid[ci * 2 + 1] ?? 0;
|
|
791
|
+
if (count > 0) occupiedSlices.add(cz);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Pre-fix: occupiedSlices === Set([0]) -> size 1.
|
|
796
|
+
// Post-fix: span at least slices 0..14 (log-z over [0.1, 6.5]).
|
|
797
|
+
expect(occupiedSlices.size).toBeGreaterThan(8);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
it('returns ok for zero lights', () => {
|
|
801
|
+
const grid = { x: 4, y: 3, z: 4 };
|
|
802
|
+
const view = makeIdentityMat4();
|
|
803
|
+
const proj = makePerspectiveProj();
|
|
804
|
+
|
|
805
|
+
const clusterGrid = new Uint32Array(grid.x * grid.y * grid.z * 2);
|
|
806
|
+
const lightIndexList = new Uint32Array(65536);
|
|
807
|
+
|
|
808
|
+
const result = bin([], view, proj, grid, 0.1, 100, clusterGrid, lightIndexList, 65536);
|
|
809
|
+
|
|
810
|
+
expect(result.ok).toBe(true);
|
|
811
|
+
// All cluster light counts should be zero
|
|
812
|
+
for (let i = 0; i < grid.x * grid.y * grid.z; i++) {
|
|
813
|
+
expect(clusterGrid[i * 2 + 1]).toBe(0);
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
}
|