@forgeax/engine-render 0.1.35 → 0.1.37
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 +28 -0
- package/dist/assembly/host-contract.d.ts +2 -2
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
- package/dist/{chunk-MZ4C7EHF.mjs → chunk-3F5XYSN3.mjs} +5 -5
- package/dist/{chunk-MZ4C7EHF.mjs.map → chunk-3F5XYSN3.mjs.map} +1 -1
- package/dist/{chunk-G6HDNGY7.mjs → chunk-3O2BBKEL.mjs} +24 -230
- package/dist/chunk-3O2BBKEL.mjs.map +1 -0
- package/dist/{chunk-MXTWKZO4.mjs → chunk-ARLAEJ45.mjs} +225 -4
- package/dist/chunk-ARLAEJ45.mjs.map +1 -0
- package/dist/{chunk-3XUBWRY2.mjs → chunk-E4NJYUJ2.mjs} +4 -4
- package/dist/{chunk-3XUBWRY2.mjs.map → chunk-E4NJYUJ2.mjs.map} +1 -1
- package/dist/{chunk-LM5IE7GF.mjs → chunk-N7NVXQWI.mjs} +3 -3
- package/dist/{chunk-LM5IE7GF.mjs.map → chunk-N7NVXQWI.mjs.map} +1 -1
- package/dist/{chunk-SGPUJW3F.mjs → chunk-NMSOOXBB.mjs} +3 -3
- package/dist/{chunk-SGPUJW3F.mjs.map → chunk-NMSOOXBB.mjs.map} +1 -1
- package/dist/{chunk-LNIUIKQ7.mjs → chunk-SJIPVWNB.mjs} +79 -21
- package/dist/chunk-SJIPVWNB.mjs.map +1 -0
- package/dist/construct-renderer.mjs +141 -74
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/features/particle-mesh-layout.d.ts +19 -0
- package/dist/features/particle-mesh-layout.d.ts.map +1 -0
- package/dist/features/types.d.ts +5 -1
- package/dist/features/types.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +1 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-views.d.ts +1 -1
- package/dist/gpu-driven/shadow-views.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +1 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/index.mjs +48 -36
- package/dist/index.mjs.map +1 -1
- package/dist/internal.mjs +5 -5
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/publication/publisher.d.ts +1 -0
- package/dist/publication/publisher.d.ts.map +1 -1
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts +1 -2
- package/dist/record/prepared-material-bindings.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +2 -0
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -2
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +1 -8
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +2 -2
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +2 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +2 -1
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/index.mjs +4 -4
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +26 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +41 -1
- package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +23 -0
- package/src/__tests__/ibl/precompute-submission.unit.test.ts +123 -0
- package/src/__tests__/ibl-residency-publication.unit.test.ts +0 -16
- package/src/__tests__/material-vertex-texture.browser.test.ts +7 -0
- package/src/__tests__/material-vertex-texture.dawn.test.ts +7 -0
- package/src/__tests__/material-vertex-texture.fixture.ts +31 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +5 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +106 -4
- package/src/__tests__/runtime-material-program.integration.test.ts +79 -0
- package/src/__tests__/vertex-layouts-ssot.unit.test.ts +23 -3
- package/src/assembly/host-contract.ts +2 -2
- package/src/assembly/webgpu-renderer.ts +12 -9
- package/src/assembly/webgpu-vertex-layouts.ts +2 -9
- package/src/device/device-scope.ts +3 -0
- package/src/features/particle-mesh-layout.ts +25 -0
- package/src/features/types.ts +5 -1
- package/src/gpu-driven/production-raster.ts +23 -6
- package/src/gpu-driven/shadow-views.ts +27 -2
- package/src/gpu-driven/view-gpu.ts +5 -0
- package/src/ibl/IblPipelineCache.ts +4 -3
- package/src/pbr-pipeline.ts +4 -1
- package/src/publication/__tests__/features.integration.test.ts +51 -0
- package/src/publication/__tests__/publication.integration.test.ts +59 -0
- package/src/publication/publisher.ts +56 -29
- package/src/record/__tests__/prepared-runtime-material.integration.test.ts +134 -0
- package/src/record/frame.ts +45 -6
- package/src/record/prepared-material-bindings.ts +11 -37
- package/src/record/render-context.ts +2 -0
- package/src/record/shadow-pass.ts +65 -4
- package/src/record/typed-frame-graph.ts +5 -1
- package/src/recovery/render-system-candidate.ts +1 -9
- package/src/render-contract.ts +2 -2
- package/src/render-system-extract-tail.ts +15 -6
- package/src/render-system-extract.ts +9 -2
- package/src/render-system.ts +2 -3
- package/src/scene/render-scene.ts +4 -2
- package/src/typed-shadow-passes.ts +36 -5
- package/dist/chunk-G6HDNGY7.mjs.map +0 -1
- package/dist/chunk-LNIUIKQ7.mjs.map +0 -1
- package/dist/chunk-MXTWKZO4.mjs.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { probeVideoHighPerfUpload } from '@forgeax/engine-graphics-extras';
|
|
2
|
+
import { box3, frustum } from '@forgeax/engine-math';
|
|
2
3
|
import {
|
|
3
4
|
type BindGroup,
|
|
4
5
|
type BindGroupEntry,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
import { assembleMaterialWithSkylightEntries } from '../ibl/skylight-bind-group';
|
|
25
26
|
import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
|
|
26
27
|
import { instanceUploadRangesForResident } from '../instances';
|
|
28
|
+
import { deriveInstancesUnionBounds } from '../instances-derived-bounds';
|
|
27
29
|
import {
|
|
28
30
|
buildPbrMaterialUserRegionEntries,
|
|
29
31
|
createPbrSkinMeshBindGroupEntries,
|
|
@@ -643,6 +645,29 @@ function shadowCasterKeys(c: _InternalRenderPipelineContext): ReadonlySet<string
|
|
|
643
645
|
return c.gpuDrivenShadowDrawKeys ?? new Set<string>();
|
|
644
646
|
}
|
|
645
647
|
|
|
648
|
+
function shadowViewContains(source: RenderableSnapshot, planes: Float32Array | undefined): boolean {
|
|
649
|
+
if (planes === undefined || planes.length === 0 || source.localAabb === undefined) return true;
|
|
650
|
+
// The extracted mesh AABB is bind-pose geometry, not a conservative bound
|
|
651
|
+
// for the current skin palette. Keep skinned residuals until extraction owns
|
|
652
|
+
// a deformation-aware bound.
|
|
653
|
+
if (source.skin !== undefined) return true;
|
|
654
|
+
if (source.spriteInstances !== undefined) return true;
|
|
655
|
+
let worldBounds: Float32Array;
|
|
656
|
+
if (source.instances !== undefined) {
|
|
657
|
+
if (source.instances.instanceCount === 0) return false;
|
|
658
|
+
const derived = deriveInstancesUnionBounds({
|
|
659
|
+
meshAabb: source.localAabb,
|
|
660
|
+
entityWorld: source.transform.world,
|
|
661
|
+
transforms: source.instances.transforms,
|
|
662
|
+
});
|
|
663
|
+
if (derived === undefined) return true;
|
|
664
|
+
worldBounds = derived;
|
|
665
|
+
} else {
|
|
666
|
+
worldBounds = box3.transformBox3(box3.create(), source.localAabb, source.transform.world);
|
|
667
|
+
}
|
|
668
|
+
return frustum.intersectsBox(planes as frustum.Frustum, worldBounds as box3.Box3Like);
|
|
669
|
+
}
|
|
670
|
+
|
|
646
671
|
function validateShadowOwnership(
|
|
647
672
|
c: _InternalRenderPipelineContext,
|
|
648
673
|
identity: ShadowViewIdentity,
|
|
@@ -654,12 +679,39 @@ function validateShadowOwnership(
|
|
|
654
679
|
(c.gpuDrivenShadowDrawKeys === undefined || c.gpuDrivenShadowDrawKeys.size === 0)
|
|
655
680
|
)
|
|
656
681
|
return;
|
|
657
|
-
const
|
|
682
|
+
const viewPlanes = c.shadowViewPlanesByView?.get(shadowViewIdentityKey(identity));
|
|
683
|
+
const visibleRenderableIndices =
|
|
684
|
+
viewPlanes === undefined
|
|
685
|
+
? undefined
|
|
686
|
+
: new Set(
|
|
687
|
+
(c.shadowValidatedOrdered ?? c.validatedOrdered)
|
|
688
|
+
.filter((entry) => shadowViewContains(entry.source, viewPlanes))
|
|
689
|
+
.map((entry) => entry.renderableIndex),
|
|
690
|
+
);
|
|
691
|
+
const allExpectedKeys = shadowCasterKeys(c);
|
|
692
|
+
const visibleExpectedKeys =
|
|
693
|
+
visibleRenderableIndices === undefined || c.shadowCasterMembership === undefined
|
|
694
|
+
? allExpectedKeys
|
|
695
|
+
: new Set(
|
|
696
|
+
c.shadowCasterMembership
|
|
697
|
+
.filter((membership) => visibleRenderableIndices.has(membership.renderableIndex))
|
|
698
|
+
.map((membership) =>
|
|
699
|
+
gpuDrivenShadowDrawKey(
|
|
700
|
+
membership.worldEntity,
|
|
701
|
+
membership.materialHandle,
|
|
702
|
+
membership.drawItemIndex,
|
|
703
|
+
membership.passIndex,
|
|
704
|
+
),
|
|
705
|
+
),
|
|
706
|
+
);
|
|
658
707
|
const claimed = claimedKeys ?? new Set<string>();
|
|
659
708
|
const overlap = [...claimed].filter((key) => recordedKeys.has(key));
|
|
660
709
|
const union = new Set([...claimed, ...recordedKeys]);
|
|
661
|
-
const missing = [...
|
|
662
|
-
|
|
710
|
+
const missing = [...visibleExpectedKeys].filter((key) => !union.has(key));
|
|
711
|
+
// GPU ownership is projected before its own per-view culling. A claim may
|
|
712
|
+
// therefore be outside the CPU residual's conservative visible subset and
|
|
713
|
+
// is still valid as long as it belongs to the extracted caster membership.
|
|
714
|
+
const unexpectedClaims = [...claimed].filter((key) => !allExpectedKeys.has(key));
|
|
663
715
|
// `shadowCasterMembership` describes the concrete draws that the GPU
|
|
664
716
|
// producer is eligible to claim. A renderable that stays on the CPU lane
|
|
665
717
|
// (for example a clustered frame without a compatible GPU-driven PBR
|
|
@@ -674,7 +726,7 @@ function validateShadowOwnership(
|
|
|
674
726
|
throw new RhiError({
|
|
675
727
|
code: 'internal-error',
|
|
676
728
|
expected: `disjoint ShadowCaster ownership for ${shadowViewIdentityKey(identity)}`,
|
|
677
|
-
hint: `claimed=${[...claimed].sort().join(',')} recorded=${[...recordedKeys].sort().join(',')} missing=${missing.sort().join(',')} unexpectedClaims=${unexpectedClaims.sort().join(',')}`,
|
|
729
|
+
hint: `expected=${[...allExpectedKeys].sort().join(',')} visibleExpected=${[...visibleExpectedKeys].sort().join(',')} claimed=${[...claimed].sort().join(',')} recorded=${[...recordedKeys].sort().join(',')} missing=${missing.sort().join(',')} unexpectedClaims=${unexpectedClaims.sort().join(',')}`,
|
|
678
730
|
});
|
|
679
731
|
}
|
|
680
732
|
|
|
@@ -842,9 +894,11 @@ export function encodeDirectionalShadowPass(
|
|
|
842
894
|
cascadeIndex: number,
|
|
843
895
|
viewport: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },
|
|
844
896
|
drawFeatures?: (view: BindGroup) => void,
|
|
897
|
+
forceRaster = false,
|
|
845
898
|
): void {
|
|
846
899
|
const directionalIdentity = { kind: 'directional' as const, index: cascadeIndex };
|
|
847
900
|
if (
|
|
901
|
+
!forceRaster &&
|
|
848
902
|
(c.gpuDrivenShadowViews?.cacheHit(directionalIdentity) === true ||
|
|
849
903
|
(c.gpuDrivenShadowViews === undefined && c.directionalShadowCacheReuse)) &&
|
|
850
904
|
drawFeatures === undefined
|
|
@@ -941,6 +995,8 @@ export function encodeDirectionalShadowPass(
|
|
|
941
995
|
}),
|
|
942
996
|
shadowShaderMap(c),
|
|
943
997
|
claimedKeys,
|
|
998
|
+
false,
|
|
999
|
+
c.shadowViewPlanesByView?.get(shadowViewIdentityKey(directionalIdentity)),
|
|
944
1000
|
);
|
|
945
1001
|
validateShadowOwnership(c, directionalIdentity, claimedKeys, recordedKeys);
|
|
946
1002
|
c.frameState.directionalShadowCacheRecorded =
|
|
@@ -1047,6 +1103,7 @@ export function encodePointShadowPass(
|
|
|
1047
1103
|
shadowShaderMap(c),
|
|
1048
1104
|
claimedKeys,
|
|
1049
1105
|
true,
|
|
1106
|
+
c.shadowViewPlanesByView?.get(shadowViewIdentityKey(pointIdentity)),
|
|
1050
1107
|
);
|
|
1051
1108
|
validateShadowOwnership(c, pointIdentity, claimedKeys, recordedKeys);
|
|
1052
1109
|
}
|
|
@@ -1171,6 +1228,8 @@ export function encodeSpotShadowPass(
|
|
|
1171
1228
|
}),
|
|
1172
1229
|
shadowShaderMap(c),
|
|
1173
1230
|
claimedKeys,
|
|
1231
|
+
false,
|
|
1232
|
+
c.shadowViewPlanesByView?.get(shadowViewIdentityKey(spotIdentity)),
|
|
1174
1233
|
);
|
|
1175
1234
|
validateShadowOwnership(c, spotIdentity, claimedKeys, recordedKeys);
|
|
1176
1235
|
}
|
|
@@ -1303,6 +1362,7 @@ export function recordShadowCasterDraws(
|
|
|
1303
1362
|
shadowDispatchByRenderableIdx: ShadowDispatchMap,
|
|
1304
1363
|
gpuDrivenShadowDrawKeys: ReadonlySet<string> | undefined,
|
|
1305
1364
|
reflected = false,
|
|
1365
|
+
viewPlanes?: Float32Array,
|
|
1306
1366
|
): ReadonlySet<string> {
|
|
1307
1367
|
const {
|
|
1308
1368
|
runtime,
|
|
@@ -1330,6 +1390,7 @@ export function recordShadowCasterDraws(
|
|
|
1330
1390
|
for (let i = 0; i < validatedOrdered.length; i++) {
|
|
1331
1391
|
const entry = validatedOrdered[i];
|
|
1332
1392
|
if (entry === undefined) continue;
|
|
1393
|
+
if (!shadowViewContains(entry.source, viewPlanes)) continue;
|
|
1333
1394
|
|
|
1334
1395
|
// feat-20260609 M2: skip entities that don't match the pass selector.
|
|
1335
1396
|
if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
|
|
@@ -2639,7 +2639,11 @@ export function ensureCompiledFrameGraph(
|
|
|
2639
2639
|
? {}
|
|
2640
2640
|
: {
|
|
2641
2641
|
projectGpuDrivenShadow: (identity) =>
|
|
2642
|
-
|
|
2642
|
+
// A newly compiled graph must include the shadow compute passes
|
|
2643
|
+
// even when the producer cache hit. The graph owns its pass
|
|
2644
|
+
// list; importing only the cached view resources would reuse
|
|
2645
|
+
// stale indirect arguments after a topology replacement.
|
|
2646
|
+
gpuDriven.projectShadow?.(builder, identity, true) ?? ok(undefined),
|
|
2643
2647
|
}),
|
|
2644
2648
|
contributeShadowFeatures: () =>
|
|
2645
2649
|
(featureShadows ??= projectRenderFeatureShadows(
|
|
@@ -53,6 +53,7 @@ import { prepareFrameLighting } from '../record/frame-lighting';
|
|
|
53
53
|
import type { RenderFrameState, ValidatedRenderable } from '../record/frame-snapshot';
|
|
54
54
|
import { resolveGeometryInstanceBuffer } from '../record/main-pass-geometry';
|
|
55
55
|
import { computeSplitLdrSprite } from '../record/main-pass-sprite-draws';
|
|
56
|
+
import type { PreparedResolverCaches } from '../record/prepared-material-bindings';
|
|
56
57
|
import { ensureProbeBlendRecordBuffer } from '../record/probe-blend-buffer';
|
|
57
58
|
import {
|
|
58
59
|
prepareRecoveryPipelineReadiness,
|
|
@@ -90,15 +91,6 @@ import type {
|
|
|
90
91
|
RecoveryPointsLinesCandidate,
|
|
91
92
|
} from './types';
|
|
92
93
|
|
|
93
|
-
export interface PreparedResolverCaches {
|
|
94
|
-
readonly preparedPipelineIds: WeakMap<object, string>;
|
|
95
|
-
readonly preparedMaterialPipelineShaders: WeakMap<object, string>;
|
|
96
|
-
readonly preparedGroup0Pipelines: WeakSet<object>;
|
|
97
|
-
readonly preparedViewOnlyPipelines: WeakSet<object>;
|
|
98
|
-
readonly preparedRenderMaterialPipelines: WeakSet<object>;
|
|
99
|
-
readonly preparedAssetHandles: WeakMap<RenderResourceScope, Map<string, number>>;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
94
|
export interface RecoveryFrameSeed {
|
|
103
95
|
readonly frame: ExtractedFrame;
|
|
104
96
|
readonly worlds: readonly RenderResourceScope[];
|
package/src/render-contract.ts
CHANGED
|
@@ -478,8 +478,8 @@ export interface Renderer {
|
|
|
478
478
|
destroyRenderTarget(target: RenderTarget): RenderResult<void, RenderError>;
|
|
479
479
|
setProfile(profile: RenderProfile): RenderResult<void, RenderError>;
|
|
480
480
|
state(): RendererState;
|
|
481
|
-
/** Detached
|
|
482
|
-
bounds(world: World, entity: number): RenderSceneBounds | undefined;
|
|
481
|
+
/** Detached bounds from an extracted World or the bound publication source; undefined if unavailable. */
|
|
482
|
+
bounds(world: World | RenderPublicationIdentity, entity: number): RenderSceneBounds | undefined;
|
|
483
483
|
inspect(): RenderInspection;
|
|
484
484
|
observe(
|
|
485
485
|
receipt: FrameReceipt,
|
|
@@ -2137,6 +2137,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2137
2137
|
if (asset.kind !== 'material') {
|
|
2138
2138
|
materialSnap = defaultMaterialSnapshot(handleRaw);
|
|
2139
2139
|
} else {
|
|
2140
|
+
// Program selection belongs to the shared source; RuntimeMaterialValue
|
|
2141
|
+
// only projects the numeric parameter content resolved below.
|
|
2142
|
+
const source = world.sharedRefs.resolve<string, Asset>(tagged);
|
|
2143
|
+
const programOwner =
|
|
2144
|
+
source.ok && source.value.kind === 'material' ? source.value : asset;
|
|
2140
2145
|
// feat-20260529 M3 / w11: material parent chain inheritance via
|
|
2141
2146
|
// read-through _materialWalk accessor (plan-strategy D-6).
|
|
2142
2147
|
// The old direct asset.passes / asset.values read never
|
|
@@ -2186,7 +2191,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2186
2191
|
};
|
|
2187
2192
|
try {
|
|
2188
2193
|
materialProgramSelections = materialProgramSelectionsForMaterial(
|
|
2189
|
-
|
|
2194
|
+
programOwner,
|
|
2190
2195
|
assets,
|
|
2191
2196
|
materialContext,
|
|
2192
2197
|
);
|
|
@@ -2198,10 +2203,14 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2198
2203
|
// publication error instead of guessing from module names/order.
|
|
2199
2204
|
if (isMissingMaterialProgram(error) && materialContext?.geometry === 'mesh') {
|
|
2200
2205
|
try {
|
|
2201
|
-
const skinnedSelections = materialProgramSelectionsForMaterial(
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2206
|
+
const skinnedSelections = materialProgramSelectionsForMaterial(
|
|
2207
|
+
programOwner,
|
|
2208
|
+
assets,
|
|
2209
|
+
{
|
|
2210
|
+
...materialContext,
|
|
2211
|
+
geometry: 'skinned',
|
|
2212
|
+
},
|
|
2213
|
+
);
|
|
2205
2214
|
if (
|
|
2206
2215
|
skinnedSelections?.some(materialProgramSelectionRequiresSkin) === true &&
|
|
2207
2216
|
gpuDrivenMesh !== undefined
|
|
@@ -2238,7 +2247,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2238
2247
|
]),
|
|
2239
2248
|
);
|
|
2240
2249
|
const materialSceneIndexProgramKeys = materialSceneIndexProgramKeysForMaterial(
|
|
2241
|
-
|
|
2250
|
+
programOwner,
|
|
2242
2251
|
assets,
|
|
2243
2252
|
materialContext,
|
|
2244
2253
|
);
|
|
@@ -2362,9 +2362,16 @@ export function resolveMaterialSnapshot(
|
|
|
2362
2362
|
if (!resolvedResult.ok) return defaultMaterialSnapshot(handleRaw);
|
|
2363
2363
|
const resolved = resolvedResult.value;
|
|
2364
2364
|
const allPasses = resolved.passes;
|
|
2365
|
-
|
|
2365
|
+
// Numeric World content changes values, never the immutable program owner.
|
|
2366
|
+
const source = world.sharedRefs.resolve<string, Asset>(tagged);
|
|
2367
|
+
const programOwner = source.ok && source.value.kind === 'material' ? source.value : asset;
|
|
2368
|
+
const materialProgramKeys = materialProgramKeysForMaterial(
|
|
2369
|
+
programOwner,
|
|
2370
|
+
assetsRef,
|
|
2371
|
+
materialContext,
|
|
2372
|
+
);
|
|
2366
2373
|
const materialSceneIndexProgramKeys = materialSceneIndexProgramKeysForMaterial(
|
|
2367
|
-
|
|
2374
|
+
programOwner,
|
|
2368
2375
|
assetsRef,
|
|
2369
2376
|
materialContext,
|
|
2370
2377
|
);
|
package/src/render-system.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { projectCaptureScene } from './capture/scene-projection';
|
|
2
2
|
import { renderMaterialContext } from './extract/material-context';
|
|
3
|
-
import { RenderPublicationError } from './publication/contract';
|
|
3
|
+
import { RenderPublicationError, type RenderPublicationIdentity } from './publication/contract';
|
|
4
4
|
import { preparePublicationGeometry } from './publication/prepare-geometry';
|
|
5
5
|
import type { PreparedRenderPublication } from './publication/receiver';
|
|
6
6
|
import { type RenderResourceScope, renderTime } from './publication/resource-scope';
|
|
@@ -357,7 +357,7 @@ export interface RecoveryProductionEvidence {
|
|
|
357
357
|
* whose shader identity is `forgeax::default-standard-pbr`).
|
|
358
358
|
*/
|
|
359
359
|
export interface RenderSystem {
|
|
360
|
-
bounds(world: World, entity: number): RenderSceneBounds | undefined;
|
|
360
|
+
bounds(world: World | RenderPublicationIdentity, entity: number): RenderSceneBounds | undefined;
|
|
361
361
|
/** Publish one renderer-owned read-only Surface dynamic page for the next frame. */
|
|
362
362
|
setSurfaceDynamicInput(frame: SurfaceDynamicInputFrame | undefined): void;
|
|
363
363
|
/** Complete explicit SSR capability preparation before drawing a device generation. */
|
|
@@ -1851,7 +1851,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1851
1851
|
) {
|
|
1852
1852
|
return preparedMaterialBindings(
|
|
1853
1853
|
runtime,
|
|
1854
|
-
caches,
|
|
1855
1854
|
worlds,
|
|
1856
1855
|
materialShaderId,
|
|
1857
1856
|
material.world,
|
|
@@ -29,6 +29,7 @@ import type { InstanceProjectionStore } from '../instances';
|
|
|
29
29
|
import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
|
|
30
30
|
import type { PointsLinesInspection } from '../points-lines/inspection';
|
|
31
31
|
import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
|
|
32
|
+
import type { RenderPublicationIdentity } from '../publication/contract';
|
|
32
33
|
import type { PreparedRenderPublication } from '../publication/receiver';
|
|
33
34
|
import type { RenderResourceScope } from '../publication/resource-scope';
|
|
34
35
|
import { worldEntityKey } from '../record/frame-snapshot';
|
|
@@ -2568,9 +2569,10 @@ export class PersistentRenderScene {
|
|
|
2568
2569
|
}
|
|
2569
2570
|
|
|
2570
2571
|
/** Read existing culling bounds; never expose cached mutable arrays. */
|
|
2571
|
-
bounds(world: World, entity: number): RenderSceneBounds | undefined {
|
|
2572
|
+
bounds(world: World | RenderPublicationIdentity, entity: number): RenderSceneBounds | undefined {
|
|
2572
2573
|
const entry = this.composition;
|
|
2573
|
-
const
|
|
2574
|
+
const identity = 'source' in world ? `${world.source}:${world.epoch}` : world.identity;
|
|
2575
|
+
const worldId = entry?.worlds.findIndex((source) => source.identity === identity) ?? -1;
|
|
2574
2576
|
if (entry === undefined || worldId < 0) return undefined;
|
|
2575
2577
|
const slot = entry.projection.slot(worldId, entity);
|
|
2576
2578
|
if (slot === undefined) return undefined;
|
|
@@ -109,6 +109,13 @@ export function addTypedShadowPasses(
|
|
|
109
109
|
index: cascade,
|
|
110
110
|
});
|
|
111
111
|
if (shadowProjection !== undefined && !shadowProjection.ok) return shadowProjection;
|
|
112
|
+
// Every pass in this freshly compiled graph owns a newly allocated
|
|
113
|
+
// raster target. Populate it once even when the producer cache hits or
|
|
114
|
+
// the GPU shadow projection is not admitted on this frame. The encoder
|
|
115
|
+
// receives the same one-shot fence so its cache-hit fast path cannot
|
|
116
|
+
// return before setting the atlas viewport and recording the casters.
|
|
117
|
+
let forceRasterPending = true;
|
|
118
|
+
let forceRasterEncodePending = false;
|
|
112
119
|
const added = recordShadowPass(
|
|
113
120
|
graph,
|
|
114
121
|
`shadowCascade${cascade}`,
|
|
@@ -128,10 +135,20 @@ export function addTypedShadowPasses(
|
|
|
128
135
|
shadowFeatures === undefined
|
|
129
136
|
? undefined
|
|
130
137
|
: (view) => shadowFeatures.encode(pass, frame, resources, view),
|
|
138
|
+
forceRasterEncodePending,
|
|
131
139
|
),
|
|
132
|
-
(frame) =>
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
(frame) => {
|
|
141
|
+
forceRasterEncodePending = false;
|
|
142
|
+
if (forceRasterPending) {
|
|
143
|
+
forceRasterPending = false;
|
|
144
|
+
forceRasterEncodePending = true;
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return (
|
|
148
|
+
shadowFeatures !== undefined ||
|
|
149
|
+
!shadowViewCacheReuse(frame, { kind: 'directional', index: cascade })
|
|
150
|
+
);
|
|
151
|
+
},
|
|
135
152
|
[
|
|
136
153
|
...(shadowProjection?.ok === true && shadowProjection.value?.graphResources !== undefined
|
|
137
154
|
? shadowViewRasterAccesses(shadowProjection.value.graphResources)
|
|
@@ -197,6 +214,7 @@ export function addTypedShadowPasses(
|
|
|
197
214
|
face,
|
|
198
215
|
});
|
|
199
216
|
if (shadowProjection !== undefined && !shadowProjection.ok) return shadowProjection;
|
|
217
|
+
let forceRasterPending = true;
|
|
200
218
|
const identity = { kind: 'point' as const, index: pointIndex, face };
|
|
201
219
|
const cacheReuse = (frame: RenderPipelineFrame): boolean =>
|
|
202
220
|
shadowViewCacheReuse(frame, identity);
|
|
@@ -228,7 +246,13 @@ export function addTypedShadowPasses(
|
|
|
228
246
|
? undefined
|
|
229
247
|
: (view) => shadowFeatures.encode(pass, frame, resources, view),
|
|
230
248
|
),
|
|
231
|
-
(frame) =>
|
|
249
|
+
(frame) => {
|
|
250
|
+
if (forceRasterPending) {
|
|
251
|
+
forceRasterPending = false;
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
return shadowFeatures !== undefined || !cacheReuse(frame);
|
|
255
|
+
},
|
|
232
256
|
[
|
|
233
257
|
...(shadowProjection?.ok === true &&
|
|
234
258
|
shadowProjection.value?.graphResources !== undefined
|
|
@@ -250,6 +274,7 @@ export function addTypedShadowPasses(
|
|
|
250
274
|
for (let spotIndex = 0; spotIndex < Math.max(1, topology.shadow.spotCount); spotIndex += 1) {
|
|
251
275
|
const shadowProjection = projectGpuDrivenShadow?.({ kind: 'spot', index: spotIndex });
|
|
252
276
|
if (shadowProjection !== undefined && !shadowProjection.ok) return shadowProjection;
|
|
277
|
+
let forceRasterPending = true;
|
|
253
278
|
const identity = { kind: 'spot' as const, index: spotIndex };
|
|
254
279
|
const cacheReuse = (frame: RenderPipelineFrame): boolean =>
|
|
255
280
|
shadowViewCacheReuse(frame, identity);
|
|
@@ -267,7 +292,13 @@ export function addTypedShadowPasses(
|
|
|
267
292
|
? undefined
|
|
268
293
|
: (view) => shadowFeatures.encode(pass, frame, resources, view),
|
|
269
294
|
),
|
|
270
|
-
(frame) =>
|
|
295
|
+
(frame) => {
|
|
296
|
+
if (forceRasterPending) {
|
|
297
|
+
forceRasterPending = false;
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
return shadowFeatures !== undefined || !cacheReuse(frame);
|
|
301
|
+
},
|
|
271
302
|
[
|
|
272
303
|
...(shadowProjection?.ok === true && shadowProjection.value?.graphResources !== undefined
|
|
273
304
|
? shadowViewRasterAccesses(shadowProjection.value.graphResources)
|