@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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type MaterialShaderManifestEntry,
|
|
13
13
|
type ShaderCatalog,
|
|
14
14
|
type ShaderCatalogDevice,
|
|
15
|
+
STANDARD_PIPELINE_PARAM_SCHEMA,
|
|
15
16
|
standardPhysicalTextureFields,
|
|
16
17
|
} from '@forgeax/engine-shader';
|
|
17
18
|
import type { AssetGuid as AssetGuidType, ParamSchemaEntry } from '@forgeax/engine-types';
|
|
@@ -282,11 +283,10 @@ export async function prepareMaterialShaders(
|
|
|
282
283
|
const capValue = (rhiDevice.limits as Readonly<Record<string, number>>)
|
|
283
284
|
.maxStorageBuffersPerShaderStage;
|
|
284
285
|
const webgl2Downlevel = rhiDevice.caps.backendKind === 'wgpu-webgl2';
|
|
285
|
-
|
|
286
|
-
if (typeof capValue === 'number') {
|
|
286
|
+
const storageBufferCapable = rhiDevice.caps.storageBuffer;
|
|
287
|
+
if (storageBufferCapable && typeof capValue === 'number') {
|
|
287
288
|
const capCheck = assertStorageBufferCap(capValue);
|
|
288
289
|
if (!capCheck.ok) throw capCheck.error;
|
|
289
|
-
storageBufferCapable = !webgl2Downlevel && capCheck.value;
|
|
290
290
|
}
|
|
291
291
|
const extendedLightingShaderAvailable = deriveExtendedLightingCapability(rhiDevice).admitted;
|
|
292
292
|
const projectorAvailable =
|
|
@@ -314,9 +314,10 @@ export async function prepareMaterialShaders(
|
|
|
314
314
|
// engine-entry compile path, NOT by installMaterialArtifact.
|
|
315
315
|
if (msEntry.identifier.startsWith('forgeax::engine-')) continue;
|
|
316
316
|
// buildVariantKey logic: sorted key=value pairs joined with +; all-true = ''.
|
|
317
|
-
const variantDefines: Record<string, boolean> = {
|
|
318
|
-
|
|
319
|
-
|
|
317
|
+
const variantDefines: Record<string, boolean> = {};
|
|
318
|
+
if (msEntry.variants.some((variant) => 'STORAGE_BUFFER_AVAILABLE' in variant.defines)) {
|
|
319
|
+
variantDefines.STORAGE_BUFFER_AVAILABLE = storageBufferCapable;
|
|
320
|
+
}
|
|
320
321
|
// User material manifests may declare WEBGL2_COMPAT alongside the shared
|
|
321
322
|
// mesh-storage axis. The fallback backend must select that authored
|
|
322
323
|
// branch at boot; otherwise it asks for the impossible bare
|
|
@@ -450,11 +451,12 @@ export async function prepareMaterialShaders(
|
|
|
450
451
|
(candidate) => candidate.identifier === shadowCasterIdentifier,
|
|
451
452
|
);
|
|
452
453
|
if (manifestEntry !== undefined) {
|
|
453
|
-
// shadow_caster
|
|
454
|
-
//
|
|
454
|
+
// shadow_caster evaluates the same default Surface as Standard. Keep
|
|
455
|
+
// its generated MaterialParameters contract aligned with the Standard
|
|
456
|
+
// pipeline even when a legacy manifest omitted the material entry.
|
|
455
457
|
registry.installMaterialArtifact(shadowCasterIdentifier, {
|
|
456
458
|
source: manifestEntry.composedWgsl,
|
|
457
|
-
paramSchema:
|
|
459
|
+
paramSchema: STANDARD_PIPELINE_PARAM_SCHEMA,
|
|
458
460
|
});
|
|
459
461
|
const shaderGuid = ENGINE_SHADER_GUIDS.get(shadowCasterIdentifier);
|
|
460
462
|
if (shaderGuid !== undefined) {
|
|
@@ -462,7 +464,7 @@ export async function prepareMaterialShaders(
|
|
|
462
464
|
kind: 'shader' as const,
|
|
463
465
|
name: shadowCasterIdentifier,
|
|
464
466
|
source: manifestEntry.composedWgsl,
|
|
465
|
-
paramSchema:
|
|
467
|
+
paramSchema: STANDARD_PIPELINE_PARAM_SCHEMA,
|
|
466
468
|
};
|
|
467
469
|
// feat-20260614 M8 (D-17): catalogue by GUID; no handle minted.
|
|
468
470
|
assets.catalog(shaderGuid, shaderAsset);
|
|
@@ -659,6 +661,82 @@ export function resolveMaterialShaderVertexInputContract(
|
|
|
659
661
|
: 'none';
|
|
660
662
|
}
|
|
661
663
|
|
|
664
|
+
/**
|
|
665
|
+
* Resolve the number of UV sets declared by one material artifact.
|
|
666
|
+
*
|
|
667
|
+
* Runtime-published Surface programs do not necessarily have a manifest
|
|
668
|
+
* reflection row, so the authored vertex input is the fallback source of
|
|
669
|
+
* truth. The renderer then feeds this count into the geometry alias
|
|
670
|
+
* projection, which clamps missing mesh UV sets to the last available set.
|
|
671
|
+
*/
|
|
672
|
+
export function resolveMaterialShaderUvSetCount(
|
|
673
|
+
source: string | undefined,
|
|
674
|
+
reflectedCount?: number,
|
|
675
|
+
): number | undefined {
|
|
676
|
+
if (reflectedCount !== undefined) return reflectedCount;
|
|
677
|
+
if (source === undefined) return undefined;
|
|
678
|
+
|
|
679
|
+
const withoutComments = source.replace(/\/\*[\s\S]*?\*\//gu, '').replace(/\/\/.*$/gmu, '');
|
|
680
|
+
const inputStructs = [
|
|
681
|
+
...withoutComments.matchAll(/struct\s+([A-Za-z_]\w*)\s*\{([^}]*)\}/gu),
|
|
682
|
+
].filter((match) => /@location\s*\(/u.test(match[2] ?? ''));
|
|
683
|
+
for (const inputStruct of inputStructs) {
|
|
684
|
+
const name = inputStruct[1];
|
|
685
|
+
const body = inputStruct[2];
|
|
686
|
+
if (name === undefined || body === undefined) continue;
|
|
687
|
+
const firstParameter = new RegExp(
|
|
688
|
+
`@vertex\\s*fn\\s+\\w+\\s*\\(\\s*\\w+\\s*:\\s*${name}\\b`,
|
|
689
|
+
'u',
|
|
690
|
+
);
|
|
691
|
+
if (!firstParameter.test(withoutComments)) continue;
|
|
692
|
+
let uvSetCount = 0;
|
|
693
|
+
for (const location of body.matchAll(/@location\s*\(\s*(\d+)\s*\)\s+([A-Za-z_]\w*)/gu)) {
|
|
694
|
+
const field = location[2];
|
|
695
|
+
if (field === 'uv') {
|
|
696
|
+
uvSetCount = Math.max(uvSetCount, 1);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
const uvIndex = field?.match(/^uv(\d+)_?$/u)?.[1];
|
|
700
|
+
if (uvIndex !== undefined) uvSetCount = Math.max(uvSetCount, Number(uvIndex) + 1);
|
|
701
|
+
}
|
|
702
|
+
return uvSetCount > 0 ? uvSetCount : undefined;
|
|
703
|
+
}
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Select a published material artifact that matches a downlevel backend.
|
|
709
|
+
*
|
|
710
|
+
* Cooked Surface publications carry immutable backend variants in their
|
|
711
|
+
* metadata. WebGL2 must select the uniform-fallback specialization before
|
|
712
|
+
* pipeline construction so its shader, bind-group layout, and cache key stay
|
|
713
|
+
* capability-consistent.
|
|
714
|
+
*/
|
|
715
|
+
export function resolveMaterialShaderBackendArtifactKey(
|
|
716
|
+
materialShaderId: string,
|
|
717
|
+
backendKind: 'webgpu' | 'wgpu-native' | 'wgpu-webgl2' | 'null',
|
|
718
|
+
artifact:
|
|
719
|
+
| {
|
|
720
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
721
|
+
}
|
|
722
|
+
| undefined,
|
|
723
|
+
): string {
|
|
724
|
+
if (backendKind !== 'wgpu-webgl2') return materialShaderId;
|
|
725
|
+
const variants = artifact?.metadata?.variants;
|
|
726
|
+
if (!Array.isArray(variants)) return materialShaderId;
|
|
727
|
+
const fallback = variants.find((variant): variant is { specializationKey: string } => {
|
|
728
|
+
if (typeof variant !== 'object' || variant === null) return false;
|
|
729
|
+
const candidate = variant as Record<string, unknown>;
|
|
730
|
+
return (
|
|
731
|
+
candidate.backend === 'webgl2' &&
|
|
732
|
+
candidate.capability === 'uniform-fallback' &&
|
|
733
|
+
typeof candidate.specializationKey === 'string' &&
|
|
734
|
+
candidate.specializationKey.length > 0
|
|
735
|
+
);
|
|
736
|
+
});
|
|
737
|
+
return fallback?.specializationKey ?? materialShaderId;
|
|
738
|
+
}
|
|
739
|
+
|
|
662
740
|
export function allowsUnlitPreparedFallback(
|
|
663
741
|
depthFormatOverride: GPUTextureFormat | null | undefined,
|
|
664
742
|
vertexLayout: string | undefined,
|
|
@@ -632,7 +632,7 @@ export function createRendererRecovery(deps: RendererRecoveryDependencies): Rend
|
|
|
632
632
|
releaseCandidate();
|
|
633
633
|
return err(new RecoverError('recover-device-unavailable'));
|
|
634
634
|
}
|
|
635
|
-
const graphPreparation = renderSystem.prepareRecoveryGraphCandidate({
|
|
635
|
+
const graphPreparation = await renderSystem.prepareRecoveryGraphCandidate({
|
|
636
636
|
internals: candidateInternals,
|
|
637
637
|
pipelineState: candidatePipelineState,
|
|
638
638
|
});
|
|
@@ -54,7 +54,6 @@ import {
|
|
|
54
54
|
} from '../ibl/skylight-bind-group';
|
|
55
55
|
import type { BloomInspection } from '../inspection-types';
|
|
56
56
|
import type { DeviceScope, RhiErrorListenerRegistry } from '../lifecycle';
|
|
57
|
-
import { assertStorageBufferCap } from '../light-buffer-layout';
|
|
58
57
|
import {
|
|
59
58
|
buildPbrPipelineLayouts,
|
|
60
59
|
buildPbrSkinLayouts,
|
|
@@ -218,22 +217,10 @@ export async function buildReadyWebGPU(
|
|
|
218
217
|
setVolumetricFogShaderSources: (sources: VolumetricFogShaderSources) => void,
|
|
219
218
|
): Promise<PipelineState> {
|
|
220
219
|
const extendedLightingShaderAvailable = deriveExtendedLightingCapability(rhiDevice).admitted;
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
// (buildPbrPipelineLayouts, which switches between read-only-storage
|
|
225
|
-
// and uniform BGL entries) walk the same axis. Step 0 already ran in
|
|
226
|
-
// `prepareMaterialShaders`; this is an idempotent re-read driven by
|
|
227
|
-
// `device.limits.maxStorageBuffersPerShaderStage`, which is 0 on the
|
|
228
|
-
// wgpu-wasm WebGL2 backend (downlevel_webgl2_defaults).
|
|
229
|
-
const capValue = (rhiDevice.limits as Readonly<Record<string, number>>)
|
|
230
|
-
.maxStorageBuffersPerShaderStage;
|
|
220
|
+
// Shader layouts and record-stage buffer usage share the RHI capability.
|
|
221
|
+
// Backend names and numeric limits must not independently re-enable storage.
|
|
222
|
+
const storageBufferCapable = rhiDevice.caps.storageBuffer;
|
|
231
223
|
const webgl2Downlevel = rhiDevice.caps.backendKind === 'wgpu-webgl2';
|
|
232
|
-
let storageBufferCapable = !webgl2Downlevel;
|
|
233
|
-
if (typeof capValue === 'number') {
|
|
234
|
-
const capCheck = assertStorageBufferCap(capValue);
|
|
235
|
-
storageBufferCapable = capCheck.ok ? !webgl2Downlevel && capCheck.value : !webgl2Downlevel;
|
|
236
|
-
}
|
|
237
224
|
const directionalPcssAvailable =
|
|
238
225
|
rhiDevice.caps.backendKind === 'webgpu' || rhiDevice.caps.backendKind === 'wgpu-native';
|
|
239
226
|
const projectorAvailable =
|
|
@@ -30,7 +30,6 @@ import { createRenderReadLease, type RenderReadLease } from '@forgeax/engine-ecs
|
|
|
30
30
|
import {
|
|
31
31
|
deriveVertexBufferLayout,
|
|
32
32
|
deriveVertexBufferLayoutFromProjection,
|
|
33
|
-
type GpuVertexBufferLayoutEntry,
|
|
34
33
|
type VertexLayoutProjection,
|
|
35
34
|
} from '@forgeax/engine-geometry';
|
|
36
35
|
import type {
|
|
@@ -176,7 +175,9 @@ import {
|
|
|
176
175
|
makeShaderDeviceAdapter,
|
|
177
176
|
normalizeMaterialShaderVariantSet,
|
|
178
177
|
prepareMaterialShaders,
|
|
178
|
+
resolveMaterialShaderBackendArtifactKey,
|
|
179
179
|
resolveMaterialShaderBindingContract,
|
|
180
|
+
resolveMaterialShaderUvSetCount,
|
|
180
181
|
resolveMaterialShaderVariantSet,
|
|
181
182
|
resolveMaterialShaderVertexInputContract,
|
|
182
183
|
type ShaderDeviceAdapterInternal,
|
|
@@ -214,14 +215,20 @@ import {
|
|
|
214
215
|
import { STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES } from './shader-prewarm-policy';
|
|
215
216
|
import { buildReadyWebGPU } from './webgpu-ready';
|
|
216
217
|
import { DEPTH_TEXTURE_FORMAT, HDR_COLOR_ATTACHMENT_FORMAT } from './webgpu-ready-contract';
|
|
218
|
+
import {
|
|
219
|
+
BILLBOARD_MATERIAL_INSTANCE_VERTEX_BUFFERS,
|
|
220
|
+
DEFAULT_VERTEX_ATTRS,
|
|
221
|
+
MESH_GEOMETRY_MATERIAL_INSTANCE_VERTEX_BUFFERS,
|
|
222
|
+
PBR_SKIN_SENTINEL_ATTR_BUFFER,
|
|
223
|
+
POSITION_SIZE_COLOR_INSTANCE_VERTEX_BUFFERS,
|
|
224
|
+
PREPARED_INSTANCE_VERTEX_ATTRS,
|
|
225
|
+
PREPARED_MATERIAL_INSTANCE_VERTEX_ATTRS,
|
|
226
|
+
TOPOLOGY_SEGMENT_INSTANCE_VERTEX_BUFFERS,
|
|
227
|
+
toGpuVertexBufferLayouts,
|
|
228
|
+
} from './webgpu-vertex-layouts';
|
|
217
229
|
|
|
218
230
|
export type { BundlerOptions } from './bundler-contract';
|
|
219
231
|
export { assembleMaterialProjection } from './material/assembly';
|
|
220
|
-
export {
|
|
221
|
-
projectMaterialPipeline,
|
|
222
|
-
routeMaterialPipeline,
|
|
223
|
-
updateMaterialRuntimeValues,
|
|
224
|
-
} from './material/pipeline-projection';
|
|
225
232
|
export type {
|
|
226
233
|
LayoutKind,
|
|
227
234
|
MaterialShaderBindingContract,
|
|
@@ -231,7 +238,9 @@ export {
|
|
|
231
238
|
allowsUnlitPreparedFallback,
|
|
232
239
|
isSharedMaterialUserRegionCompatible,
|
|
233
240
|
normalizeMaterialShaderVariantSet,
|
|
241
|
+
resolveMaterialShaderBackendArtifactKey,
|
|
234
242
|
resolveMaterialShaderBindingContract,
|
|
243
|
+
resolveMaterialShaderUvSetCount,
|
|
235
244
|
resolveMaterialShaderVariantSet,
|
|
236
245
|
resolveMaterialShaderVertexInputContract,
|
|
237
246
|
selectNoColorPbrVariant,
|
|
@@ -273,139 +282,6 @@ function adaptMipmapShaderModuleFactory(
|
|
|
273
282
|
// Transform.world mat4 derivation (audio listener sync + picking read the
|
|
274
283
|
// derived Transform.world from scripts).
|
|
275
284
|
|
|
276
|
-
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4): a single
|
|
277
|
-
// shared empty ArrayBuffer used by `buildPipelineContext` to synthesize the
|
|
278
|
-
// 6-key VertexAttributeMap when no caller-supplied attributes are available
|
|
279
|
-
// for the pbr-skin path. `deriveVertexBufferLayout` is the SSOT for the layout
|
|
280
|
-
// (vertex-attribute-layout.ts) and reads only key presence -- value identity
|
|
281
|
-
// never matters -- so a zero-byte ArrayBuffer per key produces the exact
|
|
282
|
-
// 6-attribute / 72-byte stride layout that the @forgeax::pbr-skin shader's
|
|
283
|
-
// @location(0..5) declarations expect.
|
|
284
|
-
const PBR_SKIN_SENTINEL_ATTR_BUFFER = new ArrayBuffer(0);
|
|
285
|
-
|
|
286
|
-
/** Default 4-attribute vertex layout used as fallback when meshAttributes is undefined. */
|
|
287
|
-
const DEFAULT_VERTEX_ATTRS: VertexAttributeMap = {
|
|
288
|
-
position: new Float32Array(0),
|
|
289
|
-
normal: new Float32Array(0),
|
|
290
|
-
uv: new Float32Array(0),
|
|
291
|
-
tangent: new Float32Array(0),
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const PREPARED_INSTANCE_VERTEX_ATTRS: VertexAttributeMap = {
|
|
295
|
-
position: new Float32Array(0),
|
|
296
|
-
normal: new Float32Array(0),
|
|
297
|
-
uv: new Float32Array(0),
|
|
298
|
-
tangent: new Float32Array(0),
|
|
299
|
-
uv1: new Float32Array(0),
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
const PREPARED_MATERIAL_INSTANCE_VERTEX_ATTRS: VertexAttributeMap = {
|
|
303
|
-
position: new Float32Array(0),
|
|
304
|
-
normal: new Float32Array(0),
|
|
305
|
-
uv: new Float32Array(0),
|
|
306
|
-
tangent: new Float32Array(0),
|
|
307
|
-
skinIndex: new Uint16Array(0),
|
|
308
|
-
skinWeight: new Float32Array(0),
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const POSITION_SIZE_COLOR_INSTANCE_VERTEX_BUFFERS = [
|
|
312
|
-
{
|
|
313
|
-
arrayStride: 9 * 4,
|
|
314
|
-
stepMode: 'instance' as const,
|
|
315
|
-
attributes: [
|
|
316
|
-
{ shaderLocation: 0, offset: 0, format: 'float32x3' as const },
|
|
317
|
-
{ shaderLocation: 1, offset: 3 * 4, format: 'float32x2' as const },
|
|
318
|
-
{ shaderLocation: 2, offset: 5 * 4, format: 'float32x4' as const },
|
|
319
|
-
],
|
|
320
|
-
},
|
|
321
|
-
] satisfies readonly GPUVertexBufferLayout[];
|
|
322
|
-
|
|
323
|
-
const BILLBOARD_MATERIAL_INSTANCE_VERTEX_BUFFERS = [
|
|
324
|
-
{
|
|
325
|
-
arrayStride: 31 * 4,
|
|
326
|
-
stepMode: 'instance' as const,
|
|
327
|
-
attributes: [
|
|
328
|
-
{ shaderLocation: 0, offset: 0, format: 'float32x3' as const },
|
|
329
|
-
{ shaderLocation: 1, offset: 3 * 4, format: 'float32x2' as const },
|
|
330
|
-
{ shaderLocation: 2, offset: 5 * 4, format: 'float32x2' as const },
|
|
331
|
-
{ shaderLocation: 3, offset: 7 * 4, format: 'float32x4' as const },
|
|
332
|
-
{ shaderLocation: 4, offset: 11 * 4, format: 'float32x4' as const },
|
|
333
|
-
{ shaderLocation: 5, offset: 15 * 4, format: 'float32x4' as const },
|
|
334
|
-
{ shaderLocation: 6, offset: 19 * 4, format: 'float32x4' as const },
|
|
335
|
-
{ shaderLocation: 7, offset: 23 * 4, format: 'float32x4' as const },
|
|
336
|
-
{ shaderLocation: 8, offset: 27 * 4, format: 'float32x4' as const },
|
|
337
|
-
],
|
|
338
|
-
},
|
|
339
|
-
] satisfies readonly GPUVertexBufferLayout[];
|
|
340
|
-
|
|
341
|
-
const TOPOLOGY_SEGMENT_INSTANCE_VERTEX_BUFFERS = [
|
|
342
|
-
{
|
|
343
|
-
arrayStride: 12 * 4,
|
|
344
|
-
stepMode: 'instance' as const,
|
|
345
|
-
attributes: [
|
|
346
|
-
{ shaderLocation: 0, offset: 0, format: 'float32x3' as const },
|
|
347
|
-
{ shaderLocation: 1, offset: 3 * 4, format: 'float32x3' as const },
|
|
348
|
-
{ shaderLocation: 2, offset: 6 * 4, format: 'float32x4' as const },
|
|
349
|
-
{ shaderLocation: 3, offset: 10 * 4, format: 'float32x2' as const },
|
|
350
|
-
],
|
|
351
|
-
},
|
|
352
|
-
] satisfies readonly GPUVertexBufferLayout[];
|
|
353
|
-
|
|
354
|
-
const MESH_GEOMETRY_MATERIAL_INSTANCE_VERTEX_BUFFERS = [
|
|
355
|
-
{
|
|
356
|
-
arrayStride: 12 * 4,
|
|
357
|
-
stepMode: 'vertex' as const,
|
|
358
|
-
attributes: [
|
|
359
|
-
{ shaderLocation: 0, offset: 0, format: 'float32x3' as const },
|
|
360
|
-
{ shaderLocation: 1, offset: 3 * 4, format: 'float32x3' as const },
|
|
361
|
-
{ shaderLocation: 2, offset: 6 * 4, format: 'float32x2' as const },
|
|
362
|
-
{ shaderLocation: 3, offset: 8 * 4, format: 'float32x4' as const },
|
|
363
|
-
],
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
arrayStride: 28 * 4,
|
|
367
|
-
stepMode: 'instance' as const,
|
|
368
|
-
attributes: [
|
|
369
|
-
{ shaderLocation: 4, offset: 0, format: 'float32x3' as const },
|
|
370
|
-
{ shaderLocation: 5, offset: 3 * 4, format: 'float32x3' as const },
|
|
371
|
-
{ shaderLocation: 6, offset: 6 * 4, format: 'float32x3' as const },
|
|
372
|
-
{ shaderLocation: 7, offset: 9 * 4, format: 'float32x3' as const },
|
|
373
|
-
{ shaderLocation: 8, offset: 12 * 4, format: 'float32x4' as const },
|
|
374
|
-
{ shaderLocation: 9, offset: 16 * 4, format: 'float32x4' as const },
|
|
375
|
-
{ shaderLocation: 10, offset: 20 * 4, format: 'float32x4' as const },
|
|
376
|
-
{ shaderLocation: 11, offset: 24 * 4, format: 'float32x4' as const },
|
|
377
|
-
],
|
|
378
|
-
},
|
|
379
|
-
] satisfies readonly GPUVertexBufferLayout[];
|
|
380
|
-
|
|
381
|
-
function toGpuVertexFormat(
|
|
382
|
-
format: GpuVertexBufferLayoutEntry['attributes'][number]['format'],
|
|
383
|
-
): GPUVertexFormat {
|
|
384
|
-
switch (format) {
|
|
385
|
-
case 'float32x2':
|
|
386
|
-
case 'float32x3':
|
|
387
|
-
case 'float32x4':
|
|
388
|
-
case 'uint16x4':
|
|
389
|
-
return format;
|
|
390
|
-
default:
|
|
391
|
-
throw new Error(`Unsupported geometry vertex format: ${format}`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function toGpuVertexBufferLayouts(
|
|
396
|
-
entries: readonly GpuVertexBufferLayoutEntry[],
|
|
397
|
-
): readonly GPUVertexBufferLayout[] {
|
|
398
|
-
return entries.map((entry) => ({
|
|
399
|
-
arrayStride: entry.arrayStride,
|
|
400
|
-
...(entry.stepMode === undefined ? {} : { stepMode: entry.stepMode }),
|
|
401
|
-
attributes: entry.attributes.map((attribute) => ({
|
|
402
|
-
shaderLocation: attribute.shaderLocation,
|
|
403
|
-
offset: attribute.offset,
|
|
404
|
-
format: toGpuVertexFormat(attribute.format),
|
|
405
|
-
})),
|
|
406
|
-
}));
|
|
407
|
-
}
|
|
408
|
-
|
|
409
285
|
/**
|
|
410
286
|
* Bundler-layer injection accepted by `createRenderer` (and proxied by
|
|
411
287
|
* `createApp`) as the optional third argument.
|
|
@@ -1954,15 +1830,19 @@ async function makeWebGPURenderer(
|
|
|
1954
1830
|
vertexLayout === RENDER_FEATURE_VERTEX_LAYOUTS.meshGeometryMaterialInstance)
|
|
1955
1831
|
? getOrBuildPreparedMaterialPipelineLayout(materialShaderId)
|
|
1956
1832
|
: null;
|
|
1833
|
+
const materialShaderLookup =
|
|
1834
|
+
materialShaderId === undefined
|
|
1835
|
+
? undefined
|
|
1836
|
+
: getShader().findMaterialArtifact(materialShaderId);
|
|
1837
|
+
const materialShaderSource = materialShaderLookup?.ok
|
|
1838
|
+
? materialShaderLookup.value.source
|
|
1839
|
+
: undefined;
|
|
1957
1840
|
const vertexInputContract =
|
|
1958
1841
|
materialShaderId === undefined
|
|
1959
1842
|
? 'render-material'
|
|
1960
|
-
:
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
? resolveMaterialShaderVertexInputContract(lookup.value.source)
|
|
1964
|
-
: 'render-material';
|
|
1965
|
-
})();
|
|
1843
|
+
: materialShaderSource !== undefined
|
|
1844
|
+
? resolveMaterialShaderVertexInputContract(materialShaderSource)
|
|
1845
|
+
: 'render-material';
|
|
1966
1846
|
const currentState = currentPipelineState();
|
|
1967
1847
|
const pipelineLayout =
|
|
1968
1848
|
group0Layout !== null
|
|
@@ -1979,9 +1859,16 @@ async function makeWebGPURenderer(
|
|
|
1979
1859
|
? perShaderLayout.pipelineLayout
|
|
1980
1860
|
: selectPipelineLayoutForVariant(currentState, variantSet, layoutKind);
|
|
1981
1861
|
if (pipelineLayout === null) return null;
|
|
1862
|
+
const currentMaterialUvSetCounts = currentMaterialShaderUvSetCounts();
|
|
1982
1863
|
const resolvedUvSetCount =
|
|
1983
1864
|
materialShaderId !== undefined
|
|
1984
|
-
?
|
|
1865
|
+
? (currentMaterialUvSetCounts.get(materialShaderId) ??
|
|
1866
|
+
(materialShaderSource !== undefined
|
|
1867
|
+
? resolveMaterialShaderUvSetCount(
|
|
1868
|
+
materialShaderSource,
|
|
1869
|
+
currentMaterialUvSetCounts.get(materialShaderId),
|
|
1870
|
+
)
|
|
1871
|
+
: undefined))
|
|
1985
1872
|
: undefined;
|
|
1986
1873
|
const vertexBuffers: readonly GPUVertexBufferLayout[] =
|
|
1987
1874
|
vertexInputContract === 'none'
|
|
@@ -2110,6 +1997,8 @@ async function makeWebGPURenderer(
|
|
|
2110
1997
|
shaderModuleMode?: RenderFeatureShaderModuleMode,
|
|
2111
1998
|
layoutKindOverride?: LayoutKind,
|
|
2112
1999
|
additionalColorFormats?: readonly GPUTextureFormat[],
|
|
2000
|
+
vertexEntry?: string,
|
|
2001
|
+
authoredFragmentEntry?: string,
|
|
2113
2002
|
): RenderPipeline | null => {
|
|
2114
2003
|
const group2Contract = resolvePipelineGroup2Contract(entry.source);
|
|
2115
2004
|
const ctx = buildPipelineContext(
|
|
@@ -2132,10 +2021,11 @@ async function makeWebGPURenderer(
|
|
|
2132
2021
|
if (currentState === null) return null;
|
|
2133
2022
|
const ldrColorFormat = colorFormatOverride ?? currentState.colorAttachmentFormat;
|
|
2134
2023
|
const fragmentEntry =
|
|
2135
|
-
|
|
2024
|
+
authoredFragmentEntry ??
|
|
2025
|
+
(isHdr &&
|
|
2136
2026
|
(materialShaderId === 'forgeax::sprite' || materialShaderId === 'forgeax::sprite-lit')
|
|
2137
2027
|
? 'fs_main_hdr'
|
|
2138
|
-
: undefined;
|
|
2028
|
+
: undefined);
|
|
2139
2029
|
const built = buildPipelineForMaterialShader(
|
|
2140
2030
|
cacheKey,
|
|
2141
2031
|
// The catalog entry is projected into the material-shader entry shape.
|
|
@@ -2172,7 +2062,7 @@ async function makeWebGPURenderer(
|
|
|
2172
2062
|
: {}),
|
|
2173
2063
|
}
|
|
2174
2064
|
: undefined,
|
|
2175
|
-
|
|
2065
|
+
vertexEntry,
|
|
2176
2066
|
fragmentEntry,
|
|
2177
2067
|
undefined, // defines — none
|
|
2178
2068
|
passKind,
|
|
@@ -2239,6 +2129,8 @@ async function makeWebGPURenderer(
|
|
|
2239
2129
|
shaderModuleMode: RenderFeatureShaderModuleMode = 'validated',
|
|
2240
2130
|
layoutKindOverride?: LayoutKind,
|
|
2241
2131
|
additionalColorFormats?: readonly GPUTextureFormat[],
|
|
2132
|
+
vertexEntry?: string,
|
|
2133
|
+
fragmentEntry?: string,
|
|
2242
2134
|
): RenderPipeline | null => {
|
|
2243
2135
|
// WebGL2 is a downlevel backend even when the record stage asks for the
|
|
2244
2136
|
// native-capability variant string. Resolve the variant axes at this
|
|
@@ -2252,15 +2144,31 @@ async function makeWebGPURenderer(
|
|
|
2252
2144
|
(currentState !== null ? currentState.colorAttachmentFormat : 'bgra8unorm-srgb');
|
|
2253
2145
|
// feat-20260629 M4: auto-fill shaderUvSetCount from naga reflection
|
|
2254
2146
|
// (stored in materialShaderUvSetCounts during prepareMaterialShaders).
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2147
|
+
const pipelineShaderId = resolveMaterialShaderBackendArtifactKey(
|
|
2148
|
+
materialShaderId,
|
|
2149
|
+
internals.device.caps.backendKind,
|
|
2150
|
+
assets.getMaterialArtifact(materialShaderId),
|
|
2151
|
+
);
|
|
2152
|
+
const materialShaderLookup = getShader().findMaterialArtifact(pipelineShaderId);
|
|
2153
|
+
const materialShaderSource = materialShaderLookup.ok
|
|
2154
|
+
? materialShaderLookup.value.source
|
|
2155
|
+
: undefined;
|
|
2156
|
+
const currentMaterialUvSetCounts = currentMaterialShaderUvSetCounts();
|
|
2157
|
+
const materialUvSetCount = resolveMaterialShaderUvSetCount(
|
|
2158
|
+
materialShaderSource,
|
|
2159
|
+
currentMaterialUvSetCounts.get(pipelineShaderId),
|
|
2160
|
+
);
|
|
2161
|
+
if (materialUvSetCount !== undefined) {
|
|
2162
|
+
currentMaterialUvSetCounts.set(pipelineShaderId, materialUvSetCount);
|
|
2163
|
+
}
|
|
2164
|
+
const resolvedUvSetCount = shaderUvSetCount ?? materialUvSetCount;
|
|
2165
|
+
const manifestEntry = findMaterialShaderManifestEntry(pipelineShaderId);
|
|
2258
2166
|
// Sprite's omitted request is the boot-selected default artifact. The
|
|
2259
2167
|
// explicit empty key remains the PER_INSTANCE_REGION=true request used by
|
|
2260
2168
|
// SpriteInstances, so only that path is capability-rewritten here.
|
|
2261
2169
|
const resolvedVariantSet =
|
|
2262
2170
|
variantSet === undefined &&
|
|
2263
|
-
(
|
|
2171
|
+
(pipelineShaderId === 'forgeax::sprite' || pipelineShaderId === 'forgeax::sprite-lit')
|
|
2264
2172
|
? undefined
|
|
2265
2173
|
: resolveCachedMaterialShaderVariantSet(variantSet, manifestEntry);
|
|
2266
2174
|
// The record stage also composes engine capability axes for user shaders.
|
|
@@ -2272,6 +2180,12 @@ async function makeWebGPURenderer(
|
|
|
2272
2180
|
// A single-source manifest entry must stay on its canonical module key;
|
|
2273
2181
|
// otherwise a synthesized capability axis races the prewarmed module.
|
|
2274
2182
|
let effectiveVariantSet = normalizeMaterialShaderVariantSet(resolvedVariantSet, manifestEntry);
|
|
2183
|
+
// Runtime-published authored materials are installed from their immutable
|
|
2184
|
+
// cooked artifact, not from the build-time material-shader manifest. The
|
|
2185
|
+
// published artifact identity already selects the backend-specific bytes;
|
|
2186
|
+
// an absent manifest therefore means the source itself is the canonical
|
|
2187
|
+
// pipeline module and must not carry a synthesized capability key.
|
|
2188
|
+
if (manifestEntry === undefined) effectiveVariantSet = undefined;
|
|
2275
2189
|
if (
|
|
2276
2190
|
vertexLayoutProjection === undefined &&
|
|
2277
2191
|
manifestEntry?.variants.some((variant) => 'VERTEX_COLOR_AVAILABLE' in variant.defines)
|
|
@@ -2294,7 +2208,7 @@ async function makeWebGPURenderer(
|
|
|
2294
2208
|
}
|
|
2295
2209
|
if (
|
|
2296
2210
|
manifestEntry !== undefined &&
|
|
2297
|
-
!
|
|
2211
|
+
!pipelineShaderId.startsWith('forgeax::') &&
|
|
2298
2212
|
effectiveVariantSet !== undefined &&
|
|
2299
2213
|
effectiveVariantSet !== ''
|
|
2300
2214
|
) {
|
|
@@ -2323,7 +2237,13 @@ async function makeWebGPURenderer(
|
|
|
2323
2237
|
const colorFormat: GPUTextureFormat = isHdr ? 'rgba16float' : ldrColorFormat;
|
|
2324
2238
|
const isDepthOnlyPass = passKindPolicyTable[passKind]?.shape === 'depth-only';
|
|
2325
2239
|
const spec: PipelineSpec = {
|
|
2326
|
-
shader: {
|
|
2240
|
+
shader: {
|
|
2241
|
+
id: pipelineShaderId,
|
|
2242
|
+
passKind,
|
|
2243
|
+
variantSet: effectiveVariantSet,
|
|
2244
|
+
...(vertexEntry === undefined ? {} : { vertexEntry }),
|
|
2245
|
+
...(fragmentEntry === undefined ? {} : { fragmentEntry }),
|
|
2246
|
+
},
|
|
2327
2247
|
attachments: {
|
|
2328
2248
|
colorFormats:
|
|
2329
2249
|
additionalColorFormats === undefined
|
|
@@ -2362,7 +2282,7 @@ async function makeWebGPURenderer(
|
|
|
2362
2282
|
const cached = currentPipelineCacheState().materialShaderPipelineCache.get(cacheKey);
|
|
2363
2283
|
if (cached !== undefined) return cached;
|
|
2364
2284
|
if (pipelineState === null) return null;
|
|
2365
|
-
const lookup = getShader().findMaterialArtifact(
|
|
2285
|
+
const lookup = getShader().findMaterialArtifact(pipelineShaderId);
|
|
2366
2286
|
if (!lookup.ok) {
|
|
2367
2287
|
if (shouldDeferMissingPreparedMaterialShader(vertexLayout)) {
|
|
2368
2288
|
// Prepared VFX draws bind only their feature-owned groups. The
|
|
@@ -2377,7 +2297,7 @@ async function makeWebGPURenderer(
|
|
|
2377
2297
|
// the standard pipeline from the engine-shipped PBR entry.
|
|
2378
2298
|
if (
|
|
2379
2299
|
renderState === undefined &&
|
|
2380
|
-
!allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout,
|
|
2300
|
+
!allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout, pipelineShaderId)
|
|
2381
2301
|
) {
|
|
2382
2302
|
// A prepared feature declares the vertex layout it owns. Falling back
|
|
2383
2303
|
// to default-unlit here would pair that data with the unlit shader's
|
|
@@ -2392,7 +2312,7 @@ async function makeWebGPURenderer(
|
|
|
2392
2312
|
return buildAndCachePipeline(
|
|
2393
2313
|
cacheKey,
|
|
2394
2314
|
unlitLookup.value,
|
|
2395
|
-
`pbr-pipeline-prepared-${
|
|
2315
|
+
`pbr-pipeline-prepared-${pipelineShaderId}`,
|
|
2396
2316
|
// Reuse the eagerly compiled engine unlit module. The adapter cache
|
|
2397
2317
|
// is seeded under this label during renderer readiness; a new label
|
|
2398
2318
|
// would keep color-only prepared passes in perpetual warm-up.
|
|
@@ -2420,7 +2340,7 @@ async function makeWebGPURenderer(
|
|
|
2420
2340
|
return buildAndCachePipeline(
|
|
2421
2341
|
cacheKey,
|
|
2422
2342
|
{ source: pbrEntry.wgsl, paramSchema: [] },
|
|
2423
|
-
`pbr-pipeline-fallback-${
|
|
2343
|
+
`pbr-pipeline-fallback-${pipelineShaderId}${isHdr ? '-hdr' : ''}`,
|
|
2424
2344
|
// w16-b: module identity is the fallback PBR source, stable across
|
|
2425
2345
|
// topology / renderState / HDR (all baked into the PSO) so every
|
|
2426
2346
|
// variant reuses one compiled module.
|
|
@@ -2454,8 +2374,8 @@ async function makeWebGPURenderer(
|
|
|
2454
2374
|
// layout resolution so buildPipelineContext derives the layout that
|
|
2455
2375
|
// matches the compiled module. Skin keeps its
|
|
2456
2376
|
// own id so the pbr-skin fail-fast (null layout) is preserved.
|
|
2457
|
-
|
|
2458
|
-
?
|
|
2377
|
+
pipelineShaderId === SKIN_MATERIAL_SHADER_ID
|
|
2378
|
+
? pipelineShaderId
|
|
2459
2379
|
: 'forgeax::default-standard-pbr',
|
|
2460
2380
|
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
|
|
2461
2381
|
// forward meshAttributes through the fallback path so the pbr-skin
|
|
@@ -2489,7 +2409,7 @@ async function makeWebGPURenderer(
|
|
|
2489
2409
|
if (effectiveVariantSet !== undefined) {
|
|
2490
2410
|
const registry = getShader();
|
|
2491
2411
|
for (const msEntry of registry.materialShaderManifestEntries()) {
|
|
2492
|
-
if (msEntry.identifier ===
|
|
2412
|
+
if (msEntry.identifier === pipelineShaderId) {
|
|
2493
2413
|
const variant = findVariantByKey(msEntry, effectiveVariantSet);
|
|
2494
2414
|
if (variant) {
|
|
2495
2415
|
// M3 / w12-w13: variant substitution carries the same source +
|
|
@@ -2516,7 +2436,7 @@ async function makeWebGPURenderer(
|
|
|
2516
2436
|
return buildAndCachePipeline(
|
|
2517
2437
|
cacheKey,
|
|
2518
2438
|
shaderEntry,
|
|
2519
|
-
`pbr-pipeline-${
|
|
2439
|
+
`pbr-pipeline-${pipelineShaderId}${isHdr ? '-hdr' : ''}${passKindLabelSegment}`,
|
|
2520
2440
|
// feat-20260609 M4 / w31: when variantSet is non-empty, the module identity
|
|
2521
2441
|
// includes the variant key so URP (STORAGE_BUFFER_AVAILABLE=true) and HDRP
|
|
2522
2442
|
// (CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true) variants
|
|
@@ -2530,8 +2450,8 @@ async function makeWebGPURenderer(
|
|
|
2530
2450
|
// empty-tail segment); module identity stays a function of the
|
|
2531
2451
|
// exact variantSet string.
|
|
2532
2452
|
effectiveVariantSet !== undefined
|
|
2533
|
-
? `module-${
|
|
2534
|
-
: `module-${
|
|
2453
|
+
? `module-${pipelineShaderId}#${effectiveVariantSet}`
|
|
2454
|
+
: `module-${pipelineShaderId}`,
|
|
2535
2455
|
isHdr,
|
|
2536
2456
|
renderState,
|
|
2537
2457
|
topology,
|
|
@@ -2547,7 +2467,7 @@ async function makeWebGPURenderer(
|
|
|
2547
2467
|
// bug-20260611-skin-pipeline-layout: thread the registered materialShaderId
|
|
2548
2468
|
// so buildPipelineContext can resolve LayoutKind='pbr-skin' for the skin
|
|
2549
2469
|
// shader (2-entry mesh-array BGL).
|
|
2550
|
-
|
|
2470
|
+
pipelineShaderId,
|
|
2551
2471
|
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
|
|
2552
2472
|
// forward meshAttributes so the pbr-skin path's vertex buffer layout
|
|
2553
2473
|
// is derived via the SSOT (deriveVertexBufferLayout). For URP/HDRP
|
|
@@ -2562,6 +2482,8 @@ async function makeWebGPURenderer(
|
|
|
2562
2482
|
shaderModuleMode,
|
|
2563
2483
|
layoutKindOverride,
|
|
2564
2484
|
additionalColorFormats,
|
|
2485
|
+
vertexEntry,
|
|
2486
|
+
fragmentEntry,
|
|
2565
2487
|
);
|
|
2566
2488
|
};
|
|
2567
2489
|
const getMaterialShaderPipelineEntry = (
|
|
@@ -3009,6 +2931,24 @@ async function makeWebGPURenderer(
|
|
|
3009
2931
|
featureDiagnostics: internals.featureHost?.diagnostics() ?? Object.freeze([]),
|
|
3010
2932
|
frustumStats: Object.freeze({ ...renderSystem.frustumStats }),
|
|
3011
2933
|
visibilityStats: Object.freeze({ ...renderSystem.visibilityStats }),
|
|
2934
|
+
instanceCollections: Object.freeze(
|
|
2935
|
+
renderSystem.instanceCollectionsInspection.map((collection) =>
|
|
2936
|
+
Object.freeze({
|
|
2937
|
+
...collection,
|
|
2938
|
+
uploadRanges: Object.freeze(
|
|
2939
|
+
collection.uploadRanges.map((range) => Object.freeze({ ...range })),
|
|
2940
|
+
),
|
|
2941
|
+
...(collection.error === undefined
|
|
2942
|
+
? {}
|
|
2943
|
+
: {
|
|
2944
|
+
error: Object.freeze({
|
|
2945
|
+
...collection.error,
|
|
2946
|
+
detail: Object.freeze({ ...collection.error.detail }),
|
|
2947
|
+
}),
|
|
2948
|
+
}),
|
|
2949
|
+
}),
|
|
2950
|
+
),
|
|
2951
|
+
),
|
|
3012
2952
|
renderScene: renderSystem.renderScene,
|
|
3013
2953
|
reflectionProbes: renderSystem.reflectionProbes,
|
|
3014
2954
|
ssrDependencies: renderSystem.ssrDependencies,
|