@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
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { GpuVertexBufferLayoutEntry } from '@forgeax/engine-geometry';
|
|
2
|
+
import type { VertexAttributeMap } from '@forgeax/engine-types';
|
|
3
|
+
|
|
4
|
+
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4): a single
|
|
5
|
+
// shared empty ArrayBuffer used by the pipeline builder to synthesize the
|
|
6
|
+
// pbr-skin attribute map when no caller-supplied attributes are available.
|
|
7
|
+
// `deriveVertexBufferLayout` is the SSOT for the layout and reads only key
|
|
8
|
+
// presence, so a zero-byte buffer per key preserves the six-attribute stride.
|
|
9
|
+
export const PBR_SKIN_SENTINEL_ATTR_BUFFER = new ArrayBuffer(0);
|
|
10
|
+
|
|
11
|
+
/** Default 4-attribute vertex layout used as fallback when meshAttributes is undefined. */
|
|
12
|
+
export const DEFAULT_VERTEX_ATTRS: VertexAttributeMap = {
|
|
13
|
+
position: new Float32Array(0),
|
|
14
|
+
normal: new Float32Array(0),
|
|
15
|
+
uv: new Float32Array(0),
|
|
16
|
+
tangent: new Float32Array(0),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const PREPARED_INSTANCE_VERTEX_ATTRS: VertexAttributeMap = {
|
|
20
|
+
position: new Float32Array(0),
|
|
21
|
+
normal: new Float32Array(0),
|
|
22
|
+
uv: new Float32Array(0),
|
|
23
|
+
tangent: new Float32Array(0),
|
|
24
|
+
uv1: new Float32Array(0),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const PREPARED_MATERIAL_INSTANCE_VERTEX_ATTRS: VertexAttributeMap = {
|
|
28
|
+
position: new Float32Array(0),
|
|
29
|
+
normal: new Float32Array(0),
|
|
30
|
+
uv: new Float32Array(0),
|
|
31
|
+
tangent: new Float32Array(0),
|
|
32
|
+
skinIndex: new Uint16Array(0),
|
|
33
|
+
skinWeight: new Float32Array(0),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const POSITION_SIZE_COLOR_INSTANCE_VERTEX_BUFFERS = [
|
|
37
|
+
{
|
|
38
|
+
arrayStride: 9 * 4,
|
|
39
|
+
stepMode: 'instance' as const,
|
|
40
|
+
attributes: [
|
|
41
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x3' as const },
|
|
42
|
+
{ shaderLocation: 1, offset: 3 * 4, format: 'float32x2' as const },
|
|
43
|
+
{ shaderLocation: 2, offset: 5 * 4, format: 'float32x4' as const },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
] satisfies readonly GPUVertexBufferLayout[];
|
|
47
|
+
|
|
48
|
+
export const BILLBOARD_MATERIAL_INSTANCE_VERTEX_BUFFERS = [
|
|
49
|
+
{
|
|
50
|
+
arrayStride: 31 * 4,
|
|
51
|
+
stepMode: 'instance' as const,
|
|
52
|
+
attributes: [
|
|
53
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x3' as const },
|
|
54
|
+
{ shaderLocation: 1, offset: 3 * 4, format: 'float32x2' as const },
|
|
55
|
+
{ shaderLocation: 2, offset: 5 * 4, format: 'float32x2' as const },
|
|
56
|
+
{ shaderLocation: 3, offset: 7 * 4, format: 'float32x4' as const },
|
|
57
|
+
{ shaderLocation: 4, offset: 11 * 4, format: 'float32x4' as const },
|
|
58
|
+
{ shaderLocation: 5, offset: 15 * 4, format: 'float32x4' as const },
|
|
59
|
+
{ shaderLocation: 6, offset: 19 * 4, format: 'float32x4' as const },
|
|
60
|
+
{ shaderLocation: 7, offset: 23 * 4, format: 'float32x4' as const },
|
|
61
|
+
{ shaderLocation: 8, offset: 27 * 4, format: 'float32x4' as const },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
] satisfies readonly GPUVertexBufferLayout[];
|
|
65
|
+
|
|
66
|
+
export const TOPOLOGY_SEGMENT_INSTANCE_VERTEX_BUFFERS = [
|
|
67
|
+
{
|
|
68
|
+
arrayStride: 12 * 4,
|
|
69
|
+
stepMode: 'instance' as const,
|
|
70
|
+
attributes: [
|
|
71
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x3' as const },
|
|
72
|
+
{ shaderLocation: 1, offset: 3 * 4, format: 'float32x3' as const },
|
|
73
|
+
{ shaderLocation: 2, offset: 6 * 4, format: 'float32x4' as const },
|
|
74
|
+
{ shaderLocation: 3, offset: 10 * 4, format: 'float32x2' as const },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
] satisfies readonly GPUVertexBufferLayout[];
|
|
78
|
+
|
|
79
|
+
export const MESH_GEOMETRY_MATERIAL_INSTANCE_VERTEX_BUFFERS = [
|
|
80
|
+
{
|
|
81
|
+
arrayStride: 12 * 4,
|
|
82
|
+
stepMode: 'vertex' as const,
|
|
83
|
+
attributes: [
|
|
84
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x3' as const },
|
|
85
|
+
{ shaderLocation: 1, offset: 3 * 4, format: 'float32x3' as const },
|
|
86
|
+
{ shaderLocation: 2, offset: 6 * 4, format: 'float32x2' as const },
|
|
87
|
+
{ shaderLocation: 3, offset: 8 * 4, format: 'float32x4' as const },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
arrayStride: 28 * 4,
|
|
92
|
+
stepMode: 'instance' as const,
|
|
93
|
+
attributes: [
|
|
94
|
+
{ shaderLocation: 4, offset: 0, format: 'float32x3' as const },
|
|
95
|
+
{ shaderLocation: 5, offset: 3 * 4, format: 'float32x3' as const },
|
|
96
|
+
{ shaderLocation: 6, offset: 6 * 4, format: 'float32x3' as const },
|
|
97
|
+
{ shaderLocation: 7, offset: 9 * 4, format: 'float32x3' as const },
|
|
98
|
+
{ shaderLocation: 8, offset: 12 * 4, format: 'float32x4' as const },
|
|
99
|
+
{ shaderLocation: 9, offset: 16 * 4, format: 'float32x4' as const },
|
|
100
|
+
{ shaderLocation: 10, offset: 20 * 4, format: 'float32x4' as const },
|
|
101
|
+
{ shaderLocation: 11, offset: 24 * 4, format: 'float32x4' as const },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
] satisfies readonly GPUVertexBufferLayout[];
|
|
105
|
+
|
|
106
|
+
function toGpuVertexFormat(
|
|
107
|
+
format: GpuVertexBufferLayoutEntry['attributes'][number]['format'],
|
|
108
|
+
): GPUVertexFormat {
|
|
109
|
+
switch (format) {
|
|
110
|
+
case 'float32x2':
|
|
111
|
+
case 'float32x3':
|
|
112
|
+
case 'float32x4':
|
|
113
|
+
case 'uint16x4':
|
|
114
|
+
return format;
|
|
115
|
+
default:
|
|
116
|
+
throw new Error(`Unsupported geometry vertex format: ${format}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function toGpuVertexBufferLayouts(
|
|
121
|
+
entries: readonly GpuVertexBufferLayoutEntry[],
|
|
122
|
+
): readonly GPUVertexBufferLayout[] {
|
|
123
|
+
return entries.map((entry) => ({
|
|
124
|
+
arrayStride: entry.arrayStride,
|
|
125
|
+
...(entry.stepMode === undefined ? {} : { stepMode: entry.stepMode }),
|
|
126
|
+
attributes: entry.attributes.map((attribute) => ({
|
|
127
|
+
shaderLocation: attribute.shaderLocation,
|
|
128
|
+
offset: attribute.offset,
|
|
129
|
+
format: toGpuVertexFormat(attribute.format),
|
|
130
|
+
})),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
err,
|
|
7
7
|
MATERIAL_TEXTURE_SLOTS,
|
|
8
8
|
type MaterialAsset,
|
|
9
|
+
type MaterialChildAsset,
|
|
10
|
+
type MaterialRootAsset,
|
|
9
11
|
ok,
|
|
10
12
|
type RenderPipelineAsset,
|
|
11
13
|
type SamplerAsset,
|
|
@@ -31,6 +33,12 @@ function validMaterial(value: unknown): value is MaterialAsset {
|
|
|
31
33
|
}
|
|
32
34
|
if (value.passes !== undefined && !Array.isArray(value.passes)) return false;
|
|
33
35
|
if (value.parameters !== undefined && !Array.isArray(value.parameters)) return false;
|
|
36
|
+
if (
|
|
37
|
+
value.parent !== undefined &&
|
|
38
|
+
['colorSpace', 'passes', 'parameters'].some((field) => Object.hasOwn(value, field))
|
|
39
|
+
) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
34
42
|
return value.values === undefined || record(value.values);
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -60,16 +68,22 @@ function resolveMaterialWireRefs(
|
|
|
60
68
|
if (refs.length === 0) return ok(value);
|
|
61
69
|
|
|
62
70
|
let changed = false;
|
|
63
|
-
|
|
71
|
+
const wire = value as unknown as { readonly parent?: unknown };
|
|
72
|
+
let parent = wire.parent;
|
|
64
73
|
if (typeof parent === 'number') {
|
|
65
74
|
const resolved = resolveMaterialWireRef(parent, refs, 'parent', guid);
|
|
66
75
|
if (!resolved.ok) return resolved;
|
|
67
|
-
parent = resolved.value
|
|
76
|
+
parent = resolved.value;
|
|
68
77
|
changed = true;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
if (value.values === undefined) {
|
|
72
|
-
|
|
81
|
+
if (!changed) return ok(value);
|
|
82
|
+
if (parent !== undefined) {
|
|
83
|
+
const child = value as MaterialChildAsset;
|
|
84
|
+
return ok({ ...child, parent: parent as unknown as MaterialChildAsset['parent'] });
|
|
85
|
+
}
|
|
86
|
+
return ok(value);
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
const textureFields = new Set(
|
|
@@ -105,7 +119,16 @@ function resolveMaterialWireRefs(
|
|
|
105
119
|
values[field] = rewritten as unknown as NonNullable<MaterialAsset['values']>[string];
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
|
-
|
|
122
|
+
if (!changed) return ok(value);
|
|
123
|
+
if (parent !== undefined) {
|
|
124
|
+
const child = value as MaterialChildAsset;
|
|
125
|
+
return ok({
|
|
126
|
+
...child,
|
|
127
|
+
parent: parent as unknown as MaterialChildAsset['parent'],
|
|
128
|
+
values,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return ok({ ...value, values } as MaterialRootAsset);
|
|
109
132
|
}
|
|
110
133
|
|
|
111
134
|
export const materialContribution: AssetDecoderContribution<MaterialAsset, 'material'> = {
|
package/src/components/camera.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// outside M3 scope — the short-name mat4.perspective
|
|
15
15
|
// already writes [0, 1] NDC, matching the ortho branch.
|
|
16
16
|
// projection === 1 → orthographic path; RenderSystem builds
|
|
17
|
-
// mat4.orthographic(out, left, right,
|
|
17
|
+
// mat4.orthographic(out, left, right, top, bottom,
|
|
18
18
|
// near, far) (WebGPU [0, 1] NDC; same z convention).
|
|
19
19
|
//
|
|
20
20
|
// Naming convention: forgeax uses the bare `Camera` name (no `Component`
|
|
@@ -1,206 +1,23 @@
|
|
|
1
|
-
// @forgeax/engine-render - Instances component (per-entity instanced-draw transforms).
|
|
2
|
-
//
|
|
3
|
-
// Two sources, one component (feat-20260622-chunk-gpu-instancing-sprite-tilemap
|
|
4
|
-
// / AC-09; charter P4 consistent abstraction + charter P1 progressive disclosure).
|
|
5
|
-
// The `Instances` ECS component has exactly TWO sources of `transforms` data, and
|
|
6
|
-
// AI users should hold the boundary explicitly to avoid misreading the ECS view:
|
|
7
|
-
//
|
|
8
|
-
// Source 1 (EXPLICIT, the only source that lands in ECS) — AI user opt-in.
|
|
9
|
-
// The AI user hand-attaches `Instances { transforms }` onto an entity at
|
|
10
|
-
// spawn / set time for high-performance instanced draw. `world.get(e,
|
|
11
|
-
// Instances)` returns the AI user's payload verbatim (a `Float32Array`
|
|
12
|
-
// snapshot of column-major mat4 instances; stride = 16). This is the sole
|
|
13
|
-
// code path through which `Instances` ever appears on an entity.
|
|
14
|
-
//
|
|
15
|
-
// Source 2 (TRANSPARENT, never written back into ECS) — engine record-stage
|
|
16
|
-
// fold. When the AI user spawns N independent sprite / tilemap-cell
|
|
17
|
-
// entities sharing the same (Layer.value, posZ, materialHandle) sort
|
|
18
|
-
// equivalence class, the engine record stage transparently folds the
|
|
19
|
-
// equivalent run into ONE `drawIndexed(indexCount, count)` using an
|
|
20
|
-
// INTERNAL transient buffer (see `render-system-record.ts` fold operator
|
|
21
|
-
// + `render.instancing.foldedDraws` metric, and the `RhiError
|
|
22
|
-
// 'instancing-exceeds-uniform-cap'` fallback). The fold operator does
|
|
23
|
-
// NOT auto-inject `Instances` onto those entities — `world.get(e,
|
|
24
|
-
// Instances)` on a folded entity returns `Result.err` exactly as before.
|
|
25
|
-
// The ECS view is preserved bit-for-bit (per-cell / per-sprite entity
|
|
26
|
-
// retained, query / despawn / picking unchanged); the optimisation is
|
|
27
|
-
// invisible at the component layer (charter P4: consistent abstraction —
|
|
28
|
-
// the high-performance path is opt-in via Source 1, the default spawn
|
|
29
|
-
// shape stays untouched).
|
|
30
|
-
//
|
|
31
|
-
// AI user reading this header: do NOT expect engine-side fold to materialise
|
|
32
|
-
// as an `Instances` row on your entity. Read the metric (foldedDraws) or the
|
|
33
|
-
// RhiError code to observe fold behaviour; never gate logic on a runtime
|
|
34
|
-
// `world.get(e, Instances)` lookup after spawn.
|
|
35
|
-
//
|
|
36
|
-
// Schema: 1 array<f32> field `transforms` carrying packed column-major mat4
|
|
37
|
-
// instance transforms (16 f32 per instance, stride = 16). Stride is enforced
|
|
38
|
-
// through a TWO-LAYER contract:
|
|
39
|
-
//
|
|
40
|
-
// 1. AI user set-site: spawn / `world.set` / `world.push` callers pass a
|
|
41
|
-
// `Float32Array` (or numeric array) whose `length` is a non-zero
|
|
42
|
-
// multiple of 16. The set site is the AI user's responsibility -- the
|
|
43
|
-
// engine cannot prove the column-major mat4 packing without the
|
|
44
|
-
// caller's intent.
|
|
45
|
-
// 2. RenderSystem extract entry: `render-system-extract.ts` performs a
|
|
46
|
-
// defensive `transforms.length % 16 === 0` check on every
|
|
47
|
-
// `world.get(e, Instances).transforms` snapshot at frame extract time;
|
|
48
|
-
// violations route a structured `InstanceTransformsStrideMismatchError`
|
|
49
|
-
// (`code: 'instance-transforms-stride-mismatch'`,
|
|
50
|
-
// `detail: { actualLength, expectedStride: 16 }`) through the World
|
|
51
|
-
// Layer-3 ErrorHandler and the renderable is skipped (fail-fast: the
|
|
52
|
-
// malformed length never reaches the GPU upload path).
|
|
53
|
-
//
|
|
54
|
-
// feat-20260515-buffer-array-vocab-collapse M3 / w16 (decision §2.3 stride
|
|
55
|
-
// responsibility migration): the legacy component-level
|
|
56
|
-
// stride-declaration option (`{ transforms: 16 }` keyed on the retired
|
|
57
|
-
// per-component stride defineComponent option key) was retired because:
|
|
58
|
-
// - The ECS layer no longer carries a per-component stride schema (M2 /
|
|
59
|
-
// w9 dropped the option from `DefineComponentOptions` -- the SSOT
|
|
60
|
-
// moved to the RenderSystem entry + AI user set site).
|
|
61
|
-
// - Centralising the check at the consumer (RenderSystem extract) rather
|
|
62
|
-
// than the producer (ECS write paths) lets the GPU upload path stay
|
|
63
|
-
// trust-the-snapshot; AI users get one structured error per offending
|
|
64
|
-
// frame rather than one per write.
|
|
65
|
-
//
|
|
66
|
-
// feat-20260514-ecs-children-instances-managed-buffer-array M3 / w14:
|
|
67
|
-
// migrated from the legacy `{ buffer: 'ref', count: 'u32' }` pair (which
|
|
68
|
-
// cross-coupled with the now-deleted `AssetRegistry.createInstancedBuffer`
|
|
69
|
-
// pipeline + the `InstancedBufferAsset` POD) to the ECS-managed array path.
|
|
70
|
-
// Resize is a `world.push(e, Instances, 'transforms', value)` away (one f32
|
|
71
|
-
// at a time) plus a future `world.grow` if a bulk-resize affordance is
|
|
72
|
-
// added.
|
|
73
|
-
//
|
|
74
|
-
// Storage buffer cap-gate (D-5 OOS-08 follow-up):
|
|
75
|
-
// - The runtime RenderSystem consumer (`render-system-record.ts`) checks
|
|
76
|
-
// `device.caps.storageBuffer` before binding the per-entity transforms;
|
|
77
|
-
// `caps.storageBuffer === false` (backend lacking storage buffer support,
|
|
78
|
-
// e.g. rhi-wgpu webgl backend) routes a `RhiError` ('feature-not-enabled').
|
|
79
|
-
// A future loop owns the per-draw fallback path.
|
|
80
|
-
//
|
|
81
|
-
// Group transform chained semantics (charter proposition 5 mental migration):
|
|
82
|
-
// - When the same entity carries both `Transform` (entity_world) and
|
|
83
|
-
// `Instances` (per-instance local transforms), the vertex shader
|
|
84
|
-
// composes per instance:
|
|
85
|
-
//
|
|
86
|
-
// world_position[i] = entity_world * instances_local[i] * vertex_position
|
|
87
|
-
//
|
|
88
|
-
// - `Instances.transforms[i*16..i*16+15]` is interpreted as a local-space
|
|
89
|
-
// transform under the entity, exactly like a child entity's `Transform`
|
|
90
|
-
// under `ChildOf { parent }` in the hierarchy system. AI users reuse
|
|
91
|
-
// the `parent x local` intuition without a new concept. Set the
|
|
92
|
-
// entity's `Transform` to identity to make `instances_local[i]`
|
|
93
|
-
// directly world-space.
|
|
94
|
-
//
|
|
95
|
-
// 4-segment minimum contract (read this header before reaching for
|
|
96
|
-
// the source body):
|
|
97
|
-
//
|
|
98
|
-
// ===== (a) single-component import + spawn example =====
|
|
99
|
-
//
|
|
100
|
-
// import {
|
|
101
|
-
// createRenderer, HANDLE_CUBE,
|
|
102
|
-
// MeshFilter, MeshRenderer, Transform,
|
|
103
|
-
// Instances, type InstancesData,
|
|
104
|
-
// } from '@forgeax/engine-render';
|
|
105
|
-
//
|
|
106
|
-
// // 1 entity rendering N instanced cubes (16N packed f32, column-major mat4):
|
|
107
|
-
// const transforms = new Float32Array(N * 16);
|
|
108
|
-
// // ... fill column-major mat4 columns per instance ...
|
|
109
|
-
// world.spawn(
|
|
110
|
-
// { component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
|
|
111
|
-
// { component: MeshRenderer, data: {} },
|
|
112
|
-
// { component: Instances, data: { transforms } },
|
|
113
|
-
// );
|
|
114
|
-
//
|
|
115
|
-
// ===== (b) packed mat4 layout =====
|
|
116
|
-
//
|
|
117
|
-
// const transforms = new Float32Array(N * 16); // column-major mat4 per instance
|
|
118
|
-
// // instance i occupies floats [i*16 .. i*16+15]:
|
|
119
|
-
// // [m00 m10 m20 m30 <- column 0
|
|
120
|
-
// // m01 m11 m21 m31 <- column 1
|
|
121
|
-
// // m02 m12 m22 m32 <- column 2
|
|
122
|
-
// // m03 m13 m23 m33] <- column 3 (translation in m03/m13/m23)
|
|
123
|
-
//
|
|
124
|
-
// `transforms.length` MUST be a non-zero multiple of 16. AI users gate at
|
|
125
|
-
// the set / push site; the RenderSystem extract entry holds the second
|
|
126
|
-
// defensive (AC-06: detail carries `{ expectedStride: 16, actualLength }`).
|
|
127
|
-
// The instance count is the live snapshot length / 16.
|
|
128
|
-
//
|
|
129
|
-
// ===== (c) error code consumption (typed property access, no message regex) =====
|
|
130
|
-
//
|
|
131
|
-
// // Stride violation surfaces through the engine-ecs Layer-3 ErrorHandler
|
|
132
|
-
// // when extract reads a malformed snapshot:
|
|
133
|
-
// // on('error', (err) => {
|
|
134
|
-
// // if (err.code === 'instance-transforms-stride-mismatch') {
|
|
135
|
-
// // // err.detail.expectedStride === 16
|
|
136
|
-
// // // err.detail.actualLength === <user-supplied length>
|
|
137
|
-
// // }
|
|
138
|
-
// // });
|
|
139
|
-
// //
|
|
140
|
-
// // RenderSystem cap-gate (D-5) -> RhiError 'feature-not-enabled' on the
|
|
141
|
-
// // RhiErrorListenerRegistry channel:
|
|
142
|
-
// // on('error', (err) => {
|
|
143
|
-
// // if (err.code === 'feature-not-enabled') {
|
|
144
|
-
// // console.warn(err.expected, err.hint);
|
|
145
|
-
// // }
|
|
146
|
-
// // });
|
|
147
|
-
//
|
|
148
|
-
// charter mapping: proposition 1 (single import surface
|
|
149
|
-
// `import { Instances, type InstancesData } from '@forgeax/engine-render'`);
|
|
150
|
-
// proposition 3 (machine-readable schema > prose:
|
|
151
|
-
// `{ transforms: 'array<f32>' }` is the SSOT, stride 16 documented here +
|
|
152
|
-
// enforced at the RenderSystem entry);
|
|
153
|
-
// proposition 4 (explicit failure: stride violation routes a structured
|
|
154
|
-
// EcsError, not a silent half-row); proposition 5 (consistent abstraction:
|
|
155
|
-
// Instances mirrors Children -- both ride the array-vocab path).
|
|
156
|
-
//
|
|
157
|
-
// Anchors: requirements §AC-06 (stride 16 + RenderSystem upload path);
|
|
158
|
-
// plan-strategy §2.3 (stride responsibility migration to RenderSystem entry
|
|
159
|
-
// defensive + AI user set site) + §3.3 stride error path; plan-decisions
|
|
160
|
-
// D-1 / D-5.
|
|
161
|
-
// OOS-09 (no addChild Commands API) does not apply here; OOS-01 (no
|
|
162
|
-
// dangling-entity sweep) does not apply (Instances stores f32 not entity).
|
|
163
|
-
|
|
164
1
|
import { defineComponent } from '@forgeax/engine-ecs';
|
|
165
2
|
|
|
166
3
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* The runtime RenderSystem consumer materialises a fresh `Float32Array`
|
|
174
|
-
* snapshot on every `world.get(e, Instances).transforms` access (D-4
|
|
175
|
-
* no-cache), reads the live count from the snapshot's `length`, and
|
|
176
|
-
* uploads the bytes to a per-entity GPU storage buffer in the record stage
|
|
177
|
-
* (`render-system-record.ts`).
|
|
4
|
+
* World-owned local transforms for one explicit instanced renderable.
|
|
5
|
+
* Packed column-major mat4 values compose as entityWorld * instanceLocal.
|
|
6
|
+
* ECS owns copying, mutation and entity lifetime, including large arrays.
|
|
7
|
+
* Renderer extraction derives its own snapshot and GPU residency; neither
|
|
8
|
+
* authoring nor glTF SceneAsset lowering requires a Renderer identity.
|
|
178
9
|
*
|
|
179
|
-
* @example
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
10
|
+
* @example
|
|
11
|
+
* const transforms = new Float32Array(20_000 * 16);
|
|
12
|
+
* for (let i = 0; i < 20_000; i++) {
|
|
13
|
+
* transforms[i * 16] = transforms[i * 16 + 5] =
|
|
14
|
+
* transforms[i * 16 + 10] = transforms[i * 16 + 15] = 1;
|
|
15
|
+
* }
|
|
16
|
+
* const entity = world.spawn({ component: Instances, data: { transforms } }).unwrap();
|
|
17
|
+
* world.set(entity, Instances, { transforms }).unwrap();
|
|
187
18
|
*/
|
|
188
19
|
export const Instances = defineComponent('Instances', {
|
|
189
20
|
transforms: { type: 'array<f32>' },
|
|
190
21
|
});
|
|
191
22
|
|
|
192
|
-
|
|
193
|
-
* Type-level hint for `data` at the `Instances` spawn site.
|
|
194
|
-
*
|
|
195
|
-
* The runtime ECS column shape for `array<f32>` accepts a `Float32Array`
|
|
196
|
-
* payload at spawn / set time (the bytes are copied into the BufferPool
|
|
197
|
-
* slot). The `transforms` field is documented as `Float32Array` here so
|
|
198
|
-
* AI-user IDE autocomplete picks up the typed-array shape.
|
|
199
|
-
*
|
|
200
|
-
* @example
|
|
201
|
-
* import type { InstancesData } from '@forgeax/engine-render';
|
|
202
|
-
* const data: InstancesData = { transforms: new Float32Array(N * 16) };
|
|
203
|
-
*/
|
|
204
|
-
export type InstancesData = {
|
|
205
|
-
readonly transforms: Float32Array;
|
|
206
|
-
};
|
|
23
|
+
export type InstancesData = { readonly transforms: Float32Array };
|
package/src/debug-draw-glue.ts
CHANGED
|
@@ -46,6 +46,17 @@ interface OwnedResource {
|
|
|
46
46
|
readonly cleanup: (value: unknown) => void | Promise<void>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function idempotentCleanup<T>(
|
|
50
|
+
cleanup: (value: T) => void | Promise<void>,
|
|
51
|
+
): (value: T) => void | Promise<void> {
|
|
52
|
+
let cleaned = false;
|
|
53
|
+
return (value: T) => {
|
|
54
|
+
if (cleaned) return;
|
|
55
|
+
cleaned = true;
|
|
56
|
+
return cleanup(value);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
export class DeviceScope {
|
|
50
61
|
readonly generation: number;
|
|
51
62
|
readonly owner: string;
|
|
@@ -117,7 +128,7 @@ export class DeviceScope {
|
|
|
117
128
|
const ref = new DeviceResourceRef(kind, this.owner, this.generation, value);
|
|
118
129
|
this.resources.push({
|
|
119
130
|
ref: ref as DeviceResourceRef<unknown>,
|
|
120
|
-
cleanup: cleanup as (resource: unknown) => void | Promise<void>,
|
|
131
|
+
cleanup: idempotentCleanup(cleanup) as (resource: unknown) => void | Promise<void>,
|
|
121
132
|
});
|
|
122
133
|
return ref;
|
|
123
134
|
}
|
|
@@ -142,9 +153,21 @@ export class DeviceScope {
|
|
|
142
153
|
if (this.lifecycleState === 'active') this.lifecycleState = 'retiring';
|
|
143
154
|
}
|
|
144
155
|
|
|
156
|
+
private clearChildren(): void {
|
|
157
|
+
for (const child of [...this.children].reverse()) {
|
|
158
|
+
child.retire();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private detachChild(child: DeviceScope): void {
|
|
163
|
+
const index = this.children.indexOf(child);
|
|
164
|
+
if (index >= 0) this.children.splice(index, 1);
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
retire(): void {
|
|
146
168
|
if (this.lifecycleState !== 'active' && this.lifecycleState !== 'retiring') return;
|
|
147
169
|
this.lifecycleState = 'retiring';
|
|
170
|
+
this.clearChildren();
|
|
148
171
|
this._clearResources();
|
|
149
172
|
this.lifecycleState = 'retired';
|
|
150
173
|
}
|
|
@@ -152,12 +175,14 @@ export class DeviceScope {
|
|
|
152
175
|
abandon(): void {
|
|
153
176
|
if (this.lifecycleState !== 'active') return;
|
|
154
177
|
this.lifecycleState = 'abandoned';
|
|
178
|
+
this.clearChildren();
|
|
155
179
|
this._clearResources();
|
|
156
180
|
}
|
|
157
181
|
|
|
158
182
|
dispose(): void {
|
|
159
183
|
if (this.lifecycleState === 'disposed') return;
|
|
160
184
|
this.lifecycleState = 'disposed';
|
|
185
|
+
this.clearChildren();
|
|
161
186
|
this._clearResources();
|
|
162
187
|
}
|
|
163
188
|
|
|
@@ -177,6 +202,9 @@ export class DeviceScope {
|
|
|
177
202
|
replacement.dispose();
|
|
178
203
|
return transaction.failure('dispose');
|
|
179
204
|
}
|
|
205
|
+
// The replacement is now the new owner. Detach it before retiring the
|
|
206
|
+
// old parent so parent cleanup cannot retire the just-published child.
|
|
207
|
+
this.detachChild(replacement);
|
|
180
208
|
this.retire();
|
|
181
209
|
return ok(replacement);
|
|
182
210
|
}
|
|
@@ -217,6 +245,7 @@ export interface LifecycleTransactionOptions {
|
|
|
217
245
|
interface CreatedResource {
|
|
218
246
|
readonly spec: LifecycleResourceSpec<unknown>;
|
|
219
247
|
readonly value: unknown;
|
|
248
|
+
readonly cleanup: (value: unknown) => void | Promise<void>;
|
|
220
249
|
}
|
|
221
250
|
|
|
222
251
|
export class LifecycleTransaction {
|
|
@@ -250,10 +279,10 @@ export class LifecycleTransaction {
|
|
|
250
279
|
}
|
|
251
280
|
const value = await spec.create();
|
|
252
281
|
if (!this.scope.isAlive()) {
|
|
253
|
-
await this.cleanupOne(
|
|
282
|
+
await this.cleanupOne(value, created, idempotentCleanup(spec.cleanup));
|
|
254
283
|
return this.failure('dispose');
|
|
255
284
|
}
|
|
256
|
-
created.push({ spec, value });
|
|
285
|
+
created.push({ spec, value, cleanup: idempotentCleanup(spec.cleanup) });
|
|
257
286
|
} catch (cause) {
|
|
258
287
|
const cleanupFailures = await this.cleanupCreated(created);
|
|
259
288
|
return this.failure('create', spec.kind, cleanupFailures, cause);
|
|
@@ -296,7 +325,7 @@ export class LifecycleTransaction {
|
|
|
296
325
|
const item = created[index];
|
|
297
326
|
if (item === undefined) continue;
|
|
298
327
|
try {
|
|
299
|
-
await item.
|
|
328
|
+
await item.cleanup(item.value);
|
|
300
329
|
} catch (cause) {
|
|
301
330
|
failures.push({ resourceKind: item.spec.kind, cause });
|
|
302
331
|
}
|
|
@@ -305,13 +334,13 @@ export class LifecycleTransaction {
|
|
|
305
334
|
}
|
|
306
335
|
|
|
307
336
|
private async cleanupOne(
|
|
308
|
-
spec: LifecycleResourceSpec<unknown>,
|
|
309
337
|
value: unknown,
|
|
310
338
|
created: readonly CreatedResource[],
|
|
339
|
+
cleanup: (value: unknown) => void | Promise<void>,
|
|
311
340
|
): Promise<void> {
|
|
312
341
|
await this.cleanupCreated(created);
|
|
313
342
|
try {
|
|
314
|
-
await
|
|
343
|
+
await cleanup(value);
|
|
315
344
|
} catch {
|
|
316
345
|
// The terminal dispose result is intentionally bounded; prior cleanup
|
|
317
346
|
// evidence remains in the transaction failure shape.
|