@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
package/src/record/main-pass.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type RhiRenderPassEncoder,
|
|
7
7
|
type TextureView,
|
|
8
8
|
} from '@forgeax/engine-rhi';
|
|
9
|
-
import type {
|
|
9
|
+
import type { PassKind, PassSelector } from '@forgeax/engine-types';
|
|
10
10
|
import { createHdrpUnifiedBindGroup, getOrCreateHdrpBuffers } from '../hdrp-buffers';
|
|
11
11
|
import { getOrCreateIblCache } from '../ibl/IblPipelineCache';
|
|
12
12
|
import type { SkylightBindGroupResources } from '../ibl/skylight-bind-group';
|
|
@@ -19,20 +19,17 @@ import { resolveReflectionProbeBinding } from './frame-lighting';
|
|
|
19
19
|
import { getOpaqueResourceIdentity } from './frame-snapshot';
|
|
20
20
|
import { recordGeometryDraws } from './main-pass-geometry';
|
|
21
21
|
import {
|
|
22
|
-
applyMaterialTextureUvScales,
|
|
23
|
-
applyParamSnapshotToUbo,
|
|
24
22
|
buildPerSubmeshMaterialBg as buildPerSubmeshMaterialBgImpl,
|
|
25
|
-
detectNineSliceScaleTooSmall,
|
|
26
23
|
type PerSubmeshMaterialBgDeps,
|
|
27
|
-
|
|
28
|
-
writePbrMaterialUboPayload,
|
|
24
|
+
prepareMaterialSkylight,
|
|
29
25
|
} from './main-pass-material';
|
|
30
26
|
import { recordSpritePass } from './main-pass-sprite-draws';
|
|
31
27
|
import type { _InternalRenderPipelineContext } from './render-context';
|
|
32
|
-
import {
|
|
28
|
+
import { STANDARD_PBR_UBO_SIZE } from './render-context';
|
|
33
29
|
import {
|
|
34
30
|
buildMatchedMaterialHandlesByRenderable,
|
|
35
31
|
buildMatchedRenderableIndices,
|
|
32
|
+
filterDispatchBySelector,
|
|
36
33
|
} from './shadow-pass';
|
|
37
34
|
|
|
38
35
|
export function standardReflectionProbeIndex(
|
|
@@ -41,8 +38,6 @@ export function standardReflectionProbeIndex(
|
|
|
41
38
|
return resolveReflectionProbeBinding(selection).probeIndex;
|
|
42
39
|
}
|
|
43
40
|
|
|
44
|
-
const ZERO_SKYLIGHT_PAYLOAD = new Float32Array([0, 0, 0, 0, 0, 0, 0, 1]);
|
|
45
|
-
|
|
46
41
|
function materialDiagnosticsEnabled(): boolean {
|
|
47
42
|
if (typeof globalThis !== 'object' || globalThis === null || !('process' in globalThis)) {
|
|
48
43
|
return false;
|
|
@@ -138,8 +133,6 @@ export function recordMainPass(
|
|
|
138
133
|
meshBindGroup,
|
|
139
134
|
frameState,
|
|
140
135
|
bindGroupCounts,
|
|
141
|
-
skylight,
|
|
142
|
-
skylightCount,
|
|
143
136
|
skyboxActive,
|
|
144
137
|
splitLdrSprite,
|
|
145
138
|
msaaActive,
|
|
@@ -289,6 +282,13 @@ export function recordMainPass(
|
|
|
289
282
|
const selectorMatchedMaterials =
|
|
290
283
|
selector !== undefined ? buildMatchedMaterialHandlesByRenderable(dispatch, selector) : null;
|
|
291
284
|
const matchedMaterials = matchedMaterialsForRecordMode(c, selectorMatchedMaterials, recordMode);
|
|
285
|
+
const selectedDispatch =
|
|
286
|
+
dispatch.length === 0
|
|
287
|
+
? undefined
|
|
288
|
+
: filterDispatchBySelector(
|
|
289
|
+
dispatch,
|
|
290
|
+
selector ?? { LightMode: [passKind === 'deferred' ? 'Deferred' : 'Forward'] },
|
|
291
|
+
);
|
|
292
292
|
const recordContext =
|
|
293
293
|
recordMode === undefined
|
|
294
294
|
? c
|
|
@@ -355,78 +355,7 @@ export function recordMainPass(
|
|
|
355
355
|
// (single `skylightResources` resolved once below); only the first 7
|
|
356
356
|
// material entries are rebuilt per-entity with the correct
|
|
357
357
|
// per-entity textureView at binding=2.
|
|
358
|
-
const
|
|
359
|
-
if (skylightFallback === null) {
|
|
360
|
-
throw new RhiError({
|
|
361
|
-
code: 'webgpu-runtime-error',
|
|
362
|
-
expected: 'pipelineState.skylightFallback != null when PBR pipeline is active',
|
|
363
|
-
hint: 'createRenderer must allocate skylightFallback alongside the PBR pipeline (D-5 round-4)',
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
// feat-20260520-skylight-ibl-cubemap M4 round-4 / t60 (D-5 round-4):
|
|
367
|
-
// select active vs fallback Skylight resources by `skylightCount` from
|
|
368
|
-
// the extract stage. Active path reaches into the per-device
|
|
369
|
-
// `IblPipelineCache` slots (irradianceView / prefilterView / brdfLutView)
|
|
370
|
-
// populated by the internal equirect-to-cubemap projection; fallback uses the
|
|
371
|
-
// 1x1-zero identity bundle that converges ambient to 0 (D-4 physical
|
|
372
|
-
// convergence -- no `if (hasSkylight)` shader branch).
|
|
373
|
-
// The samplers are reused from `skylightFallback.sampler` for both
|
|
374
|
-
// paths (linear / clamp-to-edge is correct for IBL cube + 2D LUT
|
|
375
|
-
// sampling either way). The intensity uniform is rewritten per-frame
|
|
376
|
-
// when active so `sampleIblSpecular * intensity` carries the user's
|
|
377
|
-
// Skylight.intensity value; fallback keeps intensity=0 (createSkylightFallback
|
|
378
|
-
// seed) so ambient = 0 even when the same buffer is shared.
|
|
379
|
-
let activeViews: { irr: TextureView; pref: TextureView; brdf: TextureView } | undefined;
|
|
380
|
-
// Per-frame Skylight uniform: std140 32 B = [intensity, colorR, colorG,
|
|
381
|
-
// colorB, rotation quaternion]. Default to all-zero so a transition from "has Skylight" ->
|
|
382
|
-
// "no Skylight" does not leak the prior frame's ambient (intensity 0
|
|
383
|
-
// muzzles everything, including the white fallback irradiance cube).
|
|
384
|
-
runtime.device.queue.writeBuffer(skylightFallback.intensityBuffer, 0, ZERO_SKYLIGHT_PAYLOAD);
|
|
385
|
-
if (skylight !== undefined && skylightCount >= 1) {
|
|
386
|
-
// A Skylight exists. Write its intensity + color regardless of whether
|
|
387
|
-
// a cubemap is bound: with a cubemap the IBL views below light the
|
|
388
|
-
// ambient; WITHOUT one, the white fallback irradiance cube + this color
|
|
389
|
-
// give an instant solid-color ambient (downstream integration #4) with
|
|
390
|
-
// no async precompute. The white fallback only contributes when a
|
|
391
|
-
// Skylight is present because the zero-payload above sets intensity 0
|
|
392
|
-
// when no Skylight exists.
|
|
393
|
-
const [cr, cg, cb] = skylight.color;
|
|
394
|
-
const [qx, qy, qz, qw] = skylight.rotation;
|
|
395
|
-
const uniformPayload = new Float32Array([skylight.intensity, cr, cg, cb, qx, qy, qz, qw]);
|
|
396
|
-
runtime.device.queue.writeBuffer(skylightFallback.intensityBuffer, 0, uniformPayload);
|
|
397
|
-
const cache = getOrCreateIblCache(runtime.deviceScope);
|
|
398
|
-
if (
|
|
399
|
-
cache.irradianceView !== undefined &&
|
|
400
|
-
cache.prefilterView !== undefined &&
|
|
401
|
-
cache.brdfLutView !== undefined
|
|
402
|
-
) {
|
|
403
|
-
activeViews = {
|
|
404
|
-
irr: cache.irradianceView,
|
|
405
|
-
pref: cache.prefilterView,
|
|
406
|
-
brdf: cache.brdfLutView,
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
const skylightResources =
|
|
411
|
-
activeViews !== undefined
|
|
412
|
-
? {
|
|
413
|
-
irradianceView: activeViews.irr,
|
|
414
|
-
irradianceSampler: skylightFallback.sampler,
|
|
415
|
-
prefilterView: activeViews.pref,
|
|
416
|
-
prefilterSampler: skylightFallback.sampler,
|
|
417
|
-
brdfLutView: activeViews.brdf,
|
|
418
|
-
brdfLutSampler: skylightFallback.sampler,
|
|
419
|
-
intensityBuffer: skylightFallback.intensityBuffer,
|
|
420
|
-
}
|
|
421
|
-
: {
|
|
422
|
-
irradianceView: skylightFallback.irradianceView,
|
|
423
|
-
irradianceSampler: skylightFallback.sampler,
|
|
424
|
-
prefilterView: skylightFallback.prefilterView,
|
|
425
|
-
prefilterSampler: skylightFallback.sampler,
|
|
426
|
-
brdfLutView: skylightFallback.brdfLutView,
|
|
427
|
-
brdfLutSampler: skylightFallback.sampler,
|
|
428
|
-
intensityBuffer: skylightFallback.intensityBuffer,
|
|
429
|
-
};
|
|
358
|
+
const { skylightResources, activeViews } = prepareMaterialSkylight(c);
|
|
430
359
|
if (materialDiagnosticsEnabled()) {
|
|
431
360
|
const activeCache =
|
|
432
361
|
activeViews === undefined ? undefined : getOrCreateIblCache(runtime.deviceScope);
|
|
@@ -438,7 +367,9 @@ export function recordMainPass(
|
|
|
438
367
|
deviceGeneration: runtime.deviceScope.generation,
|
|
439
368
|
active: activeViews === undefined ? 'fallback' : 'active',
|
|
440
369
|
cache: {
|
|
441
|
-
identity: getOpaqueResourceIdentity(
|
|
370
|
+
identity: getOpaqueResourceIdentity(
|
|
371
|
+
(activeCache ?? pipelineState.skylightFallback) as object,
|
|
372
|
+
),
|
|
442
373
|
generation: runtime.deviceScope.generation,
|
|
443
374
|
prefilterMipCount: prefilterViewMipCount,
|
|
444
375
|
prefilterViewMipCount,
|
|
@@ -563,173 +494,16 @@ export function recordMainPass(
|
|
|
563
494
|
submeshMaterial: MaterialSnapshot,
|
|
564
495
|
entityKey: number,
|
|
565
496
|
materialWorld: World = world,
|
|
497
|
+
materialShaderId: string | undefined = submeshMaterial.materialShaderId,
|
|
566
498
|
): BindGroup =>
|
|
567
499
|
buildPerSubmeshMaterialBgImpl(
|
|
568
500
|
perSubmeshMaterialBgDeps,
|
|
569
501
|
submeshMaterial,
|
|
570
502
|
entityKey,
|
|
571
503
|
materialWorld,
|
|
504
|
+
materialShaderId,
|
|
572
505
|
);
|
|
573
506
|
|
|
574
|
-
const cachedMaterialUboPayload = c.materialUboPayloadCache;
|
|
575
|
-
let materialUboPayload: Uint8Array;
|
|
576
|
-
if (
|
|
577
|
-
cachedMaterialUboPayload?.materialSlots === materialSlots &&
|
|
578
|
-
cachedMaterialUboPayload.materialSlotCount === materialSlotCount
|
|
579
|
-
) {
|
|
580
|
-
materialUboPayload = cachedMaterialUboPayload.payload;
|
|
581
|
-
} else {
|
|
582
|
-
materialUboPayload = new Uint8Array(materialSlotCount * MATERIAL_PER_ENTITY_STRIDE);
|
|
583
|
-
const slotPayload = new Uint8Array(STANDARD_PBR_UBO_SIZE);
|
|
584
|
-
const slotPayloadF32 = new Float32Array(
|
|
585
|
-
slotPayload.buffer,
|
|
586
|
-
slotPayload.byteOffset,
|
|
587
|
-
slotPayload.byteLength / 4,
|
|
588
|
-
);
|
|
589
|
-
for (let materialSlot = 0; materialSlot < materialSlots.length; materialSlot += 1) {
|
|
590
|
-
const mat = materialSlots[materialSlot];
|
|
591
|
-
const entry = validatedOrdered[materialSlotOwners[materialSlot] ?? -1];
|
|
592
|
-
if (mat === undefined || entry === undefined) continue;
|
|
593
|
-
|
|
594
|
-
// feat-20260625-refactor-sprite-as-transparent-mesh M3 / w13 (D-2):
|
|
595
|
-
// single unified Material UBO write path. Sprite materials now flow
|
|
596
|
-
// through the same `buildPbrMaterialUboPayload` baseline +
|
|
597
|
-
// `applyParamSnapshotToUbo` generic std140 overlay every other
|
|
598
|
-
// paramSchema-driven material uses. Extract folds the sprite-specific
|
|
599
|
-
// user inputs into the UBO-aligned paramSnapshot vec4 entries
|
|
600
|
-
// (colorTint / region / pivotAndSize / slicesAndMode); the writer
|
|
601
|
-
// walks `derive(paramSchema).uboLayout.entries` and writes each at
|
|
602
|
-
// its std140 offset. The legacy sprite-specific UBO builder + the
|
|
603
|
-
// sprite-vs-PBR branch are gone (AC-03).
|
|
604
|
-
writePbrMaterialUboPayload(slotPayloadF32, mat);
|
|
605
|
-
// Schema-driven paramSnapshot overlay generalised in feat-20260625
|
|
606
|
-
// M1 / w3: the writer walks `derive(paramSchema).uboLayout.entries`
|
|
607
|
-
// and writes each numeric field at its std140 offset (plan-strategy
|
|
608
|
-
// section 2 D-2). The engine's stock PBR material ships
|
|
609
|
-
// `paramSnapshot: undefined`, so this is a no-op on the default
|
|
610
|
-
// PBR path -- the explicit field writes in buildPbrMaterialUboPayload
|
|
611
|
-
// already cover every byte. User shaders carrying a paramSnapshot
|
|
612
|
-
// (including the post-ablation sprite path) get their fields
|
|
613
|
-
// written at the derive-computed offsets; R-H gate keeps the
|
|
614
|
-
// helper snapshot-only, no asset get.
|
|
615
|
-
const materialShaderId = mat.materialShaderId;
|
|
616
|
-
const schema =
|
|
617
|
-
mat.materialParamSchema ??
|
|
618
|
-
(materialShaderId !== undefined ? runtime.getParamSchema?.(materialShaderId) : undefined);
|
|
619
|
-
applyParamSnapshotToUbo(slotPayloadF32, schema, mat.paramSnapshot);
|
|
620
|
-
const processValue = (
|
|
621
|
-
globalThis as {
|
|
622
|
-
process?: { env?: Record<string, string | undefined> };
|
|
623
|
-
}
|
|
624
|
-
).process;
|
|
625
|
-
if (
|
|
626
|
-
processValue?.env?.FORGEAX_MATERIAL_PIPELINE_DIAGNOSTICS === '1' &&
|
|
627
|
-
(materialShaderId === 'forgeax::default-standard-pbr' ||
|
|
628
|
-
materialShaderId === 'forgeax::pbr-skin')
|
|
629
|
-
) {
|
|
630
|
-
console.error(
|
|
631
|
-
`[render-material] UBO receipt: ${JSON.stringify({
|
|
632
|
-
entityIndex: entry.renderableIndex,
|
|
633
|
-
materialHandle: mat.materialHandle,
|
|
634
|
-
materialShaderId,
|
|
635
|
-
clearcoat: slotPayloadF32[32],
|
|
636
|
-
clearcoatRoughness: slotPayloadF32[33],
|
|
637
|
-
clearcoatNormalScale: slotPayloadF32[34],
|
|
638
|
-
paramSnapshot: mat.paramSnapshot,
|
|
639
|
-
})}`,
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
applyMaterialTextureUvScales(slotPayloadF32, mat, world);
|
|
643
|
-
// Missing-texture detection: structural debug-pink fallback overrides
|
|
644
|
-
// the baseColor/colorTint slot when a bound baseColorTexture handle
|
|
645
|
-
// resolves to no GPU view. Runs for every textured material path
|
|
646
|
-
// (sprite / sprite-lit / standard-pbr / pbr-skin / unlit) — the bound
|
|
647
|
-
// texture would otherwise silently fall back to the 1x1 white view in
|
|
648
|
-
// the per-submesh BG (main-pass-material.ts), rendering flat with no
|
|
649
|
-
// warn / RhiError. Mirroring the telemetry here makes a missing/failed
|
|
650
|
-
// GLB texture immediately diagnosable instead of a silent flat render
|
|
651
|
-
// (feat-future-pbr-missing-texture-fallback-explicit; feedback
|
|
652
|
-
// 2026-07-04-glb-pbr-textures-not-applied-flat-render).
|
|
653
|
-
//
|
|
654
|
-
// Reads only `mat.baseColorTexture` + the GPU view registry (plan R-H
|
|
655
|
-
// gate: no asset.get<MaterialAsset> reach-back). The debug-pink write
|
|
656
|
-
// lands on f32[0..2], which is baseColor.rgb for the PBR/skin UBO and
|
|
657
|
-
// colorTint.rgb for the sprite UBO — same offset, so one override
|
|
658
|
-
// covers both.
|
|
659
|
-
{
|
|
660
|
-
const matHandleRaw = mat.baseColorTexture as Handle<'TextureAsset', 'shared'> | undefined;
|
|
661
|
-
if (matHandleRaw !== undefined) {
|
|
662
|
-
const view = residentTextureView(entry.world ?? world, store, runtime, matHandleRaw);
|
|
663
|
-
if (view === undefined) {
|
|
664
|
-
const rawId: number = matHandleRaw;
|
|
665
|
-
if (!frameState.warnedMissingBaseColorTextureHandles.has(rawId)) {
|
|
666
|
-
frameState.warnedMissingBaseColorTextureHandles.add(rawId);
|
|
667
|
-
console.warn(
|
|
668
|
-
`[forgeax] baseColor texture ${rawId} missing GPU view, rendering debug pink (shader=${materialShaderId ?? '<none>'} entityIndex=${entry.renderableIndex})`,
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
runtime.errorRegistry.fire(
|
|
672
|
-
new RhiError({
|
|
673
|
-
code: 'asset-not-registered',
|
|
674
|
-
expected: 'material baseColor TextureAsset uploaded to GPU',
|
|
675
|
-
hint: 'register + uploadTexture the baseColor texture before draw([world], { cameraOwner: 0, resourceOwner: 0 }); rendering falls back to debug pink until then',
|
|
676
|
-
detail: { assetHandle: rawId },
|
|
677
|
-
}),
|
|
678
|
-
);
|
|
679
|
-
// Debug pink override on slot 0 baseColor/colorTint.rgb (alpha preserved).
|
|
680
|
-
slotPayloadF32[0] = 1.0;
|
|
681
|
-
slotPayloadF32[1] = 0.4;
|
|
682
|
-
slotPayloadF32[2] = 0.7;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
materialUboPayload.set(slotPayload, materialSlot * MATERIAL_PER_ENTITY_STRIDE);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Material UBO slots can share one snapshot identity, but nine-slice
|
|
691
|
-
// validity depends on each entity's transform. Preserve diagnostics per
|
|
692
|
-
// renderable instead of inheriting the first owner of a shared slot.
|
|
693
|
-
for (const entry of validatedOrdered) {
|
|
694
|
-
const mat = entry.source.material;
|
|
695
|
-
const materialShaderId = mat.materialShaderId;
|
|
696
|
-
if (materialShaderId !== 'forgeax::sprite' && materialShaderId !== 'forgeax::sprite-lit')
|
|
697
|
-
continue;
|
|
698
|
-
const slicesAndMode = mat.paramSnapshot?.slicesAndMode as readonly number[] | undefined;
|
|
699
|
-
if (slicesAndMode === undefined || slicesAndMode.length < 4) continue;
|
|
700
|
-
const slicesArr: readonly [number, number, number, number] = [
|
|
701
|
-
slicesAndMode[0] ?? 0,
|
|
702
|
-
slicesAndMode[1] ?? 0,
|
|
703
|
-
slicesAndMode[2] ?? 0,
|
|
704
|
-
slicesAndMode[3] ?? 0,
|
|
705
|
-
];
|
|
706
|
-
if (slicesArr[0] === 0 && slicesArr[1] === 0 && slicesArr[2] === 0 && slicesArr[3] === 0)
|
|
707
|
-
continue;
|
|
708
|
-
detectNineSliceScaleTooSmall(
|
|
709
|
-
entry.source.transform.world,
|
|
710
|
-
slicesArr,
|
|
711
|
-
entry.renderableIndex,
|
|
712
|
-
frameState.warnedNineSliceScaleEntities,
|
|
713
|
-
runtime.metrics,
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
c.materialUboPayloadCache = {
|
|
717
|
-
materialSlots,
|
|
718
|
-
materialSlotCount,
|
|
719
|
-
payload: materialUboPayload,
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// All material slots are staged before the first geometry draw. One
|
|
724
|
-
// stride-shaped upload preserves every dynamic offset while avoiding one
|
|
725
|
-
// queue call per material; the RHI still validates alignment and bounds at
|
|
726
|
-
// this single owner-level write boundary.
|
|
727
|
-
const materialUboUpload = runtime.device.queue.writeBuffer(
|
|
728
|
-
pipelineState.materialUniformBuffer.buffer,
|
|
729
|
-
0,
|
|
730
|
-
materialUboPayload,
|
|
731
|
-
);
|
|
732
|
-
if (!materialUboUpload.ok) throw materialUboUpload.error;
|
|
733
507
|
// Static material resources are a property of the frame-local material
|
|
734
508
|
// slot, not of each submesh draw. Resolve them once here so the geometry
|
|
735
509
|
// loop only selects a prepared binding. Video fields remain entity-bound:
|
|
@@ -752,11 +526,13 @@ export function recordMainPass(
|
|
|
752
526
|
material: MaterialSnapshot,
|
|
753
527
|
entityKey: number,
|
|
754
528
|
materialWorld: World = world,
|
|
529
|
+
materialShaderId: string | undefined = material.materialShaderId,
|
|
755
530
|
): BindGroup =>
|
|
756
|
-
material.materialShaderId
|
|
531
|
+
materialShaderId === material.materialShaderId &&
|
|
532
|
+
materialShaderId !== POINTS_LINES_MATERIAL_SHADER_ID
|
|
757
533
|
? (preparedMaterialBindGroups[materialSlot] ??
|
|
758
|
-
buildPerSubmeshMaterialBg(material, entityKey, materialWorld))
|
|
759
|
-
: buildPerSubmeshMaterialBg(material, entityKey, materialWorld);
|
|
534
|
+
buildPerSubmeshMaterialBg(material, entityKey, materialWorld, materialShaderId))
|
|
535
|
+
: buildPerSubmeshMaterialBg(material, entityKey, materialWorld, materialShaderId);
|
|
760
536
|
|
|
761
537
|
pass.setBindGroup(0, viewBindGroup as BindGroup, [viewBindGroupDynamicOffset, 0]);
|
|
762
538
|
|
|
@@ -777,6 +553,7 @@ export function recordMainPass(
|
|
|
777
553
|
meshBindGroup,
|
|
778
554
|
resolveMaterialBindGroup,
|
|
779
555
|
passKind,
|
|
556
|
+
selectedDispatch,
|
|
780
557
|
);
|
|
781
558
|
};
|
|
782
559
|
if (c.profilePhase === undefined) {
|
|
@@ -808,6 +585,7 @@ export function recordMainPass(
|
|
|
808
585
|
resolveMaterialBindGroup,
|
|
809
586
|
skylightResources,
|
|
810
587
|
graphPass,
|
|
588
|
+
selectedDispatch,
|
|
811
589
|
);
|
|
812
590
|
}
|
|
813
591
|
} // end if (validatedOrdered.length > 0) -- Case E falls through to pass.end()
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { RhiError } from '@forgeax/engine-rhi';
|
|
2
|
+
import type { Handle } from '@forgeax/engine-types';
|
|
3
|
+
import {
|
|
4
|
+
applyMaterialTextureUvScales,
|
|
5
|
+
applyParamSnapshotToUbo,
|
|
6
|
+
detectNineSliceScaleTooSmall,
|
|
7
|
+
residentTextureView,
|
|
8
|
+
writePbrMaterialUboPayload,
|
|
9
|
+
} from './main-pass-material';
|
|
10
|
+
import type { _InternalRenderPipelineContext } from './render-context';
|
|
11
|
+
import { MATERIAL_PER_ENTITY_STRIDE, STANDARD_PBR_UBO_SIZE } from './render-context';
|
|
12
|
+
|
|
13
|
+
/** Stage the root parameter storage before any shadow or color pass consumes it. */
|
|
14
|
+
export function uploadMaterialUniforms(c: _InternalRenderPipelineContext): void {
|
|
15
|
+
const {
|
|
16
|
+
runtime,
|
|
17
|
+
world,
|
|
18
|
+
store,
|
|
19
|
+
pipelineState,
|
|
20
|
+
validatedOrdered,
|
|
21
|
+
frameState,
|
|
22
|
+
materialSlots,
|
|
23
|
+
materialSlotOwners,
|
|
24
|
+
materialSlotCount,
|
|
25
|
+
} = c;
|
|
26
|
+
if (materialSlotCount === 0) return;
|
|
27
|
+
const cachedMaterialUboPayload = c.materialUboPayloadCache;
|
|
28
|
+
let materialUboPayload: Uint8Array;
|
|
29
|
+
if (
|
|
30
|
+
cachedMaterialUboPayload?.materialSlots === materialSlots &&
|
|
31
|
+
cachedMaterialUboPayload.materialSlotCount === materialSlotCount
|
|
32
|
+
) {
|
|
33
|
+
materialUboPayload = cachedMaterialUboPayload.payload;
|
|
34
|
+
} else {
|
|
35
|
+
materialUboPayload = new Uint8Array(materialSlotCount * MATERIAL_PER_ENTITY_STRIDE);
|
|
36
|
+
const slotPayload = new Uint8Array(STANDARD_PBR_UBO_SIZE);
|
|
37
|
+
const slotPayloadF32 = new Float32Array(
|
|
38
|
+
slotPayload.buffer,
|
|
39
|
+
slotPayload.byteOffset,
|
|
40
|
+
slotPayload.byteLength / 4,
|
|
41
|
+
);
|
|
42
|
+
for (let materialSlot = 0; materialSlot < materialSlots.length; materialSlot += 1) {
|
|
43
|
+
const mat = materialSlots[materialSlot];
|
|
44
|
+
const entry = validatedOrdered[materialSlotOwners[materialSlot] ?? -1];
|
|
45
|
+
if (mat === undefined || entry === undefined) continue;
|
|
46
|
+
|
|
47
|
+
// feat-20260625-refactor-sprite-as-transparent-mesh M3 / w13 (D-2):
|
|
48
|
+
// single unified Material UBO write path. Sprite materials now flow
|
|
49
|
+
// through the same `buildPbrMaterialUboPayload` baseline +
|
|
50
|
+
// `applyParamSnapshotToUbo` generic std140 overlay every other
|
|
51
|
+
// paramSchema-driven material uses. Extract folds the sprite-specific
|
|
52
|
+
// user inputs into the UBO-aligned paramSnapshot vec4 entries
|
|
53
|
+
// (colorTint / region / pivotAndSize / slicesAndMode); the writer
|
|
54
|
+
// walks `derive(paramSchema).uboLayout.entries` and writes each at
|
|
55
|
+
// its std140 offset. The legacy sprite-specific UBO builder + the
|
|
56
|
+
// sprite-vs-PBR branch are gone (AC-03).
|
|
57
|
+
writePbrMaterialUboPayload(slotPayloadF32, mat);
|
|
58
|
+
// Schema-driven paramSnapshot overlay generalised in feat-20260625
|
|
59
|
+
// M1 / w3: the writer walks `derive(paramSchema).uboLayout.entries`
|
|
60
|
+
// and writes each numeric field at its std140 offset (plan-strategy
|
|
61
|
+
// section 2 D-2). The engine's stock PBR material ships
|
|
62
|
+
// `paramSnapshot: undefined`, so this is a no-op on the default
|
|
63
|
+
// PBR path -- the explicit field writes in buildPbrMaterialUboPayload
|
|
64
|
+
// already cover every byte. User shaders carrying a paramSnapshot
|
|
65
|
+
// (including the post-ablation sprite path) get their fields
|
|
66
|
+
// written at the derive-computed offsets; R-H gate keeps the
|
|
67
|
+
// helper snapshot-only, no asset get.
|
|
68
|
+
const materialShaderId = mat.materialShaderId;
|
|
69
|
+
const schema =
|
|
70
|
+
mat.materialParamSchema ??
|
|
71
|
+
(materialShaderId !== undefined ? runtime.getParamSchema?.(materialShaderId) : undefined);
|
|
72
|
+
applyParamSnapshotToUbo(slotPayloadF32, schema, mat.paramSnapshot);
|
|
73
|
+
const processValue = (
|
|
74
|
+
globalThis as {
|
|
75
|
+
process?: { env?: Record<string, string | undefined> };
|
|
76
|
+
}
|
|
77
|
+
).process;
|
|
78
|
+
if (
|
|
79
|
+
processValue?.env?.FORGEAX_MATERIAL_PIPELINE_DIAGNOSTICS === '1' &&
|
|
80
|
+
(materialShaderId === 'forgeax::default-standard-pbr' ||
|
|
81
|
+
materialShaderId === 'forgeax::pbr-skin')
|
|
82
|
+
) {
|
|
83
|
+
console.error(
|
|
84
|
+
`[render-material] UBO receipt: ${JSON.stringify({
|
|
85
|
+
entityIndex: entry.renderableIndex,
|
|
86
|
+
materialHandle: mat.materialHandle,
|
|
87
|
+
materialShaderId,
|
|
88
|
+
clearcoat: slotPayloadF32[32],
|
|
89
|
+
clearcoatRoughness: slotPayloadF32[33],
|
|
90
|
+
clearcoatNormalScale: slotPayloadF32[34],
|
|
91
|
+
paramSnapshot: mat.paramSnapshot,
|
|
92
|
+
})}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
applyMaterialTextureUvScales(slotPayloadF32, mat, world);
|
|
96
|
+
// Missing-texture detection: structural debug-pink fallback overrides
|
|
97
|
+
// the baseColor/colorTint slot when a bound baseColorTexture handle
|
|
98
|
+
// resolves to no GPU view. Runs for every textured material path
|
|
99
|
+
// (sprite / sprite-lit / standard-pbr / pbr-skin / unlit) — the bound
|
|
100
|
+
// texture would otherwise silently fall back to the 1x1 white view in
|
|
101
|
+
// the per-submesh BG (main-pass-material.ts), rendering flat with no
|
|
102
|
+
// warn / RhiError. Mirroring the telemetry here makes a missing/failed
|
|
103
|
+
// GLB texture immediately diagnosable instead of a silent flat render
|
|
104
|
+
// (feat-future-pbr-missing-texture-fallback-explicit; feedback
|
|
105
|
+
// 2026-07-04-glb-pbr-textures-not-applied-flat-render).
|
|
106
|
+
//
|
|
107
|
+
// Reads only `mat.baseColorTexture` + the GPU view registry (plan R-H
|
|
108
|
+
// gate: no asset.get<MaterialAsset> reach-back). The debug-pink write
|
|
109
|
+
// lands on f32[0..2], which is baseColor.rgb for the PBR/skin UBO and
|
|
110
|
+
// colorTint.rgb for the sprite UBO — same offset, so one override
|
|
111
|
+
// covers both.
|
|
112
|
+
{
|
|
113
|
+
const matHandleRaw = mat.baseColorTexture as Handle<'TextureAsset', 'shared'> | undefined;
|
|
114
|
+
if (matHandleRaw !== undefined) {
|
|
115
|
+
const view = residentTextureView(entry.world ?? world, store, runtime, matHandleRaw);
|
|
116
|
+
if (view === undefined) {
|
|
117
|
+
const rawId: number = matHandleRaw;
|
|
118
|
+
if (!frameState.warnedMissingBaseColorTextureHandles.has(rawId)) {
|
|
119
|
+
frameState.warnedMissingBaseColorTextureHandles.add(rawId);
|
|
120
|
+
console.warn(
|
|
121
|
+
`[forgeax] baseColor texture ${rawId} missing GPU view, rendering debug pink (shader=${materialShaderId ?? '<none>'} entityIndex=${entry.renderableIndex})`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
runtime.errorRegistry.fire(
|
|
125
|
+
new RhiError({
|
|
126
|
+
code: 'asset-not-registered',
|
|
127
|
+
expected: 'material baseColor TextureAsset uploaded to GPU',
|
|
128
|
+
hint: 'register + uploadTexture the baseColor texture before draw([world], { cameraOwner: 0, resourceOwner: 0 }); rendering falls back to debug pink until then',
|
|
129
|
+
detail: { assetHandle: rawId },
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
// Debug pink override on slot 0 baseColor/colorTint.rgb (alpha preserved).
|
|
133
|
+
slotPayloadF32[0] = 1.0;
|
|
134
|
+
slotPayloadF32[1] = 0.4;
|
|
135
|
+
slotPayloadF32[2] = 0.7;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
materialUboPayload.set(slotPayload, materialSlot * MATERIAL_PER_ENTITY_STRIDE);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Material UBO slots can share one snapshot identity, but nine-slice
|
|
144
|
+
// validity depends on each entity's transform. Preserve diagnostics per
|
|
145
|
+
// renderable instead of inheriting the first owner of a shared slot.
|
|
146
|
+
for (const entry of validatedOrdered) {
|
|
147
|
+
const mat = entry.source.material;
|
|
148
|
+
const materialShaderId = mat.materialShaderId;
|
|
149
|
+
if (materialShaderId !== 'forgeax::sprite' && materialShaderId !== 'forgeax::sprite-lit')
|
|
150
|
+
continue;
|
|
151
|
+
const slicesAndMode = mat.paramSnapshot?.slicesAndMode as readonly number[] | undefined;
|
|
152
|
+
if (slicesAndMode === undefined || slicesAndMode.length < 4) continue;
|
|
153
|
+
const slicesArr: readonly [number, number, number, number] = [
|
|
154
|
+
slicesAndMode[0] ?? 0,
|
|
155
|
+
slicesAndMode[1] ?? 0,
|
|
156
|
+
slicesAndMode[2] ?? 0,
|
|
157
|
+
slicesAndMode[3] ?? 0,
|
|
158
|
+
];
|
|
159
|
+
if (slicesArr[0] === 0 && slicesArr[1] === 0 && slicesArr[2] === 0 && slicesArr[3] === 0)
|
|
160
|
+
continue;
|
|
161
|
+
detectNineSliceScaleTooSmall(
|
|
162
|
+
entry.source.transform.world,
|
|
163
|
+
slicesArr,
|
|
164
|
+
entry.renderableIndex,
|
|
165
|
+
frameState.warnedNineSliceScaleEntities,
|
|
166
|
+
runtime.metrics,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
c.materialUboPayloadCache = {
|
|
170
|
+
materialSlots,
|
|
171
|
+
materialSlotCount,
|
|
172
|
+
payload: materialUboPayload,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// All material slots are staged before the first geometry draw. One
|
|
177
|
+
// stride-shaped upload preserves every dynamic offset while avoiding one
|
|
178
|
+
// queue call per material; the RHI still validates alignment and bounds at
|
|
179
|
+
// this single owner-level write boundary.
|
|
180
|
+
const materialUboUpload = runtime.device.queue.writeBuffer(
|
|
181
|
+
pipelineState.materialUniformBuffer.buffer,
|
|
182
|
+
0,
|
|
183
|
+
materialUboPayload,
|
|
184
|
+
);
|
|
185
|
+
if (!materialUboUpload.ok) throw materialUboUpload.error;
|
|
186
|
+
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type MaterialShaderArtifact,
|
|
27
27
|
} from '@forgeax/engine-shader';
|
|
28
28
|
import type {
|
|
29
|
+
MaterialAsset,
|
|
29
30
|
MaterialRenderState,
|
|
30
31
|
ParamSchemaEntry,
|
|
31
32
|
PassKind,
|
|
@@ -155,6 +156,9 @@ export interface RenderSystemRuntime {
|
|
|
155
156
|
readonly errorRegistry: RhiErrorListenerRegistry;
|
|
156
157
|
readonly debugOverlay?: RenderDebugOverlay | undefined;
|
|
157
158
|
readonly healthRegistry: HealthListenerRegistry;
|
|
159
|
+
readonly getMaterialProjectionForPayload?: (
|
|
160
|
+
material: MaterialAsset,
|
|
161
|
+
) => MaterialRenderProjection | undefined;
|
|
158
162
|
readonly getMaterialProjection?: (materialGuid: string) => MaterialRenderProjection | undefined;
|
|
159
163
|
readonly getMaterialArtifact?: (specializationKey: string) => MaterialRuntimeArtifact | undefined;
|
|
160
164
|
/** Producer-owned cooked Standard PBR artifact used by GPU-driven projection. */
|
|
@@ -179,6 +183,8 @@ export interface RenderSystemRuntime {
|
|
|
179
183
|
shaderModuleMode?: RenderFeatureShaderModuleMode,
|
|
180
184
|
layoutKindOverride?: 'pbr' | 'pbr-skin',
|
|
181
185
|
additionalColorFormats?: readonly GPUTextureFormat[],
|
|
186
|
+
vertexEntry?: string,
|
|
187
|
+
fragmentEntry?: string,
|
|
182
188
|
) => RhiRenderPipeline | null;
|
|
183
189
|
readonly getMaterialShaderPipelineEntry?: (
|
|
184
190
|
materialShaderId: string,
|
|
@@ -198,6 +204,8 @@ export interface RenderSystemRuntime {
|
|
|
198
204
|
shaderModuleMode?: RenderFeatureShaderModuleMode,
|
|
199
205
|
layoutKindOverride?: 'pbr' | 'pbr-skin',
|
|
200
206
|
additionalColorFormats?: readonly GPUTextureFormat[],
|
|
207
|
+
vertexEntry?: string,
|
|
208
|
+
fragmentEntry?: string,
|
|
201
209
|
) => MaterialShaderPipelineEntry | null;
|
|
202
210
|
/** Reflection-derived UV count included in the material PipelineSpec cache key. */
|
|
203
211
|
readonly getMaterialShaderUvSetCount?: (materialShaderId: string) => number | undefined;
|