@forgeax/engine-render 0.1.25 → 0.1.27

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.
Files changed (87) hide show
  1. package/README.md +18 -0
  2. package/dist/assembly/factory.d.ts +1 -1
  3. package/dist/assembly/factory.d.ts.map +1 -1
  4. package/dist/assembly/material/assembly.d.ts +4 -17
  5. package/dist/assembly/material/assembly.d.ts.map +1 -1
  6. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -1
  7. package/dist/assembly/material/surface-projection.d.ts.map +1 -1
  8. package/dist/assembly/webgpu-renderer.d.ts +0 -1
  9. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  10. package/dist/authoring.mjs +1 -1
  11. package/dist/{chunk-JSEDTZYS.mjs → chunk-GF523LHF.mjs} +6 -6
  12. package/dist/chunk-GF523LHF.mjs.map +1 -0
  13. package/dist/{chunk-TCLDX7SZ.mjs → chunk-TMIASV2N.mjs} +403 -457
  14. package/dist/chunk-TMIASV2N.mjs.map +1 -0
  15. package/dist/{chunk-N3RT2EUR.mjs → chunk-XDP6XE5V.mjs} +104 -56
  16. package/dist/chunk-XDP6XE5V.mjs.map +1 -0
  17. package/dist/construct-renderer.mjs +153 -13
  18. package/dist/construct-renderer.mjs.map +1 -1
  19. package/dist/extract/contracts.d.ts +6 -6
  20. package/dist/extract/contracts.d.ts.map +1 -1
  21. package/dist/index.mjs +1 -1
  22. package/dist/internal.mjs +1 -1
  23. package/dist/pipeline-spec-types.d.ts +2 -0
  24. package/dist/pipeline-spec-types.d.ts.map +1 -1
  25. package/dist/pipeline-spec.d.ts.map +1 -1
  26. package/dist/record/frame.d.ts.map +1 -1
  27. package/dist/record/main-pass-geometry.d.ts +2 -2
  28. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  29. package/dist/record/main-pass-material.d.ts +20 -2
  30. package/dist/record/main-pass-material.d.ts.map +1 -1
  31. package/dist/record/main-pass-sprite-draws.d.ts +3 -3
  32. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  33. package/dist/record/main-pass.d.ts.map +1 -1
  34. package/dist/record/material-uniforms.d.ts +4 -0
  35. package/dist/record/material-uniforms.d.ts.map +1 -0
  36. package/dist/record/render-context.d.ts +2 -2
  37. package/dist/record/render-context.d.ts.map +1 -1
  38. package/dist/record/shadow-pass.d.ts +2 -3
  39. package/dist/record/shadow-pass.d.ts.map +1 -1
  40. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  41. package/dist/render-system-extract-tail.d.ts +2 -0
  42. package/dist/render-system-extract-tail.d.ts.map +1 -1
  43. package/dist/render-system-extract.d.ts +27 -10
  44. package/dist/render-system-extract.d.ts.map +1 -1
  45. package/dist/render-system.d.ts.map +1 -1
  46. package/dist/temporal/index.mjs +2 -2
  47. package/package.json +20 -20
  48. package/src/__tests__/fixtures/ai-weapon-spirit/README.md +20 -0
  49. package/src/__tests__/fixtures/ai-weapon-spirit/low-poly-toon.wgsl +178 -0
  50. package/src/__tests__/fixtures/ai-weapon-spirit/material.json +97 -0
  51. package/src/__tests__/fixtures/ai-weapon-spirit/painterly-surface.wgsl +55 -0
  52. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +3 -3
  53. package/src/__tests__/gpu-driven-view.browser.test.ts +11 -3
  54. package/src/__tests__/material-contract-inventory.unit.test.ts +43 -0
  55. package/src/__tests__/{material-static-dynamic-route.unit.test.ts → material-layer-projection.unit.test.ts} +30 -47
  56. package/src/__tests__/material-program-dispatch.unit.test.ts +48 -0
  57. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +2 -2
  58. package/src/__tests__/skinned-shadow-caster.test.ts +14 -14
  59. package/src/__tests__/surface-variant-provenance.integration.test.ts +1 -1
  60. package/src/assembly/factory.ts +0 -3
  61. package/src/assembly/material/assembly.ts +8 -40
  62. package/src/assembly/material/standard-layer-projection.ts +3 -1
  63. package/src/assembly/material/surface-projection.ts +2 -1
  64. package/src/assembly/webgpu-renderer.ts +17 -9
  65. package/src/extract/contracts.ts +6 -6
  66. package/src/materials.ts +3 -3
  67. package/src/pipeline-spec-types.ts +2 -0
  68. package/src/pipeline-spec.ts +7 -3
  69. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +68 -1
  70. package/src/record/frame.ts +7 -1
  71. package/src/record/main-pass-geometry.ts +340 -279
  72. package/src/record/main-pass-material.ts +87 -2
  73. package/src/record/main-pass-sprite-draws.ts +65 -168
  74. package/src/record/main-pass.ts +24 -246
  75. package/src/record/material-uniforms.ts +186 -0
  76. package/src/record/render-context.ts +4 -0
  77. package/src/record/shadow-pass.ts +86 -28
  78. package/src/recovery/render-system-candidate.ts +5 -1
  79. package/src/render-system-extract-tail.ts +54 -13
  80. package/src/render-system-extract.ts +103 -64
  81. package/src/render-system.ts +19 -0
  82. package/dist/assembly/material/pipeline-projection.d.ts +0 -30
  83. package/dist/assembly/material/pipeline-projection.d.ts.map +0 -1
  84. package/dist/chunk-JSEDTZYS.mjs.map +0 -1
  85. package/dist/chunk-N3RT2EUR.mjs.map +0 -1
  86. package/dist/chunk-TCLDX7SZ.mjs.map +0 -1
  87. 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 materialHandle: number;
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
- materialHandle: entry.materialHandle,
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) return;
383
- c.frameState.directionalShadowCacheRecorded = true;
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
- ): void {
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) continue;
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
- // A skinned caster's pipeline was selected above together with its
690
- // two-binding mesh/palette group. Keep that pairing for every submesh;
691
- // resetting to the static shadow pipeline here would bind the skin
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
- // On a cache miss (async build in flight / build failure), fall back
702
- // to the default PSO so the caster still writes depth rather than
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
- ) ?? shadowPipeline;
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(candidateValidated, camera.tonemap !== 'none'),
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';
@@ -147,7 +148,8 @@ import {
147
148
  isEngineInjectedTextureField,
148
149
  materialColorParameterSchema,
149
150
  materialNormalScale,
150
- materialParametersToParamSchema,
151
+ materialParamSchemaForMaterial,
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.materialShaderId,
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.materialShaderId,
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 firstPassShader = runtimeMaterialShaderIdForMaterial(asset, allPasses, assets);
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),
@@ -2047,9 +2073,10 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2047
2073
  }
2048
2074
  }
2049
2075
 
2050
- const materialParamSchema = materialParametersToParamSchema(
2076
+ const materialParamSchema = materialParamSchemaForMaterial(
2051
2077
  resolved.parameters ?? [],
2052
- firstPassShader,
2078
+ authoredFirstPassShader,
2079
+ allPasses,
2053
2080
  );
2054
2081
  // feat-20260611-fox-skinning-vertex-attribute-chain M4 / w17 (D-5):
2055
2082
  // bidirectional Skin <-> pbr-skin material fail-fast at extract.
@@ -2338,6 +2365,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2338
2365
  }),
2339
2366
  normalScale: normalScalePv,
2340
2367
  materialShaderId: firstPassShader,
2368
+ materialProgramKeys,
2341
2369
  materialHandle: handleRaw,
2342
2370
  renderState: pipelineRenderState(allPasses[0]?.renderState),
2343
2371
  paramSnapshot: paramSnap,
@@ -2399,7 +2427,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2399
2427
  layerVal,
2400
2428
  paramSnap,
2401
2429
  0,
2402
- firstPassShader,
2430
+ materialProgramKeys,
2403
2431
  );
2404
2432
  }
2405
2433
  }
@@ -2610,15 +2638,22 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2610
2638
  if (assets !== undefined && assets !== null) {
2611
2639
  for (let mi = 1; mi < materialHandles.length; mi++) {
2612
2640
  const subHandle = materialHandles[mi] ?? 0;
2613
- const cachedSubmaterial = materialSnapshotCache.get(subHandle);
2641
+ const subCache = hasSkin ? skinnedMaterialSnapshotCache : materialSnapshotCache;
2642
+ const cachedSubmaterial = subCache.get(subHandle);
2614
2643
  materialsArr.push(
2615
2644
  cachedSubmaterial?.snapshot ??
2616
2645
  resolveMaterialSnapshot(
2617
2646
  subHandle,
2618
2647
  world,
2619
2648
  assets,
2620
- materialSnapshotCache,
2621
- persistentMaterialSnapshotCache,
2649
+ subCache,
2650
+ hasSkin ? undefined : persistentMaterialSnapshotCache,
2651
+ context.materialContext === undefined
2652
+ ? undefined
2653
+ : {
2654
+ ...context.materialContext,
2655
+ geometry: hasSkin ? 'skinned' : context.materialContext.geometry,
2656
+ },
2622
2657
  ),
2623
2658
  );
2624
2659
  }
@@ -2628,8 +2663,14 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2628
2663
  const subHandle = materialHandles[mi] ?? 0;
2629
2664
  if (subHandle === handleRaw) continue;
2630
2665
  const subEntry =
2631
- materialSnapshotCache.get(subHandle) ??
2632
- readPersistentMaterialSnapshot(persistentMaterialSnapshotCache, subHandle, assets);
2666
+ (hasSkin ? skinnedMaterialSnapshotCache : materialSnapshotCache).get(subHandle) ??
2667
+ (hasSkin
2668
+ ? undefined
2669
+ : readPersistentMaterialSnapshot(
2670
+ persistentMaterialSnapshotCache,
2671
+ subHandle,
2672
+ assets,
2673
+ ));
2633
2674
  appendMaterialDispatchEntries(
2634
2675
  pendingDispatch,
2635
2676
  subEntry?.passes ?? (subHandle === 0 ? [DEFAULT_FORWARD_PASS] : []),
@@ -2639,7 +2680,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2639
2680
  layerVal,
2640
2681
  materialsArr[mi]?.paramSnapshot,
2641
2682
  0,
2642
- materialsArr[mi]?.materialShaderId,
2683
+ materialsArr[mi]?.materialProgramKeys,
2643
2684
  );
2644
2685
  }
2645
2686
  }