@forgeax/engine-render 0.1.24 → 0.1.26
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 +118 -1
- package/dist/assembly/factory.d.ts +1 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +4 -17
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -1
- 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 +20 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts +1 -2
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- 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-Q752SHI5.mjs → chunk-ADHHVYLW.mjs} +3 -3
- package/dist/chunk-ADHHVYLW.mjs.map +1 -0
- package/dist/{chunk-TFH5I7JJ.mjs → chunk-E34VL5VI.mjs} +3 -3
- package/dist/{chunk-TFH5I7JJ.mjs.map → chunk-E34VL5VI.mjs.map} +1 -1
- package/dist/{chunk-YKB5DQV7.mjs → chunk-GF523LHF.mjs} +89 -21
- package/dist/chunk-GF523LHF.mjs.map +1 -0
- package/dist/{chunk-KZFTK6DO.mjs → chunk-HKXTW355.mjs} +7 -4
- package/dist/chunk-HKXTW355.mjs.map +1 -0
- package/dist/{chunk-UQYS7FRM.mjs → chunk-KM2NOX2I.mjs} +4 -4
- package/dist/{chunk-UQYS7FRM.mjs.map → chunk-KM2NOX2I.mjs.map} +1 -1
- package/dist/{chunk-YRUYGA4A.mjs → chunk-TMIASV2N.mjs} +944 -577
- package/dist/chunk-TMIASV2N.mjs.map +1 -0
- package/dist/{chunk-Z3YYH2MB.mjs → chunk-YUQXSEHG.mjs} +160 -73
- package/dist/chunk-YUQXSEHG.mjs.map +1 -0
- package/dist/{chunk-RR5L4SRS.mjs → chunk-ZZ4YQ474.mjs} +2 -2
- package/dist/{chunk-RR5L4SRS.mjs.map → chunk-ZZ4YQ474.mjs.map} +1 -1
- package/dist/components/instances.d.ts +13 -31
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +1012 -149
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/extract/contracts.d.ts +6 -6
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +6 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +4 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- 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 +5 -5
- package/dist/materials.d.ts +63 -2
- package/dist/materials.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec-types.d.ts +2 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +13 -2
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +23 -4
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +20 -2
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +3 -3
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/material-uniforms.d.ts +4 -0
- package/dist/record/material-uniforms.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +4 -3
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +10 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +4 -0
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +6 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +44 -9
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +4 -1
- 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 +12 -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/index.mjs +5 -5
- package/package.json +20 -20
- package/src/__tests__/fixtures/ai-weapon-spirit/README.md +20 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/low-poly-toon.wgsl +178 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/material.json +97 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/painterly-surface.wgsl +55 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +3 -3
- package/src/__tests__/gpu-driven-view.browser.test.ts +11 -3
- package/src/__tests__/gpu-scene.unit.test.ts +38 -2
- 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 +48 -13
- package/src/__tests__/{material-static-dynamic-route.unit.test.ts → material-layer-projection.unit.test.ts} +30 -47
- package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
- package/src/__tests__/material-program-dispatch.unit.test.ts +48 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
- package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
- package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +0 -4
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +44 -2
- package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
- 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__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
- package/src/assembly/factory.ts +2 -3
- 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 +8 -40
- package/src/assembly/material/standard-layer-projection.ts +3 -1
- package/src/assembly/material/surface-projection.ts +91 -0
- package/src/assembly/material-shader-policy.ts +88 -10
- package/src/assembly/recovery/renderer-recover.ts +1 -1
- package/src/assembly/webgpu-ready.ts +3 -16
- package/src/assembly/webgpu-renderer.ts +106 -166
- package/src/assembly/webgpu-vertex-layouts.ts +132 -0
- package/src/assets/asset-decoders.ts +27 -4
- package/src/components/instances.ts +14 -197
- package/src/extract/contracts.ts +6 -6
- package/src/gpu-driven/production-raster.ts +34 -1
- package/src/gpu-scene.ts +84 -29
- package/src/index.ts +1 -0
- package/src/instance-buffer-cache.ts +14 -1
- package/src/instances.ts +288 -0
- package/src/materials.ts +109 -14
- package/src/pipeline-builder.ts +14 -10
- package/src/pipeline-spec-types.ts +2 -0
- package/src/pipeline-spec.ts +13 -17
- package/src/record/frame-snapshot.ts +19 -2
- package/src/record/frame.ts +38 -3
- package/src/record/main-pass-geometry.ts +837 -337
- package/src/record/main-pass-material.ts +87 -2
- package/src/record/main-pass-sprite-draws.ts +66 -169
- package/src/record/main-pass.ts +24 -246
- package/src/record/material-uniforms.ts +186 -0
- package/src/record/render-context.ts +8 -0
- package/src/record/shadow-pass.ts +120 -115
- package/src/recovery/render-system-candidate.ts +46 -5
- package/src/render-contract.ts +4 -0
- package/src/render-system-extract-tail.ts +153 -57
- package/src/render-system-extract.ts +110 -73
- package/src/render-system.ts +59 -5
- package/src/scene/render-scene-types.ts +2 -0
- package/src/scene/render-scene.ts +579 -36
- package/src/systems/skin-palette-allocator.ts +129 -7
- package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
- package/dist/assembly/material/pipeline-projection.d.ts +0 -30
- package/dist/assembly/material/pipeline-projection.d.ts.map +0 -1
- package/dist/chunk-KZFTK6DO.mjs.map +0 -1
- package/dist/chunk-Q752SHI5.mjs.map +0 -1
- package/dist/chunk-YKB5DQV7.mjs.map +0 -1
- package/dist/chunk-YRUYGA4A.mjs.map +0 -1
- package/dist/chunk-Z3YYH2MB.mjs.map +0 -1
- package/src/assembly/material/pipeline-projection.ts +0 -63
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MaterialCookProgramContext } from '@forgeax/engine-pack/material-cook';
|
|
1
2
|
// @forgeax/engine-runtime - RenderSystem Extract stage (D-S2 + plan-strategy R-15
|
|
2
3
|
// fallback split). Pure ECS query phase: walks Camera / DirectionalLight /
|
|
3
4
|
// the merged-MeshRenderer renderable archetype and produces SoA-free snapshot
|
|
@@ -75,31 +76,15 @@
|
|
|
75
76
|
// `MeshFilter.assetHandle` dangling path
|
|
76
77
|
// (charter proposition 5; AC-11).
|
|
77
78
|
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
// `InstancedBufferAsset` POD) is gone. Per-entity instance transforms now
|
|
82
|
-
// live inside the ECS `Instances { transforms: 'array<f32>' }` column;
|
|
83
|
-
// extract materialises a `Float32Array` snapshot per Instances-bearing
|
|
84
|
-
// entity (via `world.get(e, Instances).transforms`) so the record stage
|
|
85
|
-
// can upload to a per-entity GPU storage buffer cached by entity packed
|
|
86
|
-
// u32.
|
|
87
|
-
//
|
|
88
|
-
// feat-20260515-buffer-array-vocab-collapse M3 / w15: the component-level
|
|
89
|
-
// `arrayStride` hook on `Instances.transforms` was retired (decision section
|
|
90
|
-
// 2.3 stride responsibility migration -- AI users gate at the set / push
|
|
91
|
-
// call sites + RenderSystem extract entry holds a defensive fail-fast).
|
|
92
|
-
// The extract entry checks `transforms.length % 16 === 0` directly after
|
|
93
|
-
// the `world.get(entity, Instances)` snapshot is taken; violations route
|
|
94
|
-
// `InstanceTransformsStrideMismatchError`
|
|
95
|
-
// (`code: 'instance-transforms-stride-mismatch'`,
|
|
96
|
-
// `detail: { actualLength, expectedStride: 16 }`) through the World
|
|
97
|
-
// Layer-3 ErrorHandler and the renderable is skipped. The record stage
|
|
98
|
-
// trusts the invariant and does not re-check.
|
|
79
|
+
// World owns explicit Instances transforms. Extraction makes a detached
|
|
80
|
+
// per-renderer projection; GPU residents acknowledge complete snapshots only
|
|
81
|
+
// after successful uploads. Reading a snapshot never changes author state.
|
|
99
82
|
|
|
100
83
|
import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
|
|
101
84
|
import {
|
|
85
|
+
materialParametersToParamSchema as projectMaterialParametersToParamSchema,
|
|
102
86
|
resolveAssetHandle,
|
|
87
|
+
selectMaterialPassProgram,
|
|
103
88
|
walkMaterialPassesOverSharedRefs,
|
|
104
89
|
} from '@forgeax/engine-assets-runtime';
|
|
105
90
|
import type { Component, ComponentSchema, EntityHandle, Query, World } from '@forgeax/engine-ecs';
|
|
@@ -166,6 +151,7 @@ import type {
|
|
|
166
151
|
RenderFeatureHiddenEntityReport,
|
|
167
152
|
RenderFeatureWorldVisibilitySnapshot,
|
|
168
153
|
} from './features/types';
|
|
154
|
+
import type { InstanceCollectionId, InstanceProjectionStore } from './instances';
|
|
169
155
|
import type {
|
|
170
156
|
MeshMaterialBindingDiagnostic,
|
|
171
157
|
MeshMaterialBindingSource,
|
|
@@ -696,6 +682,8 @@ export interface RenderableSnapshot {
|
|
|
696
682
|
readonly materialBindingSources: readonly MeshMaterialBindingSource[];
|
|
697
683
|
/** Active structured override diagnostics for this renderable. */
|
|
698
684
|
readonly materialBindingDiagnostics?: readonly MeshMaterialBindingDiagnostic[];
|
|
685
|
+
/** Effective authored visibility retained by the persistent scene owner. */
|
|
686
|
+
readonly authorVisible?: boolean;
|
|
699
687
|
/**
|
|
700
688
|
* feat-20260708-composited-multi-world-rendering M1 / D-1: the worldId
|
|
701
689
|
* of the world this renderable was extracted from. Defaults to 0 in
|
|
@@ -761,6 +749,8 @@ export interface RenderableSnapshot {
|
|
|
761
749
|
* Absent (`undefined`) means the entity is not skinned.
|
|
762
750
|
*/
|
|
763
751
|
readonly skin?: SkinPaletteSlice;
|
|
752
|
+
/** Joint entity identities used by the persistent skin dependency index. */
|
|
753
|
+
readonly skinJointEntities?: readonly number[];
|
|
764
754
|
/**
|
|
765
755
|
* ECS-owned morph weights. Presence selects the Standard Pipeline CPU
|
|
766
756
|
* specialized deformation lane; the asset remains the source of target
|
|
@@ -822,10 +812,14 @@ export interface GpuDrivenDrawSnapshot {
|
|
|
822
812
|
}
|
|
823
813
|
|
|
824
814
|
export interface InstancesSnapshot {
|
|
815
|
+
/** Renderer-owned collection identity. */
|
|
816
|
+
readonly collectionId?: InstanceCollectionId;
|
|
825
817
|
/** Packed column-major mat4 transforms (16 f32 per instance). */
|
|
826
818
|
readonly transforms: Float32Array;
|
|
827
819
|
/** Number of instances (transforms.length / 16). */
|
|
828
820
|
readonly instanceCount: number;
|
|
821
|
+
/** Collection revision captured by this detached frame snapshot. */
|
|
822
|
+
readonly revision?: number;
|
|
829
823
|
/** Stable per-entity GPU buffer cache key (the packed Entity u32). */
|
|
830
824
|
readonly cacheKey: number;
|
|
831
825
|
/**
|
|
@@ -918,6 +912,7 @@ export interface MaterialSnapshot {
|
|
|
918
912
|
* discriminator (M4-T06).
|
|
919
913
|
*/
|
|
920
914
|
readonly materialShaderId?: string | undefined;
|
|
915
|
+
readonly materialProgramKeys?: Readonly<Record<string, string>> | undefined;
|
|
921
916
|
/**
|
|
922
917
|
* Stable source handle for the material asset. Record uses this only for
|
|
923
918
|
* same-frame assembly reuse; GPU bind-group validity still comes from the
|
|
@@ -1024,12 +1019,9 @@ export interface MaterialSnapshot {
|
|
|
1024
1019
|
* from the legacy `shadingModel` discriminant (feat-20260625 M2 D-3,
|
|
1025
1020
|
* finalised in w15; `shadingModel` field removed in tweak-20260701 M1).
|
|
1026
1021
|
*
|
|
1027
|
-
*
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1030
|
-
* pipeline cache key"). Multi-pass materials whose mix of opaque +
|
|
1031
|
-
* transparent passes need finer routing should split into separate
|
|
1032
|
-
* MaterialAsset entries (the normal forgeax pattern).
|
|
1022
|
+
* This describes the primary-pass snapshot for consumers that need a
|
|
1023
|
+
* material summary. Actual draw routing and the LDR split read the selected
|
|
1024
|
+
* DispatchEntry.renderState, so mixed opaque/transparent Passes remain valid.
|
|
1033
1025
|
*
|
|
1034
1026
|
* Type is `boolean | undefined` (derived): `undefined` means "no
|
|
1035
1027
|
* passes / unknown"; consumers must read `=== true` / `!== true` to
|
|
@@ -1134,6 +1126,7 @@ export function appendMaterialDispatchEntries(
|
|
|
1134
1126
|
layer: number,
|
|
1135
1127
|
paramSnapshot: Readonly<Record<string, number | number[] | string>> | undefined,
|
|
1136
1128
|
passIndexOffset = 0,
|
|
1129
|
+
materialProgramKeys?: Readonly<Record<string, string>>,
|
|
1137
1130
|
): void {
|
|
1138
1131
|
const matchedPasses = selectPasses(passes, {});
|
|
1139
1132
|
for (let pIdx = 0; pIdx < matchedPasses.length; pIdx++) {
|
|
@@ -1144,6 +1137,11 @@ export function appendMaterialDispatchEntries(
|
|
|
1144
1137
|
readonly queue?: number;
|
|
1145
1138
|
readonly stencilReference?: number;
|
|
1146
1139
|
};
|
|
1140
|
+
const authoredShaderId = runtimeMaterialShaderId(pass.program.module, pass.name);
|
|
1141
|
+
const dispatchShaderId =
|
|
1142
|
+
materialProgramKeys === undefined ? authoredShaderId : materialProgramKeys[pass.name];
|
|
1143
|
+
if (materialProgramKeys !== undefined && dispatchShaderId === undefined)
|
|
1144
|
+
throw new Error(`Missing published material program for Pass ${pass.name}`);
|
|
1147
1145
|
pendingDispatch.push({
|
|
1148
1146
|
entityIndex: entity,
|
|
1149
1147
|
materialHandle,
|
|
@@ -1158,7 +1156,7 @@ export function appendMaterialDispatchEntries(
|
|
|
1158
1156
|
defines: undefined,
|
|
1159
1157
|
vertexEntry: pass.program.vertexEntry,
|
|
1160
1158
|
fragmentEntry: pass.program.fragmentEntry,
|
|
1161
|
-
materialShaderId:
|
|
1159
|
+
materialShaderId: dispatchShaderId,
|
|
1162
1160
|
paramSnapshot,
|
|
1163
1161
|
...(passState.stencilReference !== undefined && {
|
|
1164
1162
|
stencilReference: passState.stencilReference,
|
|
@@ -1410,6 +1408,57 @@ export function runtimeMaterialShaderId(
|
|
|
1410
1408
|
}
|
|
1411
1409
|
}
|
|
1412
1410
|
|
|
1411
|
+
/**
|
|
1412
|
+
* Select the runtime identity for one resolved material. A cooked publication
|
|
1413
|
+
* owns the composed material artifact identity; the authored pass module is
|
|
1414
|
+
* the built-in fallback used before that publication is ready.
|
|
1415
|
+
*/
|
|
1416
|
+
export function materialProgramKeysForMaterial(
|
|
1417
|
+
material: MaterialAsset,
|
|
1418
|
+
assets: AssetRegistry,
|
|
1419
|
+
context: MaterialCookProgramContext | undefined,
|
|
1420
|
+
): Readonly<Record<string, string>> | undefined {
|
|
1421
|
+
const projection = assets.getMaterialProjectionForPayload(material);
|
|
1422
|
+
if (projection === undefined) return undefined;
|
|
1423
|
+
if (context === undefined)
|
|
1424
|
+
throw new Error('Published material selection requires renderer-owned compiler context');
|
|
1425
|
+
return Object.fromEntries(
|
|
1426
|
+
projection.passes.map((pass) => {
|
|
1427
|
+
const mode = String(
|
|
1428
|
+
(pass.renderState?.tags as Record<string, unknown> | undefined)?.LightMode ?? pass.name,
|
|
1429
|
+
);
|
|
1430
|
+
const selectedContext: MaterialCookProgramContext = {
|
|
1431
|
+
...context,
|
|
1432
|
+
pass: /shadow/i.test(mode) ? 'shadow' : /depth/i.test(mode) ? 'depth' : 'forward',
|
|
1433
|
+
pipeline: /deferred|gbuffer/i.test(mode) ? 'deferred' : context.pipeline,
|
|
1434
|
+
};
|
|
1435
|
+
return [
|
|
1436
|
+
pass.name,
|
|
1437
|
+
selectMaterialPassProgram(projection, pass.name, selectedContext).specializationKey,
|
|
1438
|
+
];
|
|
1439
|
+
}),
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
export function runtimeMaterialShaderIdForMaterial(
|
|
1444
|
+
passes: readonly MaterialPass[],
|
|
1445
|
+
programs: Readonly<Record<string, string>> | undefined,
|
|
1446
|
+
): string | undefined {
|
|
1447
|
+
const pass =
|
|
1448
|
+
passes.find(
|
|
1449
|
+
(pass) =>
|
|
1450
|
+
!/shadow|depth/i.test(
|
|
1451
|
+
String(
|
|
1452
|
+
(pass.renderState?.tags as Record<string, unknown> | undefined)?.LightMode ?? pass.name,
|
|
1453
|
+
),
|
|
1454
|
+
),
|
|
1455
|
+
) ?? passes[0];
|
|
1456
|
+
if (pass === undefined) return undefined;
|
|
1457
|
+
return programs === undefined
|
|
1458
|
+
? runtimeMaterialShaderId(pass.program.module, pass.name)
|
|
1459
|
+
: programs[pass.name];
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1413
1462
|
export function pipelineRenderState(
|
|
1414
1463
|
renderState:
|
|
1415
1464
|
| (MaterialRenderState & {
|
|
@@ -1714,49 +1763,21 @@ export function isEngineInjectedTextureField(
|
|
|
1714
1763
|
return isStandardPbrMaterialShader(shaderId) && ENGINE_INJECTED_TEXTURE_FIELDS.has(fieldName);
|
|
1715
1764
|
}
|
|
1716
1765
|
|
|
1766
|
+
/**
|
|
1767
|
+
* Project one material's authoring parameters into the runtime ABI.
|
|
1768
|
+
*
|
|
1769
|
+
* Engine-owned Standard roots are compiled against the canonical superset
|
|
1770
|
+
* schema even when the authoring asset omits optional physical fields. A
|
|
1771
|
+
* compact authored schema would move later UBO members (for example `ior`)
|
|
1772
|
+
* into the wrong slots. Custom material roots keep their own published
|
|
1773
|
+
* parameter projection.
|
|
1774
|
+
*/
|
|
1717
1775
|
export function materialParametersToParamSchema(
|
|
1718
1776
|
parameters: readonly MaterialParameter[],
|
|
1719
|
-
shaderId
|
|
1777
|
+
shaderId?: string,
|
|
1720
1778
|
): readonly ParamSchemaEntry[] {
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
// but using that compact projection as the runtime UBO schema would shift
|
|
1724
|
-
// every field after an omitted physical entry.
|
|
1725
|
-
if (isCanonicalStandardPbrMaterialShader(shaderId)) {
|
|
1726
|
-
return STANDARD_PIPELINE_PARAM_SCHEMA;
|
|
1727
|
-
}
|
|
1728
|
-
const engineInjectedTextureFields = isStandardPbrMaterialShader(shaderId)
|
|
1729
|
-
? ENGINE_INJECTED_TEXTURE_FIELDS
|
|
1730
|
-
: undefined;
|
|
1731
|
-
return parameters.flatMap((parameter): ParamSchemaEntry[] => {
|
|
1732
|
-
// Boolean material values are runtime-only and have no UBO representation.
|
|
1733
|
-
if (parameter.type === 'bool') return [];
|
|
1734
|
-
if (parameter.type === 'texture' || parameter.type === 'texture_cube') {
|
|
1735
|
-
if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
|
|
1736
|
-
return [
|
|
1737
|
-
{
|
|
1738
|
-
name: parameter.name,
|
|
1739
|
-
type: parameter.type === 'texture_cube' ? 'texture_cube' : 'texture2d',
|
|
1740
|
-
default: parameter.default,
|
|
1741
|
-
},
|
|
1742
|
-
];
|
|
1743
|
-
}
|
|
1744
|
-
const numericDefault =
|
|
1745
|
-
typeof parameter.default === 'number'
|
|
1746
|
-
? parameter.default
|
|
1747
|
-
: Array.isArray(parameter.default) &&
|
|
1748
|
-
parameter.default.every((entry) => typeof entry === 'number')
|
|
1749
|
-
? parameter.default
|
|
1750
|
-
: undefined;
|
|
1751
|
-
const numericEntry = {
|
|
1752
|
-
name: parameter.name,
|
|
1753
|
-
type: parameter.type,
|
|
1754
|
-
...(parameter.colorSpace === undefined ? {} : { colorSpace: parameter.colorSpace }),
|
|
1755
|
-
};
|
|
1756
|
-
return numericDefault === undefined
|
|
1757
|
-
? [numericEntry]
|
|
1758
|
-
: [{ ...numericEntry, default: numericDefault }];
|
|
1759
|
-
});
|
|
1779
|
+
if (isCanonicalStandardPbrMaterialShader(shaderId)) return STANDARD_PIPELINE_PARAM_SCHEMA;
|
|
1780
|
+
return projectMaterialParametersToParamSchema(parameters);
|
|
1760
1781
|
}
|
|
1761
1782
|
|
|
1762
1783
|
/**
|
|
@@ -1903,6 +1924,7 @@ export function resolveMaterialSnapshot(
|
|
|
1903
1924
|
assetsRef: AssetRegistry,
|
|
1904
1925
|
materialSnapshotCache?: MaterialSnapshotCache,
|
|
1905
1926
|
persistentMaterialSnapshotCache?: MaterialSnapshotCache,
|
|
1927
|
+
materialContext?: MaterialCookProgramContext,
|
|
1906
1928
|
): MaterialSnapshot {
|
|
1907
1929
|
if (handleRaw === 0) return defaultMaterialSnapshot(handleRaw);
|
|
1908
1930
|
const cached = materialSnapshotCache?.get(handleRaw);
|
|
@@ -1934,10 +1956,9 @@ export function resolveMaterialSnapshot(
|
|
|
1934
1956
|
if (!resolvedResult.ok) return defaultMaterialSnapshot(handleRaw);
|
|
1935
1957
|
const resolved = resolvedResult.value;
|
|
1936
1958
|
const allPasses = resolved.passes;
|
|
1937
|
-
const
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
: undefined;
|
|
1959
|
+
const materialProgramKeys = materialProgramKeysForMaterial(asset, assetsRef, materialContext);
|
|
1960
|
+
const authoredFirstPassShader = runtimeMaterialShaderIdForMaterial(allPasses, undefined);
|
|
1961
|
+
const firstPassShader = runtimeMaterialShaderIdForMaterial(allPasses, materialProgramKeys);
|
|
1941
1962
|
const pv = materialValuesToLinearRuntime(
|
|
1942
1963
|
resolved.values,
|
|
1943
1964
|
materialColorParameterSchema(resolved.parameters ?? [], firstPassShader, assetsRef),
|
|
@@ -1964,7 +1985,7 @@ export function resolveMaterialSnapshot(
|
|
|
1964
1985
|
}
|
|
1965
1986
|
const materialParamSchema = materialParametersToParamSchema(
|
|
1966
1987
|
resolved.parameters ?? [],
|
|
1967
|
-
|
|
1988
|
+
authoredFirstPassShader,
|
|
1968
1989
|
);
|
|
1969
1990
|
// feat-20260614 M8 (D-19): texture / sampler values are embedded GUIDs
|
|
1970
1991
|
// (dash-form strings) after loadByGuid. Resolve each to a user-tier column
|
|
@@ -2032,6 +2053,7 @@ export function resolveMaterialSnapshot(
|
|
|
2032
2053
|
}),
|
|
2033
2054
|
normalScale: normalScalePv,
|
|
2034
2055
|
materialShaderId: firstPassShader,
|
|
2056
|
+
materialProgramKeys,
|
|
2035
2057
|
materialHandle: handleRaw,
|
|
2036
2058
|
renderState: pipelineRenderState(allPasses[0]?.renderState),
|
|
2037
2059
|
paramSnapshot: paramSnap,
|
|
@@ -2533,6 +2555,7 @@ export interface ExtractFramesOwner {
|
|
|
2533
2555
|
}
|
|
2534
2556
|
|
|
2535
2557
|
export interface PreparedExtractContext {
|
|
2558
|
+
readonly materialContext?: MaterialCookProgramContext | undefined;
|
|
2536
2559
|
readonly assets: AssetRegistry | null | undefined;
|
|
2537
2560
|
readonly pipelineState: ExtractPipelineSurface | null | undefined;
|
|
2538
2561
|
readonly materialSnapshotCache: MaterialSnapshotCache | undefined;
|
|
@@ -2542,11 +2565,17 @@ export interface PreparedExtractContext {
|
|
|
2542
2565
|
readonly cull: 'self' | 'none' | 'external';
|
|
2543
2566
|
readonly cullCameras: readonly CameraSnapshot[] | undefined;
|
|
2544
2567
|
readonly renderables: 'full' | 'none';
|
|
2568
|
+
/** Restrict renderable extraction to these entities while still reading frame resources. */
|
|
2569
|
+
readonly renderableEntities: ReadonlySet<number> | undefined;
|
|
2570
|
+
/** Keep authored-hidden renderables in the persistent source projection. */
|
|
2571
|
+
readonly retainHidden: boolean;
|
|
2545
2572
|
readonly visibility: VisibilitySnapshot;
|
|
2546
2573
|
/** Producer-owned artifact lookup; extraction only, never record-time asset reads. */
|
|
2547
2574
|
readonly getMaterialShaderArtifact:
|
|
2548
2575
|
| ((materialShaderId: string) => MaterialShaderArtifact | undefined)
|
|
2549
2576
|
| undefined;
|
|
2577
|
+
/** Renderer-owned canonical source for explicit large instance collections. */
|
|
2578
|
+
readonly instanceCollections: InstanceProjectionStore | undefined;
|
|
2550
2579
|
}
|
|
2551
2580
|
|
|
2552
2581
|
const ALL_VISIBLE: VisibilitySnapshot = {
|
|
@@ -2727,6 +2756,7 @@ export function tonemapParams(camera: CameraSnapshot): Uint8Array {
|
|
|
2727
2756
|
export function prepareExtractContext(
|
|
2728
2757
|
world: World,
|
|
2729
2758
|
options: {
|
|
2759
|
+
readonly materialContext?: MaterialCookProgramContext;
|
|
2730
2760
|
readonly assets?: AssetRegistry | null;
|
|
2731
2761
|
readonly pipelineState?: ExtractPipelineSurface | null;
|
|
2732
2762
|
readonly materialSnapshotCache?: MaterialSnapshotCache;
|
|
@@ -2734,13 +2764,17 @@ export function prepareExtractContext(
|
|
|
2734
2764
|
readonly cull?: 'self' | 'none' | 'external';
|
|
2735
2765
|
readonly cullCameras?: readonly CameraSnapshot[];
|
|
2736
2766
|
readonly renderables?: 'full' | 'none';
|
|
2767
|
+
readonly renderableEntities?: ReadonlySet<number>;
|
|
2768
|
+
readonly retainHidden?: boolean;
|
|
2737
2769
|
readonly getMaterialShaderArtifact?: (
|
|
2738
2770
|
materialShaderId: string,
|
|
2739
2771
|
) => MaterialShaderArtifact | undefined;
|
|
2772
|
+
readonly instanceCollections?: InstanceProjectionStore;
|
|
2740
2773
|
} = {},
|
|
2741
2774
|
): PreparedExtractContext {
|
|
2742
2775
|
const renderables = options.renderables ?? 'full';
|
|
2743
2776
|
return {
|
|
2777
|
+
materialContext: options.materialContext,
|
|
2744
2778
|
assets: options.assets,
|
|
2745
2779
|
pipelineState: options.pipelineState,
|
|
2746
2780
|
materialSnapshotCache: options.materialSnapshotCache,
|
|
@@ -2748,8 +2782,11 @@ export function prepareExtractContext(
|
|
|
2748
2782
|
cull: options.cull ?? 'self',
|
|
2749
2783
|
cullCameras: options.cullCameras,
|
|
2750
2784
|
renderables,
|
|
2785
|
+
renderableEntities: options.renderableEntities,
|
|
2786
|
+
retainHidden: options.retainHidden ?? false,
|
|
2751
2787
|
visibility: renderables === 'none' ? ALL_VISIBLE : resolveVisibility(world),
|
|
2752
2788
|
getMaterialShaderArtifact: options.getMaterialShaderArtifact,
|
|
2789
|
+
instanceCollections: options.instanceCollections,
|
|
2753
2790
|
};
|
|
2754
2791
|
}
|
|
2755
2792
|
|
package/src/render-system.ts
CHANGED
|
@@ -118,7 +118,13 @@ import type { EnvironmentInspection } from './environment/inspection';
|
|
|
118
118
|
import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from './gpu-usage';
|
|
119
119
|
import { resetHdrpBuffers } from './hdrp-buffers';
|
|
120
120
|
import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
|
|
121
|
-
import {
|
|
121
|
+
import {
|
|
122
|
+
disposeInstanceBufferChunks,
|
|
123
|
+
disposeInstanceBuffers,
|
|
124
|
+
disposeTransientInstanceBuffers,
|
|
125
|
+
} from './instance-buffer-cache';
|
|
126
|
+
import type { InstanceCollectionInspection } from './instances';
|
|
127
|
+
import { InstanceProjectionStore } from './instances';
|
|
122
128
|
import {
|
|
123
129
|
type MeshMaterialBindingObservation,
|
|
124
130
|
type MeshMaterialBindingPreparationFailure,
|
|
@@ -182,7 +188,11 @@ import type {
|
|
|
182
188
|
RenderableSnapshot,
|
|
183
189
|
} from './render-system-extract';
|
|
184
190
|
import { extractFrames } from './render-system-extract';
|
|
185
|
-
import {
|
|
191
|
+
import {
|
|
192
|
+
type PersistentGpuDrivenCandidate,
|
|
193
|
+
type PersistentRenderCandidateRequest,
|
|
194
|
+
PersistentRenderScene,
|
|
195
|
+
} from './scene/render-scene';
|
|
186
196
|
import { inspectLodOcclusion } from './scene/visibility/inspection';
|
|
187
197
|
import { resolveSsaoParameters } from './ssao-config';
|
|
188
198
|
import { projectSsrDependencies, resolveSsrAdmissionGeneration } from './ssr/admission';
|
|
@@ -660,7 +670,7 @@ export interface RenderSystem {
|
|
|
660
670
|
/** Prepare a detached graph candidate without entering the frame record path. */
|
|
661
671
|
prepareRecoveryGraphCandidate(
|
|
662
672
|
runtime: RecoveryGraphCandidateRuntime,
|
|
663
|
-
): RecoveryGraphCandidatePreparation
|
|
673
|
+
): Promise<RecoveryGraphCandidatePreparation>;
|
|
664
674
|
/** Finish and submit detached candidate setup, never a frame receipt. */
|
|
665
675
|
submitCandidateSetup(
|
|
666
676
|
candidate: RecoveryGraphCandidate,
|
|
@@ -686,6 +696,8 @@ export interface RenderSystem {
|
|
|
686
696
|
readonly pipelineDispatchCounts: {
|
|
687
697
|
readonly unlit: number;
|
|
688
698
|
};
|
|
699
|
+
/** Detached residency and upload facts for explicit instance collections. */
|
|
700
|
+
readonly instanceCollectionsInspection: readonly InstanceCollectionInspection[];
|
|
689
701
|
/**
|
|
690
702
|
* feat-20260528-frustum-culling M5 / w14: per-frame frustum-culling counters.
|
|
691
703
|
* Updated by `draw([world], { cameraOwner: 0, resourceOwner: 0 })` on every call from the Extract stage.
|
|
@@ -1071,6 +1083,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1071
1083
|
let preparedWorlds: readonly World[] = [];
|
|
1072
1084
|
let latestCamera: CameraSnapshot | undefined;
|
|
1073
1085
|
const pointsLinesOwner = new StandardPointsLinesOwner(internals);
|
|
1086
|
+
const instanceCollections = new InstanceProjectionStore();
|
|
1074
1087
|
const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
|
|
1075
1088
|
let ssrFormatReceipt: import('@forgeax/engine-rhi').RhiTextureFormatCapabilityReceipt | undefined;
|
|
1076
1089
|
let ssrFormatProbeGeneration = -1;
|
|
@@ -1112,6 +1125,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1112
1125
|
onSharedRefMutation: (worldId, handle) => {
|
|
1113
1126
|
internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
|
|
1114
1127
|
},
|
|
1128
|
+
instanceCollections,
|
|
1129
|
+
getSkinPaletteAllocator: () => internals.getPipelineState()?.skinPaletteAllocator ?? null,
|
|
1115
1130
|
});
|
|
1116
1131
|
let environmentLifecycle = new EnvironmentLifecycle(internals.deviceScope);
|
|
1117
1132
|
const transmissionAdmission = new TransmissionCandidateAdmission();
|
|
@@ -1362,6 +1377,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1362
1377
|
currentDirectionalShadowView: null,
|
|
1363
1378
|
currentSpotShadowView: null,
|
|
1364
1379
|
instanceBuffers: new Map(),
|
|
1380
|
+
instanceResidency: new Map(),
|
|
1381
|
+
instanceBufferChunks: new Map(),
|
|
1365
1382
|
probeBlendBuffers: new Map(),
|
|
1366
1383
|
probeBlendRecordBufferCapacity: 0,
|
|
1367
1384
|
morphBuffers: new Map(),
|
|
@@ -2294,6 +2311,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2294
2311
|
createPreparedResolverCaches,
|
|
2295
2312
|
});
|
|
2296
2313
|
return {
|
|
2314
|
+
get instanceCollectionsInspection(): readonly InstanceCollectionInspection[] {
|
|
2315
|
+
return instanceCollections._inspections(
|
|
2316
|
+
frameState.instanceResidency,
|
|
2317
|
+
frameState.frameNumber - 1,
|
|
2318
|
+
);
|
|
2319
|
+
},
|
|
2297
2320
|
releaseProfilerCatalog(): void {
|
|
2298
2321
|
releaseProfilerCatalog?.();
|
|
2299
2322
|
releaseProfilerCatalog = undefined;
|
|
@@ -2555,7 +2578,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2555
2578
|
compositionWorlds,
|
|
2556
2579
|
{ cameraOwner, resourceOwner },
|
|
2557
2580
|
internals.assets.catalogEpoch,
|
|
2558
|
-
(
|
|
2581
|
+
(request: PersistentRenderCandidateRequest) =>
|
|
2559
2582
|
extractFrames(
|
|
2560
2583
|
compositionWorlds,
|
|
2561
2584
|
{ cameraOwner, resourceOwner },
|
|
@@ -2563,8 +2586,32 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2563
2586
|
internals.getPipelineState(),
|
|
2564
2587
|
persistentRenderScene.materialSnapshotCacheStore(),
|
|
2565
2588
|
{
|
|
2589
|
+
...(internals.device.caps.backendKind === 'null'
|
|
2590
|
+
? {}
|
|
2591
|
+
: {
|
|
2592
|
+
materialContext: {
|
|
2593
|
+
backend:
|
|
2594
|
+
internals.device.caps.backendKind === 'wgpu-webgl2'
|
|
2595
|
+
? ('webgl2' as const)
|
|
2596
|
+
: internals.device.caps.backendKind,
|
|
2597
|
+
capability: internals.device.caps.storageBuffer
|
|
2598
|
+
? ('storage-buffer' as const)
|
|
2599
|
+
: ('uniform-fallback' as const),
|
|
2600
|
+
pipeline: 'forward' as const,
|
|
2601
|
+
geometry: 'mesh' as const,
|
|
2602
|
+
pass: 'forward' as const,
|
|
2603
|
+
profile: 'forgeax-material-wgsl-v1' as const,
|
|
2604
|
+
toolchain: 'naga-oil' as const,
|
|
2605
|
+
instrumentation: 'none' as const,
|
|
2606
|
+
},
|
|
2607
|
+
}),
|
|
2566
2608
|
cull: 'none',
|
|
2567
|
-
renderables,
|
|
2609
|
+
renderables: request === 'none' ? 'none' : 'full',
|
|
2610
|
+
...(request === 'none' || request === 'full'
|
|
2611
|
+
? {}
|
|
2612
|
+
: { renderableEntitiesByWorld: request.entitiesByWorld }),
|
|
2613
|
+
retainHidden: true,
|
|
2614
|
+
instanceCollections,
|
|
2568
2615
|
...(internals.getMaterialShaderArtifact === undefined
|
|
2569
2616
|
? {}
|
|
2570
2617
|
: { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
|
|
@@ -3496,6 +3543,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3496
3543
|
(internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
|
|
3497
3544
|
pointsLinesOwner.dispose();
|
|
3498
3545
|
persistentRenderScene.dispose();
|
|
3546
|
+
instanceCollections.dispose();
|
|
3499
3547
|
gpuDrivenProduction.dispose();
|
|
3500
3548
|
occlusionRuntime?.dispose();
|
|
3501
3549
|
disposeFeatureGpuWork();
|
|
@@ -3565,6 +3613,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3565
3613
|
// so destroy failures fire structured errors (unified per-frame +
|
|
3566
3614
|
// dispose error strategy).
|
|
3567
3615
|
disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
|
|
3616
|
+
frameState.instanceResidency?.clear();
|
|
3617
|
+
if (frameState.instanceBufferChunks !== undefined) {
|
|
3618
|
+
disposeInstanceBufferChunks(frameState.instanceBufferChunks, internals.errorRegistry);
|
|
3619
|
+
}
|
|
3568
3620
|
disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
|
|
3569
3621
|
if (frameState.probeBlendRecordBuffer !== undefined) {
|
|
3570
3622
|
if (!frameState.probeBlendRecordBuffer.isDestroyed) {
|
|
@@ -3706,6 +3758,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
3706
3758
|
}
|
|
3707
3759
|
frameState.retiredCompiledFrameGraphs.clear();
|
|
3708
3760
|
frameState.instanceBuffers.clear();
|
|
3761
|
+
frameState.instanceBufferChunks?.clear();
|
|
3762
|
+
frameState.instanceResidency?.clear();
|
|
3709
3763
|
delete frameState.probeBlendRecordBuffer;
|
|
3710
3764
|
frameState.probeBlendRecordBufferCapacity = 0;
|
|
3711
3765
|
frameState.probeBlendBuffers.clear();
|
|
@@ -36,6 +36,8 @@ export interface RenderSceneApplyResult {
|
|
|
36
36
|
readonly ignoredLateUpdates: number;
|
|
37
37
|
readonly createdSlots: readonly RenderSceneSlot[];
|
|
38
38
|
readonly updatedSlots: readonly RenderSceneSlot[];
|
|
39
|
+
/** Full retained-snapshot updates; transform spans stay in updatedSlots only. */
|
|
40
|
+
readonly contentUpdatedSlots?: readonly RenderSceneSlot[];
|
|
39
41
|
readonly removedSlots: readonly RenderSceneRecord[];
|
|
40
42
|
readonly recreatedSlots: readonly RenderSceneSlot[];
|
|
41
43
|
readonly resynced: number;
|