@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
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
RhiError,
|
|
11
11
|
type RhiRenderPassEncoder,
|
|
12
12
|
} from '@forgeax/engine-rhi';
|
|
13
|
-
import type { PassKind, PrimitiveTopology } from '@forgeax/engine-types';
|
|
13
|
+
import type { ParamSchemaEntry, PassKind, PrimitiveTopology } from '@forgeax/engine-types';
|
|
14
14
|
import { GpuBuffer } from '../gpu-resource';
|
|
15
15
|
import {
|
|
16
16
|
GPU_BUFFER_USAGE_COPY_DST,
|
|
@@ -19,11 +19,20 @@ import {
|
|
|
19
19
|
} from '../gpu-usage';
|
|
20
20
|
import { createHdrpSkinUnifiedBindGroup, getOrCreateHdrpBuffers } from '../hdrp-buffers';
|
|
21
21
|
import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
|
|
22
|
+
import {
|
|
23
|
+
type InstanceCollectionFailureCode,
|
|
24
|
+
recordInstanceFailure,
|
|
25
|
+
recordInstanceResidency,
|
|
26
|
+
} from '../instances';
|
|
22
27
|
import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from '../materials';
|
|
23
28
|
import {
|
|
24
29
|
createPbrSkinMeshBindGroupEntries,
|
|
30
|
+
isCanonicalStandardPbrMaterialShader,
|
|
25
31
|
isStandardPbrMaterialShader,
|
|
32
|
+
isStandardPbrSkinMaterialShader,
|
|
33
|
+
materialBindGroupLayoutIdentity,
|
|
26
34
|
pbrSkinMeshDynamicOffsets,
|
|
35
|
+
physicalTextureFields,
|
|
27
36
|
SKIN_MATERIAL_SHADER_ID,
|
|
28
37
|
} from '../pbr-pipeline';
|
|
29
38
|
import {
|
|
@@ -35,13 +44,17 @@ import {
|
|
|
35
44
|
import type { PointsLinesRecordPlan } from '../points-lines/record';
|
|
36
45
|
import { POINTS_LINES_MATERIAL_SHADER_ID } from '../points-lines/record';
|
|
37
46
|
import type { RenderRecordPhase } from '../render-contract';
|
|
38
|
-
import type { MaterialSnapshot } from '../render-system-extract';
|
|
47
|
+
import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
|
|
39
48
|
import {
|
|
40
49
|
PROBE_BLEND_RECORD_BYTE_SIZE,
|
|
41
50
|
PROBE_BLEND_RECORD_STRIDE,
|
|
42
51
|
probeBlendRecordOffset,
|
|
43
52
|
} from '../scene/probe-blend-record';
|
|
44
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
getOpaqueResourceIdentity,
|
|
55
|
+
instanceCollectionCacheKey,
|
|
56
|
+
worldEntityKey,
|
|
57
|
+
} from './frame-snapshot';
|
|
45
58
|
import {
|
|
46
59
|
geometryRenderStateForTopology,
|
|
47
60
|
isEntityFullyTransparent,
|
|
@@ -50,6 +63,7 @@ import {
|
|
|
50
63
|
} from './main-pass-material';
|
|
51
64
|
import {
|
|
52
65
|
getOrCreateFromChain,
|
|
66
|
+
INSTANCE_STORAGE_STRIDE_FLOATS,
|
|
53
67
|
INSTANCE_UBO_FULL_ARRAY_BYTES,
|
|
54
68
|
MAX_UNIFORM_INSTANCES,
|
|
55
69
|
MESH_PER_ENTITY_STRIDE,
|
|
@@ -69,12 +83,36 @@ type GeometryInstanceDraw = {
|
|
|
69
83
|
readonly instanceCount: number;
|
|
70
84
|
};
|
|
71
85
|
|
|
86
|
+
type InstanceUploadResult =
|
|
87
|
+
| {
|
|
88
|
+
readonly ok: true;
|
|
89
|
+
readonly ranges: readonly { readonly start: number; readonly end: number }[];
|
|
90
|
+
readonly bytes: number;
|
|
91
|
+
}
|
|
92
|
+
| {
|
|
93
|
+
readonly ok: false;
|
|
94
|
+
readonly ranges: readonly { readonly start: number; readonly end: number }[];
|
|
95
|
+
readonly bytes: number;
|
|
96
|
+
};
|
|
97
|
+
|
|
72
98
|
type GeometryBindingState = {
|
|
73
99
|
pipeline: RenderPipeline | null;
|
|
74
100
|
instancesBuffer: Buffer | null;
|
|
75
101
|
probeOffset: number;
|
|
76
102
|
};
|
|
77
103
|
|
|
104
|
+
function materialDiagnosticsEnabled(): boolean {
|
|
105
|
+
if (typeof globalThis !== 'object' || globalThis === null || !('process' in globalThis)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
const processValue = (
|
|
109
|
+
globalThis as {
|
|
110
|
+
readonly process?: { readonly env?: Record<string, string | undefined> };
|
|
111
|
+
}
|
|
112
|
+
).process;
|
|
113
|
+
return processValue?.env?.FORGEAX_MATERIAL_DIAGNOSTICS === '1';
|
|
114
|
+
}
|
|
115
|
+
|
|
78
116
|
type MaterialPipelineLookup = {
|
|
79
117
|
readonly materialShaderId: string;
|
|
80
118
|
readonly isHdrTarget: boolean;
|
|
@@ -94,6 +132,121 @@ type GeometryRecordProfileSegment = 'material-bind-groups' | 'pipeline-selection
|
|
|
94
132
|
|
|
95
133
|
type GeometryRenderState = ReturnType<typeof geometryRenderStateForTopology>;
|
|
96
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Select the transmission shader axis from authored material data, not the
|
|
137
|
+
* canonical boot schema. The latter reserves transmission fields for the
|
|
138
|
+
* shared Standard ABI even when a material remains base-only.
|
|
139
|
+
*/
|
|
140
|
+
export function requestsStandardTransmissionVariant(
|
|
141
|
+
material: Pick<MaterialSnapshot, 'materialShaderId' | 'paramSnapshot'>,
|
|
142
|
+
): boolean {
|
|
143
|
+
return (
|
|
144
|
+
material.materialShaderId === 'forgeax::default-standard-pbr' &&
|
|
145
|
+
material.paramSnapshot?.transmission !== undefined
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function recordIblPipelineBinding(
|
|
150
|
+
c: _InternalRenderPipelineContext,
|
|
151
|
+
material: MaterialSnapshot,
|
|
152
|
+
pipeline: RenderPipeline,
|
|
153
|
+
materialGroup: BindGroup,
|
|
154
|
+
): void {
|
|
155
|
+
const receipt = c.frameState.iblBindingInspection;
|
|
156
|
+
if (receipt === undefined || !isStandardPbrMaterialShader(material.materialShaderId)) return;
|
|
157
|
+
const materialBgl =
|
|
158
|
+
c.runtime.getMaterialBindGroupLayout?.(
|
|
159
|
+
material.materialShaderId ?? 'forgeax::default-standard-pbr',
|
|
160
|
+
) ?? c.pipelineState.materialBindGroupLayout;
|
|
161
|
+
c.frameState.iblBindingInspection = {
|
|
162
|
+
...receipt,
|
|
163
|
+
pipeline: {
|
|
164
|
+
pipelineIdentity: getOpaqueResourceIdentity(pipeline as object),
|
|
165
|
+
effectiveMaterialLayoutIdentity: effectiveMaterialLayoutIdentity(
|
|
166
|
+
material.materialShaderId ?? 'forgeax::default-standard-pbr',
|
|
167
|
+
material.materialParamSchema,
|
|
168
|
+
),
|
|
169
|
+
materialBglIdentity: getOpaqueResourceIdentity(materialBgl as object),
|
|
170
|
+
bindGroupIdentity: getOpaqueResourceIdentity(materialGroup as object),
|
|
171
|
+
drawFrameId: c.frameState.frameNumber,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
const materialReceipt = c.frameState.iblBindingInspection.material;
|
|
175
|
+
const errors: string[] = [];
|
|
176
|
+
if (
|
|
177
|
+
materialReceipt !== undefined &&
|
|
178
|
+
materialReceipt.materialBglIdentity !== getOpaqueResourceIdentity(materialBgl as object)
|
|
179
|
+
) {
|
|
180
|
+
errors.push('material-pipeline-bgl-identity-mismatch');
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
materialReceipt !== undefined &&
|
|
184
|
+
materialReceipt.bindGroupIdentity !== getOpaqueResourceIdentity(materialGroup as object)
|
|
185
|
+
) {
|
|
186
|
+
errors.push('material-bind-group-identity-mismatch');
|
|
187
|
+
}
|
|
188
|
+
if (materialReceipt !== undefined) {
|
|
189
|
+
const expectedResourceIdentities = [
|
|
190
|
+
receipt.resources.irradiance.viewIdentity,
|
|
191
|
+
receipt.resources.irradiance.samplerIdentity,
|
|
192
|
+
receipt.resources.prefilter.viewIdentity,
|
|
193
|
+
receipt.resources.prefilter.samplerIdentity,
|
|
194
|
+
receipt.resources.brdfLut.viewIdentity,
|
|
195
|
+
receipt.resources.brdfLut.samplerIdentity,
|
|
196
|
+
receipt.resources.intensityBufferIdentity,
|
|
197
|
+
];
|
|
198
|
+
const actualResourceIdentities = materialReceipt.entries
|
|
199
|
+
.filter((entry) => entry.binding >= materialReceipt.skylightBindingStart)
|
|
200
|
+
.slice(0, expectedResourceIdentities.length)
|
|
201
|
+
.map((entry) => entry.resourceIdentity);
|
|
202
|
+
if (
|
|
203
|
+
actualResourceIdentities.length !== expectedResourceIdentities.length ||
|
|
204
|
+
actualResourceIdentities.some(
|
|
205
|
+
(identity, index) => identity !== expectedResourceIdentities[index],
|
|
206
|
+
)
|
|
207
|
+
) {
|
|
208
|
+
errors.push('material-ibl-resource-identity-mismatch');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
c.frameState.iblBindingInspection = {
|
|
212
|
+
...c.frameState.iblBindingInspection,
|
|
213
|
+
status: errors.length === 0 ? 'binding-chain-consistent' : 'binding-chain-mismatch',
|
|
214
|
+
errors,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const SHARED_BOOT_MATERIAL_SHADER_IDS = new Set([
|
|
219
|
+
'forgeax::default-unlit',
|
|
220
|
+
'forgeax::default-shadow-caster',
|
|
221
|
+
'forgeax::sprite',
|
|
222
|
+
'forgeax::sprite-lit',
|
|
223
|
+
'forgeax::default-sprite',
|
|
224
|
+
'forgeax::msdf-text',
|
|
225
|
+
'forgeax::default-standard-pbr',
|
|
226
|
+
'forgeax::pbr-skin',
|
|
227
|
+
'forgeax::default-standard-pbr-skin',
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Returns the layout identity that can distinguish a material PSO in the
|
|
232
|
+
* record cache. Shared boot shaders keep the boot layout identity even when a
|
|
233
|
+
* manifest exposes a schema; Standard PBR only opts into a schema-derived
|
|
234
|
+
* identity when authored clearcoat texture bindings extend that layout.
|
|
235
|
+
*/
|
|
236
|
+
export function effectiveMaterialLayoutIdentity(
|
|
237
|
+
materialShaderId: string,
|
|
238
|
+
materialParamSchema?: readonly ParamSchemaEntry[],
|
|
239
|
+
): string | undefined {
|
|
240
|
+
if (materialParamSchema === undefined) return undefined;
|
|
241
|
+
const hasStandardPhysicalMaps =
|
|
242
|
+
isStandardPbrMaterialShader(materialShaderId) &&
|
|
243
|
+
physicalTextureFields(materialParamSchema).length > 0;
|
|
244
|
+
if (SHARED_BOOT_MATERIAL_SHADER_IDS.has(materialShaderId) && !hasStandardPhysicalMaps) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
return materialBindGroupLayoutIdentity(materialShaderId, materialParamSchema);
|
|
248
|
+
}
|
|
249
|
+
|
|
97
250
|
/**
|
|
98
251
|
* Resolve the depth contract for a geometry pass without changing the base
|
|
99
252
|
* material state. Temporal scene-data shares the main pass depth attachment:
|
|
@@ -228,6 +381,7 @@ export function recordGeometryDraws(
|
|
|
228
381
|
viewBindGroup,
|
|
229
382
|
viewBindGroupDynamicOffset = 0,
|
|
230
383
|
} = c;
|
|
384
|
+
const diagnosticsEnabled = materialDiagnosticsEnabled();
|
|
231
385
|
const colorFormatOverride = c.transparentColorFormat as GPUTextureFormat | undefined;
|
|
232
386
|
// The prepared Standard topology is the frame authority. A bind-group
|
|
233
387
|
// identity is only a resource result; using it to infer the shader lane
|
|
@@ -587,7 +741,22 @@ export function recordGeometryDraws(
|
|
|
587
741
|
// and walks 0, 1536, 3072, ... on the storage path.
|
|
588
742
|
const skinAllocator = pipelineState.skinPaletteAllocator;
|
|
589
743
|
const skinSlice = entry.source.skin;
|
|
590
|
-
|
|
744
|
+
// Authored Standard skin aliases deliberately publish the physical root
|
|
745
|
+
// with only their STORAGE_BUFFER capability axis. Their composed WGSL is
|
|
746
|
+
// the direct-lighting/palette (`skin`) ABI, even when the frame's
|
|
747
|
+
// canonical Standard lane uses clustered lighting. Only the canonical
|
|
748
|
+
// engine skin artifact can consume the HDRP unified skin group(2) layout;
|
|
749
|
+
// selecting it from the frame topology alone would bind
|
|
750
|
+
// `hdrp-skin-unified-bg-group2` to a three-entry URP skin PSO.
|
|
751
|
+
const clusteredSkin =
|
|
752
|
+
clusteredLighting &&
|
|
753
|
+
entry.source.materials.every((material) => {
|
|
754
|
+
const shaderId = material.materialShaderId;
|
|
755
|
+
return (
|
|
756
|
+
!isStandardPbrSkinMaterialShader(shaderId) ||
|
|
757
|
+
isCanonicalStandardPbrMaterialShader(shaderId)
|
|
758
|
+
);
|
|
759
|
+
});
|
|
591
760
|
const skinMeshBindGroupLayout = clusteredSkin
|
|
592
761
|
? pipelineState.hdrpSkinMeshBindGroupLayout
|
|
593
762
|
: pipelineState.pbrSkinMeshBindGroupLayout;
|
|
@@ -734,6 +903,19 @@ export function recordGeometryDraws(
|
|
|
734
903
|
// the async PSO compile resolves the cache hits and the next frame
|
|
735
904
|
// routes the skin BG + skin pipeline together. Mirrors the uniform
|
|
736
905
|
// null skip-draw shape (M6-T1, charter P3 explicit failure).
|
|
906
|
+
if (diagnosticsEnabled) {
|
|
907
|
+
console.error(
|
|
908
|
+
`[render-material] skin draw skipped: ${JSON.stringify({
|
|
909
|
+
entityKey: entry.source.entityKey,
|
|
910
|
+
materialHandle: entry.source.material.materialHandle,
|
|
911
|
+
reason: 'skin-pipeline-unavailable',
|
|
912
|
+
skinResourcesReady: skinResources !== null,
|
|
913
|
+
skinPsoReady: skinPsoProbe !== null,
|
|
914
|
+
paletteBufferReady: skinSlice?.buffer !== undefined,
|
|
915
|
+
paletteByteOffset: skinSlice?.byteOffset,
|
|
916
|
+
})}`,
|
|
917
|
+
);
|
|
918
|
+
}
|
|
737
919
|
continue;
|
|
738
920
|
}
|
|
739
921
|
// feat-20260520-2d-sprite-layer-mvp M-3 / w25 (@fallback sprite
|
|
@@ -828,6 +1010,26 @@ export function recordGeometryDraws(
|
|
|
828
1010
|
// injection, and Skylight merge; deduped cross-entity via the
|
|
829
1011
|
// shaderId-outer `materialBgShared` cache.
|
|
830
1012
|
const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
|
|
1013
|
+
const processValue = (
|
|
1014
|
+
globalThis as {
|
|
1015
|
+
process?: { env?: Record<string, string | undefined> };
|
|
1016
|
+
}
|
|
1017
|
+
).process;
|
|
1018
|
+
if (
|
|
1019
|
+
processValue?.env?.FORGEAX_MATERIAL_PIPELINE_DIAGNOSTICS === '1' &&
|
|
1020
|
+
(submeshMaterial.materialShaderId === 'forgeax::default-standard-pbr' ||
|
|
1021
|
+
submeshMaterial.materialShaderId === 'forgeax::pbr-skin')
|
|
1022
|
+
) {
|
|
1023
|
+
console.error(
|
|
1024
|
+
`[render-material] draw receipt: ${JSON.stringify({
|
|
1025
|
+
entityIndex: entry.renderableIndex,
|
|
1026
|
+
materialSlot,
|
|
1027
|
+
materialHandle: submeshMaterial.materialHandle,
|
|
1028
|
+
clearcoat: submeshMaterial.paramSnapshot?.clearcoat,
|
|
1029
|
+
shader: submeshMaterial.materialShaderId,
|
|
1030
|
+
})}`,
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
831
1033
|
perSubmeshBg =
|
|
832
1034
|
profilePhase === undefined
|
|
833
1035
|
? resolveMaterialBindGroup(
|
|
@@ -849,7 +1051,9 @@ export function recordGeometryDraws(
|
|
|
849
1051
|
const smTopology = sm.topology;
|
|
850
1052
|
const smMaterialShaderId =
|
|
851
1053
|
entry.source.skin !== undefined
|
|
852
|
-
?
|
|
1054
|
+
? isStandardPbrSkinMaterialShader(submeshMaterial.materialShaderId)
|
|
1055
|
+
? submeshMaterial.materialShaderId
|
|
1056
|
+
: SKIN_MATERIAL_SHADER_ID
|
|
853
1057
|
: submeshMaterial.materialShaderId;
|
|
854
1058
|
const probeBlendAvailable =
|
|
855
1059
|
probeBlendRecordAvailable && isStandardPbrMaterialShader(smMaterialShaderId);
|
|
@@ -930,6 +1134,11 @@ export function recordGeometryDraws(
|
|
|
930
1134
|
hasVertexColor,
|
|
931
1135
|
probeBlendAvailable,
|
|
932
1136
|
);
|
|
1137
|
+
const transmissionAvailable = requestsStandardTransmissionVariant(submeshMaterial);
|
|
1138
|
+
const materialCapabilityVariantSet =
|
|
1139
|
+
transmissionAvailable && !capabilityVariantSet.includes('TRANSMISSION_AVAILABLE=')
|
|
1140
|
+
? `${capabilityVariantSet === '' ? '' : `${capabilityVariantSet}+`}TRANSMISSION_AVAILABLE=true`
|
|
1141
|
+
: capabilityVariantSet;
|
|
933
1142
|
// Sprite's base request must retain the boot-selected artifact. Only
|
|
934
1143
|
// SpriteInstances uses the explicit empty key for the
|
|
935
1144
|
// PER_INSTANCE_REGION=true variant; synthesizing the generic PBR
|
|
@@ -940,7 +1149,7 @@ export function recordGeometryDraws(
|
|
|
940
1149
|
? ''
|
|
941
1150
|
: undefined
|
|
942
1151
|
: entry.variantSet === undefined
|
|
943
|
-
?
|
|
1152
|
+
? materialCapabilityVariantSet
|
|
944
1153
|
: capabilityVariantSet === ''
|
|
945
1154
|
? entry.variantSet
|
|
946
1155
|
: `${capabilityVariantSet}+${entry.variantSet}`;
|
|
@@ -973,6 +1182,16 @@ export function recordGeometryDraws(
|
|
|
973
1182
|
}
|
|
974
1183
|
|
|
975
1184
|
if (smPipelineHandle === null) {
|
|
1185
|
+
if (diagnosticsEnabled) {
|
|
1186
|
+
console.error(
|
|
1187
|
+
`[render-material] draw skipped: ${JSON.stringify({
|
|
1188
|
+
entityKey: entry.source.entityKey,
|
|
1189
|
+
materialHandle: submeshMaterial.materialHandle,
|
|
1190
|
+
shader: smMaterialShaderId,
|
|
1191
|
+
reason: 'pipeline-unavailable',
|
|
1192
|
+
})}`,
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
976
1195
|
continue;
|
|
977
1196
|
}
|
|
978
1197
|
// The probe record is an ABI extension of the built-in Standard PBR
|
|
@@ -1003,6 +1222,31 @@ export function recordGeometryDraws(
|
|
|
1003
1222
|
if (selectedGroup === null) continue;
|
|
1004
1223
|
group2BindGroup = selectedGroup;
|
|
1005
1224
|
}
|
|
1225
|
+
if (diagnosticsEnabled && submeshMaterial.textureHandles !== undefined) {
|
|
1226
|
+
console.error(
|
|
1227
|
+
`[render-material] draw submitted: ${JSON.stringify({
|
|
1228
|
+
entityKey: entry.source.entityKey,
|
|
1229
|
+
materialHandle: submeshMaterial.materialHandle,
|
|
1230
|
+
shader: smMaterialShaderId,
|
|
1231
|
+
textureHandles: [...submeshMaterial.textureHandles.entries()].map(
|
|
1232
|
+
([field, handle]) => ({
|
|
1233
|
+
field,
|
|
1234
|
+
handle,
|
|
1235
|
+
}),
|
|
1236
|
+
),
|
|
1237
|
+
skin: isSkinEntry
|
|
1238
|
+
? {
|
|
1239
|
+
resourcesReady: skinResources !== null,
|
|
1240
|
+
psoReady: skinPsoProbe !== null,
|
|
1241
|
+
paletteBufferReady: skinSlice?.buffer !== undefined,
|
|
1242
|
+
paletteByteOffset: skinSlice?.byteOffset,
|
|
1243
|
+
dynamicOffsets: [...group2DynamicOffsets],
|
|
1244
|
+
}
|
|
1245
|
+
: undefined,
|
|
1246
|
+
pipelineReady: true,
|
|
1247
|
+
})}`,
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1006
1250
|
pass.setBindGroup(2, group2BindGroup, group2DynamicOffsets);
|
|
1007
1251
|
|
|
1008
1252
|
if (profilePhase === undefined) {
|
|
@@ -1034,6 +1278,9 @@ export function recordGeometryDraws(
|
|
|
1034
1278
|
),
|
|
1035
1279
|
);
|
|
1036
1280
|
}
|
|
1281
|
+
if (diagnosticsEnabled && perSubmeshBg !== null) {
|
|
1282
|
+
recordIblPipelineBinding(c, submeshMaterial, smPipelineHandle, perSubmeshBg);
|
|
1283
|
+
}
|
|
1037
1284
|
}
|
|
1038
1285
|
}
|
|
1039
1286
|
}
|
|
@@ -1043,8 +1290,285 @@ export function recordGeometryDraws(
|
|
|
1043
1290
|
*
|
|
1044
1291
|
* @internal
|
|
1045
1292
|
*/
|
|
1046
|
-
function
|
|
1047
|
-
c:
|
|
1293
|
+
function uploadInstanceRanges(
|
|
1294
|
+
c: Pick<
|
|
1295
|
+
_InternalRenderPipelineContext,
|
|
1296
|
+
'runtime' | 'pipelineState' | 'frameState' | 'bindGroupCounts'
|
|
1297
|
+
>,
|
|
1298
|
+
inst: NonNullable<RenderableSnapshot['instances']>,
|
|
1299
|
+
buffer: Buffer,
|
|
1300
|
+
ranges: readonly { readonly start: number; readonly end: number }[],
|
|
1301
|
+
storageBacked: boolean,
|
|
1302
|
+
sourceOffsetInstances = 0,
|
|
1303
|
+
): InstanceUploadResult {
|
|
1304
|
+
const strideBytes =
|
|
1305
|
+
(storageBacked ? INSTANCE_STORAGE_STRIDE_FLOATS : 16) * Float32Array.BYTES_PER_ELEMENT;
|
|
1306
|
+
const uploadedRanges: { start: number; end: number }[] = [];
|
|
1307
|
+
let uploadedBytes = 0;
|
|
1308
|
+
for (const range of ranges) {
|
|
1309
|
+
const start = Math.max(0, Math.min(inst.instanceCount, range.start));
|
|
1310
|
+
const end = Math.max(start, Math.min(inst.instanceCount, range.end));
|
|
1311
|
+
if (end <= start) continue;
|
|
1312
|
+
const source = inst.transforms.subarray(
|
|
1313
|
+
(sourceOffsetInstances + start) * 16,
|
|
1314
|
+
(sourceOffsetInstances + end) * 16,
|
|
1315
|
+
);
|
|
1316
|
+
const payload = storageBacked ? packInstanceStorageBuffer(source) : source;
|
|
1317
|
+
const written = c.runtime.device.queue.writeBuffer(buffer, start * strideBytes, payload);
|
|
1318
|
+
if (!written.ok) {
|
|
1319
|
+
c.runtime.errorRegistry.fire(written.error);
|
|
1320
|
+
return { ok: false, ranges: uploadedRanges, bytes: uploadedBytes };
|
|
1321
|
+
}
|
|
1322
|
+
uploadedRanges.push({ start, end });
|
|
1323
|
+
uploadedBytes += payload.byteLength;
|
|
1324
|
+
}
|
|
1325
|
+
return { ok: true, ranges: uploadedRanges, bytes: uploadedBytes };
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
export function reportInstanceResidency(
|
|
1329
|
+
c: Pick<
|
|
1330
|
+
_InternalRenderPipelineContext,
|
|
1331
|
+
'runtime' | 'pipelineState' | 'frameState' | 'bindGroupCounts'
|
|
1332
|
+
>,
|
|
1333
|
+
inst: NonNullable<RenderableSnapshot['instances']>,
|
|
1334
|
+
input: {
|
|
1335
|
+
readonly lane: 'direct-storage' | 'chunked-storage' | 'direct-uniform' | 'chunked-uniform';
|
|
1336
|
+
readonly requestedBytes: number;
|
|
1337
|
+
readonly supportedBytes: number | undefined;
|
|
1338
|
+
readonly uploadRanges: readonly { readonly start: number; readonly end: number }[];
|
|
1339
|
+
readonly uploadedBytes: number;
|
|
1340
|
+
},
|
|
1341
|
+
): void {
|
|
1342
|
+
if (inst.collectionId === undefined || c.frameState.instanceResidency === undefined) return;
|
|
1343
|
+
recordInstanceResidency(c.frameState.instanceResidency, {
|
|
1344
|
+
collectionId: inst.collectionId,
|
|
1345
|
+
frameNumber: c.frameState.frameNumber,
|
|
1346
|
+
residentGeneration: c.runtime.deviceScope.generation,
|
|
1347
|
+
lane: input.lane,
|
|
1348
|
+
requestedBytes: input.requestedBytes,
|
|
1349
|
+
supportedBytes: input.supportedBytes,
|
|
1350
|
+
uploadRanges: input.uploadRanges,
|
|
1351
|
+
uploadedBytes: input.uploadedBytes,
|
|
1352
|
+
backend: c.runtime.device.caps.backendKind,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function reportInstanceFailure(
|
|
1357
|
+
c: Pick<
|
|
1358
|
+
_InternalRenderPipelineContext,
|
|
1359
|
+
'runtime' | 'pipelineState' | 'frameState' | 'bindGroupCounts'
|
|
1360
|
+
>,
|
|
1361
|
+
inst: NonNullable<RenderableSnapshot['instances']>,
|
|
1362
|
+
error: {
|
|
1363
|
+
readonly code: InstanceCollectionFailureCode;
|
|
1364
|
+
readonly expected: string;
|
|
1365
|
+
readonly hint: string;
|
|
1366
|
+
},
|
|
1367
|
+
requestedBytes: number,
|
|
1368
|
+
supportedBytes: number | undefined,
|
|
1369
|
+
): void {
|
|
1370
|
+
if (inst.collectionId === undefined || c.frameState.instanceResidency === undefined) return;
|
|
1371
|
+
recordInstanceFailure(c.frameState.instanceResidency, {
|
|
1372
|
+
collectionId: inst.collectionId,
|
|
1373
|
+
code: error.code,
|
|
1374
|
+
expected: error.expected,
|
|
1375
|
+
hint: error.hint,
|
|
1376
|
+
facts: {
|
|
1377
|
+
requestedBytes,
|
|
1378
|
+
supportedBytes,
|
|
1379
|
+
backend: c.runtime.device.caps.backendKind,
|
|
1380
|
+
owner: 'renderer.instances',
|
|
1381
|
+
cause: error.code,
|
|
1382
|
+
recovery: error.hint,
|
|
1383
|
+
},
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
function clearInstanceChunksForOwner(
|
|
1388
|
+
c: Pick<
|
|
1389
|
+
_InternalRenderPipelineContext,
|
|
1390
|
+
'runtime' | 'pipelineState' | 'frameState' | 'bindGroupCounts'
|
|
1391
|
+
>,
|
|
1392
|
+
ownerKey: number,
|
|
1393
|
+
): void {
|
|
1394
|
+
const chunks = c.frameState.instanceBufferChunks;
|
|
1395
|
+
if (chunks === undefined) return;
|
|
1396
|
+
const prefix = `${ownerKey}:`;
|
|
1397
|
+
for (const [key, entry] of chunks.entries()) {
|
|
1398
|
+
if (!key.startsWith(prefix)) continue;
|
|
1399
|
+
if (!entry.buffer.isDestroyed) {
|
|
1400
|
+
const destroyed = entry.buffer.destroy();
|
|
1401
|
+
if (!destroyed.ok) c.runtime.errorRegistry.fire(destroyed.error);
|
|
1402
|
+
}
|
|
1403
|
+
chunks.delete(key);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
export function resolveStorageInstanceChunks(
|
|
1408
|
+
c: Pick<
|
|
1409
|
+
_InternalRenderPipelineContext,
|
|
1410
|
+
'runtime' | 'pipelineState' | 'frameState' | 'bindGroupCounts'
|
|
1411
|
+
>,
|
|
1412
|
+
entry: _InternalRenderPipelineContext['validatedOrdered'][number],
|
|
1413
|
+
inst: NonNullable<RenderableSnapshot['instances']>,
|
|
1414
|
+
cap: number,
|
|
1415
|
+
probeBuffer: Buffer | undefined,
|
|
1416
|
+
probeOffset: number,
|
|
1417
|
+
): readonly GeometryInstanceDraw[] | null {
|
|
1418
|
+
const { runtime, frameState } = c;
|
|
1419
|
+
const strideBytes = INSTANCE_STORAGE_STRIDE_FLOATS * Float32Array.BYTES_PER_ELEMENT;
|
|
1420
|
+
const chunkCapacity = Math.floor(cap / strideBytes);
|
|
1421
|
+
if (chunkCapacity < 1) {
|
|
1422
|
+
reportInstanceFailure(
|
|
1423
|
+
c,
|
|
1424
|
+
inst,
|
|
1425
|
+
{
|
|
1426
|
+
code: 'limit-exceeded',
|
|
1427
|
+
expected: `maxStorageBufferBindingSize (${cap}) >= ${strideBytes}`,
|
|
1428
|
+
hint: 'use a backend with storage-buffer bindings large enough for one InstanceData record',
|
|
1429
|
+
},
|
|
1430
|
+
inst.instanceCount * strideBytes,
|
|
1431
|
+
cap,
|
|
1432
|
+
);
|
|
1433
|
+
runtime.errorRegistry.fire(
|
|
1434
|
+
new RhiError({
|
|
1435
|
+
code: 'limit-exceeded',
|
|
1436
|
+
expected: `maxStorageBufferBindingSize (${cap}) >= ${strideBytes}`,
|
|
1437
|
+
hint: 'use a backend with storage-buffer bindings large enough for one InstanceData record',
|
|
1438
|
+
detail: { maxStorageBufferBindingSize: cap, requestedBytes: strideBytes },
|
|
1439
|
+
}),
|
|
1440
|
+
);
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
const ownerKey = instanceCollectionCacheKey(entry.source.worldId, inst);
|
|
1444
|
+
const chunks = frameState.instanceBufferChunks;
|
|
1445
|
+
const activeKeys = new Set<string>();
|
|
1446
|
+
const draws: GeometryInstanceDraw[] = [];
|
|
1447
|
+
const uploadedRanges: { start: number; end: number }[] = [];
|
|
1448
|
+
let uploadedBytes = 0;
|
|
1449
|
+
for (let start = 0; start < inst.instanceCount; start += chunkCapacity) {
|
|
1450
|
+
const end = Math.min(inst.instanceCount, start + chunkCapacity);
|
|
1451
|
+
const count = end - start;
|
|
1452
|
+
const chunkKey = `${ownerKey}:${start}`;
|
|
1453
|
+
activeKeys.add(chunkKey);
|
|
1454
|
+
// Keep stable-resident checks arithmetic-only; packing is deferred to the
|
|
1455
|
+
// upload path below, where a new or dirty range actually needs payload.
|
|
1456
|
+
const payloadBytes = count * strideBytes;
|
|
1457
|
+
const previous = chunks?.get(chunkKey);
|
|
1458
|
+
let active =
|
|
1459
|
+
previous !== undefined &&
|
|
1460
|
+
previous.uploadedArchVersion === inst.archVersion &&
|
|
1461
|
+
previous.uploadedByteLength === payloadBytes
|
|
1462
|
+
? previous
|
|
1463
|
+
: undefined;
|
|
1464
|
+
let activeIsNew = false;
|
|
1465
|
+
if (active === undefined) {
|
|
1466
|
+
const created = runtime.device.createBuffer({
|
|
1467
|
+
size: payloadBytes,
|
|
1468
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
1469
|
+
mappedAtCreation: false,
|
|
1470
|
+
});
|
|
1471
|
+
if (!created.ok) {
|
|
1472
|
+
runtime.errorRegistry.fire(created.error);
|
|
1473
|
+
reportInstanceFailure(c, inst, created.error, inst.instanceCount * strideBytes, cap);
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
1476
|
+
if (previous !== undefined && !previous.buffer.isDestroyed) {
|
|
1477
|
+
const destroyed = previous.buffer.destroy();
|
|
1478
|
+
if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
|
|
1479
|
+
}
|
|
1480
|
+
active = {
|
|
1481
|
+
buffer: new GpuBuffer(runtime.device, created.value),
|
|
1482
|
+
uploadedArchVersion: inst.archVersion,
|
|
1483
|
+
uploadedByteLength: payloadBytes,
|
|
1484
|
+
};
|
|
1485
|
+
activeIsNew = true;
|
|
1486
|
+
chunks?.set(chunkKey, active);
|
|
1487
|
+
if (chunks === undefined) frameState.transientInstanceBuffers.push(active);
|
|
1488
|
+
}
|
|
1489
|
+
if (active === undefined) return null;
|
|
1490
|
+
const needsUpload =
|
|
1491
|
+
activeIsNew || active.uploadedRevision !== (inst.revision ?? inst.archVersion);
|
|
1492
|
+
const rangesToUpload = [
|
|
1493
|
+
{
|
|
1494
|
+
start: 0,
|
|
1495
|
+
end: count,
|
|
1496
|
+
},
|
|
1497
|
+
];
|
|
1498
|
+
if (needsUpload && rangesToUpload.length > 0) {
|
|
1499
|
+
const upload = uploadInstanceRanges(
|
|
1500
|
+
c,
|
|
1501
|
+
inst,
|
|
1502
|
+
active.buffer.handle,
|
|
1503
|
+
rangesToUpload,
|
|
1504
|
+
true,
|
|
1505
|
+
start,
|
|
1506
|
+
);
|
|
1507
|
+
for (const range of upload.ranges) {
|
|
1508
|
+
uploadedRanges.push({ start: range.start + start, end: range.end + start });
|
|
1509
|
+
}
|
|
1510
|
+
uploadedBytes += upload.bytes;
|
|
1511
|
+
if (!upload.ok) {
|
|
1512
|
+
reportInstanceFailure(
|
|
1513
|
+
c,
|
|
1514
|
+
inst,
|
|
1515
|
+
{
|
|
1516
|
+
code: 'queue-write-buffer-failed',
|
|
1517
|
+
expected:
|
|
1518
|
+
'the renderer-owned instance storage buffer accepts the complete instance payload',
|
|
1519
|
+
hint: 'retry after the active device is healthy or recover the renderer',
|
|
1520
|
+
},
|
|
1521
|
+
inst.instanceCount * strideBytes,
|
|
1522
|
+
cap,
|
|
1523
|
+
);
|
|
1524
|
+
return null;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (needsUpload) {
|
|
1528
|
+
{
|
|
1529
|
+
const published = { ...active, uploadedRevision: inst.revision ?? inst.archVersion };
|
|
1530
|
+
active = published;
|
|
1531
|
+
chunks?.set(chunkKey, published);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
const activeBuffer = active;
|
|
1535
|
+
draws.push({
|
|
1536
|
+
instanceBuffer: activeBuffer.buffer.handle,
|
|
1537
|
+
instanceBindGroup: resolveGeometryInstancesBindGroup(
|
|
1538
|
+
c,
|
|
1539
|
+
activeBuffer.buffer.handle,
|
|
1540
|
+
probeBuffer,
|
|
1541
|
+
),
|
|
1542
|
+
probeOffset,
|
|
1543
|
+
instanceCount: count,
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
if (chunks !== undefined) {
|
|
1547
|
+
const prefix = `${ownerKey}:`;
|
|
1548
|
+
for (const [key, stale] of chunks.entries()) {
|
|
1549
|
+
if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
|
|
1550
|
+
if (!stale.buffer.isDestroyed) {
|
|
1551
|
+
const destroyed = stale.buffer.destroy();
|
|
1552
|
+
if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
|
|
1553
|
+
}
|
|
1554
|
+
chunks.delete(key);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
reportInstanceResidency(c, inst, {
|
|
1558
|
+
lane: 'chunked-storage',
|
|
1559
|
+
requestedBytes: inst.instanceCount * strideBytes,
|
|
1560
|
+
supportedBytes: cap,
|
|
1561
|
+
uploadRanges: uploadedRanges,
|
|
1562
|
+
uploadedBytes,
|
|
1563
|
+
});
|
|
1564
|
+
return draws;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
export function resolveGeometryInstanceBuffer(
|
|
1568
|
+
c: Pick<
|
|
1569
|
+
_InternalRenderPipelineContext,
|
|
1570
|
+
'runtime' | 'pipelineState' | 'frameState' | 'bindGroupCounts'
|
|
1571
|
+
>,
|
|
1048
1572
|
entry: _InternalRenderPipelineContext['validatedOrdered'][number],
|
|
1049
1573
|
identityInstanceDraws: readonly GeometryInstanceDraw[],
|
|
1050
1574
|
useProbeBlend: boolean,
|
|
@@ -1079,6 +1603,11 @@ function resolveGeometryInstanceBuffer(
|
|
|
1079
1603
|
// A present, empty Instances array means zero draws. Do not fall back to
|
|
1080
1604
|
// the shared identity buffer, which would silently render one instance.
|
|
1081
1605
|
if (inst.instanceCount === 0) return [];
|
|
1606
|
+
const entityCacheKey = instanceCollectionCacheKey(entry.source.worldId, inst);
|
|
1607
|
+
const revision = inst.revision ?? inst.archVersion;
|
|
1608
|
+
const sameRevision = (cached: InstanceBufferCacheEntry): boolean =>
|
|
1609
|
+
cached.uploadedRevision === revision;
|
|
1610
|
+
const fullRange = [{ start: 0, end: inst.instanceCount }] as const;
|
|
1082
1611
|
let instanceBuffer: Buffer = pipelineState.identityInstanceBuffer;
|
|
1083
1612
|
let instanceCount = 1;
|
|
1084
1613
|
{
|
|
@@ -1090,41 +1619,126 @@ function resolveGeometryInstanceBuffer(
|
|
|
1090
1619
|
let instanceBufferUsage = GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
|
|
1091
1620
|
|
|
1092
1621
|
if (uniformFallback && inst.instanceCount > MAX_UNIFORM_INSTANCES) {
|
|
1622
|
+
const ownerKey = instanceCollectionCacheKey(entry.source.worldId, inst);
|
|
1623
|
+
const chunks = frameState.instanceBufferChunks;
|
|
1624
|
+
const activeKeys = new Set<string>();
|
|
1093
1625
|
const draws: GeometryInstanceDraw[] = [];
|
|
1626
|
+
const uniformStrideBytes = 16 * Float32Array.BYTES_PER_ELEMENT;
|
|
1627
|
+
const uploadedRanges: { start: number; end: number }[] = [];
|
|
1628
|
+
let uploadedBytes = 0;
|
|
1094
1629
|
for (let start = 0; start < inst.instanceCount; start += MAX_UNIFORM_INSTANCES) {
|
|
1095
1630
|
const count = Math.min(MAX_UNIFORM_INSTANCES, inst.instanceCount - start);
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1631
|
+
const chunkKey = `${ownerKey}:${start}`;
|
|
1632
|
+
activeKeys.add(chunkKey);
|
|
1633
|
+
const payloadBytes = count * uniformStrideBytes;
|
|
1634
|
+
const previous = chunks?.get(chunkKey);
|
|
1635
|
+
let active =
|
|
1636
|
+
previous !== undefined &&
|
|
1637
|
+
previous.uploadedArchVersion === inst.archVersion &&
|
|
1638
|
+
previous.uploadedByteLength === payloadBytes
|
|
1639
|
+
? previous
|
|
1640
|
+
: undefined;
|
|
1641
|
+
let activeIsNew = false;
|
|
1642
|
+
if (active === undefined) {
|
|
1643
|
+
const created = runtime.device.createBuffer({
|
|
1644
|
+
size: INSTANCE_UBO_FULL_ARRAY_BYTES,
|
|
1645
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
1646
|
+
mappedAtCreation: false,
|
|
1647
|
+
});
|
|
1648
|
+
if (!created.ok) {
|
|
1649
|
+
runtime.errorRegistry.fire(created.error);
|
|
1650
|
+
reportInstanceFailure(
|
|
1651
|
+
c,
|
|
1652
|
+
inst,
|
|
1653
|
+
created.error,
|
|
1654
|
+
inst.instanceCount * uniformStrideBytes,
|
|
1655
|
+
MAX_UNIFORM_INSTANCES * uniformStrideBytes,
|
|
1656
|
+
);
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
if (previous !== undefined && !previous.buffer.isDestroyed) {
|
|
1660
|
+
const destroyed = previous.buffer.destroy();
|
|
1661
|
+
if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
|
|
1662
|
+
}
|
|
1663
|
+
active = {
|
|
1664
|
+
buffer: new GpuBuffer(runtime.device, created.value),
|
|
1665
|
+
uploadedArchVersion: inst.archVersion,
|
|
1666
|
+
uploadedByteLength: payloadBytes,
|
|
1667
|
+
};
|
|
1668
|
+
activeIsNew = true;
|
|
1669
|
+
chunks?.set(chunkKey, active);
|
|
1670
|
+
if (chunks === undefined) frameState.transientInstanceBuffers.push(active);
|
|
1105
1671
|
}
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
if (
|
|
1114
|
-
|
|
1115
|
-
|
|
1672
|
+
const needsUpload = activeIsNew || !sameRevision(active);
|
|
1673
|
+
const rangesToUpload = [
|
|
1674
|
+
{
|
|
1675
|
+
start: 0,
|
|
1676
|
+
end: count,
|
|
1677
|
+
},
|
|
1678
|
+
];
|
|
1679
|
+
if (needsUpload && rangesToUpload.length > 0) {
|
|
1680
|
+
const upload = uploadInstanceRanges(
|
|
1681
|
+
c,
|
|
1682
|
+
inst,
|
|
1683
|
+
active.buffer.handle,
|
|
1684
|
+
rangesToUpload,
|
|
1685
|
+
false,
|
|
1686
|
+
start,
|
|
1687
|
+
);
|
|
1688
|
+
for (const range of upload.ranges) {
|
|
1689
|
+
uploadedRanges.push({ start: range.start + start, end: range.end + start });
|
|
1690
|
+
}
|
|
1691
|
+
uploadedBytes += upload.bytes;
|
|
1692
|
+
if (!upload.ok) {
|
|
1693
|
+
reportInstanceFailure(
|
|
1694
|
+
c,
|
|
1695
|
+
inst,
|
|
1696
|
+
{
|
|
1697
|
+
code: 'queue-write-buffer-failed',
|
|
1698
|
+
expected:
|
|
1699
|
+
'the renderer-owned instance uniform buffer accepts the complete instance payload',
|
|
1700
|
+
hint: 'retry after the active device is healthy or recover the renderer',
|
|
1701
|
+
},
|
|
1702
|
+
inst.instanceCount * uniformStrideBytes,
|
|
1703
|
+
MAX_UNIFORM_INSTANCES * uniformStrideBytes,
|
|
1704
|
+
);
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
if (needsUpload) {
|
|
1709
|
+
const published = { ...active, uploadedRevision: revision };
|
|
1710
|
+
active = published;
|
|
1711
|
+
chunks?.set(chunkKey, published);
|
|
1116
1712
|
}
|
|
1117
1713
|
draws.push({
|
|
1118
|
-
instanceBuffer: buffer.handle,
|
|
1714
|
+
instanceBuffer: active.buffer.handle,
|
|
1119
1715
|
instanceBindGroup: resolveGeometryInstancesBindGroup(
|
|
1120
1716
|
c,
|
|
1121
|
-
buffer.handle,
|
|
1717
|
+
active.buffer.handle,
|
|
1122
1718
|
probeBinding?.buffer,
|
|
1123
1719
|
),
|
|
1124
1720
|
probeOffset: probeBinding?.offset ?? -1,
|
|
1125
1721
|
instanceCount: count,
|
|
1126
1722
|
});
|
|
1127
1723
|
}
|
|
1724
|
+
if (chunks !== undefined) {
|
|
1725
|
+
const prefix = `${ownerKey}:`;
|
|
1726
|
+
for (const [key, stale] of chunks.entries()) {
|
|
1727
|
+
if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
|
|
1728
|
+
if (!stale.buffer.isDestroyed) {
|
|
1729
|
+
const destroyed = stale.buffer.destroy();
|
|
1730
|
+
if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
|
|
1731
|
+
}
|
|
1732
|
+
chunks.delete(key);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
reportInstanceResidency(c, inst, {
|
|
1736
|
+
lane: 'chunked-uniform',
|
|
1737
|
+
requestedBytes: inst.instanceCount * uniformStrideBytes,
|
|
1738
|
+
supportedBytes: MAX_UNIFORM_INSTANCES * uniformStrideBytes,
|
|
1739
|
+
uploadRanges: uploadedRanges,
|
|
1740
|
+
uploadedBytes,
|
|
1741
|
+
});
|
|
1128
1742
|
return draws;
|
|
1129
1743
|
}
|
|
1130
1744
|
if (uniformFallback) {
|
|
@@ -1134,31 +1748,30 @@ function resolveGeometryInstanceBuffer(
|
|
|
1134
1748
|
{
|
|
1135
1749
|
// Cap-gate (LimitExceededDetail single emit point — feat-20260514
|
|
1136
1750
|
// M3 / w15 anchor): `requestedBytes <= maxStorageBufferBindingSize`.
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1751
|
+
// Derive the size from the authoring count; storage packing is deferred
|
|
1752
|
+
// until uploadInstanceRanges confirms a new or dirty resident.
|
|
1753
|
+
const requestedBytes =
|
|
1754
|
+
inst.instanceCount *
|
|
1755
|
+
(uniformFallback ? 16 : INSTANCE_STORAGE_STRIDE_FLOATS) *
|
|
1756
|
+
Float32Array.BYTES_PER_ELEMENT;
|
|
1141
1757
|
const cap = runtime.device.limits.maxStorageBufferBindingSize;
|
|
1142
1758
|
if (!uniformFallback && typeof cap === 'number' && cap > 0 && requestedBytes > cap) {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
requestedBytes,
|
|
1151
|
-
},
|
|
1152
|
-
}),
|
|
1759
|
+
const chunked = resolveStorageInstanceChunks(
|
|
1760
|
+
c,
|
|
1761
|
+
entry,
|
|
1762
|
+
inst,
|
|
1763
|
+
cap,
|
|
1764
|
+
probeBinding?.buffer,
|
|
1765
|
+
probeBinding?.offset ?? -1,
|
|
1153
1766
|
);
|
|
1154
|
-
return
|
|
1767
|
+
return chunked;
|
|
1155
1768
|
} else {
|
|
1156
1769
|
// Look up the cached GPU buffer or create a fresh one when the
|
|
1157
1770
|
// archetype version bumped or the byte length changed.
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
);
|
|
1771
|
+
// entityCacheKey is the worldEntityKey + collectionId namespace.
|
|
1772
|
+
const cached = frameState.instanceBuffers.get(entityCacheKey);
|
|
1161
1773
|
let active: InstanceBufferCacheEntry | null = null;
|
|
1774
|
+
let activeIsNew = false;
|
|
1162
1775
|
if (
|
|
1163
1776
|
cached !== undefined &&
|
|
1164
1777
|
cached.uploadedArchVersion === inst.archVersion &&
|
|
@@ -1173,6 +1786,8 @@ function resolveGeometryInstanceBuffer(
|
|
|
1173
1786
|
});
|
|
1174
1787
|
if (!bufRes.ok) {
|
|
1175
1788
|
runtime.errorRegistry.fire(bufRes.error);
|
|
1789
|
+
reportInstanceFailure(c, inst, bufRes.error, requestedBytes, cap);
|
|
1790
|
+
return null;
|
|
1176
1791
|
} else {
|
|
1177
1792
|
// feat-20260619 M4 / F12: destroy the old cached buffer
|
|
1178
1793
|
// before replacing it with the new one (D-6).
|
|
@@ -1186,24 +1801,67 @@ function resolveGeometryInstanceBuffer(
|
|
|
1186
1801
|
uploadedArchVersion: inst.archVersion,
|
|
1187
1802
|
uploadedByteLength: requestedBytes,
|
|
1188
1803
|
};
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1804
|
+
activeIsNew = true;
|
|
1805
|
+
if (active !== null) {
|
|
1806
|
+
// Keep the collection identity in the worldEntityKey namespace.
|
|
1807
|
+
frameState.instanceBuffers.set(entityCacheKey, active);
|
|
1808
|
+
}
|
|
1193
1809
|
}
|
|
1194
1810
|
}
|
|
1195
1811
|
if (active !== null) {
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
)
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1812
|
+
const needsUpload = activeIsNew || !sameRevision(active);
|
|
1813
|
+
const ranges = [fullRange[0]];
|
|
1814
|
+
let uploadRanges: readonly { readonly start: number; readonly end: number }[] = [];
|
|
1815
|
+
let uploadedBytes = 0;
|
|
1816
|
+
if (needsUpload && ranges.length > 0) {
|
|
1817
|
+
const upload = uploadInstanceRanges(
|
|
1818
|
+
c,
|
|
1819
|
+
inst,
|
|
1820
|
+
active.buffer.handle,
|
|
1821
|
+
ranges,
|
|
1822
|
+
!uniformFallback,
|
|
1823
|
+
);
|
|
1824
|
+
uploadRanges = upload.ranges;
|
|
1825
|
+
uploadedBytes = upload.bytes;
|
|
1826
|
+
if (!upload.ok) {
|
|
1827
|
+
reportInstanceFailure(
|
|
1828
|
+
c,
|
|
1829
|
+
inst,
|
|
1830
|
+
{
|
|
1831
|
+
code: 'queue-write-buffer-failed',
|
|
1832
|
+
expected:
|
|
1833
|
+
'the renderer-owned instance buffer accepts the complete instance payloads',
|
|
1834
|
+
hint: 'retry after the active device is healthy or recover the renderer',
|
|
1835
|
+
},
|
|
1836
|
+
requestedBytes,
|
|
1837
|
+
uniformFallback
|
|
1838
|
+
? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16
|
|
1839
|
+
: typeof cap === 'number'
|
|
1840
|
+
? cap
|
|
1841
|
+
: undefined,
|
|
1842
|
+
);
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1206
1845
|
}
|
|
1846
|
+
if (needsUpload) {
|
|
1847
|
+
const published = { ...active, uploadedRevision: revision };
|
|
1848
|
+
frameState.instanceBuffers.set(entityCacheKey, published);
|
|
1849
|
+
active = published;
|
|
1850
|
+
}
|
|
1851
|
+
instanceBuffer = active.buffer.handle;
|
|
1852
|
+
instanceCount = Math.max(1, inst.instanceCount);
|
|
1853
|
+
reportInstanceResidency(c, inst, {
|
|
1854
|
+
lane: uniformFallback ? 'direct-uniform' : 'direct-storage',
|
|
1855
|
+
requestedBytes,
|
|
1856
|
+
supportedBytes: uniformFallback
|
|
1857
|
+
? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16
|
|
1858
|
+
: typeof cap === 'number'
|
|
1859
|
+
? cap
|
|
1860
|
+
: undefined,
|
|
1861
|
+
uploadRanges,
|
|
1862
|
+
uploadedBytes,
|
|
1863
|
+
});
|
|
1864
|
+
clearInstanceChunksForOwner(c, entityCacheKey);
|
|
1207
1865
|
}
|
|
1208
1866
|
}
|
|
1209
1867
|
// The cache write above is keyed by worldEntityKey(worldId, cacheKey)
|
|
@@ -1221,7 +1879,10 @@ function resolveGeometryInstanceBuffer(
|
|
|
1221
1879
|
}
|
|
1222
1880
|
|
|
1223
1881
|
export function resolveProbeBlendBuffer(
|
|
1224
|
-
c:
|
|
1882
|
+
c: Pick<
|
|
1883
|
+
_InternalRenderPipelineContext,
|
|
1884
|
+
'runtime' | 'pipelineState' | 'frameState' | 'bindGroupCounts'
|
|
1885
|
+
>,
|
|
1225
1886
|
record: import('../scene/probe-blend-record').ProbeBlendRecord | undefined,
|
|
1226
1887
|
entityKey?: number,
|
|
1227
1888
|
): { readonly buffer: Buffer; readonly offset: number } {
|
|
@@ -1269,7 +1930,10 @@ export function resolveProbeBlendBuffer(
|
|
|
1269
1930
|
}
|
|
1270
1931
|
|
|
1271
1932
|
export function resolveGeometryInstancesBindGroup(
|
|
1272
|
-
c:
|
|
1933
|
+
c: Pick<
|
|
1934
|
+
_InternalRenderPipelineContext,
|
|
1935
|
+
'runtime' | 'pipelineState' | 'frameState' | 'bindGroupCounts'
|
|
1936
|
+
>,
|
|
1273
1937
|
instanceBuffer: Buffer,
|
|
1274
1938
|
probeBuffer?: Buffer,
|
|
1275
1939
|
): BindGroup {
|