@forgeax/engine-render 0.1.23 → 0.1.25
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 +231 -10
- package/dist/assembly/bundler-contract.d.ts +9 -0
- package/dist/assembly/bundler-contract.d.ts.map +1 -0
- package/dist/assembly/device-feature-admission.d.ts +8 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
- package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +7 -243
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +3 -2
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
- package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
- package/dist/assembly/material/surface-projection.d.ts +24 -0
- package/dist/assembly/material/surface-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +188 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -0
- package/dist/assembly/material-shader-prewarm.d.ts +21 -0
- package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
- package/dist/assembly/recovery/generation.d.ts +46 -0
- package/dist/assembly/recovery/generation.d.ts.map +1 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts +10 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +19 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -0
- package/dist/assembly/renderer-lifecycle.d.ts +34 -0
- package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +55 -0
- package/dist/assembly/webgpu-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +152 -0
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
- package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
- package/dist/assets/asset-decoders.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-FF7K3FSO.mjs → chunk-ADHHVYLW.mjs} +89 -89
- package/dist/chunk-ADHHVYLW.mjs.map +1 -0
- package/dist/{chunk-YALPS44A.mjs → chunk-E34VL5VI.mjs} +47 -47
- package/dist/chunk-E34VL5VI.mjs.map +1 -0
- package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
- package/dist/chunk-GE3SDD72.mjs.map +1 -0
- package/dist/{chunk-HZUPSYW7.mjs → chunk-HKXTW355.mjs} +105 -15
- package/dist/chunk-HKXTW355.mjs.map +1 -0
- package/dist/chunk-JSEDTZYS.mjs +413 -0
- package/dist/chunk-JSEDTZYS.mjs.map +1 -0
- package/dist/{chunk-FBC4R44J.mjs → chunk-KM2NOX2I.mjs} +57 -57
- package/dist/chunk-KM2NOX2I.mjs.map +1 -0
- package/dist/{chunk-DIYW3M5D.mjs → chunk-N3RT2EUR.mjs} +2288 -2238
- package/dist/chunk-N3RT2EUR.mjs.map +1 -0
- package/dist/{chunk-ZUWKAZ2I.mjs → chunk-TCLDX7SZ.mjs} +1167 -368
- package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
- package/dist/{chunk-QODA5UKU.mjs → chunk-ZZ4YQ474.mjs} +2 -2
- package/dist/chunk-ZZ4YQ474.mjs.map +1 -0
- package/dist/components/instances.d.ts +13 -31
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +20688 -16314
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +2 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +24 -4
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/frame.d.ts +5 -0
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/inspection.d.ts +7 -0
- package/dist/environment/inspection.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +20 -2
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +58 -2
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +13 -0
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +899 -0
- package/dist/extract/contracts.d.ts.map +1 -0
- package/dist/features/host.d.ts +2 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +3 -0
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts +2 -0
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +34 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +5 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +7 -6
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +7 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +27 -9
- package/dist/index.mjs.map +1 -1
- package/dist/instance-buffer-cache.d.ts +7 -1
- package/dist/instance-buffer-cache.d.ts.map +1 -1
- package/dist/instances.d.ts +101 -0
- package/dist/instances.d.ts.map +1 -0
- package/dist/internal.mjs +6 -6
- package/dist/light-buffer-layout.d.ts +3 -0
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts +96 -11
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +56 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/occlusion-proxy.d.ts +4 -0
- package/dist/occlusion-proxy.d.ts.map +1 -0
- package/dist/pbr-pipeline.d.ts +32 -9
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/points-lines/prepare.d.ts +3 -0
- package/dist/points-lines/prepare.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +33 -0
- package/dist/points-lines/standard-owner.d.ts.map +1 -0
- package/dist/record/frame-snapshot.d.ts +18 -2
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +14 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +36 -4
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -17
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +11 -3
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/recovery-pipeline.d.ts +30 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +28 -5
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +11 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +1 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +65 -0
- package/dist/recovery/render-system-candidate.d.ts.map +1 -0
- package/dist/render-contract.d.ts +68 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +16 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +180 -22
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +131 -4
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +2 -0
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +31 -1
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/systems/skin-palette-allocator.d.ts +6 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/taa-history-store.d.ts +15 -1
- package/dist/temporal/taa-history-store.d.ts.map +1 -1
- package/dist/temporal/transaction.d.ts +5 -0
- package/dist/temporal/transaction.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
- package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
- package/src/__tests__/environment-generation.integration.test.ts +23 -0
- package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
- package/src/__tests__/factory-contract.integration.test.ts +122 -7
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
- package/src/__tests__/gpu-scene.unit.test.ts +38 -2
- package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
- package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
- package/src/__tests__/instances-store.unit.test.ts +46 -0
- package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +29 -22
- package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
- package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
- package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
- package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
- package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
- package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -5
- package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
- package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +127 -18
- package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
- package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
- package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
- package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
- package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
- package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
- package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
- package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
- package/src/__tests__/shader-manifest-fixture.ts +13 -16
- package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
- package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
- package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
- package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
- package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
- package/src/assembly/bundler-contract.ts +9 -0
- package/src/assembly/device-feature-admission.ts +15 -5
- package/src/assembly/extended-lighting-fallback.ts +216 -0
- package/src/assembly/factory.ts +83 -10088
- package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
- package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
- package/src/assembly/material/assembly.ts +18 -2
- package/src/assembly/material/standard-layer-projection.ts +16 -0
- package/src/assembly/material/surface-projection.ts +90 -0
- package/src/assembly/material-shader-policy.ts +932 -0
- package/src/assembly/material-shader-prewarm.ts +88 -0
- package/src/assembly/mesh-ssbo-grow.ts +270 -0
- package/src/assembly/recovery/device-loss-fanout.ts +89 -0
- package/src/assembly/recovery/generation.ts +141 -0
- package/src/assembly/recovery/recovery-attempt.ts +800 -0
- package/src/assembly/recovery/renderer-recover.ts +906 -0
- package/src/assembly/renderer-facade.ts +342 -0
- package/src/assembly/renderer-frame-transaction.ts +45 -0
- package/src/assembly/renderer-helpers.ts +99 -0
- package/src/assembly/renderer-lifecycle.ts +159 -0
- package/src/assembly/shader-prewarm-policy.ts +157 -0
- package/src/assembly/webgpu-ready-contract.ts +14 -0
- package/src/assembly/webgpu-ready.ts +4066 -0
- package/src/assembly/webgpu-renderer.ts +4027 -0
- package/src/assembly/webgpu-vertex-layouts.ts +132 -0
- package/src/assets/asset-decoders.ts +27 -4
- package/src/components/camera.ts +1 -1
- package/src/components/instances.ts +14 -197
- package/src/debug-draw-glue.ts +1 -1
- package/src/device/device-scope.ts +35 -6
- package/src/device/gpu-residency.ts +194 -26
- package/src/environment/frame.ts +6 -0
- package/src/environment/inspection.ts +10 -0
- package/src/environment/lifecycle.ts +114 -8
- package/src/errors/recover.ts +76 -5
- package/src/errors/render.ts +17 -0
- package/src/extract/contracts.ts +974 -0
- package/src/features/host.ts +16 -0
- package/src/features/prepared-gpu-work.ts +20 -2
- package/src/features/prepared-graphics-store.ts +17 -0
- package/src/gpu-driven/production-raster.ts +131 -3
- package/src/gpu-scene.ts +87 -31
- package/src/ibl/IblPipelineCache.ts +134 -50
- package/src/ibl/skylight-bind-group.ts +26 -0
- package/src/index.ts +10 -1
- package/src/instance-buffer-cache.ts +14 -1
- package/src/instances.ts +288 -0
- package/src/light-buffer-layout.ts +3 -0
- package/src/materials.ts +365 -53
- package/src/mesh-material-bindings.ts +59 -0
- package/src/occlusion-proxy.ts +64 -0
- package/src/pbr-pipeline.ts +139 -15
- package/src/pipeline-builder.ts +14 -10
- package/src/pipeline-spec.ts +6 -14
- package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
- package/src/points-lines/prepare.ts +9 -0
- package/src/points-lines/standard-owner.ts +403 -0
- package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
- package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
- package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
- package/src/record/frame-snapshot.ts +34 -2
- package/src/record/frame-targets.ts +11 -1
- package/src/record/frame.ts +37 -4
- package/src/record/helpers.ts +1 -1
- package/src/record/main-pass-geometry.ts +727 -63
- package/src/record/main-pass-material.ts +316 -209
- package/src/record/main-pass-sprite-draws.ts +7 -8
- package/src/record/main-pass.ts +94 -6
- package/src/record/mesh-ssbo.ts +32 -10
- package/src/record/recovery-pipeline.ts +695 -0
- package/src/record/render-context.ts +39 -4
- package/src/record/shadow-pass.ts +46 -99
- package/src/record/typed-frame-graph.ts +45 -1
- package/src/record/view-ubo.ts +3 -3
- package/src/recovery/render-system-candidate.ts +876 -0
- package/src/render-contract.ts +69 -7
- package/src/render-system-extract-tail.ts +3039 -0
- package/src/render-system-extract.ts +177 -3003
- package/src/render-system.ts +906 -662
- package/src/scene/render-scene-types.ts +2 -0
- package/src/scene/render-scene.ts +703 -37
- package/src/systems/skin-palette-allocator.ts +129 -7
- package/src/temporal/frame-coordinator.ts +4 -1
- package/src/temporal/taa-history-store.ts +46 -2
- package/src/temporal/temporal-view.ts +1 -1
- package/src/temporal/transaction.ts +9 -0
- package/src/tilemap-chunk-extract-system.ts +1 -1
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
- package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
- package/dist/chunk-4L3P6DT5.mjs.map +0 -1
- package/dist/chunk-4NPUASZR.mjs +0 -203
- package/dist/chunk-4NPUASZR.mjs.map +0 -1
- package/dist/chunk-DIYW3M5D.mjs.map +0 -1
- package/dist/chunk-FBC4R44J.mjs.map +0 -1
- package/dist/chunk-FF7K3FSO.mjs.map +0 -1
- package/dist/chunk-HZUPSYW7.mjs.map +0 -1
- package/dist/chunk-QODA5UKU.mjs.map +0 -1
- package/dist/chunk-YALPS44A.mjs.map +0 -1
- package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { Query, World } from '@forgeax/engine-ecs';
|
|
1
|
+
import type { EntityHandle, Query, World } from '@forgeax/engine-ecs';
|
|
2
2
|
import { componentId } from '@forgeax/engine-ecs/internal';
|
|
3
3
|
import type { RenderReadLease, RenderReadVersion } from '@forgeax/engine-ecs/projection';
|
|
4
4
|
import { box3, frustum, mat4 } from '@forgeax/engine-math';
|
|
5
5
|
import { type RhiDevice, RhiError } from '@forgeax/engine-rhi';
|
|
6
|
-
import { ChildOf, GlobalTransform, Transform } from '@forgeax/engine-scene';
|
|
6
|
+
import { ChildOf, GlobalTransform, MorphWeights, Transform } from '@forgeax/engine-scene';
|
|
7
7
|
import { Skin } from '@forgeax/engine-skinning';
|
|
8
8
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
9
9
|
import {
|
|
@@ -12,20 +12,24 @@ import {
|
|
|
12
12
|
Instances,
|
|
13
13
|
Layer,
|
|
14
14
|
LightProbe,
|
|
15
|
+
Lines,
|
|
15
16
|
MeshFilter,
|
|
16
17
|
MeshRenderer,
|
|
17
18
|
MotionBlur,
|
|
18
19
|
PointLight,
|
|
19
20
|
PointLightShadow,
|
|
21
|
+
Points,
|
|
20
22
|
PostProcessParams,
|
|
21
23
|
ReflectionProbe,
|
|
22
24
|
SkyboxBackground,
|
|
23
25
|
Skylight,
|
|
26
|
+
SortKey,
|
|
24
27
|
SpotLight,
|
|
25
28
|
SpriteInstances,
|
|
26
29
|
SpriteRegionOverride,
|
|
27
30
|
Visibility,
|
|
28
31
|
} from '../components';
|
|
32
|
+
import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
|
|
29
33
|
import {
|
|
30
34
|
BatchTopology,
|
|
31
35
|
type BatchTopologyInspection,
|
|
@@ -33,6 +37,7 @@ import {
|
|
|
33
37
|
} from '../gpu-driven/batch-topology';
|
|
34
38
|
import { GpuScene } from '../gpu-scene';
|
|
35
39
|
import type { PersistentRenderSceneInspection } from '../inspection-types';
|
|
40
|
+
import type { InstanceProjectionStore } from '../instances';
|
|
36
41
|
import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
|
|
37
42
|
import type { PointsLinesInspection } from '../points-lines/inspection';
|
|
38
43
|
import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
|
|
@@ -52,6 +57,7 @@ import type {
|
|
|
52
57
|
RenderableSnapshot,
|
|
53
58
|
RenderableTemporalSnapshot,
|
|
54
59
|
} from '../render-system-extract';
|
|
60
|
+
import type { SkinPaletteAllocator } from '../systems/skin-palette-allocator';
|
|
55
61
|
import {
|
|
56
62
|
PersistentTransmissionDemandProjection,
|
|
57
63
|
type TransmissionDemand,
|
|
@@ -96,6 +102,7 @@ interface PendingIdentity {
|
|
|
96
102
|
readonly initial: RenderSceneSlot | undefined;
|
|
97
103
|
current: RenderableSnapshot | undefined;
|
|
98
104
|
removed: boolean;
|
|
105
|
+
contentChanged: boolean;
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
interface WorldTransformSpan {
|
|
@@ -106,6 +113,16 @@ interface WorldTransformSpan {
|
|
|
106
113
|
|
|
107
114
|
type GlobalTransformChangeQuery = Query<readonly [typeof GlobalTransform]>;
|
|
108
115
|
|
|
116
|
+
/** The normal extract request used by the persistent scene owner. */
|
|
117
|
+
export type PersistentRenderCandidateRequest =
|
|
118
|
+
| 'full'
|
|
119
|
+
| 'none'
|
|
120
|
+
| {
|
|
121
|
+
readonly kind: 'partial';
|
|
122
|
+
/** Entity keys are local to the corresponding World entry. */
|
|
123
|
+
readonly entitiesByWorld: readonly (ReadonlySet<number> | undefined)[];
|
|
124
|
+
};
|
|
125
|
+
|
|
109
126
|
export interface RenderSceneSubmissionCapture {
|
|
110
127
|
readonly revision: number;
|
|
111
128
|
readonly slots: readonly {
|
|
@@ -187,6 +204,9 @@ function ownSnapshot(snapshot: RenderableSnapshot): RenderableSnapshot {
|
|
|
187
204
|
...(snapshot.pointsLines === undefined
|
|
188
205
|
? {}
|
|
189
206
|
: { pointsLines: ownPointsLinesSnapshot(snapshot.pointsLines) }),
|
|
207
|
+
...(snapshot.skinJointEntities === undefined
|
|
208
|
+
? {}
|
|
209
|
+
: { skinJointEntities: [...snapshot.skinJointEntities] }),
|
|
190
210
|
};
|
|
191
211
|
}
|
|
192
212
|
|
|
@@ -356,6 +376,7 @@ export class RenderScene {
|
|
|
356
376
|
initial,
|
|
357
377
|
current: initial?.snapshot,
|
|
358
378
|
removed: false,
|
|
379
|
+
contentChanged: false,
|
|
359
380
|
};
|
|
360
381
|
entities.set(entityKey, pending);
|
|
361
382
|
pendingOrder.push(pending);
|
|
@@ -367,6 +388,7 @@ export class RenderScene {
|
|
|
367
388
|
const { worldId, entityKey } = operation.snapshot;
|
|
368
389
|
const pending = pendingFor(worldId, entityKey);
|
|
369
390
|
pending.current = ownSnapshot(operation.snapshot);
|
|
391
|
+
pending.contentChanged = true;
|
|
370
392
|
continue;
|
|
371
393
|
}
|
|
372
394
|
const pending = pendingFor(operation.worldId, operation.entityKey);
|
|
@@ -388,6 +410,7 @@ export class RenderScene {
|
|
|
388
410
|
let recreated = 0;
|
|
389
411
|
const createdSlots: RenderSceneSlot[] = [];
|
|
390
412
|
const updatedSlots: RenderSceneSlot[] = [];
|
|
413
|
+
const contentUpdatedSlots: RenderSceneSlot[] = [];
|
|
391
414
|
const removedSlots: RenderSceneRecord[] = [];
|
|
392
415
|
const recreatedSlots: RenderSceneSlot[] = [];
|
|
393
416
|
for (const pending of pendingOrder) {
|
|
@@ -425,6 +448,7 @@ export class RenderScene {
|
|
|
425
448
|
this.indexMaterials(updatedSlot);
|
|
426
449
|
this.dirtySinceSubmitted.add(updatedSlot.slot);
|
|
427
450
|
updatedSlots.push(updatedSlot);
|
|
451
|
+
if (pending.contentChanged) contentUpdatedSlots.push(updatedSlot);
|
|
428
452
|
updated += 1;
|
|
429
453
|
}
|
|
430
454
|
|
|
@@ -441,6 +465,7 @@ export class RenderScene {
|
|
|
441
465
|
ignoredLateUpdates,
|
|
442
466
|
createdSlots,
|
|
443
467
|
updatedSlots,
|
|
468
|
+
contentUpdatedSlots,
|
|
444
469
|
removedSlots,
|
|
445
470
|
recreatedSlots,
|
|
446
471
|
resynced: 0,
|
|
@@ -865,7 +890,7 @@ export class RenderScene {
|
|
|
865
890
|
entityKey: snapshot.entityKey,
|
|
866
891
|
meshGeneration: fingerprintNumericArray(snapshot.localAabb) ^ (snapshot.assetHandle >>> 0),
|
|
867
892
|
transformGeneration: fingerprintNumericArray(snapshot.transform.world),
|
|
868
|
-
matrixGeneration: snapshot.instances.archVersion,
|
|
893
|
+
matrixGeneration: snapshot.instances.revision ?? snapshot.instances.archVersion,
|
|
869
894
|
meshAabb: snapshot.localAabb,
|
|
870
895
|
entityWorld: snapshot.transform.world,
|
|
871
896
|
transforms: snapshot.instances.transforms,
|
|
@@ -889,6 +914,9 @@ export interface PersistentRenderSceneOptions {
|
|
|
889
914
|
readonly onGpuError?: ((error: RhiError) => void) | undefined;
|
|
890
915
|
readonly onSharedRefMutation?: ((worldId: number, handle: number) => void) | undefined;
|
|
891
916
|
readonly configuredQueryBudget?: number | undefined;
|
|
917
|
+
/** Rebuildable projection of World-owned instance matrices. */
|
|
918
|
+
readonly instanceCollections?: InstanceProjectionStore | undefined;
|
|
919
|
+
readonly getSkinPaletteAllocator?: (() => SkinPaletteAllocator | null) | undefined;
|
|
892
920
|
}
|
|
893
921
|
|
|
894
922
|
export interface PersistentGpuDrivenState {
|
|
@@ -943,6 +971,23 @@ export function projectCpuDirectCandidates(
|
|
|
943
971
|
return candidates;
|
|
944
972
|
}
|
|
945
973
|
|
|
974
|
+
/** Detached GPU scene prepared from the retained CPU composition. */
|
|
975
|
+
export interface PersistentGpuDrivenCandidate {
|
|
976
|
+
readonly state: PersistentGpuDrivenState | undefined;
|
|
977
|
+
/** Candidate-owned scene root; never references the active GPU scene. */
|
|
978
|
+
createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
|
|
979
|
+
publish(): void;
|
|
980
|
+
discard(): void;
|
|
981
|
+
/** Release the candidate scene even after it has crossed publication. */
|
|
982
|
+
release(): void;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
export interface RenderSceneRecoveryRoot {
|
|
986
|
+
readonly generation: number;
|
|
987
|
+
readonly revision: number;
|
|
988
|
+
readonly visibleSlots: number;
|
|
989
|
+
}
|
|
990
|
+
|
|
946
991
|
type PersistentGpuSceneInspection = PersistentRenderSceneInspection['gpu'];
|
|
947
992
|
|
|
948
993
|
interface PersistentCompositionEntry {
|
|
@@ -953,7 +998,7 @@ interface PersistentCompositionEntry {
|
|
|
953
998
|
readonly projection: RenderScene;
|
|
954
999
|
readonly topology: BatchTopology;
|
|
955
1000
|
readonly transmissionDemand: PersistentTransmissionDemandProjection;
|
|
956
|
-
|
|
1001
|
+
structure: Pick<
|
|
957
1002
|
ExtractedFrame,
|
|
958
1003
|
| 'dispatch'
|
|
959
1004
|
| 'visibilityStats'
|
|
@@ -966,15 +1011,24 @@ interface PersistentCompositionEntry {
|
|
|
966
1011
|
readonly readVersions: RenderReadVersion[];
|
|
967
1012
|
readonly leaseIdentities: readonly string[];
|
|
968
1013
|
readonly transformQueries: readonly GlobalTransformChangeQuery[];
|
|
1014
|
+
readonly renderChangeQueries: readonly ReadonlyMap<number, Query>[];
|
|
1015
|
+
readonly dispatchBySlot: Map<number, readonly DispatchEntry[]>;
|
|
1016
|
+
dispatchRevision: number;
|
|
1017
|
+
dispatchCache:
|
|
1018
|
+
| { readonly revision: number; readonly value: readonly DispatchEntry[] }
|
|
1019
|
+
| undefined;
|
|
1020
|
+
readonly skinConsumersByJoint: Map<string, Set<number>>;
|
|
1021
|
+
/** Renderable identities whose partial producer still needs a successful publish. */
|
|
1022
|
+
readonly pendingRenderableEntitiesByWorld: Set<number>[];
|
|
1023
|
+
/** Slots retained for recovery but excluded from direct and indirect submission. */
|
|
1024
|
+
readonly unavailableSlots: Set<number>;
|
|
969
1025
|
catalogEpoch: number;
|
|
970
|
-
readonly rigidOnly: boolean;
|
|
971
1026
|
readonly probeProjection: ProbeBlendSceneProjection;
|
|
972
1027
|
temporalDemanded: boolean;
|
|
973
1028
|
gpuDrivenSceneRequired: boolean;
|
|
974
1029
|
}
|
|
975
1030
|
|
|
976
1031
|
const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
977
|
-
componentId(Transform),
|
|
978
1032
|
componentId(GlobalTransform),
|
|
979
1033
|
componentId(ChildOf),
|
|
980
1034
|
componentId(MeshFilter),
|
|
@@ -985,6 +1039,10 @@ const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
|
985
1039
|
componentId(Skin),
|
|
986
1040
|
componentId(Layer),
|
|
987
1041
|
componentId(Visibility),
|
|
1042
|
+
componentId(MorphWeights),
|
|
1043
|
+
componentId(Points),
|
|
1044
|
+
componentId(Lines),
|
|
1045
|
+
componentId(SortKey),
|
|
988
1046
|
componentId(Camera),
|
|
989
1047
|
componentId(MotionBlur),
|
|
990
1048
|
componentId(DirectionalLight),
|
|
@@ -999,6 +1057,41 @@ const RENDER_RELEVANT_COMPONENT_IDS = new Set([
|
|
|
999
1057
|
componentId(VolumetricFog),
|
|
1000
1058
|
]);
|
|
1001
1059
|
|
|
1060
|
+
// These components can change the retained renderable source. Other relevant
|
|
1061
|
+
// components affect frame resources and still require a composition rebuild;
|
|
1062
|
+
// they must not cause a renderable-only partial extraction to silently retain
|
|
1063
|
+
// stale lights, cameras, or environment facts.
|
|
1064
|
+
const RENDERABLE_SOURCE_COMPONENTS = [
|
|
1065
|
+
GlobalTransform,
|
|
1066
|
+
ChildOf,
|
|
1067
|
+
MeshFilter,
|
|
1068
|
+
MeshRenderer,
|
|
1069
|
+
Instances,
|
|
1070
|
+
SpriteInstances,
|
|
1071
|
+
SpriteRegionOverride,
|
|
1072
|
+
Skin,
|
|
1073
|
+
Layer,
|
|
1074
|
+
Visibility,
|
|
1075
|
+
MorphWeights,
|
|
1076
|
+
Points,
|
|
1077
|
+
Lines,
|
|
1078
|
+
SortKey,
|
|
1079
|
+
] as const;
|
|
1080
|
+
const RENDERABLE_SOURCE_COMPONENT_IDS = new Set(
|
|
1081
|
+
RENDERABLE_SOURCE_COMPONENTS.map((component) => componentId(component)),
|
|
1082
|
+
);
|
|
1083
|
+
const VISIBILITY_DEPENDENCY_COMPONENT_IDS = new Set([
|
|
1084
|
+
componentId(ChildOf),
|
|
1085
|
+
componentId(Visibility),
|
|
1086
|
+
]);
|
|
1087
|
+
const STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS = new Set([
|
|
1088
|
+
componentId(ChildOf),
|
|
1089
|
+
componentId(Layer),
|
|
1090
|
+
componentId(Visibility),
|
|
1091
|
+
componentId(SpriteRegionOverride),
|
|
1092
|
+
componentId(SortKey),
|
|
1093
|
+
]);
|
|
1094
|
+
|
|
1002
1095
|
function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQuery {
|
|
1003
1096
|
const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
|
|
1004
1097
|
if (!result.ok) throw result.error;
|
|
@@ -1009,6 +1102,149 @@ function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQu
|
|
|
1009
1102
|
return query;
|
|
1010
1103
|
}
|
|
1011
1104
|
|
|
1105
|
+
function createRenderChangeQueries(world: World): ReadonlyMap<number, Query> {
|
|
1106
|
+
const queries = new Map<number, Query>();
|
|
1107
|
+
for (const component of RENDERABLE_SOURCE_COMPONENTS) {
|
|
1108
|
+
const result = world.query({ read: [component], changed: [component] });
|
|
1109
|
+
if (!result.ok) throw result.error;
|
|
1110
|
+
const query = result.value as Query;
|
|
1111
|
+
// The initial full extraction has already consumed this change evidence.
|
|
1112
|
+
// Keep the query compiled so subsequent frames iterate only rows whose
|
|
1113
|
+
// component version changed.
|
|
1114
|
+
for (const _span of query.spans().unwrap()) {
|
|
1115
|
+
// Establish the observation baseline.
|
|
1116
|
+
}
|
|
1117
|
+
queries.set(componentId(component), query);
|
|
1118
|
+
}
|
|
1119
|
+
return queries;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
function collectRenderableEntities(world: World): ReadonlySet<number> {
|
|
1123
|
+
const result = new Set<number>();
|
|
1124
|
+
const query = world.query({ read: [MeshRenderer], with: [Transform, MeshFilter] }).unwrap();
|
|
1125
|
+
for (const row of query) result.add(row.entity as number);
|
|
1126
|
+
return result;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function sourceMembershipChanged(world: World, slot: RenderSceneSlot): boolean {
|
|
1130
|
+
const entity = slot.entityKey as EntityHandle;
|
|
1131
|
+
const snapshot = slot.snapshot;
|
|
1132
|
+
return (
|
|
1133
|
+
!world.hasComponent(entity, GlobalTransform) ||
|
|
1134
|
+
(snapshot.instances !== undefined) !== world.hasComponent(entity, Instances) ||
|
|
1135
|
+
(snapshot.spriteInstances !== undefined) !== world.hasComponent(entity, SpriteInstances) ||
|
|
1136
|
+
(snapshot.skin !== undefined) !== world.hasComponent(entity, Skin) ||
|
|
1137
|
+
(snapshot.morph !== undefined) !== world.hasComponent(entity, MorphWeights) ||
|
|
1138
|
+
(snapshot.pointsLines?.component === 'Points') !== world.hasComponent(entity, Points) ||
|
|
1139
|
+
(snapshot.pointsLines?.component === 'Lines') !== world.hasComponent(entity, Lines)
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function skinJointKey(worldId: number, jointEntity: number): string {
|
|
1144
|
+
return `${worldId}:${jointEntity}`;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function addSkinConsumer(index: Map<string, Set<number>>, snapshot: RenderableSnapshot): void {
|
|
1148
|
+
for (const jointEntity of snapshot.skinJointEntities ?? []) {
|
|
1149
|
+
const key = skinJointKey(snapshot.worldId, jointEntity);
|
|
1150
|
+
let consumers = index.get(key);
|
|
1151
|
+
if (consumers === undefined) {
|
|
1152
|
+
consumers = new Set<number>();
|
|
1153
|
+
index.set(key, consumers);
|
|
1154
|
+
}
|
|
1155
|
+
consumers.add(snapshot.entityKey);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function removeSkinConsumer(index: Map<string, Set<number>>, snapshot: RenderableSnapshot): void {
|
|
1160
|
+
for (const jointEntity of snapshot.skinJointEntities ?? []) {
|
|
1161
|
+
const key = skinJointKey(snapshot.worldId, jointEntity);
|
|
1162
|
+
const consumers = index.get(key);
|
|
1163
|
+
consumers?.delete(snapshot.entityKey);
|
|
1164
|
+
if (consumers?.size === 0) index.delete(key);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function dispatchEntriesBySlot(
|
|
1169
|
+
frame: ExtractedFrame,
|
|
1170
|
+
projection: RenderScene,
|
|
1171
|
+
): Map<number, readonly DispatchEntry[]> {
|
|
1172
|
+
const bySlot = new Map<number, DispatchEntry[]>();
|
|
1173
|
+
const byRenderable = new Map<number, DispatchEntry[]>();
|
|
1174
|
+
for (const entry of frame.dispatch) {
|
|
1175
|
+
const list = byRenderable.get(entry.renderableIndex);
|
|
1176
|
+
if (list === undefined) byRenderable.set(entry.renderableIndex, [entry]);
|
|
1177
|
+
else list.push(entry);
|
|
1178
|
+
}
|
|
1179
|
+
for (let index = 0; index < frame.renderables.length; index += 1) {
|
|
1180
|
+
const renderable = frame.renderables[index];
|
|
1181
|
+
if (renderable === undefined) continue;
|
|
1182
|
+
const slot = projection.slot(renderable.worldId, renderable.entityKey);
|
|
1183
|
+
if (slot === undefined) continue;
|
|
1184
|
+
bySlot.set(slot.slot, byRenderable.get(index) ?? []);
|
|
1185
|
+
}
|
|
1186
|
+
return bySlot;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function dispatchForProjection(
|
|
1190
|
+
dispatchBySlot: ReadonlyMap<number, readonly DispatchEntry[]>,
|
|
1191
|
+
projection: RenderScene,
|
|
1192
|
+
unavailableSlots: ReadonlySet<number>,
|
|
1193
|
+
): readonly DispatchEntry[] {
|
|
1194
|
+
const dispatch: DispatchEntry[] = [];
|
|
1195
|
+
const renderables = projection.materialize();
|
|
1196
|
+
for (let renderableIndex = 0; renderableIndex < renderables.length; renderableIndex += 1) {
|
|
1197
|
+
const renderable = renderables[renderableIndex];
|
|
1198
|
+
if (renderable === undefined) continue;
|
|
1199
|
+
const slot = projection.slot(renderable.worldId, renderable.entityKey);
|
|
1200
|
+
if (slot === undefined) continue;
|
|
1201
|
+
if (unavailableSlots.has(slot.slot)) continue;
|
|
1202
|
+
for (const entry of dispatchBySlot.get(slot.slot) ?? []) {
|
|
1203
|
+
dispatch.push({ ...entry, renderableIndex });
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return dispatch;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function topologyDeltaForAvailable(
|
|
1210
|
+
delta: RenderSceneApplyResult,
|
|
1211
|
+
unavailableSlots: ReadonlySet<number>,
|
|
1212
|
+
newlyUnavailable: readonly RenderSceneSlot[],
|
|
1213
|
+
): RenderSceneApplyResult {
|
|
1214
|
+
const updatedSlots = delta.updatedSlots.filter((slot) => !unavailableSlots.has(slot.slot));
|
|
1215
|
+
const contentUpdatedSlots = delta.contentUpdatedSlots?.filter(
|
|
1216
|
+
(slot) => !unavailableSlots.has(slot.slot),
|
|
1217
|
+
);
|
|
1218
|
+
return {
|
|
1219
|
+
...delta,
|
|
1220
|
+
updatedSlots: [
|
|
1221
|
+
...updatedSlots,
|
|
1222
|
+
...newlyUnavailable.map((slot) => ({
|
|
1223
|
+
...slot,
|
|
1224
|
+
snapshot: { ...slot.snapshot, gpuDrivenDraws: [] },
|
|
1225
|
+
})),
|
|
1226
|
+
],
|
|
1227
|
+
...(contentUpdatedSlots === undefined ? {} : { contentUpdatedSlots }),
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function addSharedRefConsumers(
|
|
1232
|
+
projection: RenderScene,
|
|
1233
|
+
worldId: number,
|
|
1234
|
+
handle: number,
|
|
1235
|
+
target: Set<number>,
|
|
1236
|
+
): void {
|
|
1237
|
+
for (const slot of projection.slotsSnapshot()) {
|
|
1238
|
+
if (
|
|
1239
|
+
slot.worldId === worldId &&
|
|
1240
|
+
(slot.snapshot.assetHandle === handle ||
|
|
1241
|
+
slot.snapshot.materials.some((material) => material.materialHandle === handle))
|
|
1242
|
+
) {
|
|
1243
|
+
target.add(slot.entityKey);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1012
1248
|
function frameDemandsTemporal(frame: ExtractedFrame): boolean {
|
|
1013
1249
|
const camera = frame.cameras[0];
|
|
1014
1250
|
return camera?.antialias === 'taa' || (camera?.motionBlur?.shutterAngle ?? 0) > 0;
|
|
@@ -1030,8 +1266,8 @@ function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array
|
|
|
1030
1266
|
projection,
|
|
1031
1267
|
camera.orthoLeft,
|
|
1032
1268
|
camera.orthoRight,
|
|
1033
|
-
camera.orthoBottom,
|
|
1034
1269
|
camera.orthoTop,
|
|
1270
|
+
camera.orthoBottom,
|
|
1035
1271
|
camera.near,
|
|
1036
1272
|
camera.far,
|
|
1037
1273
|
);
|
|
@@ -1072,6 +1308,10 @@ function cullPersistentFrame(
|
|
|
1072
1308
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
1073
1309
|
const candidate = candidates[index];
|
|
1074
1310
|
if (candidate === undefined) continue;
|
|
1311
|
+
if (candidate.authorVisible === false) {
|
|
1312
|
+
culled += 1;
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1075
1315
|
let isVisible = true;
|
|
1076
1316
|
const candidateBounds = boundsOf(candidate, index);
|
|
1077
1317
|
if (candidateBounds !== undefined) {
|
|
@@ -1377,7 +1617,7 @@ export class PersistentRenderScene {
|
|
|
1377
1617
|
bounds.length >= 6 &&
|
|
1378
1618
|
[...bounds].every((value) => Number.isFinite(value));
|
|
1379
1619
|
draw = decideVisibility({
|
|
1380
|
-
authorVisible:
|
|
1620
|
+
authorVisible: renderable.authorVisible !== false,
|
|
1381
1621
|
validBounds,
|
|
1382
1622
|
frustumVisible: true,
|
|
1383
1623
|
lodReady: true,
|
|
@@ -1481,7 +1721,7 @@ export class PersistentRenderScene {
|
|
|
1481
1721
|
worlds: readonly World[],
|
|
1482
1722
|
owner: { readonly cameraOwner: number; readonly resourceOwner: number },
|
|
1483
1723
|
catalogEpoch: number,
|
|
1484
|
-
buildCandidateFrame: (
|
|
1724
|
+
buildCandidateFrame: (request: PersistentRenderCandidateRequest) => ExtractedFrame,
|
|
1485
1725
|
leases?: readonly RenderReadLease[],
|
|
1486
1726
|
): ExtractedFrame {
|
|
1487
1727
|
this.worldEntitiesScanned = 0;
|
|
@@ -1526,11 +1766,16 @@ export class PersistentRenderScene {
|
|
|
1526
1766
|
preserveVisibilityOnReorder,
|
|
1527
1767
|
);
|
|
1528
1768
|
}
|
|
1529
|
-
if (!entry.rigidOnly) {
|
|
1530
|
-
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
1769
|
let requiresRebuild = false;
|
|
1770
|
+
const changedEntitiesByWorld = worlds.map(
|
|
1771
|
+
(_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? []),
|
|
1772
|
+
);
|
|
1773
|
+
const nextReadVersions = entry.readVersions.slice();
|
|
1774
|
+
const structureChangedWorlds = new Set<number>();
|
|
1775
|
+
const structuralComponentIdsByWorld = worlds.map(() => new Set<number>());
|
|
1776
|
+
let refreshStructureFacts = false;
|
|
1777
|
+
let currentRenderableEntitiesByWorld: readonly (ReadonlySet<number> | undefined)[] | undefined;
|
|
1778
|
+
const removedSlots: RenderSceneSlot[] = [];
|
|
1534
1779
|
for (let worldId = 0; worldId < worlds.length; worldId += 1) {
|
|
1535
1780
|
const world = worlds[worldId];
|
|
1536
1781
|
if (world === undefined) continue;
|
|
@@ -1551,35 +1796,294 @@ export class PersistentRenderScene {
|
|
|
1551
1796
|
requiresRebuild = true;
|
|
1552
1797
|
break;
|
|
1553
1798
|
}
|
|
1554
|
-
if (read.status
|
|
1555
|
-
|
|
1556
|
-
|
|
1799
|
+
if (read.status === 'rebuild') {
|
|
1800
|
+
structureChangedWorlds.add(worldId);
|
|
1801
|
+
refreshStructureFacts = true;
|
|
1557
1802
|
}
|
|
1558
1803
|
const changes = read.world;
|
|
1559
1804
|
const shared = read.sharedRefs;
|
|
1560
1805
|
if (shared.records.length > 0) {
|
|
1561
1806
|
for (const record of shared.records) {
|
|
1562
1807
|
this.options.onSharedRefMutation?.(worldId, record.handle);
|
|
1808
|
+
const changed = changedEntitiesByWorld[worldId];
|
|
1809
|
+
if (changed !== undefined)
|
|
1810
|
+
addSharedRefConsumers(entry.projection, worldId, record.handle, changed);
|
|
1563
1811
|
}
|
|
1564
|
-
requiresRebuild = true;
|
|
1565
|
-
break;
|
|
1566
1812
|
}
|
|
1567
1813
|
for (const changedComponentId of changes.changedComponentIds) {
|
|
1814
|
+
if (read.status === 'rebuild') {
|
|
1815
|
+
structuralComponentIdsByWorld[worldId]?.add(changedComponentId);
|
|
1816
|
+
}
|
|
1568
1817
|
if (!RENDER_RELEVANT_COMPONENT_IDS.has(changedComponentId)) continue;
|
|
1569
|
-
if (
|
|
1570
|
-
changedComponentId !== componentId(Transform) &&
|
|
1571
|
-
changedComponentId !== componentId(GlobalTransform)
|
|
1572
|
-
) {
|
|
1818
|
+
if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) {
|
|
1573
1819
|
requiresRebuild = true;
|
|
1574
1820
|
break;
|
|
1575
1821
|
}
|
|
1822
|
+
const changed = changedEntitiesByWorld[worldId];
|
|
1823
|
+
if (changed === undefined) continue;
|
|
1824
|
+
if (VISIBILITY_DEPENDENCY_COMPONENT_IDS.has(changedComponentId)) {
|
|
1825
|
+
refreshStructureFacts = true;
|
|
1826
|
+
// Visibility and parent changes affect the effective state of a
|
|
1827
|
+
// subtree. The normal update path evaluates the current rows; the
|
|
1828
|
+
// set is deliberately allowed to be broad, while empty work still
|
|
1829
|
+
// exits naturally without a candidate loop.
|
|
1830
|
+
for (const entity of collectRenderableEntities(world)) changed.add(entity);
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
if (changedComponentId === componentId(GlobalTransform)) {
|
|
1834
|
+
const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
|
|
1835
|
+
for (const row of query ?? []) {
|
|
1836
|
+
const consumers = entry.skinConsumersByJoint.get(
|
|
1837
|
+
skinJointKey(worldId, row.entity as number),
|
|
1838
|
+
);
|
|
1839
|
+
for (const consumer of consumers ?? []) changed.add(consumer);
|
|
1840
|
+
}
|
|
1841
|
+
continue;
|
|
1842
|
+
}
|
|
1843
|
+
const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
|
|
1844
|
+
if (query !== undefined) {
|
|
1845
|
+
for (const row of query) changed.add(row.entity as number);
|
|
1846
|
+
}
|
|
1576
1847
|
}
|
|
1577
1848
|
if (requiresRebuild) break;
|
|
1578
|
-
|
|
1849
|
+
nextReadVersions[worldId] = read.version;
|
|
1579
1850
|
}
|
|
1580
1851
|
if (requiresRebuild) {
|
|
1581
1852
|
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1582
1853
|
}
|
|
1854
|
+
|
|
1855
|
+
if (structureChangedWorlds.size > 0) {
|
|
1856
|
+
this.visibilityFacets.clear();
|
|
1857
|
+
this.invalidateActiveEntityRevision();
|
|
1858
|
+
const currentByWorld = worlds.map((world) => collectRenderableEntities(world));
|
|
1859
|
+
currentRenderableEntitiesByWorld = currentByWorld;
|
|
1860
|
+
for (let worldId = 0; worldId < currentByWorld.length; worldId += 1) {
|
|
1861
|
+
const world = worlds[worldId];
|
|
1862
|
+
const current = currentByWorld[worldId];
|
|
1863
|
+
if (world === undefined || current === undefined) continue;
|
|
1864
|
+
const changed = changedEntitiesByWorld[worldId];
|
|
1865
|
+
if (changed !== undefined && structureChangedWorlds.has(worldId)) {
|
|
1866
|
+
// A joint can disappear without leaving a row in the joint's
|
|
1867
|
+
// GlobalTransform change query. Wake every dependent renderable so
|
|
1868
|
+
// the next partial extraction validates the dangling reference and
|
|
1869
|
+
// either republishes or retires its palette data.
|
|
1870
|
+
for (const [jointKey, consumers] of entry.skinConsumersByJoint) {
|
|
1871
|
+
const separator = jointKey.indexOf(':');
|
|
1872
|
+
const indexedWorldId = Number(jointKey.slice(0, separator));
|
|
1873
|
+
if (indexedWorldId !== worldId) continue;
|
|
1874
|
+
const jointEntity = Number(jointKey.slice(separator + 1));
|
|
1875
|
+
if (world.hasComponent(jointEntity as EntityHandle, GlobalTransform)) continue;
|
|
1876
|
+
for (const consumer of consumers) changed.add(consumer);
|
|
1877
|
+
}
|
|
1878
|
+
for (const entity of current) {
|
|
1879
|
+
if (entry.projection.slot(worldId, entity) === undefined) changed.add(entity);
|
|
1880
|
+
}
|
|
1881
|
+
const structuralComponentIds = structuralComponentIdsByWorld[worldId];
|
|
1882
|
+
const scanAllMembership =
|
|
1883
|
+
structuralComponentIds !== undefined &&
|
|
1884
|
+
[...structuralComponentIds].some((id) =>
|
|
1885
|
+
STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS.has(id),
|
|
1886
|
+
);
|
|
1887
|
+
for (const slot of entry.projection.slotsSnapshot()) {
|
|
1888
|
+
if (slot.worldId !== worldId || !current.has(slot.entityKey)) continue;
|
|
1889
|
+
const membershipChanged = sourceMembershipChanged(world, slot);
|
|
1890
|
+
if (scanAllMembership || membershipChanged) {
|
|
1891
|
+
changed.add(slot.entityKey);
|
|
1892
|
+
if (
|
|
1893
|
+
slot.snapshot.instances !== undefined &&
|
|
1894
|
+
!world.hasComponent(slot.entityKey as EntityHandle, Instances)
|
|
1895
|
+
) {
|
|
1896
|
+
this.options.instanceCollections?.release(world, slot.entityKey);
|
|
1897
|
+
}
|
|
1898
|
+
if (
|
|
1899
|
+
slot.snapshot.skin !== undefined &&
|
|
1900
|
+
!world.hasComponent(slot.entityKey as EntityHandle, Skin)
|
|
1901
|
+
) {
|
|
1902
|
+
this.options
|
|
1903
|
+
.getSkinPaletteAllocator?.()
|
|
1904
|
+
?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
for (const slot of entry.projection.slotsSnapshot()) {
|
|
1910
|
+
if (slot.worldId !== worldId || current.has(slot.entityKey)) continue;
|
|
1911
|
+
removedSlots.push(slot);
|
|
1912
|
+
}
|
|
1913
|
+
this.worldEntitiesScanned += current.size;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// Query iteration is an observation step, not publication. Persist every
|
|
1918
|
+
// requested identity before invoking any producer so a thrown or omitted
|
|
1919
|
+
// partial extraction is retried even after ECS change evidence is drained.
|
|
1920
|
+
for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
|
|
1921
|
+
const pending = entry.pendingRenderableEntitiesByWorld[worldId];
|
|
1922
|
+
if (pending === undefined) continue;
|
|
1923
|
+
for (const entityKey of changedEntitiesByWorld[worldId] ?? []) pending.add(entityKey);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
let changedDelta: RenderSceneApplyResult | undefined;
|
|
1927
|
+
const hasChangedEntities = changedEntitiesByWorld.some((entities) => entities.size > 0);
|
|
1928
|
+
if (hasChangedEntities || removedSlots.length > 0) {
|
|
1929
|
+
const operations: RenderSceneOperation[] = [];
|
|
1930
|
+
const removedDemand: TransmissionDemandOperation[] = [];
|
|
1931
|
+
const replacedSkinSnapshots: RenderableSnapshot[] = [];
|
|
1932
|
+
for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
|
|
1933
|
+
for (const entityKey of changedEntitiesByWorld[worldId] ?? []) {
|
|
1934
|
+
const slot = entry.projection.slot(worldId, entityKey);
|
|
1935
|
+
if (slot !== undefined) replacedSkinSnapshots.push(slot.snapshot);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
for (const slot of removedSlots) {
|
|
1939
|
+
operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
|
|
1940
|
+
if (slot.snapshot.skinJointEntities !== undefined) {
|
|
1941
|
+
removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
|
|
1942
|
+
}
|
|
1943
|
+
const world = worlds[slot.worldId];
|
|
1944
|
+
if (world !== undefined) this.options.instanceCollections?.release(world, slot.entityKey);
|
|
1945
|
+
this.options
|
|
1946
|
+
.getSkinPaletteAllocator?.()
|
|
1947
|
+
?.releaseSliceFor(`${world?.identity ?? slot.worldId}:${slot.entityKey}`);
|
|
1948
|
+
for (
|
|
1949
|
+
let materialIndex = 0;
|
|
1950
|
+
materialIndex < slot.snapshot.materials.length;
|
|
1951
|
+
materialIndex += 1
|
|
1952
|
+
) {
|
|
1953
|
+
removedDemand.push({
|
|
1954
|
+
kind: 'remove',
|
|
1955
|
+
key: `${slot.worldId}:${slot.entityKey}:${materialIndex}`,
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
|
|
1960
|
+
for (const entityKey of changedEntitiesByWorld[worldId] ?? []) {
|
|
1961
|
+
const slot = entry.projection.slot(worldId, entityKey);
|
|
1962
|
+
if (slot === undefined) continue;
|
|
1963
|
+
for (
|
|
1964
|
+
let materialIndex = 0;
|
|
1965
|
+
materialIndex < slot.snapshot.materials.length;
|
|
1966
|
+
materialIndex += 1
|
|
1967
|
+
) {
|
|
1968
|
+
removedDemand.push({ kind: 'remove', key: `${worldId}:${entityKey}:${materialIndex}` });
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
const partial = hasChangedEntities
|
|
1973
|
+
? buildCandidateFrame({ kind: 'partial', entitiesByWorld: changedEntitiesByWorld })
|
|
1974
|
+
: undefined;
|
|
1975
|
+
const publishedEntitiesByWorld = worlds.map(() => new Set<number>());
|
|
1976
|
+
for (const snapshot of partial?.renderables ?? []) {
|
|
1977
|
+
operations.push({ kind: 'create', snapshot });
|
|
1978
|
+
publishedEntitiesByWorld[snapshot.worldId]?.add(snapshot.entityKey);
|
|
1979
|
+
}
|
|
1980
|
+
changedDelta = entry.projection.apply(operations);
|
|
1981
|
+
const publishedSkinKeys = new Set<string>();
|
|
1982
|
+
for (const snapshot of partial?.renderables ?? []) {
|
|
1983
|
+
publishedSkinKeys.add(`${snapshot.worldId}:${snapshot.entityKey}`);
|
|
1984
|
+
}
|
|
1985
|
+
for (const snapshot of replacedSkinSnapshots) {
|
|
1986
|
+
// Keep the old dependency while a failed/omitted partial publication
|
|
1987
|
+
// retains its CPU slot. The pending identity must still be woken by
|
|
1988
|
+
// a later joint transform change so the producer gets another chance.
|
|
1989
|
+
if (
|
|
1990
|
+
snapshot.skinJointEntities !== undefined &&
|
|
1991
|
+
publishedSkinKeys.has(`${snapshot.worldId}:${snapshot.entityKey}`)
|
|
1992
|
+
) {
|
|
1993
|
+
removeSkinConsumer(entry.skinConsumersByJoint, snapshot);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
for (const snapshot of partial?.renderables ?? []) {
|
|
1997
|
+
if (snapshot.skinJointEntities !== undefined) {
|
|
1998
|
+
addSkinConsumer(entry.skinConsumersByJoint, snapshot);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
entry.transmissionDemand.apply([
|
|
2002
|
+
...removedDemand,
|
|
2003
|
+
...transmissionDemandOperations(partial?.renderables ?? []),
|
|
2004
|
+
]);
|
|
2005
|
+
for (const removed of changedDelta.removedSlots) entry.dispatchBySlot.delete(removed.slot);
|
|
2006
|
+
for (const recreated of changedDelta.recreatedSlots)
|
|
2007
|
+
entry.dispatchBySlot.delete(recreated.slot);
|
|
2008
|
+
if (partial !== undefined) {
|
|
2009
|
+
if (refreshStructureFacts) {
|
|
2010
|
+
entry.structure = {
|
|
2011
|
+
...entry.structure,
|
|
2012
|
+
dispatch: partial.dispatch,
|
|
2013
|
+
visibilityStats: partial.visibilityStats,
|
|
2014
|
+
visibilitySnapshots: partial.visibilitySnapshots,
|
|
2015
|
+
featureVisibilitySnapshots: partial.featureVisibilitySnapshots,
|
|
2016
|
+
hiddenEntityReports: partial.hiddenEntityReports,
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
const partialDispatch = dispatchEntriesBySlot(partial, entry.projection);
|
|
2020
|
+
for (const [slot, dispatch] of partialDispatch) entry.dispatchBySlot.set(slot, dispatch);
|
|
2021
|
+
// A changed renderable which now fails producer validation still owns
|
|
2022
|
+
// its retained identity; replace its old dispatch with an empty list.
|
|
2023
|
+
for (const snapshot of partial.renderables) {
|
|
2024
|
+
const slot = entry.projection.slot(snapshot.worldId, snapshot.entityKey);
|
|
2025
|
+
if (slot !== undefined && !partialDispatch.has(slot.slot))
|
|
2026
|
+
entry.dispatchBySlot.set(slot.slot, []);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
|
|
2030
|
+
const requested = changedEntitiesByWorld[worldId];
|
|
2031
|
+
const published = publishedEntitiesByWorld[worldId];
|
|
2032
|
+
if (requested === undefined || published === undefined) continue;
|
|
2033
|
+
const pending = entry.pendingRenderableEntitiesByWorld[worldId];
|
|
2034
|
+
for (const entityKey of requested) {
|
|
2035
|
+
const slot = entry.projection.slot(worldId, entityKey);
|
|
2036
|
+
if (published.has(entityKey)) {
|
|
2037
|
+
pending?.delete(entityKey);
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
if (slot === undefined) {
|
|
2041
|
+
const current = currentRenderableEntitiesByWorld?.[worldId];
|
|
2042
|
+
if (current !== undefined && !current.has(entityKey)) pending?.delete(entityKey);
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
// The producer did not publish this requested identity. Keep the
|
|
2046
|
+
// old CPU slot for recovery, but stop submitting stale dispatch and
|
|
2047
|
+
// retry the same identity even if its World version stays still.
|
|
2048
|
+
pending?.add(entityKey);
|
|
2049
|
+
if (slot !== undefined) entry.dispatchBySlot.set(slot.slot, []);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
const newlyUnavailable: RenderSceneSlot[] = [];
|
|
2053
|
+
for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
|
|
2054
|
+
const requested = changedEntitiesByWorld[worldId];
|
|
2055
|
+
const published = publishedEntitiesByWorld[worldId];
|
|
2056
|
+
if (requested === undefined || published === undefined) continue;
|
|
2057
|
+
for (const entityKey of requested) {
|
|
2058
|
+
const slot = entry.projection.slot(worldId, entityKey);
|
|
2059
|
+
if (slot === undefined || published.has(entityKey)) {
|
|
2060
|
+
if (slot !== undefined) entry.unavailableSlots.delete(slot.slot);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
if (!entry.unavailableSlots.has(slot.slot)) newlyUnavailable.push(slot);
|
|
2064
|
+
entry.unavailableSlots.add(slot.slot);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
for (const removed of changedDelta.removedSlots) entry.unavailableSlots.delete(removed.slot);
|
|
2068
|
+
for (const recreated of changedDelta.recreatedSlots)
|
|
2069
|
+
entry.unavailableSlots.delete(recreated.slot);
|
|
2070
|
+
entry.dispatchRevision += 1;
|
|
2071
|
+
entry.dispatchCache = undefined;
|
|
2072
|
+
entry.topology.apply(
|
|
2073
|
+
topologyDeltaForAvailable(changedDelta, entry.unavailableSlots, newlyUnavailable),
|
|
2074
|
+
);
|
|
2075
|
+
this.syncGpuOwner(entry.token, entry.projection, changedDelta);
|
|
2076
|
+
this.deltaFrames +=
|
|
2077
|
+
changedDelta.created +
|
|
2078
|
+
changedDelta.updated +
|
|
2079
|
+
changedDelta.removed +
|
|
2080
|
+
changedDelta.recreated >
|
|
2081
|
+
0
|
|
2082
|
+
? 1
|
|
2083
|
+
: 0;
|
|
2084
|
+
this.transformUpdates += changedDelta.updated;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
1583
2087
|
const transformSpans: WorldTransformSpan[] = [];
|
|
1584
2088
|
for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
|
|
1585
2089
|
const query = entry.transformQueries[worldId];
|
|
@@ -1597,17 +2101,29 @@ export class PersistentRenderScene {
|
|
|
1597
2101
|
if (requiresRebuild) {
|
|
1598
2102
|
return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
|
|
1599
2103
|
}
|
|
1600
|
-
if (transformSpans.length === 0) {
|
|
2104
|
+
if (transformSpans.length === 0 && changedDelta === undefined) {
|
|
1601
2105
|
this.noChangeFrames += 1;
|
|
1602
2106
|
this.syncGpuOwner(entry.token, entry.projection);
|
|
1603
|
-
|
|
2107
|
+
const result = this.deriveFramePlan(entry, buildCandidateFrame);
|
|
2108
|
+
for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
|
|
2109
|
+
const version = nextReadVersions[worldId];
|
|
2110
|
+
if (version !== undefined) entry.readVersions[worldId] = version;
|
|
2111
|
+
}
|
|
2112
|
+
return result;
|
|
1604
2113
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
2114
|
+
if (transformSpans.length > 0) {
|
|
2115
|
+
const delta = entry.projection.applyTransformSpans(transformSpans);
|
|
2116
|
+
entry.topology.apply(topologyDeltaForAvailable(delta, entry.unavailableSlots, []));
|
|
2117
|
+
this.syncGpuOwner(entry.token, entry.projection, delta);
|
|
2118
|
+
this.deltaFrames += 1;
|
|
2119
|
+
this.transformUpdates += delta.updated;
|
|
2120
|
+
}
|
|
2121
|
+
const result = this.deriveFramePlan(entry, buildCandidateFrame);
|
|
2122
|
+
for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
|
|
2123
|
+
const version = nextReadVersions[worldId];
|
|
2124
|
+
if (version !== undefined) entry.readVersions[worldId] = version;
|
|
2125
|
+
}
|
|
2126
|
+
return result;
|
|
1611
2127
|
}
|
|
1612
2128
|
|
|
1613
2129
|
invalidate(): void {
|
|
@@ -1633,6 +2149,16 @@ export class PersistentRenderScene {
|
|
|
1633
2149
|
const detachedComposition = this.composition?.worlds.includes(world)
|
|
1634
2150
|
? this.composition
|
|
1635
2151
|
: undefined;
|
|
2152
|
+
if (detachedComposition !== undefined) {
|
|
2153
|
+
for (const slot of detachedComposition.projection.slotsSnapshot()) {
|
|
2154
|
+
if (detachedComposition.worlds[slot.worldId] !== world) continue;
|
|
2155
|
+
this.options.instanceCollections?.release(world, slot.entityKey);
|
|
2156
|
+
if (slot.snapshot.skin === undefined) continue;
|
|
2157
|
+
this.options
|
|
2158
|
+
.getSkinPaletteAllocator?.()
|
|
2159
|
+
?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
1636
2162
|
if (detachedComposition !== undefined) this.composition = undefined;
|
|
1637
2163
|
if (detachedComposition !== undefined) this.temporalCapture = undefined;
|
|
1638
2164
|
if (detachedComposition !== undefined) {
|
|
@@ -1661,6 +2187,111 @@ export class PersistentRenderScene {
|
|
|
1661
2187
|
this.gpuStatus = this.options.getDevice === undefined ? 'inactive' : 'rebuild-pending';
|
|
1662
2188
|
}
|
|
1663
2189
|
|
|
2190
|
+
/** Prepare a detached GPU scene without changing the active scene owner. */
|
|
2191
|
+
prepareRecoveryGpuDrivenCandidate(
|
|
2192
|
+
device: RhiDevice,
|
|
2193
|
+
): Result<PersistentGpuDrivenCandidate, RhiError> {
|
|
2194
|
+
const composition = this.composition;
|
|
2195
|
+
if (composition === undefined) {
|
|
2196
|
+
return ok({
|
|
2197
|
+
state: undefined,
|
|
2198
|
+
createRecoveryRoot: (scope) => ({
|
|
2199
|
+
kind: 'scene-table',
|
|
2200
|
+
create: () => {
|
|
2201
|
+
if (!scope.isAlive()) throw new Error('GPU-driven candidate scope is not active.');
|
|
2202
|
+
throw new Error('GPU-driven candidate has no scene resource.');
|
|
2203
|
+
},
|
|
2204
|
+
cleanup: () => undefined,
|
|
2205
|
+
}),
|
|
2206
|
+
publish: () => undefined,
|
|
2207
|
+
discard: () => undefined,
|
|
2208
|
+
release: () => undefined,
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
const created = GpuScene.create(
|
|
2212
|
+
device,
|
|
2213
|
+
Math.max(256, composition.projection.inspect().slotCapacity),
|
|
2214
|
+
);
|
|
2215
|
+
if (!created.ok) return created;
|
|
2216
|
+
if (created.value.status === 'unavailable') {
|
|
2217
|
+
return ok({
|
|
2218
|
+
state: undefined,
|
|
2219
|
+
createRecoveryRoot: (scope) => ({
|
|
2220
|
+
kind: 'scene-table',
|
|
2221
|
+
create: () => {
|
|
2222
|
+
if (!scope.isAlive()) throw new Error('GPU-driven candidate scope is not active.');
|
|
2223
|
+
throw new Error('GPU-driven candidate has no scene resource.');
|
|
2224
|
+
},
|
|
2225
|
+
cleanup: () => undefined,
|
|
2226
|
+
}),
|
|
2227
|
+
publish: () => undefined,
|
|
2228
|
+
discard: () => undefined,
|
|
2229
|
+
release: () => undefined,
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
const scene = created.value.scene;
|
|
2233
|
+
const slots = composition.projection.slotsSnapshot();
|
|
2234
|
+
const rebuilt = scene.rebuild(slots);
|
|
2235
|
+
if (!rebuilt.ok) {
|
|
2236
|
+
scene.dispose();
|
|
2237
|
+
return rebuilt;
|
|
2238
|
+
}
|
|
2239
|
+
const state: PersistentGpuDrivenState = Object.freeze({
|
|
2240
|
+
scene,
|
|
2241
|
+
plan: composition.topology.plan(),
|
|
2242
|
+
slots,
|
|
2243
|
+
});
|
|
2244
|
+
let published = false;
|
|
2245
|
+
let released = false;
|
|
2246
|
+
return ok({
|
|
2247
|
+
state,
|
|
2248
|
+
createRecoveryRoot: (scope) => ({
|
|
2249
|
+
kind: 'scene-table',
|
|
2250
|
+
create: () => {
|
|
2251
|
+
if (!scope.isAlive()) throw new Error('GPU-driven candidate scope is not active.');
|
|
2252
|
+
return scene;
|
|
2253
|
+
},
|
|
2254
|
+
cleanup: () => undefined,
|
|
2255
|
+
}),
|
|
2256
|
+
publish: () => {
|
|
2257
|
+
if (published || released) return;
|
|
2258
|
+
published = true;
|
|
2259
|
+
this.gpuScene = scene;
|
|
2260
|
+
this.gpuOwner = composition.token;
|
|
2261
|
+
this.gpuDevice = device;
|
|
2262
|
+
this.gpuStatus = 'resident';
|
|
2263
|
+
},
|
|
2264
|
+
discard: () => {
|
|
2265
|
+
if (published || released) return;
|
|
2266
|
+
released = true;
|
|
2267
|
+
scene.dispose();
|
|
2268
|
+
},
|
|
2269
|
+
release: () => {
|
|
2270
|
+
if (published || released) return;
|
|
2271
|
+
released = true;
|
|
2272
|
+
scene.dispose();
|
|
2273
|
+
},
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown> {
|
|
2278
|
+
return {
|
|
2279
|
+
kind: 'scene-table',
|
|
2280
|
+
create: () => {
|
|
2281
|
+
const composition = this.composition;
|
|
2282
|
+
if (!scope.isAlive() || composition === undefined) {
|
|
2283
|
+
throw new Error('RenderScene CPU projection is not ready for recovery.');
|
|
2284
|
+
}
|
|
2285
|
+
return Object.freeze({
|
|
2286
|
+
generation: scope.generation,
|
|
2287
|
+
revision: composition.projection.inspect().revision,
|
|
2288
|
+
visibleSlots: composition.projection.slotsSnapshot().length,
|
|
2289
|
+
});
|
|
2290
|
+
},
|
|
2291
|
+
cleanup: () => undefined,
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
|
|
1664
2295
|
setPointsLinesInspections(inspections: readonly PointsLinesInspection[]): void {
|
|
1665
2296
|
this.pointsLinesInspections = inspections.map((inspection) => ({
|
|
1666
2297
|
...inspection,
|
|
@@ -1781,7 +2412,7 @@ export class PersistentRenderScene {
|
|
|
1781
2412
|
worlds: readonly World[],
|
|
1782
2413
|
owner: { readonly cameraOwner: number; readonly resourceOwner: number },
|
|
1783
2414
|
catalogEpoch: number,
|
|
1784
|
-
buildCandidateFrame: (
|
|
2415
|
+
buildCandidateFrame: (request: PersistentRenderCandidateRequest) => ExtractedFrame,
|
|
1785
2416
|
leases?: readonly RenderReadLease[],
|
|
1786
2417
|
preserveVisibilityOnReorder = false,
|
|
1787
2418
|
): ExtractedFrame {
|
|
@@ -1851,6 +2482,10 @@ export class PersistentRenderScene {
|
|
|
1851
2482
|
transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
|
|
1852
2483
|
const topology = new BatchTopology();
|
|
1853
2484
|
topology.rebuild(projection.slotsSnapshot());
|
|
2485
|
+
const skinConsumersByJoint = new Map<string, Set<number>>();
|
|
2486
|
+
for (const snapshot of stableCandidateFrame.renderables) {
|
|
2487
|
+
if (snapshot.skinJointEntities !== undefined) addSkinConsumer(skinConsumersByJoint, snapshot);
|
|
2488
|
+
}
|
|
1854
2489
|
const token = {};
|
|
1855
2490
|
const entry: PersistentCompositionEntry = {
|
|
1856
2491
|
token,
|
|
@@ -1880,8 +2515,17 @@ export class PersistentRenderScene {
|
|
|
1880
2515
|
leases === undefined || leases.length !== worlds.length
|
|
1881
2516
|
? []
|
|
1882
2517
|
: worlds.map(createGlobalTransformChangeQuery),
|
|
2518
|
+
renderChangeQueries:
|
|
2519
|
+
leases === undefined || leases.length !== worlds.length
|
|
2520
|
+
? []
|
|
2521
|
+
: worlds.map(createRenderChangeQueries),
|
|
2522
|
+
dispatchBySlot: dispatchEntriesBySlot(stableCandidateFrame, projection),
|
|
2523
|
+
dispatchRevision: 0,
|
|
2524
|
+
dispatchCache: undefined,
|
|
2525
|
+
skinConsumersByJoint,
|
|
2526
|
+
pendingRenderableEntitiesByWorld: worlds.map(() => new Set<number>()),
|
|
2527
|
+
unavailableSlots: new Set<number>(),
|
|
1883
2528
|
catalogEpoch,
|
|
1884
|
-
rigidOnly: candidateFrame.renderables.every((renderable) => renderable.skin === undefined),
|
|
1885
2529
|
probeProjection: new ProbeBlendSceneProjection(),
|
|
1886
2530
|
temporalDemanded: frameDemandsTemporal(candidateFrame),
|
|
1887
2531
|
gpuDrivenSceneRequired: true,
|
|
@@ -1941,10 +2585,23 @@ export class PersistentRenderScene {
|
|
|
1941
2585
|
*/
|
|
1942
2586
|
private deriveFramePlan(
|
|
1943
2587
|
entry: PersistentCompositionEntry,
|
|
1944
|
-
buildCandidateFrame: (
|
|
2588
|
+
buildCandidateFrame: (request: PersistentRenderCandidateRequest) => ExtractedFrame,
|
|
1945
2589
|
): ExtractedFrame {
|
|
1946
2590
|
const resourceFrame = buildCandidateFrame('none');
|
|
1947
|
-
const
|
|
2591
|
+
const cachedDispatch = entry.dispatchCache;
|
|
2592
|
+
const dispatch: DispatchEntry[] = [
|
|
2593
|
+
...(cachedDispatch?.revision === entry.dispatchRevision
|
|
2594
|
+
? cachedDispatch.value
|
|
2595
|
+
: dispatchForProjection(entry.dispatchBySlot, entry.projection, entry.unavailableSlots)),
|
|
2596
|
+
];
|
|
2597
|
+
if (cachedDispatch?.revision !== entry.dispatchRevision) {
|
|
2598
|
+
entry.dispatchCache = { revision: entry.dispatchRevision, value: dispatch };
|
|
2599
|
+
}
|
|
2600
|
+
const candidateFrame: ExtractedFrame = {
|
|
2601
|
+
...resourceFrame,
|
|
2602
|
+
...entry.structure,
|
|
2603
|
+
dispatch,
|
|
2604
|
+
};
|
|
1948
2605
|
entry.temporalDemanded = frameDemandsTemporal(candidateFrame);
|
|
1949
2606
|
entry.projection.setTemporalTracking(entry.temporalDemanded);
|
|
1950
2607
|
this.projectProbes(entry, candidateFrame);
|
|
@@ -1953,9 +2610,18 @@ export class PersistentRenderScene {
|
|
|
1953
2610
|
const candidates = entry.temporalDemanded
|
|
1954
2611
|
? materialized.map((snapshot) => entry.projection.withTemporal(snapshot))
|
|
1955
2612
|
: materialized;
|
|
2613
|
+
const drawableCandidates =
|
|
2614
|
+
entry.unavailableSlots.size === 0
|
|
2615
|
+
? candidates
|
|
2616
|
+
: candidates.map((snapshot) => {
|
|
2617
|
+
const slot = entry.projection.slot(snapshot.worldId, snapshot.entityKey);
|
|
2618
|
+
return slot !== undefined && entry.unavailableSlots.has(slot.slot)
|
|
2619
|
+
? { ...snapshot, authorVisible: false }
|
|
2620
|
+
: snapshot;
|
|
2621
|
+
});
|
|
1956
2622
|
const frameWithProbes = attachProbeRecords(
|
|
1957
|
-
{ ...candidateFrame, renderables:
|
|
1958
|
-
|
|
2623
|
+
{ ...candidateFrame, renderables: drawableCandidates },
|
|
2624
|
+
drawableCandidates,
|
|
1959
2625
|
entry.projection.slotsSnapshot(),
|
|
1960
2626
|
entry.probeProjection,
|
|
1961
2627
|
);
|