@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 { probeVideoHighPerfUpload } from '@forgeax/engine-graphics-extras';
|
|
1
2
|
import {
|
|
2
3
|
type BindGroup,
|
|
3
4
|
type BindGroupEntry,
|
|
@@ -8,14 +9,8 @@ import {
|
|
|
8
9
|
type RhiRenderPassEncoder,
|
|
9
10
|
} from '@forgeax/engine-rhi';
|
|
10
11
|
import type { MaterialRenderState, PassSelector } from '@forgeax/engine-types';
|
|
11
|
-
import { GpuBuffer } from '../gpu-resource';
|
|
12
|
-
import {
|
|
13
|
-
GPU_BUFFER_USAGE_COPY_DST,
|
|
14
|
-
GPU_BUFFER_USAGE_STORAGE,
|
|
15
|
-
GPU_BUFFER_USAGE_UNIFORM,
|
|
16
|
-
} from '../gpu-usage';
|
|
12
|
+
import type { GpuBuffer } from '../gpu-resource';
|
|
17
13
|
import { assembleMaterialWithSkylightEntries } from '../ibl/skylight-bind-group';
|
|
18
|
-
import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
|
|
19
14
|
import {
|
|
20
15
|
buildPbrMaterialUserRegionEntries,
|
|
21
16
|
createPbrSkinMeshBindGroupEntries,
|
|
@@ -26,18 +21,23 @@ import {
|
|
|
26
21
|
import { COOKIE_MATRIX_BYTES } from '../prepare/extended-lighting/resources';
|
|
27
22
|
import type { DispatchEntry, ExtractedLights } from '../render-system-extract';
|
|
28
23
|
import { matchPass } from '../systems/pass-selector';
|
|
29
|
-
import {
|
|
30
|
-
|
|
24
|
+
import {
|
|
25
|
+
resolveGeometryInstanceBuffer,
|
|
26
|
+
resolveGeometryInstancesBindGroup,
|
|
27
|
+
} from './main-pass-geometry';
|
|
28
|
+
import {
|
|
29
|
+
buildPerSubmeshMaterialBg,
|
|
30
|
+
type PerSubmeshMaterialBgDeps,
|
|
31
|
+
prepareMaterialSkylight,
|
|
32
|
+
} from './main-pass-material';
|
|
31
33
|
import {
|
|
32
34
|
getOrCreateFromChain,
|
|
33
|
-
MAX_UNIFORM_INSTANCES,
|
|
34
35
|
MESH_PER_ENTITY_STRIDE,
|
|
35
36
|
MESH_SSBO_BYTES,
|
|
36
37
|
MESH_UBO_FULL_ARRAY_BYTES,
|
|
37
|
-
packInstanceStorageBuffer,
|
|
38
38
|
} from './mesh-ssbo';
|
|
39
39
|
import type { _InternalRenderPipelineContext } from './render-context';
|
|
40
|
-
import { STANDARD_PBR_UBO_SIZE } from './render-context';
|
|
40
|
+
import { MATERIAL_PER_ENTITY_STRIDE, STANDARD_PBR_UBO_SIZE } from './render-context';
|
|
41
41
|
import { POINTS_LINES_VIEW_BYTES, pointShadowViewOffset, VIEW_UNIFORM_BYTES } from './view-ubo';
|
|
42
42
|
|
|
43
43
|
export const SHADOW_CASTER_SLOT_STRIDE = 256;
|
|
@@ -253,13 +253,15 @@ function ensureTypedShadowViewBg(
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
interface ShadowDispatch {
|
|
256
|
+
readonly vertexEntry: string | undefined;
|
|
257
|
+
readonly fragmentEntry: string | undefined;
|
|
256
258
|
readonly materialShaderId: string;
|
|
257
259
|
readonly renderState: MaterialRenderState | undefined;
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
type ShadowDispatchMap = ReadonlyMap<number, ReadonlyMap<number, ShadowDispatch>>;
|
|
261
263
|
|
|
262
|
-
function shadowShaderMap(c: _InternalRenderPipelineContext): ShadowDispatchMap {
|
|
264
|
+
export function shadowShaderMap(c: _InternalRenderPipelineContext): ShadowDispatchMap {
|
|
263
265
|
const shaders = new Map<number, Map<number, ShadowDispatch>>();
|
|
264
266
|
for (const entry of c.dispatch) {
|
|
265
267
|
if (entry.tags.LightMode === 'ShadowCaster' && entry.materialShaderId !== undefined) {
|
|
@@ -269,7 +271,12 @@ function shadowShaderMap(c: _InternalRenderPipelineContext): ShadowDispatchMap {
|
|
|
269
271
|
shaders.set(entry.renderableIndex, byMaterial);
|
|
270
272
|
}
|
|
271
273
|
byMaterial.set(entry.materialHandle, {
|
|
272
|
-
|
|
274
|
+
vertexEntry: entry.vertexEntry,
|
|
275
|
+
fragmentEntry: entry.fragmentEntry,
|
|
276
|
+
materialShaderId:
|
|
277
|
+
entry.materialShaderId === 'forgeax::default-standard-pbr'
|
|
278
|
+
? SHADOW_CASTER_SHADER_ID
|
|
279
|
+
: entry.materialShaderId,
|
|
273
280
|
renderState: entry.renderState,
|
|
274
281
|
});
|
|
275
282
|
}
|
|
@@ -371,13 +378,15 @@ export function encodeDirectionalShadowPass(
|
|
|
371
378
|
`view-shadow-directional-${cascadeIndex}`,
|
|
372
379
|
);
|
|
373
380
|
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
374
|
-
if (pipeline === null || viewBg === null || materialBg === null)
|
|
375
|
-
|
|
381
|
+
if (pipeline === null || viewBg === null || materialBg === null) {
|
|
382
|
+
c.frameState.directionalShadowCacheRecorded = false;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
376
385
|
pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
|
|
377
386
|
pass.setPipeline(pipeline);
|
|
378
387
|
pass.setBindGroup(0, viewBg, [0, 0]);
|
|
379
388
|
pass.setBindGroup(1, materialBg, [0]);
|
|
380
|
-
recordShadowCasterDraws(
|
|
389
|
+
const complete = recordShadowCasterDraws(
|
|
381
390
|
c,
|
|
382
391
|
pass,
|
|
383
392
|
pipeline,
|
|
@@ -385,6 +394,8 @@ export function encodeDirectionalShadowPass(
|
|
|
385
394
|
buildMatchedRenderableIndices(c.dispatch, { LightMode: ['ShadowCaster'] }),
|
|
386
395
|
shadowShaderMap(c),
|
|
387
396
|
);
|
|
397
|
+
c.frameState.directionalShadowCacheRecorded =
|
|
398
|
+
cascadeIndex === 0 ? complete : c.frameState.directionalShadowCacheRecorded && complete;
|
|
388
399
|
}
|
|
389
400
|
|
|
390
401
|
export function encodePointShadowPass(
|
|
@@ -569,7 +580,7 @@ function recordShadowCasterDraws(
|
|
|
569
580
|
shadowMeshBindGroup: BindGroup,
|
|
570
581
|
matchedIndices: Set<number> | null,
|
|
571
582
|
shadowDispatchByRenderableIdx: ShadowDispatchMap,
|
|
572
|
-
):
|
|
583
|
+
): boolean {
|
|
573
584
|
const { runtime, pipelineState, validatedOrdered } = c;
|
|
574
585
|
// M-3 / w12: vertexBuffer/indexBuffer state locals migrate to GpuBuffer
|
|
575
586
|
// (the wrapper) -- the de-dup compare uses wrapper identity (one wrapper
|
|
@@ -583,6 +594,8 @@ function recordShadowCasterDraws(
|
|
|
583
594
|
// bound by the setPipeline call above; the loop switches to a custom
|
|
584
595
|
// ShadowCaster PSO when a material supplies one.
|
|
585
596
|
let shadowLastPipeline: RenderPipeline = shadowPipeline;
|
|
597
|
+
let materialDeps: PerSubmeshMaterialBgDeps | undefined;
|
|
598
|
+
let complete = true;
|
|
586
599
|
|
|
587
600
|
for (let i = 0; i < validatedOrdered.length; i++) {
|
|
588
601
|
const entry = validatedOrdered[i];
|
|
@@ -593,7 +606,10 @@ function recordShadowCasterDraws(
|
|
|
593
606
|
|
|
594
607
|
if (entry.source.skin !== undefined) {
|
|
595
608
|
const skinPipeline = skinnedShadowPipeline(c, entry);
|
|
596
|
-
if (skinPipeline === null)
|
|
609
|
+
if (skinPipeline === null) {
|
|
610
|
+
complete = false;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
597
613
|
if (skinPipeline !== shadowLastPipeline) {
|
|
598
614
|
shadowPass.setPipeline(skinPipeline);
|
|
599
615
|
shadowLastPipeline = skinPipeline;
|
|
@@ -640,89 +656,25 @@ function recordShadowCasterDraws(
|
|
|
640
656
|
shadowPass.setBindGroup(2, shadowMeshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
|
|
641
657
|
}
|
|
642
658
|
|
|
643
|
-
//
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const instancePayload = uniformFallback
|
|
662
|
-
? shadowInst.transforms
|
|
663
|
-
: packInstanceStorageBuffer(shadowInst.transforms);
|
|
664
|
-
const requestedBytes = instancePayload.byteLength;
|
|
665
|
-
const cached = c.frameState.instanceBuffers.get(
|
|
666
|
-
worldEntityKey(entry.source.worldId, shadowInst.cacheKey),
|
|
667
|
-
);
|
|
668
|
-
let active: InstanceBufferCacheEntry | null = null;
|
|
669
|
-
if (
|
|
670
|
-
cached !== undefined &&
|
|
671
|
-
cached.uploadedArchVersion === shadowInst.archVersion &&
|
|
672
|
-
cached.uploadedByteLength === requestedBytes
|
|
673
|
-
) {
|
|
674
|
-
active = cached;
|
|
675
|
-
} else if (requestedBytes > 0) {
|
|
676
|
-
const bufRes = runtime.device.createBuffer({
|
|
677
|
-
size: requestedBytes,
|
|
678
|
-
usage: bufUsage,
|
|
679
|
-
mappedAtCreation: false,
|
|
680
|
-
});
|
|
681
|
-
if (!bufRes.ok) {
|
|
682
|
-
runtime.errorRegistry.fire(bufRes.error);
|
|
683
|
-
} else {
|
|
684
|
-
// feat-20260619 M4 / F12: destroy the old cached buffer
|
|
685
|
-
// before replacing it with the new one (D-6).
|
|
686
|
-
if (cached !== undefined && !cached.buffer.isDestroyed) {
|
|
687
|
-
const r = cached.buffer.destroy();
|
|
688
|
-
if (!r.ok) runtime.errorRegistry.fire(r.error);
|
|
689
|
-
}
|
|
690
|
-
const newBuffer = new GpuBuffer(runtime.device, bufRes.value);
|
|
691
|
-
active = {
|
|
692
|
-
buffer: newBuffer,
|
|
693
|
-
uploadedArchVersion: shadowInst.archVersion,
|
|
694
|
-
uploadedByteLength: requestedBytes,
|
|
695
|
-
};
|
|
696
|
-
c.frameState.instanceBuffers.set(
|
|
697
|
-
worldEntityKey(entry.source.worldId, shadowInst.cacheKey),
|
|
698
|
-
active,
|
|
699
|
-
);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
if (active !== null) {
|
|
703
|
-
const writeRes = runtime.device.queue.writeBuffer(
|
|
704
|
-
active.buffer.handle,
|
|
705
|
-
0,
|
|
706
|
-
instancePayload,
|
|
707
|
-
);
|
|
708
|
-
if (!writeRes.ok) {
|
|
709
|
-
runtime.errorRegistry.fire(writeRes.error);
|
|
710
|
-
} else {
|
|
711
|
-
shadowInstanceBuffer = active.buffer.handle;
|
|
712
|
-
shadowInstanceCount = Math.max(1, shadowInst.instanceCount);
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
}
|
|
659
|
+
// Main and shadow passes share one instance residency owner and upload receipt.
|
|
660
|
+
const shadowInstanceDraws = resolveGeometryInstanceBuffer(
|
|
661
|
+
c,
|
|
662
|
+
entry,
|
|
663
|
+
[
|
|
664
|
+
{
|
|
665
|
+
instanceBuffer: pipelineState.identityInstanceBuffer,
|
|
666
|
+
instanceBindGroup: resolveGeometryInstancesBindGroup(
|
|
667
|
+
c,
|
|
668
|
+
pipelineState.identityInstanceBuffer,
|
|
669
|
+
),
|
|
670
|
+
instanceCount: 1,
|
|
671
|
+
probeOffset: -1,
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
false,
|
|
675
|
+
);
|
|
676
|
+
if (shadowInstanceDraws === null) continue;
|
|
717
677
|
|
|
718
|
-
// Bind per-entity instances BG for @group(3) (or fallback identity).
|
|
719
|
-
// D-4: write end of the HDRP shadow-instances producer/consumer pair.
|
|
720
|
-
// outerKey = worldEntityKey(entry.source.worldId, entry.source.entityKey),
|
|
721
|
-
// handle = shadowInstanceBuffer; the HDRP main pass read end (:3820 below)
|
|
722
|
-
// must look up the same (compositeKey, instBuffer) leaf or the shadow
|
|
723
|
-
// instances silently drop.
|
|
724
|
-
const shadowInstancesBg = resolveGeometryInstancesBindGroup(c, shadowInstanceBuffer);
|
|
725
|
-
shadowPass.setBindGroup(3, shadowInstancesBg);
|
|
726
678
|
// feat-20260608 M4 / w16: per-submesh shadow draw loop.
|
|
727
679
|
// Only draw submeshes whose topology is triangle-list or triangle-strip
|
|
728
680
|
// (line-list / point-list submeshes cast no shadow and are skipped).
|
|
@@ -742,21 +694,17 @@ function recordShadowCasterDraws(
|
|
|
742
694
|
?.get(submeshMaterial.materialHandle ?? 0);
|
|
743
695
|
const entryShadowShaderId = shadowDispatch?.materialShaderId;
|
|
744
696
|
const entryShadowRenderState = shadowDispatch?.renderState;
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
// group against `pbr-pl` and Dawn rejects the draw at submit time.
|
|
749
|
-
let entryShadowPipeline =
|
|
697
|
+
// Canonical skin casters retain their palette layout. Authored casters
|
|
698
|
+
// select their own program below with the same geometry layout.
|
|
699
|
+
let entryShadowPipeline: RenderPipeline | null =
|
|
750
700
|
entry.source.skin !== undefined ? shadowLastPipeline : shadowPipeline;
|
|
751
701
|
if (
|
|
752
|
-
entry.source.skin === undefined &&
|
|
753
702
|
entryShadowShaderId !== undefined &&
|
|
754
703
|
(entryShadowShaderId !== 'forgeax::default-shadow-caster' ||
|
|
755
|
-
entryShadowRenderState !== undefined)
|
|
704
|
+
(entryShadowRenderState !== undefined && entry.source.skin === undefined))
|
|
756
705
|
) {
|
|
757
|
-
//
|
|
758
|
-
//
|
|
759
|
-
// dropping its draw.
|
|
706
|
+
// Pending or failed authored programs must not draw using a different
|
|
707
|
+
// caster. Retry preparation on the next frame with the same program.
|
|
760
708
|
entryShadowPipeline =
|
|
761
709
|
runtime.getMaterialShaderPipeline?.(
|
|
762
710
|
entryShadowShaderId,
|
|
@@ -766,19 +714,76 @@ function recordShadowCasterDraws(
|
|
|
766
714
|
undefined, // indexFormat
|
|
767
715
|
undefined, // variantSet — shadow caster has no variant axes
|
|
768
716
|
'shadow-caster', // passKind
|
|
769
|
-
|
|
717
|
+
undefined, // meshAttributes
|
|
718
|
+
1,
|
|
719
|
+
undefined, // colorFormatOverride
|
|
720
|
+
undefined, // shaderUvSetCount
|
|
721
|
+
undefined, // depthFormatOverride
|
|
722
|
+
undefined, // vertexLayout
|
|
723
|
+
entry.mesh.layoutProjection,
|
|
724
|
+
undefined, // shaderModuleMode
|
|
725
|
+
entry.source.skin === undefined ? 'pbr' : 'pbr-skin',
|
|
726
|
+
undefined, // additionalColorFormats
|
|
727
|
+
shadowDispatch?.vertexEntry,
|
|
728
|
+
shadowDispatch?.fragmentEntry,
|
|
729
|
+
) ?? null;
|
|
730
|
+
}
|
|
731
|
+
if (entryShadowPipeline === null) {
|
|
732
|
+
complete = false;
|
|
733
|
+
continue;
|
|
770
734
|
}
|
|
771
735
|
if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
|
|
772
736
|
shadowPass.setPipeline(entryShadowPipeline);
|
|
773
737
|
shadowLastPipeline = entryShadowPipeline;
|
|
774
738
|
}
|
|
775
|
-
if (
|
|
776
|
-
|
|
739
|
+
if (entryShadowShaderId !== undefined && entryShadowShaderId !== SHADOW_CASTER_SHADER_ID) {
|
|
740
|
+
materialDeps ??= {
|
|
741
|
+
runtime,
|
|
742
|
+
pipelineState,
|
|
743
|
+
world: c.world,
|
|
744
|
+
store: c.store,
|
|
745
|
+
materialSlice: STANDARD_PBR_UBO_SIZE,
|
|
746
|
+
videoHighPerfAvailable: probeVideoHighPerfUpload(runtime.device),
|
|
747
|
+
skylightResources: prepareMaterialSkylight(c).skylightResources,
|
|
748
|
+
resolveRenderTargetTextureSource: runtime.resolveRenderTargetTextureSource,
|
|
749
|
+
materialBgShared: c.frameState.materialBgShared,
|
|
750
|
+
materialBgAssemblyCache: c.materialBgAssemblyCache,
|
|
751
|
+
frameState: c.frameState,
|
|
752
|
+
bindGroupCounts: c.bindGroupCounts,
|
|
753
|
+
};
|
|
754
|
+
const materialSlot =
|
|
755
|
+
c.materialSlotIndices[i]?.[sm.materialSlot] ?? c.materialSlotIndices[i]?.[0];
|
|
756
|
+
if (materialSlot === undefined) {
|
|
757
|
+
throw new RhiError({
|
|
758
|
+
code: 'rhi-descriptor-invalid',
|
|
759
|
+
expected: 'a prepared material slot for each shadow submesh',
|
|
760
|
+
hint: 'repair material slot preparation before recording shadows',
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
const materialGroup = buildPerSubmeshMaterialBg(
|
|
764
|
+
materialDeps,
|
|
765
|
+
submeshMaterial,
|
|
766
|
+
entry.source.entityKey,
|
|
767
|
+
entry.world ?? c.world,
|
|
768
|
+
entryShadowShaderId,
|
|
769
|
+
);
|
|
770
|
+
shadowPass.setBindGroup(1, materialGroup, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
|
|
777
771
|
} else {
|
|
778
|
-
|
|
772
|
+
const materialGroup = ensureSpotShadowMaterialBg(c);
|
|
773
|
+
if (materialGroup === null) continue;
|
|
774
|
+
shadowPass.setBindGroup(1, materialGroup, [0]);
|
|
775
|
+
}
|
|
776
|
+
for (const instanceDraw of shadowInstanceDraws) {
|
|
777
|
+
shadowPass.setBindGroup(3, instanceDraw.instanceBindGroup);
|
|
778
|
+
if (entry.mesh.indexed) {
|
|
779
|
+
shadowPass.drawIndexed(sm.indexCount, instanceDraw.instanceCount, sm.indexOffset, 0, 0);
|
|
780
|
+
} else {
|
|
781
|
+
shadowPass.draw(sm.vertexCount, instanceDraw.instanceCount, 0, 0);
|
|
782
|
+
}
|
|
779
783
|
}
|
|
780
784
|
}
|
|
781
785
|
}
|
|
786
|
+
return complete;
|
|
782
787
|
}
|
|
783
788
|
|
|
784
789
|
/**
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
postProcessShaderEntrySignature,
|
|
29
29
|
} from '../fullscreen-post-process-pass';
|
|
30
30
|
import { GpuDrivenProduction } from '../gpu-driven/production-raster';
|
|
31
|
+
import { disposeInstanceBufferChunks, disposeInstanceBuffers } from '../instance-buffer-cache';
|
|
31
32
|
import { inspectStandardLighting } from '../pipeline/standard-lighting/inspection';
|
|
32
33
|
import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
|
|
33
34
|
import { inspectPointShadow } from '../point-shadow-inspection';
|
|
@@ -35,6 +36,7 @@ import type { PreparedGraphicsResolver } from '../prepare/prepared-graphics-reso
|
|
|
35
36
|
import { validateRenderables } from '../record/frame';
|
|
36
37
|
import { prepareFrameLighting } from '../record/frame-lighting';
|
|
37
38
|
import type { RenderFrameState, ValidatedRenderable } from '../record/frame-snapshot';
|
|
39
|
+
import { resolveGeometryInstanceBuffer } from '../record/main-pass-geometry';
|
|
38
40
|
import { computeSplitLdrSprite } from '../record/main-pass-sprite-draws';
|
|
39
41
|
import {
|
|
40
42
|
prepareRecoveryPipelineReadiness,
|
|
@@ -121,7 +123,7 @@ export interface RenderSystemRecoveryOwner {
|
|
|
121
123
|
setLastSuccessfulFrameSeed(seed: RecoveryFrameSeed): void;
|
|
122
124
|
prepareRecoveryGraphCandidate(
|
|
123
125
|
runtime: RecoveryGraphCandidateRuntime,
|
|
124
|
-
): RecoveryGraphCandidatePreparation
|
|
126
|
+
): Promise<RecoveryGraphCandidatePreparation>;
|
|
125
127
|
submitCandidateSetup(
|
|
126
128
|
candidate: RecoveryGraphCandidate,
|
|
127
129
|
isValid: () => boolean,
|
|
@@ -225,6 +227,8 @@ export function createRenderSystemRecovery(
|
|
|
225
227
|
currentDirectionalShadowView: null,
|
|
226
228
|
currentSpotShadowView: null,
|
|
227
229
|
instanceBuffers: new Map(),
|
|
230
|
+
instanceBufferChunks: new Map(),
|
|
231
|
+
instanceResidency: new Map(),
|
|
228
232
|
morphBuffers: new Map(),
|
|
229
233
|
hdrpClusterBinScratch: createClusterBinScratch(),
|
|
230
234
|
hdrpClusterGridScratch: null,
|
|
@@ -313,9 +317,9 @@ export function createRenderSystemRecovery(
|
|
|
313
317
|
candidate.release();
|
|
314
318
|
};
|
|
315
319
|
|
|
316
|
-
const prepareRecoveryGraphCandidate = (
|
|
320
|
+
const prepareRecoveryGraphCandidate = async (
|
|
317
321
|
runtime: RecoveryGraphCandidateRuntime,
|
|
318
|
-
): RecoveryGraphCandidatePreparation => {
|
|
322
|
+
): Promise<RecoveryGraphCandidatePreparation> => {
|
|
319
323
|
const seed = lastSuccessfulFrameSeed;
|
|
320
324
|
if (seed === undefined) return { kind: 'no-seed' };
|
|
321
325
|
const camera = seed.frame.cameras[0];
|
|
@@ -382,6 +386,13 @@ export function createRenderSystemRecovery(
|
|
|
382
386
|
candidateGpuDrivenScene = undefined;
|
|
383
387
|
candidatePointsLines?.release();
|
|
384
388
|
candidatePointsLines = undefined;
|
|
389
|
+
disposeInstanceBuffers(candidateFrameState.instanceBuffers, runtime.internals.errorRegistry);
|
|
390
|
+
if (candidateFrameState.instanceBufferChunks !== undefined) {
|
|
391
|
+
disposeInstanceBufferChunks(
|
|
392
|
+
candidateFrameState.instanceBufferChunks,
|
|
393
|
+
runtime.internals.errorRegistry,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
385
396
|
candidateGpuStore.destroyAll();
|
|
386
397
|
};
|
|
387
398
|
const markPublished = (): void => {
|
|
@@ -403,10 +414,17 @@ export function createRenderSystemRecovery(
|
|
|
403
414
|
if (!gpuScene.ok) return failed('candidate GPU scene preparation failed', gpuScene.error);
|
|
404
415
|
candidateGpuDrivenScene = gpuScene.value;
|
|
405
416
|
if (candidateGpuDrivenScene.state !== undefined) {
|
|
406
|
-
|
|
417
|
+
const production = await GpuDrivenProduction.forRecoveryDevice(
|
|
407
418
|
runtime.internals.device,
|
|
408
419
|
gpuDrivenShaderFactory,
|
|
420
|
+
(descriptor) =>
|
|
421
|
+
internals.createShaderModule === undefined
|
|
422
|
+
? Promise.resolve(gpuDrivenShaderFactory.createShaderModule(descriptor))
|
|
423
|
+
: internals.createShaderModule(runtime.internals.device, descriptor),
|
|
409
424
|
);
|
|
425
|
+
if (!production.ok)
|
|
426
|
+
return failed('candidate GPU-driven shader preparation failed', production.error);
|
|
427
|
+
candidateGpuDrivenProduction = production.value;
|
|
410
428
|
const prepared = candidateGpuDrivenProduction.prepare({
|
|
411
429
|
scene: candidateGpuDrivenScene.state,
|
|
412
430
|
camera,
|
|
@@ -578,6 +596,25 @@ export function createRenderSystemRecovery(
|
|
|
578
596
|
if (candidateValidated.length !== seed.frame.renderables.length) {
|
|
579
597
|
return failed('candidate validation did not retain every LKG-visible renderable');
|
|
580
598
|
}
|
|
599
|
+
for (const entry of candidateValidated) {
|
|
600
|
+
if (entry.source.instances === undefined) continue;
|
|
601
|
+
const prepared = resolveGeometryInstanceBuffer(
|
|
602
|
+
{
|
|
603
|
+
runtime: runtime.internals,
|
|
604
|
+
pipelineState: runtime.pipelineState,
|
|
605
|
+
frameState: candidateFrameState,
|
|
606
|
+
bindGroupCounts: { createBindGroup: 0, keys: [] },
|
|
607
|
+
},
|
|
608
|
+
entry,
|
|
609
|
+
[],
|
|
610
|
+
false,
|
|
611
|
+
);
|
|
612
|
+
if (prepared === null) return failed('candidate instance preparation failed');
|
|
613
|
+
}
|
|
614
|
+
// These uploads belong to recovery preparation, never to a rendered frame.
|
|
615
|
+
for (const [id, residency] of candidateFrameState.instanceResidency ?? []) {
|
|
616
|
+
candidateFrameState.instanceResidency?.set(id, { ...residency, frameNumber: -1 });
|
|
617
|
+
}
|
|
581
618
|
const lighting = prepareFrameLighting(
|
|
582
619
|
runtime.internals,
|
|
583
620
|
candidateFrameState,
|
|
@@ -602,7 +639,11 @@ export function createRenderSystemRecovery(
|
|
|
602
639
|
seed.frame.lights.pointShadow.length > 0 ||
|
|
603
640
|
seed.frame.lights.spot.some((spot) => spot.castShadow === true) ||
|
|
604
641
|
(seed.frame.lights.lightViewProj?.length ?? 0) > 0,
|
|
605
|
-
splitLdrSprite: computeSplitLdrSprite(
|
|
642
|
+
splitLdrSprite: computeSplitLdrSprite(
|
|
643
|
+
candidateValidated,
|
|
644
|
+
camera.tonemap !== 'none',
|
|
645
|
+
seed.frame.dispatch,
|
|
646
|
+
),
|
|
606
647
|
reflectionFallbackAvailable: candidateFrameState.reflectionFallbackDemand === true,
|
|
607
648
|
});
|
|
608
649
|
const candidatePostProcessIds = new Set<string>([
|
package/src/render-contract.ts
CHANGED
|
@@ -42,6 +42,7 @@ import type {
|
|
|
42
42
|
TemporalTargetInspection,
|
|
43
43
|
TransmissionInspection,
|
|
44
44
|
} from './inspection-types';
|
|
45
|
+
import type { InstanceCollectionInspection } from './instances';
|
|
45
46
|
import type { SsrAdmissionIdentity } from './ssr/identity';
|
|
46
47
|
import type {
|
|
47
48
|
RenderTarget,
|
|
@@ -294,6 +295,7 @@ export interface SpotLightProjectorFrameContext {
|
|
|
294
295
|
* leases, receipts, inspection PODs, and recovery Result values.
|
|
295
296
|
*/
|
|
296
297
|
export interface Renderer {
|
|
298
|
+
/** Renderer-owned authoring and update path for large instance collections. */
|
|
297
299
|
attach(world: World): RenderResult<RenderWorldLease, RenderError>;
|
|
298
300
|
draw(input: RenderFrameInput): RenderResult<FrameReceipt, RenderError>;
|
|
299
301
|
createRenderTarget(descriptor: RenderTargetDescriptor): RenderResult<RenderTarget, RenderError>;
|
|
@@ -509,6 +511,8 @@ export interface RenderInspection {
|
|
|
509
511
|
readonly featureDiagnostics: readonly RenderFeatureDiagnostics[];
|
|
510
512
|
readonly frustumStats: { readonly culled: number; readonly total: number };
|
|
511
513
|
readonly visibilityStats: { readonly explicitlyHidden: number };
|
|
514
|
+
/** Renderer-owned large-instance residency and upload facts. */
|
|
515
|
+
readonly instanceCollections: readonly InstanceCollectionInspection[];
|
|
512
516
|
readonly renderScene: RenderSceneInspection;
|
|
513
517
|
readonly reflectionProbes: ReflectionProbeInspection;
|
|
514
518
|
/** Renderer-owned SSR dependency seam; consumer facts are projected from live owners. */
|