@forgeax/engine-render 0.1.25 → 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 +18 -0
- 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.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts +0 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-JSEDTZYS.mjs → chunk-GF523LHF.mjs} +6 -6
- package/dist/chunk-GF523LHF.mjs.map +1 -0
- package/dist/{chunk-TCLDX7SZ.mjs → chunk-TMIASV2N.mjs} +403 -457
- package/dist/chunk-TMIASV2N.mjs.map +1 -0
- package/dist/{chunk-N3RT2EUR.mjs → chunk-YUQXSEHG.mjs} +87 -53
- package/dist/chunk-YUQXSEHG.mjs.map +1 -0
- package/dist/construct-renderer.mjs +153 -13
- 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/index.mjs +1 -1
- package/dist/internal.mjs +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.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +2 -2
- 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 +2 -2
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -3
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +2 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +21 -10
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/temporal/index.mjs +2 -2
- 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__/material-contract-inventory.unit.test.ts +43 -0
- package/src/__tests__/{material-static-dynamic-route.unit.test.ts → material-layer-projection.unit.test.ts} +30 -47
- package/src/__tests__/material-program-dispatch.unit.test.ts +48 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +2 -2
- package/src/__tests__/skinned-shadow-caster.test.ts +14 -14
- package/src/__tests__/surface-variant-provenance.integration.test.ts +1 -1
- package/src/assembly/factory.ts +0 -3
- 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 +2 -1
- package/src/assembly/webgpu-renderer.ts +17 -9
- package/src/extract/contracts.ts +6 -6
- package/src/materials.ts +3 -3
- package/src/pipeline-spec-types.ts +2 -0
- package/src/pipeline-spec.ts +7 -3
- package/src/record/frame.ts +7 -1
- package/src/record/main-pass-geometry.ts +340 -279
- package/src/record/main-pass-material.ts +87 -2
- package/src/record/main-pass-sprite-draws.ts +65 -168
- package/src/record/main-pass.ts +24 -246
- package/src/record/material-uniforms.ts +186 -0
- package/src/record/render-context.ts +4 -0
- package/src/record/shadow-pass.ts +86 -28
- package/src/recovery/render-system-candidate.ts +5 -1
- package/src/render-system-extract-tail.ts +51 -11
- package/src/render-system-extract.ts +73 -63
- package/src/render-system.ts +19 -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-JSEDTZYS.mjs.map +0 -1
- package/dist/chunk-N3RT2EUR.mjs.map +0 -1
- package/dist/chunk-TCLDX7SZ.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,
|
|
@@ -24,6 +25,11 @@ import {
|
|
|
24
25
|
resolveGeometryInstanceBuffer,
|
|
25
26
|
resolveGeometryInstancesBindGroup,
|
|
26
27
|
} from './main-pass-geometry';
|
|
28
|
+
import {
|
|
29
|
+
buildPerSubmeshMaterialBg,
|
|
30
|
+
type PerSubmeshMaterialBgDeps,
|
|
31
|
+
prepareMaterialSkylight,
|
|
32
|
+
} from './main-pass-material';
|
|
27
33
|
import {
|
|
28
34
|
getOrCreateFromChain,
|
|
29
35
|
MESH_PER_ENTITY_STRIDE,
|
|
@@ -31,7 +37,7 @@ import {
|
|
|
31
37
|
MESH_UBO_FULL_ARRAY_BYTES,
|
|
32
38
|
} from './mesh-ssbo';
|
|
33
39
|
import type { _InternalRenderPipelineContext } from './render-context';
|
|
34
|
-
import { STANDARD_PBR_UBO_SIZE } from './render-context';
|
|
40
|
+
import { MATERIAL_PER_ENTITY_STRIDE, STANDARD_PBR_UBO_SIZE } from './render-context';
|
|
35
41
|
import { POINTS_LINES_VIEW_BYTES, pointShadowViewOffset, VIEW_UNIFORM_BYTES } from './view-ubo';
|
|
36
42
|
|
|
37
43
|
export const SHADOW_CASTER_SLOT_STRIDE = 256;
|
|
@@ -247,11 +253,10 @@ function ensureTypedShadowViewBg(
|
|
|
247
253
|
}
|
|
248
254
|
|
|
249
255
|
interface ShadowDispatch {
|
|
250
|
-
readonly
|
|
256
|
+
readonly vertexEntry: string | undefined;
|
|
257
|
+
readonly fragmentEntry: string | undefined;
|
|
251
258
|
readonly materialShaderId: string;
|
|
252
259
|
readonly renderState: MaterialRenderState | undefined;
|
|
253
|
-
readonly materialSlot: number | undefined;
|
|
254
|
-
readonly paramSnapshot: DispatchEntry['paramSnapshot'];
|
|
255
260
|
}
|
|
256
261
|
|
|
257
262
|
type ShadowDispatchMap = ReadonlyMap<number, ReadonlyMap<number, ShadowDispatch>>;
|
|
@@ -265,20 +270,14 @@ export function shadowShaderMap(c: _InternalRenderPipelineContext): ShadowDispat
|
|
|
265
270
|
byMaterial = new Map<number, ShadowDispatch>();
|
|
266
271
|
shaders.set(entry.renderableIndex, byMaterial);
|
|
267
272
|
}
|
|
268
|
-
const candidateSlots =
|
|
269
|
-
c.materialSlotIndices[entry.renderableIndex] ?? c.materialSlotIndices.flat();
|
|
270
|
-
const materialSlot = candidateSlots.find(
|
|
271
|
-
(slot) => c.materialSlots[slot]?.materialHandle === entry.materialHandle,
|
|
272
|
-
);
|
|
273
273
|
byMaterial.set(entry.materialHandle, {
|
|
274
|
-
|
|
274
|
+
vertexEntry: entry.vertexEntry,
|
|
275
|
+
fragmentEntry: entry.fragmentEntry,
|
|
275
276
|
materialShaderId:
|
|
276
277
|
entry.materialShaderId === 'forgeax::default-standard-pbr'
|
|
277
278
|
? SHADOW_CASTER_SHADER_ID
|
|
278
279
|
: entry.materialShaderId,
|
|
279
280
|
renderState: entry.renderState,
|
|
280
|
-
materialSlot,
|
|
281
|
-
paramSnapshot: entry.paramSnapshot,
|
|
282
281
|
});
|
|
283
282
|
}
|
|
284
283
|
}
|
|
@@ -379,13 +378,15 @@ export function encodeDirectionalShadowPass(
|
|
|
379
378
|
`view-shadow-directional-${cascadeIndex}`,
|
|
380
379
|
);
|
|
381
380
|
const materialBg = ensureSpotShadowMaterialBg(c);
|
|
382
|
-
if (pipeline === null || viewBg === null || materialBg === null)
|
|
383
|
-
|
|
381
|
+
if (pipeline === null || viewBg === null || materialBg === null) {
|
|
382
|
+
c.frameState.directionalShadowCacheRecorded = false;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
384
385
|
pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
|
|
385
386
|
pass.setPipeline(pipeline);
|
|
386
387
|
pass.setBindGroup(0, viewBg, [0, 0]);
|
|
387
388
|
pass.setBindGroup(1, materialBg, [0]);
|
|
388
|
-
recordShadowCasterDraws(
|
|
389
|
+
const complete = recordShadowCasterDraws(
|
|
389
390
|
c,
|
|
390
391
|
pass,
|
|
391
392
|
pipeline,
|
|
@@ -393,6 +394,8 @@ export function encodeDirectionalShadowPass(
|
|
|
393
394
|
buildMatchedRenderableIndices(c.dispatch, { LightMode: ['ShadowCaster'] }),
|
|
394
395
|
shadowShaderMap(c),
|
|
395
396
|
);
|
|
397
|
+
c.frameState.directionalShadowCacheRecorded =
|
|
398
|
+
cascadeIndex === 0 ? complete : c.frameState.directionalShadowCacheRecorded && complete;
|
|
396
399
|
}
|
|
397
400
|
|
|
398
401
|
export function encodePointShadowPass(
|
|
@@ -577,7 +580,7 @@ function recordShadowCasterDraws(
|
|
|
577
580
|
shadowMeshBindGroup: BindGroup,
|
|
578
581
|
matchedIndices: Set<number> | null,
|
|
579
582
|
shadowDispatchByRenderableIdx: ShadowDispatchMap,
|
|
580
|
-
):
|
|
583
|
+
): boolean {
|
|
581
584
|
const { runtime, pipelineState, validatedOrdered } = c;
|
|
582
585
|
// M-3 / w12: vertexBuffer/indexBuffer state locals migrate to GpuBuffer
|
|
583
586
|
// (the wrapper) -- the de-dup compare uses wrapper identity (one wrapper
|
|
@@ -591,6 +594,8 @@ function recordShadowCasterDraws(
|
|
|
591
594
|
// bound by the setPipeline call above; the loop switches to a custom
|
|
592
595
|
// ShadowCaster PSO when a material supplies one.
|
|
593
596
|
let shadowLastPipeline: RenderPipeline = shadowPipeline;
|
|
597
|
+
let materialDeps: PerSubmeshMaterialBgDeps | undefined;
|
|
598
|
+
let complete = true;
|
|
594
599
|
|
|
595
600
|
for (let i = 0; i < validatedOrdered.length; i++) {
|
|
596
601
|
const entry = validatedOrdered[i];
|
|
@@ -601,7 +606,10 @@ function recordShadowCasterDraws(
|
|
|
601
606
|
|
|
602
607
|
if (entry.source.skin !== undefined) {
|
|
603
608
|
const skinPipeline = skinnedShadowPipeline(c, entry);
|
|
604
|
-
if (skinPipeline === null)
|
|
609
|
+
if (skinPipeline === null) {
|
|
610
|
+
complete = false;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
605
613
|
if (skinPipeline !== shadowLastPipeline) {
|
|
606
614
|
shadowPass.setPipeline(skinPipeline);
|
|
607
615
|
shadowLastPipeline = skinPipeline;
|
|
@@ -686,21 +694,17 @@ function recordShadowCasterDraws(
|
|
|
686
694
|
?.get(submeshMaterial.materialHandle ?? 0);
|
|
687
695
|
const entryShadowShaderId = shadowDispatch?.materialShaderId;
|
|
688
696
|
const entryShadowRenderState = shadowDispatch?.renderState;
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
// group against `pbr-pl` and Dawn rejects the draw at submit time.
|
|
693
|
-
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 =
|
|
694
700
|
entry.source.skin !== undefined ? shadowLastPipeline : shadowPipeline;
|
|
695
701
|
if (
|
|
696
|
-
entry.source.skin === undefined &&
|
|
697
702
|
entryShadowShaderId !== undefined &&
|
|
698
703
|
(entryShadowShaderId !== 'forgeax::default-shadow-caster' ||
|
|
699
|
-
entryShadowRenderState !== undefined)
|
|
704
|
+
(entryShadowRenderState !== undefined && entry.source.skin === undefined))
|
|
700
705
|
) {
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
// 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.
|
|
704
708
|
entryShadowPipeline =
|
|
705
709
|
runtime.getMaterialShaderPipeline?.(
|
|
706
710
|
entryShadowShaderId,
|
|
@@ -710,12 +714,65 @@ function recordShadowCasterDraws(
|
|
|
710
714
|
undefined, // indexFormat
|
|
711
715
|
undefined, // variantSet — shadow caster has no variant axes
|
|
712
716
|
'shadow-caster', // passKind
|
|
713
|
-
|
|
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;
|
|
714
734
|
}
|
|
715
735
|
if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
|
|
716
736
|
shadowPass.setPipeline(entryShadowPipeline);
|
|
717
737
|
shadowLastPipeline = entryShadowPipeline;
|
|
718
738
|
}
|
|
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]);
|
|
771
|
+
} else {
|
|
772
|
+
const materialGroup = ensureSpotShadowMaterialBg(c);
|
|
773
|
+
if (materialGroup === null) continue;
|
|
774
|
+
shadowPass.setBindGroup(1, materialGroup, [0]);
|
|
775
|
+
}
|
|
719
776
|
for (const instanceDraw of shadowInstanceDraws) {
|
|
720
777
|
shadowPass.setBindGroup(3, instanceDraw.instanceBindGroup);
|
|
721
778
|
if (entry.mesh.indexed) {
|
|
@@ -726,6 +783,7 @@ function recordShadowCasterDraws(
|
|
|
726
783
|
}
|
|
727
784
|
}
|
|
728
785
|
}
|
|
786
|
+
return complete;
|
|
729
787
|
}
|
|
730
788
|
|
|
731
789
|
/**
|
|
@@ -639,7 +639,11 @@ export function createRenderSystemRecovery(
|
|
|
639
639
|
seed.frame.lights.pointShadow.length > 0 ||
|
|
640
640
|
seed.frame.lights.spot.some((spot) => spot.castShadow === true) ||
|
|
641
641
|
(seed.frame.lights.lightViewProj?.length ?? 0) > 0,
|
|
642
|
-
splitLdrSprite: computeSplitLdrSprite(
|
|
642
|
+
splitLdrSprite: computeSplitLdrSprite(
|
|
643
|
+
candidateValidated,
|
|
644
|
+
camera.tonemap !== 'none',
|
|
645
|
+
seed.frame.dispatch,
|
|
646
|
+
),
|
|
643
647
|
reflectionFallbackAvailable: candidateFrameState.reflectionFallbackDemand === true,
|
|
644
648
|
});
|
|
645
649
|
const candidatePostProcessIds = new Set<string>([
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
16
16
|
import { box3, frustum, type Mat4, mat4, type Vec3, vec3 } from '@forgeax/engine-math';
|
|
17
17
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
18
|
+
import type { MaterialCookProgramContext } from '@forgeax/engine-pack/material-cook';
|
|
18
19
|
import { RhiError } from '@forgeax/engine-rhi';
|
|
19
20
|
import { GlobalTransform, MorphWeights, Transform } from '@forgeax/engine-scene';
|
|
20
21
|
import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
|
|
@@ -148,6 +149,7 @@ import {
|
|
|
148
149
|
materialColorParameterSchema,
|
|
149
150
|
materialNormalScale,
|
|
150
151
|
materialParametersToParamSchema,
|
|
152
|
+
materialProgramKeysForMaterial,
|
|
151
153
|
materialTextureFields,
|
|
152
154
|
materialTextureRef,
|
|
153
155
|
pipelineRenderState,
|
|
@@ -174,6 +176,7 @@ export function extractFrames(
|
|
|
174
176
|
pipelineState?: ExtractPipelineSurface | null,
|
|
175
177
|
materialSnapshotCachesByWorld?: MaterialSnapshotCachesByWorld,
|
|
176
178
|
options: {
|
|
179
|
+
readonly materialContext?: MaterialCookProgramContext;
|
|
177
180
|
readonly cull?: 'normal' | 'none';
|
|
178
181
|
readonly renderables?: 'full' | 'none';
|
|
179
182
|
readonly renderableEntitiesByWorld?: readonly (ReadonlySet<number> | undefined)[];
|
|
@@ -251,6 +254,9 @@ export function extractFrames(
|
|
|
251
254
|
return cache;
|
|
252
255
|
})(),
|
|
253
256
|
}),
|
|
257
|
+
...(options.materialContext === undefined
|
|
258
|
+
? {}
|
|
259
|
+
: { materialContext: options.materialContext }),
|
|
254
260
|
cull: options.cull === 'none' ? 'none' : isCameraOwner ? 'self' : 'external',
|
|
255
261
|
renderables: options.renderables ?? 'full',
|
|
256
262
|
...(options.renderableEntitiesByWorld?.[wi] === undefined
|
|
@@ -1559,6 +1565,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
1559
1565
|
// extraction. Shared handles are immutable inputs for the frame, while
|
|
1560
1566
|
// dispatch entries still need to be rebuilt for each entity.
|
|
1561
1567
|
const materialSnapshotCache: MaterialSnapshotCache = new Map();
|
|
1568
|
+
const skinnedMaterialSnapshotCache: MaterialSnapshotCache = new Map();
|
|
1562
1569
|
// tweak-20260611 M1: MeshRenderer renderable archetype walk routes
|
|
1563
1570
|
// through one World-owned Query. K-2 sniffing scheme B
|
|
1564
1571
|
// (`row.get(X) !== undefined` edge sniff) replaces the prior
|
|
@@ -1927,7 +1934,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
1927
1934
|
layerVal,
|
|
1928
1935
|
materialSnap.paramSnapshot,
|
|
1929
1936
|
0,
|
|
1930
|
-
materialSnap.
|
|
1937
|
+
materialSnap.materialProgramKeys,
|
|
1931
1938
|
);
|
|
1932
1939
|
}
|
|
1933
1940
|
} else if (handleRaw === 0 || assets === undefined || assets === null) {
|
|
@@ -1956,7 +1963,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
1956
1963
|
layerVal,
|
|
1957
1964
|
materialSnap.paramSnapshot,
|
|
1958
1965
|
0,
|
|
1959
|
-
materialSnap.
|
|
1966
|
+
materialSnap.materialProgramKeys,
|
|
1960
1967
|
);
|
|
1961
1968
|
}
|
|
1962
1969
|
} else {
|
|
@@ -2020,7 +2027,26 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2020
2027
|
}
|
|
2021
2028
|
const resolved = resolvedResult.value;
|
|
2022
2029
|
const allPasses = resolved.passes;
|
|
2023
|
-
const
|
|
2030
|
+
const materialContext =
|
|
2031
|
+
context.materialContext === undefined
|
|
2032
|
+
? undefined
|
|
2033
|
+
: {
|
|
2034
|
+
...context.materialContext,
|
|
2035
|
+
geometry: hasSkin ? ('skinned' as const) : context.materialContext.geometry,
|
|
2036
|
+
};
|
|
2037
|
+
const materialProgramKeys = materialProgramKeysForMaterial(
|
|
2038
|
+
asset,
|
|
2039
|
+
assets,
|
|
2040
|
+
materialContext,
|
|
2041
|
+
);
|
|
2042
|
+
const authoredFirstPassShader = runtimeMaterialShaderIdForMaterial(
|
|
2043
|
+
allPasses,
|
|
2044
|
+
undefined,
|
|
2045
|
+
);
|
|
2046
|
+
const firstPassShader = runtimeMaterialShaderIdForMaterial(
|
|
2047
|
+
allPasses,
|
|
2048
|
+
materialProgramKeys,
|
|
2049
|
+
);
|
|
2024
2050
|
const pv = materialValuesToLinearRuntime(
|
|
2025
2051
|
resolved.values,
|
|
2026
2052
|
materialColorParameterSchema(resolved.parameters ?? [], firstPassShader, assets),
|
|
@@ -2049,7 +2075,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2049
2075
|
|
|
2050
2076
|
const materialParamSchema = materialParametersToParamSchema(
|
|
2051
2077
|
resolved.parameters ?? [],
|
|
2052
|
-
|
|
2078
|
+
authoredFirstPassShader,
|
|
2053
2079
|
);
|
|
2054
2080
|
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w17 (D-5):
|
|
2055
2081
|
// bidirectional Skin <-> pbr-skin material fail-fast at extract.
|
|
@@ -2338,6 +2364,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2338
2364
|
}),
|
|
2339
2365
|
normalScale: normalScalePv,
|
|
2340
2366
|
materialShaderId: firstPassShader,
|
|
2367
|
+
materialProgramKeys,
|
|
2341
2368
|
materialHandle: handleRaw,
|
|
2342
2369
|
renderState: pipelineRenderState(allPasses[0]?.renderState),
|
|
2343
2370
|
paramSnapshot: paramSnap,
|
|
@@ -2399,7 +2426,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2399
2426
|
layerVal,
|
|
2400
2427
|
paramSnap,
|
|
2401
2428
|
0,
|
|
2402
|
-
|
|
2429
|
+
materialProgramKeys,
|
|
2403
2430
|
);
|
|
2404
2431
|
}
|
|
2405
2432
|
}
|
|
@@ -2610,15 +2637,22 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2610
2637
|
if (assets !== undefined && assets !== null) {
|
|
2611
2638
|
for (let mi = 1; mi < materialHandles.length; mi++) {
|
|
2612
2639
|
const subHandle = materialHandles[mi] ?? 0;
|
|
2613
|
-
const
|
|
2640
|
+
const subCache = hasSkin ? skinnedMaterialSnapshotCache : materialSnapshotCache;
|
|
2641
|
+
const cachedSubmaterial = subCache.get(subHandle);
|
|
2614
2642
|
materialsArr.push(
|
|
2615
2643
|
cachedSubmaterial?.snapshot ??
|
|
2616
2644
|
resolveMaterialSnapshot(
|
|
2617
2645
|
subHandle,
|
|
2618
2646
|
world,
|
|
2619
2647
|
assets,
|
|
2620
|
-
|
|
2621
|
-
persistentMaterialSnapshotCache,
|
|
2648
|
+
subCache,
|
|
2649
|
+
hasSkin ? undefined : persistentMaterialSnapshotCache,
|
|
2650
|
+
context.materialContext === undefined
|
|
2651
|
+
? undefined
|
|
2652
|
+
: {
|
|
2653
|
+
...context.materialContext,
|
|
2654
|
+
geometry: hasSkin ? 'skinned' : context.materialContext.geometry,
|
|
2655
|
+
},
|
|
2622
2656
|
),
|
|
2623
2657
|
);
|
|
2624
2658
|
}
|
|
@@ -2628,8 +2662,14 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2628
2662
|
const subHandle = materialHandles[mi] ?? 0;
|
|
2629
2663
|
if (subHandle === handleRaw) continue;
|
|
2630
2664
|
const subEntry =
|
|
2631
|
-
materialSnapshotCache.get(subHandle) ??
|
|
2632
|
-
|
|
2665
|
+
(hasSkin ? skinnedMaterialSnapshotCache : materialSnapshotCache).get(subHandle) ??
|
|
2666
|
+
(hasSkin
|
|
2667
|
+
? undefined
|
|
2668
|
+
: readPersistentMaterialSnapshot(
|
|
2669
|
+
persistentMaterialSnapshotCache,
|
|
2670
|
+
subHandle,
|
|
2671
|
+
assets,
|
|
2672
|
+
));
|
|
2633
2673
|
appendMaterialDispatchEntries(
|
|
2634
2674
|
pendingDispatch,
|
|
2635
2675
|
subEntry?.passes ?? (subHandle === 0 ? [DEFAULT_FORWARD_PASS] : []),
|
|
@@ -2639,7 +2679,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2639
2679
|
layerVal,
|
|
2640
2680
|
materialsArr[mi]?.paramSnapshot,
|
|
2641
2681
|
0,
|
|
2642
|
-
materialsArr[mi]?.
|
|
2682
|
+
materialsArr[mi]?.materialProgramKeys,
|
|
2643
2683
|
);
|
|
2644
2684
|
}
|
|
2645
2685
|
}
|
|
@@ -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
|
|
@@ -81,7 +82,9 @@
|
|
|
81
82
|
|
|
82
83
|
import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
|
|
83
84
|
import {
|
|
85
|
+
materialParametersToParamSchema as projectMaterialParametersToParamSchema,
|
|
84
86
|
resolveAssetHandle,
|
|
87
|
+
selectMaterialPassProgram,
|
|
85
88
|
walkMaterialPassesOverSharedRefs,
|
|
86
89
|
} from '@forgeax/engine-assets-runtime';
|
|
87
90
|
import type { Component, ComponentSchema, EntityHandle, Query, World } from '@forgeax/engine-ecs';
|
|
@@ -909,6 +912,7 @@ export interface MaterialSnapshot {
|
|
|
909
912
|
* discriminator (M4-T06).
|
|
910
913
|
*/
|
|
911
914
|
readonly materialShaderId?: string | undefined;
|
|
915
|
+
readonly materialProgramKeys?: Readonly<Record<string, string>> | undefined;
|
|
912
916
|
/**
|
|
913
917
|
* Stable source handle for the material asset. Record uses this only for
|
|
914
918
|
* same-frame assembly reuse; GPU bind-group validity still comes from the
|
|
@@ -1015,12 +1019,9 @@ export interface MaterialSnapshot {
|
|
|
1015
1019
|
* from the legacy `shadingModel` discriminant (feat-20260625 M2 D-3,
|
|
1016
1020
|
* finalised in w15; `shadingModel` field removed in tweak-20260701 M1).
|
|
1017
1021
|
*
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
* pipeline cache key"). Multi-pass materials whose mix of opaque +
|
|
1022
|
-
* transparent passes need finer routing should split into separate
|
|
1023
|
-
* 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.
|
|
1024
1025
|
*
|
|
1025
1026
|
* Type is `boolean | undefined` (derived): `undefined` means "no
|
|
1026
1027
|
* passes / unknown"; consumers must read `=== true` / `!== true` to
|
|
@@ -1125,7 +1126,7 @@ export function appendMaterialDispatchEntries(
|
|
|
1125
1126
|
layer: number,
|
|
1126
1127
|
paramSnapshot: Readonly<Record<string, number | number[] | string>> | undefined,
|
|
1127
1128
|
passIndexOffset = 0,
|
|
1128
|
-
|
|
1129
|
+
materialProgramKeys?: Readonly<Record<string, string>>,
|
|
1129
1130
|
): void {
|
|
1130
1131
|
const matchedPasses = selectPasses(passes, {});
|
|
1131
1132
|
for (let pIdx = 0; pIdx < matchedPasses.length; pIdx++) {
|
|
@@ -1138,9 +1139,9 @@ export function appendMaterialDispatchEntries(
|
|
|
1138
1139
|
};
|
|
1139
1140
|
const authoredShaderId = runtimeMaterialShaderId(pass.program.module, pass.name);
|
|
1140
1141
|
const dispatchShaderId =
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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}`);
|
|
1144
1145
|
pendingDispatch.push({
|
|
1145
1146
|
entityIndex: entity,
|
|
1146
1147
|
materialHandle,
|
|
@@ -1155,13 +1156,6 @@ export function appendMaterialDispatchEntries(
|
|
|
1155
1156
|
defines: undefined,
|
|
1156
1157
|
vertexEntry: pass.program.vertexEntry,
|
|
1157
1158
|
fragmentEntry: pass.program.fragmentEntry,
|
|
1158
|
-
// A cooked Standard publication supplies one specialization key for its
|
|
1159
|
-
// forward/deferred artifact, but its ShadowCaster pass still owns the
|
|
1160
|
-
// engine depth-only shader. Keep the pass-local canonical identity so a
|
|
1161
|
-
// specialized Standard source (which only declares fs_main/fs_gbuffer)
|
|
1162
|
-
// is never asked to build an fs_shadow pipeline. Authored non-Standard
|
|
1163
|
-
// ShadowCaster modules retain the publication override and continue to
|
|
1164
|
-
// use their producer-owned source.
|
|
1165
1159
|
materialShaderId: dispatchShaderId,
|
|
1166
1160
|
paramSnapshot,
|
|
1167
1161
|
...(passState.stencilReference !== undefined && {
|
|
@@ -1419,13 +1413,50 @@ export function runtimeMaterialShaderId(
|
|
|
1419
1413
|
* owns the composed material artifact identity; the authored pass module is
|
|
1420
1414
|
* the built-in fallback used before that publication is ready.
|
|
1421
1415
|
*/
|
|
1422
|
-
export function
|
|
1416
|
+
export function materialProgramKeysForMaterial(
|
|
1423
1417
|
material: MaterialAsset,
|
|
1424
|
-
passes: readonly MaterialPass[],
|
|
1425
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,
|
|
1426
1446
|
): string | undefined {
|
|
1427
|
-
const
|
|
1428
|
-
|
|
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];
|
|
1429
1460
|
}
|
|
1430
1461
|
|
|
1431
1462
|
export function pipelineRenderState(
|
|
@@ -1732,49 +1763,21 @@ export function isEngineInjectedTextureField(
|
|
|
1732
1763
|
return isStandardPbrMaterialShader(shaderId) && ENGINE_INJECTED_TEXTURE_FIELDS.has(fieldName);
|
|
1733
1764
|
}
|
|
1734
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
|
+
*/
|
|
1735
1775
|
export function materialParametersToParamSchema(
|
|
1736
1776
|
parameters: readonly MaterialParameter[],
|
|
1737
|
-
shaderId
|
|
1777
|
+
shaderId?: string,
|
|
1738
1778
|
): readonly ParamSchemaEntry[] {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
// but using that compact projection as the runtime UBO schema would shift
|
|
1742
|
-
// every field after an omitted physical entry.
|
|
1743
|
-
if (isCanonicalStandardPbrMaterialShader(shaderId)) {
|
|
1744
|
-
return STANDARD_PIPELINE_PARAM_SCHEMA;
|
|
1745
|
-
}
|
|
1746
|
-
const engineInjectedTextureFields = isStandardPbrMaterialShader(shaderId)
|
|
1747
|
-
? ENGINE_INJECTED_TEXTURE_FIELDS
|
|
1748
|
-
: undefined;
|
|
1749
|
-
return parameters.flatMap((parameter): ParamSchemaEntry[] => {
|
|
1750
|
-
// Boolean material values are runtime-only and have no UBO representation.
|
|
1751
|
-
if (parameter.type === 'bool') return [];
|
|
1752
|
-
if (parameter.type === 'texture' || parameter.type === 'texture_cube') {
|
|
1753
|
-
if (engineInjectedTextureFields?.has(parameter.name) === true) return [];
|
|
1754
|
-
return [
|
|
1755
|
-
{
|
|
1756
|
-
name: parameter.name,
|
|
1757
|
-
type: parameter.type === 'texture_cube' ? 'texture_cube' : 'texture2d',
|
|
1758
|
-
default: parameter.default,
|
|
1759
|
-
},
|
|
1760
|
-
];
|
|
1761
|
-
}
|
|
1762
|
-
const numericDefault =
|
|
1763
|
-
typeof parameter.default === 'number'
|
|
1764
|
-
? parameter.default
|
|
1765
|
-
: Array.isArray(parameter.default) &&
|
|
1766
|
-
parameter.default.every((entry) => typeof entry === 'number')
|
|
1767
|
-
? parameter.default
|
|
1768
|
-
: undefined;
|
|
1769
|
-
const numericEntry = {
|
|
1770
|
-
name: parameter.name,
|
|
1771
|
-
type: parameter.type,
|
|
1772
|
-
...(parameter.colorSpace === undefined ? {} : { colorSpace: parameter.colorSpace }),
|
|
1773
|
-
};
|
|
1774
|
-
return numericDefault === undefined
|
|
1775
|
-
? [numericEntry]
|
|
1776
|
-
: [{ ...numericEntry, default: numericDefault }];
|
|
1777
|
-
});
|
|
1779
|
+
if (isCanonicalStandardPbrMaterialShader(shaderId)) return STANDARD_PIPELINE_PARAM_SCHEMA;
|
|
1780
|
+
return projectMaterialParametersToParamSchema(parameters);
|
|
1778
1781
|
}
|
|
1779
1782
|
|
|
1780
1783
|
/**
|
|
@@ -1921,6 +1924,7 @@ export function resolveMaterialSnapshot(
|
|
|
1921
1924
|
assetsRef: AssetRegistry,
|
|
1922
1925
|
materialSnapshotCache?: MaterialSnapshotCache,
|
|
1923
1926
|
persistentMaterialSnapshotCache?: MaterialSnapshotCache,
|
|
1927
|
+
materialContext?: MaterialCookProgramContext,
|
|
1924
1928
|
): MaterialSnapshot {
|
|
1925
1929
|
if (handleRaw === 0) return defaultMaterialSnapshot(handleRaw);
|
|
1926
1930
|
const cached = materialSnapshotCache?.get(handleRaw);
|
|
@@ -1952,7 +1956,9 @@ export function resolveMaterialSnapshot(
|
|
|
1952
1956
|
if (!resolvedResult.ok) return defaultMaterialSnapshot(handleRaw);
|
|
1953
1957
|
const resolved = resolvedResult.value;
|
|
1954
1958
|
const allPasses = resolved.passes;
|
|
1955
|
-
const
|
|
1959
|
+
const materialProgramKeys = materialProgramKeysForMaterial(asset, assetsRef, materialContext);
|
|
1960
|
+
const authoredFirstPassShader = runtimeMaterialShaderIdForMaterial(allPasses, undefined);
|
|
1961
|
+
const firstPassShader = runtimeMaterialShaderIdForMaterial(allPasses, materialProgramKeys);
|
|
1956
1962
|
const pv = materialValuesToLinearRuntime(
|
|
1957
1963
|
resolved.values,
|
|
1958
1964
|
materialColorParameterSchema(resolved.parameters ?? [], firstPassShader, assetsRef),
|
|
@@ -1979,7 +1985,7 @@ export function resolveMaterialSnapshot(
|
|
|
1979
1985
|
}
|
|
1980
1986
|
const materialParamSchema = materialParametersToParamSchema(
|
|
1981
1987
|
resolved.parameters ?? [],
|
|
1982
|
-
|
|
1988
|
+
authoredFirstPassShader,
|
|
1983
1989
|
);
|
|
1984
1990
|
// feat-20260614 M8 (D-19): texture / sampler values are embedded GUIDs
|
|
1985
1991
|
// (dash-form strings) after loadByGuid. Resolve each to a user-tier column
|
|
@@ -2047,6 +2053,7 @@ export function resolveMaterialSnapshot(
|
|
|
2047
2053
|
}),
|
|
2048
2054
|
normalScale: normalScalePv,
|
|
2049
2055
|
materialShaderId: firstPassShader,
|
|
2056
|
+
materialProgramKeys,
|
|
2050
2057
|
materialHandle: handleRaw,
|
|
2051
2058
|
renderState: pipelineRenderState(allPasses[0]?.renderState),
|
|
2052
2059
|
paramSnapshot: paramSnap,
|
|
@@ -2548,6 +2555,7 @@ export interface ExtractFramesOwner {
|
|
|
2548
2555
|
}
|
|
2549
2556
|
|
|
2550
2557
|
export interface PreparedExtractContext {
|
|
2558
|
+
readonly materialContext?: MaterialCookProgramContext | undefined;
|
|
2551
2559
|
readonly assets: AssetRegistry | null | undefined;
|
|
2552
2560
|
readonly pipelineState: ExtractPipelineSurface | null | undefined;
|
|
2553
2561
|
readonly materialSnapshotCache: MaterialSnapshotCache | undefined;
|
|
@@ -2748,6 +2756,7 @@ export function tonemapParams(camera: CameraSnapshot): Uint8Array {
|
|
|
2748
2756
|
export function prepareExtractContext(
|
|
2749
2757
|
world: World,
|
|
2750
2758
|
options: {
|
|
2759
|
+
readonly materialContext?: MaterialCookProgramContext;
|
|
2751
2760
|
readonly assets?: AssetRegistry | null;
|
|
2752
2761
|
readonly pipelineState?: ExtractPipelineSurface | null;
|
|
2753
2762
|
readonly materialSnapshotCache?: MaterialSnapshotCache;
|
|
@@ -2765,6 +2774,7 @@ export function prepareExtractContext(
|
|
|
2765
2774
|
): PreparedExtractContext {
|
|
2766
2775
|
const renderables = options.renderables ?? 'full';
|
|
2767
2776
|
return {
|
|
2777
|
+
materialContext: options.materialContext,
|
|
2768
2778
|
assets: options.assets,
|
|
2769
2779
|
pipelineState: options.pipelineState,
|
|
2770
2780
|
materialSnapshotCache: options.materialSnapshotCache,
|
package/src/render-system.ts
CHANGED
|
@@ -2586,6 +2586,25 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2586
2586
|
internals.getPipelineState(),
|
|
2587
2587
|
persistentRenderScene.materialSnapshotCacheStore(),
|
|
2588
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
|
+
}),
|
|
2589
2608
|
cull: 'none',
|
|
2590
2609
|
renderables: request === 'none' ? 'none' : 'full',
|
|
2591
2610
|
...(request === 'none' || request === 'full'
|