@forgeax/engine-render 0.1.23 → 0.1.24
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 +131 -9
- package/dist/assembly/bundler-contract.d.ts +9 -0
- package/dist/assembly/bundler-contract.d.ts.map +1 -0
- package/dist/assembly/device-feature-admission.d.ts +8 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
- package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +7 -243
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +3 -2
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
- package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +168 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -0
- package/dist/assembly/material-shader-prewarm.d.ts +21 -0
- package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
- package/dist/assembly/recovery/generation.d.ts +46 -0
- package/dist/assembly/recovery/generation.d.ts.map +1 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts +10 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +19 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -0
- package/dist/assembly/renderer-lifecycle.d.ts +34 -0
- package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +55 -0
- package/dist/assembly/webgpu-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +152 -0
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
- package/dist/chunk-GE3SDD72.mjs.map +1 -0
- package/dist/{chunk-HZUPSYW7.mjs → chunk-KZFTK6DO.mjs} +102 -15
- package/dist/chunk-KZFTK6DO.mjs.map +1 -0
- package/dist/{chunk-FF7K3FSO.mjs → chunk-Q752SHI5.mjs} +89 -89
- package/dist/{chunk-FF7K3FSO.mjs.map → chunk-Q752SHI5.mjs.map} +1 -1
- package/dist/{chunk-QODA5UKU.mjs → chunk-RR5L4SRS.mjs} +2 -2
- package/dist/chunk-RR5L4SRS.mjs.map +1 -0
- package/dist/{chunk-YALPS44A.mjs → chunk-TFH5I7JJ.mjs} +47 -47
- package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
- package/dist/{chunk-FBC4R44J.mjs → chunk-UQYS7FRM.mjs} +57 -57
- package/dist/chunk-UQYS7FRM.mjs.map +1 -0
- package/dist/{chunk-4NPUASZR.mjs → chunk-YKB5DQV7.mjs} +161 -19
- package/dist/chunk-YKB5DQV7.mjs.map +1 -0
- package/dist/{chunk-ZUWKAZ2I.mjs → chunk-YRUYGA4A.mjs} +615 -237
- package/dist/chunk-YRUYGA4A.mjs.map +1 -0
- package/dist/{chunk-DIYW3M5D.mjs → chunk-Z3YYH2MB.mjs} +2210 -2213
- package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
- package/dist/construct-renderer.mjs +19841 -16190
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +2 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +24 -4
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/frame.d.ts +5 -0
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/inspection.d.ts +7 -0
- package/dist/environment/inspection.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +20 -2
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +58 -2
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +13 -0
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +899 -0
- package/dist/extract/contracts.d.ts.map +1 -0
- package/dist/features/host.d.ts +2 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +3 -0
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts +2 -0
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +28 -0
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +1 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +7 -6
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +7 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +6 -6
- package/dist/internal.mjs +6 -6
- package/dist/light-buffer-layout.d.ts +3 -0
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts +34 -10
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +56 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/occlusion-proxy.d.ts +4 -0
- package/dist/occlusion-proxy.d.ts.map +1 -0
- package/dist/pbr-pipeline.d.ts +32 -9
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/points-lines/prepare.d.ts +3 -0
- package/dist/points-lines/prepare.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +33 -0
- package/dist/points-lines/standard-owner.d.ts.map +1 -0
- package/dist/record/frame-snapshot.d.ts +5 -0
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +14 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +14 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -17
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +11 -3
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/recovery-pipeline.d.ts +30 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +26 -4
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +1 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +65 -0
- package/dist/recovery/render-system-candidate.d.ts.map +1 -0
- package/dist/render-contract.d.ts +64 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +12 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +156 -22
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +128 -4
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +19 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/taa-history-store.d.ts +15 -1
- package/dist/temporal/taa-history-store.d.ts.map +1 -1
- package/dist/temporal/transaction.d.ts +5 -0
- package/dist/temporal/transaction.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
- package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
- package/src/__tests__/environment-generation.integration.test.ts +23 -0
- package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
- package/src/__tests__/factory-contract.integration.test.ts +122 -7
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
- package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
- package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +24 -9
- package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
- package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
- package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
- package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
- package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +85 -18
- package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
- package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
- package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
- package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
- package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
- package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
- package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
- package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
- package/src/__tests__/shader-manifest-fixture.ts +13 -16
- package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
- package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
- package/src/assembly/bundler-contract.ts +9 -0
- package/src/assembly/device-feature-admission.ts +15 -5
- package/src/assembly/extended-lighting-fallback.ts +216 -0
- package/src/assembly/factory.ts +81 -10088
- package/src/assembly/material/assembly.ts +18 -2
- package/src/assembly/material/standard-layer-projection.ts +16 -0
- package/src/assembly/material-shader-policy.ts +854 -0
- package/src/assembly/material-shader-prewarm.ts +88 -0
- package/src/assembly/mesh-ssbo-grow.ts +270 -0
- package/src/assembly/recovery/device-loss-fanout.ts +89 -0
- package/src/assembly/recovery/generation.ts +141 -0
- package/src/assembly/recovery/recovery-attempt.ts +800 -0
- package/src/assembly/recovery/renderer-recover.ts +906 -0
- package/src/assembly/renderer-facade.ts +342 -0
- package/src/assembly/renderer-frame-transaction.ts +45 -0
- package/src/assembly/renderer-helpers.ts +99 -0
- package/src/assembly/renderer-lifecycle.ts +159 -0
- package/src/assembly/shader-prewarm-policy.ts +157 -0
- package/src/assembly/webgpu-ready-contract.ts +14 -0
- package/src/assembly/webgpu-ready.ts +4079 -0
- package/src/assembly/webgpu-renderer.ts +4095 -0
- package/src/components/camera.ts +1 -1
- package/src/debug-draw-glue.ts +1 -1
- package/src/device/device-scope.ts +35 -6
- package/src/device/gpu-residency.ts +194 -26
- package/src/environment/frame.ts +6 -0
- package/src/environment/inspection.ts +10 -0
- package/src/environment/lifecycle.ts +114 -8
- package/src/errors/recover.ts +76 -5
- package/src/errors/render.ts +17 -0
- package/src/extract/contracts.ts +974 -0
- package/src/features/host.ts +16 -0
- package/src/features/prepared-gpu-work.ts +20 -2
- package/src/features/prepared-graphics-store.ts +17 -0
- package/src/gpu-driven/production-raster.ts +97 -2
- package/src/gpu-scene.ts +3 -2
- package/src/ibl/IblPipelineCache.ts +134 -50
- package/src/ibl/skylight-bind-group.ts +26 -0
- package/src/index.ts +9 -1
- package/src/light-buffer-layout.ts +3 -0
- package/src/materials.ts +262 -45
- package/src/mesh-material-bindings.ts +59 -0
- package/src/occlusion-proxy.ts +64 -0
- package/src/pbr-pipeline.ts +139 -15
- package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
- package/src/points-lines/prepare.ts +9 -0
- package/src/points-lines/standard-owner.ts +403 -0
- package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
- package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
- package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
- package/src/record/frame-snapshot.ts +15 -0
- package/src/record/frame-targets.ts +11 -1
- package/src/record/frame.ts +6 -2
- package/src/record/helpers.ts +1 -1
- package/src/record/main-pass-geometry.ts +230 -5
- package/src/record/main-pass-material.ts +316 -209
- package/src/record/main-pass-sprite-draws.ts +6 -7
- package/src/record/main-pass.ts +94 -6
- package/src/record/mesh-ssbo.ts +32 -10
- package/src/record/recovery-pipeline.ts +695 -0
- package/src/record/render-context.ts +35 -4
- package/src/record/typed-frame-graph.ts +45 -1
- package/src/record/view-ubo.ts +3 -3
- package/src/recovery/render-system-candidate.ts +839 -0
- package/src/render-contract.ts +65 -7
- package/src/render-system-extract-tail.ts +2983 -0
- package/src/render-system-extract.ts +124 -2977
- package/src/render-system.ts +868 -659
- package/src/scene/render-scene.ts +124 -1
- package/src/temporal/frame-coordinator.ts +4 -1
- package/src/temporal/taa-history-store.ts +46 -2
- package/src/temporal/temporal-view.ts +1 -1
- package/src/temporal/transaction.ts +9 -0
- package/src/tilemap-chunk-extract-system.ts +1 -1
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
- package/dist/chunk-4L3P6DT5.mjs.map +0 -1
- package/dist/chunk-4NPUASZR.mjs.map +0 -1
- package/dist/chunk-DIYW3M5D.mjs.map +0 -1
- package/dist/chunk-FBC4R44J.mjs.map +0 -1
- package/dist/chunk-HZUPSYW7.mjs.map +0 -1
- package/dist/chunk-QODA5UKU.mjs.map +0 -1
- package/dist/chunk-YALPS44A.mjs.map +0 -1
- package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
import {
|
|
21
21
|
DEFAULT_MSDF_TEXT_PARAM_SCHEMA,
|
|
22
22
|
DEFAULT_SPRITE_PARAM_SCHEMA,
|
|
23
|
-
DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
|
|
24
23
|
DEFAULT_UNLIT_PARAM_SCHEMA,
|
|
24
|
+
STANDARD_PIPELINE_PARAM_SCHEMA,
|
|
25
25
|
} from '@forgeax/engine-shader';
|
|
26
26
|
import type {
|
|
27
27
|
Handle,
|
|
@@ -38,10 +38,16 @@ import { VideoUploadUnsupportedError } from '../errors/render';
|
|
|
38
38
|
import {
|
|
39
39
|
assembleMaterialWithSkylightEntries,
|
|
40
40
|
type SkylightBindGroupResources,
|
|
41
|
+
type TextureInjectionResource,
|
|
41
42
|
type TransmissionBindGroupResources,
|
|
42
43
|
} from '../ibl/skylight-bind-group';
|
|
44
|
+
import type { IblBindingEntryInspection } from '../mesh-material-bindings';
|
|
43
45
|
import type { PipelineGroup2Contract } from '../pbr-pipeline';
|
|
44
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
isCanonicalStandardPbrMaterialShader,
|
|
48
|
+
isStandardPbrMaterialShader,
|
|
49
|
+
physicalTextureFields,
|
|
50
|
+
} from '../pbr-pipeline';
|
|
45
51
|
import { deriveTextureExtent } from '../render-data';
|
|
46
52
|
import type { MaterialSnapshot } from '../render-system-extract';
|
|
47
53
|
import type { RenderTargetTextureSource } from '../targets/contracts';
|
|
@@ -50,7 +56,12 @@ import {
|
|
|
50
56
|
resolveRenderTargetMaterialSource,
|
|
51
57
|
} from '../targets/material-source';
|
|
52
58
|
import type { StandardReflectionProbeBinding } from './frame-lighting';
|
|
53
|
-
import type {
|
|
59
|
+
import type {
|
|
60
|
+
BindGroupCounts,
|
|
61
|
+
MaterialBgAssemblyCacheEntry,
|
|
62
|
+
RenderFrameState,
|
|
63
|
+
} from './frame-snapshot';
|
|
64
|
+
import { getOpaqueResourceIdentity } from './frame-snapshot';
|
|
54
65
|
import { extractEntryResourceHandle, getOrCreatePerEntity } from './mesh-ssbo';
|
|
55
66
|
import {
|
|
56
67
|
type PipelineState,
|
|
@@ -232,6 +243,18 @@ function isImageError(error: unknown): error is ImageError {
|
|
|
232
243
|
return typeof code === 'string' && code.startsWith('image-');
|
|
233
244
|
}
|
|
234
245
|
|
|
246
|
+
function materialDiagnosticsEnabled(): boolean {
|
|
247
|
+
if (typeof globalThis !== 'object' || globalThis === null || !('process' in globalThis)) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const processValue = (
|
|
251
|
+
globalThis as {
|
|
252
|
+
readonly process?: { readonly env?: Record<string, string | undefined> };
|
|
253
|
+
}
|
|
254
|
+
).process;
|
|
255
|
+
return processValue?.env?.FORGEAX_MATERIAL_DIAGNOSTICS === '1';
|
|
256
|
+
}
|
|
257
|
+
|
|
235
258
|
export function residentTextureView(
|
|
236
259
|
world: World,
|
|
237
260
|
store: GpuResidencyCache,
|
|
@@ -240,7 +263,15 @@ export function residentTextureView(
|
|
|
240
263
|
worldId: World | number = world,
|
|
241
264
|
): TextureView | undefined {
|
|
242
265
|
const podRes = resolveAssetHandle<TextureAsset>(world, handle);
|
|
243
|
-
|
|
266
|
+
const diagnosticsEnabled = materialDiagnosticsEnabled();
|
|
267
|
+
if (!podRes.ok) {
|
|
268
|
+
if (diagnosticsEnabled) {
|
|
269
|
+
console.error(
|
|
270
|
+
`[render-material] texture resolve failed: ${JSON.stringify({ handle: handleSlot(handle), error: podRes.error })}`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
244
275
|
const residentRes = store.ensureResident(handle, podRes.value, worldId);
|
|
245
276
|
if (!residentRes.ok) {
|
|
246
277
|
// Preserve the concrete asset-capability boundary on the renderer error
|
|
@@ -249,9 +280,41 @@ export function residentTextureView(
|
|
|
249
280
|
if (residentRes.error instanceof RhiError || isImageError(residentRes.error)) {
|
|
250
281
|
runtime.errorRegistry.fire(residentRes.error);
|
|
251
282
|
}
|
|
283
|
+
if (diagnosticsEnabled) {
|
|
284
|
+
console.error(
|
|
285
|
+
`[render-material] texture residency failed: ${JSON.stringify({
|
|
286
|
+
handle: handleSlot(handle),
|
|
287
|
+
pod: {
|
|
288
|
+
kind: podRes.value.kind,
|
|
289
|
+
shape: podRes.value.shape,
|
|
290
|
+
format: podRes.value.format,
|
|
291
|
+
dataByteLength: podRes.value.data.byteLength,
|
|
292
|
+
mips: podRes.value.mips,
|
|
293
|
+
},
|
|
294
|
+
error: residentRes.error,
|
|
295
|
+
})}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
252
298
|
return undefined;
|
|
253
299
|
}
|
|
254
|
-
|
|
300
|
+
const view = store.getTextureGpuView(handle, worldId);
|
|
301
|
+
if (diagnosticsEnabled) {
|
|
302
|
+
console.error(
|
|
303
|
+
`[render-material] texture residency ready: ${JSON.stringify({
|
|
304
|
+
handle: handleSlot(handle),
|
|
305
|
+
pod: {
|
|
306
|
+
kind: podRes.value.kind,
|
|
307
|
+
shape: podRes.value.shape,
|
|
308
|
+
format: podRes.value.format,
|
|
309
|
+
dataByteLength: podRes.value.data.byteLength,
|
|
310
|
+
mips: podRes.value.mips,
|
|
311
|
+
},
|
|
312
|
+
receipt: 'receipt' in residentRes.value ? residentRes.value.receipt : undefined,
|
|
313
|
+
viewReady: view !== undefined,
|
|
314
|
+
})}`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return view;
|
|
255
318
|
}
|
|
256
319
|
|
|
257
320
|
function residentSampler(
|
|
@@ -398,7 +461,6 @@ export const BUILTIN_USER_REGION_TEXTURE_FIELDS: readonly string[] = [
|
|
|
398
461
|
'baseColorTexture',
|
|
399
462
|
'metallicRoughnessTexture',
|
|
400
463
|
'normalTexture',
|
|
401
|
-
'specularTintTexture',
|
|
402
464
|
'emissiveTexture',
|
|
403
465
|
'occlusionTexture',
|
|
404
466
|
'transmissionTexture',
|
|
@@ -406,21 +468,6 @@ export const BUILTIN_USER_REGION_TEXTURE_FIELDS: readonly string[] = [
|
|
|
406
468
|
];
|
|
407
469
|
|
|
408
470
|
const LEGACY_MATERIAL_TEXTURE_SCALE_OFFSET = 80;
|
|
409
|
-
// The Standard schema owns five transmission numeric fields between
|
|
410
|
-
// normalScale and the engine-owned coordinate records. Keep this fallback
|
|
411
|
-
// aligned with derive(DEFAULT_STANDARD_PBR_PARAM_SCHEMA): the first record is
|
|
412
|
-
// at byte 128, not the pre-transmission byte 96.
|
|
413
|
-
const STANDARD_PBR_TEXTURE_SCALE_OFFSET = 128;
|
|
414
|
-
const MATERIAL_TEXTURE_SCALE_FIELDS = [
|
|
415
|
-
'baseColorTexture',
|
|
416
|
-
'metallicRoughnessTexture',
|
|
417
|
-
'normalTexture',
|
|
418
|
-
'specularTintTexture',
|
|
419
|
-
'emissiveTexture',
|
|
420
|
-
'occlusionTexture',
|
|
421
|
-
'transmissionTexture',
|
|
422
|
-
'thicknessTexture',
|
|
423
|
-
] as const;
|
|
424
471
|
const LEGACY_MATERIAL_TEXTURE_SCALE_FIELDS = [
|
|
425
472
|
'baseColorTexture',
|
|
426
473
|
'metallicRoughnessTexture',
|
|
@@ -428,13 +475,7 @@ const LEGACY_MATERIAL_TEXTURE_SCALE_FIELDS = [
|
|
|
428
475
|
'emissiveTexture',
|
|
429
476
|
'occlusionTexture',
|
|
430
477
|
] as const;
|
|
431
|
-
const STANDARD_PBR_TEXTURE_COORDINATE_OFFSET = 128;
|
|
432
|
-
const STANDARD_PBR_TEXTURE_COORDINATE_STRIDE = 8;
|
|
433
478
|
const DEFAULT_LEGACY_TEXTURE_SCALES = new Float32Array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
|
434
|
-
const DEFAULT_STANDARD_TEXTURE_COORDINATES = new Float32Array([
|
|
435
|
-
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,
|
|
436
|
-
0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
|
|
437
|
-
]);
|
|
438
479
|
|
|
439
480
|
export interface StandardReflectionProbeMaterialProjection {
|
|
440
481
|
readonly probeIndex: number | undefined;
|
|
@@ -536,13 +577,10 @@ export function applyMaterialTextureUvScales(
|
|
|
536
577
|
}
|
|
537
578
|
return;
|
|
538
579
|
}
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
const textureScaleOffset =
|
|
543
|
-
? STANDARD_PBR_TEXTURE_SCALE_OFFSET
|
|
544
|
-
: LEGACY_MATERIAL_TEXTURE_SCALE_OFFSET;
|
|
545
|
-
const isStandardPbr = textureScaleOffset === STANDARD_PBR_TEXTURE_SCALE_OFFSET;
|
|
580
|
+
// Unknown legacy material paths retain their historical packed scale tail.
|
|
581
|
+
// Every registered Standard/material-schema path returns above and is
|
|
582
|
+
// therefore fully schema-driven (including physical texture coordinates).
|
|
583
|
+
const textureScaleOffset = LEGACY_MATERIAL_TEXTURE_SCALE_OFFSET;
|
|
546
584
|
const hasTextureMetadata =
|
|
547
585
|
material.textureCoordinates !== undefined ||
|
|
548
586
|
material.textureHandles !== undefined ||
|
|
@@ -553,15 +591,10 @@ export function applyMaterialTextureUvScales(
|
|
|
553
591
|
material.emissiveTexture !== undefined ||
|
|
554
592
|
material.occlusionTexture !== undefined;
|
|
555
593
|
if (!hasTextureMetadata) {
|
|
556
|
-
f32.set(
|
|
557
|
-
isStandardPbr ? DEFAULT_STANDARD_TEXTURE_COORDINATES : DEFAULT_LEGACY_TEXTURE_SCALES,
|
|
558
|
-
textureScaleOffset / 4,
|
|
559
|
-
);
|
|
594
|
+
f32.set(DEFAULT_LEGACY_TEXTURE_SCALES, textureScaleOffset / 4);
|
|
560
595
|
return;
|
|
561
596
|
}
|
|
562
|
-
const fields =
|
|
563
|
-
? MATERIAL_TEXTURE_SCALE_FIELDS
|
|
564
|
-
: LEGACY_MATERIAL_TEXTURE_SCALE_FIELDS;
|
|
597
|
+
const fields = LEGACY_MATERIAL_TEXTURE_SCALE_FIELDS;
|
|
565
598
|
for (let index = 0; index < fields.length; index++) {
|
|
566
599
|
const field = fields[index];
|
|
567
600
|
if (field === undefined) continue;
|
|
@@ -570,39 +603,19 @@ export function applyMaterialTextureUvScales(
|
|
|
570
603
|
handle === undefined ? undefined : resolveAssetHandle<TextureAsset>(world, handle);
|
|
571
604
|
const texture = resolved?.ok === true ? resolved.value : undefined;
|
|
572
605
|
const [u, v] = materialTextureUvScale(texture);
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const offset =
|
|
577
|
-
STANDARD_PBR_TEXTURE_COORDINATE_OFFSET / 4 + index * STANDARD_PBR_TEXTURE_COORDINATE_STRIDE;
|
|
578
|
-
f32[offset] = transform?.offset?.[0] ?? 0;
|
|
579
|
-
f32[offset + 1] = transform?.offset?.[1] ?? 0;
|
|
580
|
-
f32[offset + 2] = transform?.scale?.[0] ?? 1;
|
|
581
|
-
f32[offset + 3] = transform?.scale?.[1] ?? 1;
|
|
582
|
-
f32[offset + 4] = coordinates?.set ?? 0;
|
|
583
|
-
f32[offset + 5] = transform?.rotation ?? 0;
|
|
584
|
-
f32[offset + 6] = u;
|
|
585
|
-
f32[offset + 7] = v;
|
|
586
|
-
} else {
|
|
587
|
-
const offset = textureScaleOffset / 4 + index * 2;
|
|
588
|
-
f32[offset] = u;
|
|
589
|
-
f32[offset + 1] = v;
|
|
590
|
-
}
|
|
606
|
+
const offset = textureScaleOffset / 4 + index * 2;
|
|
607
|
+
f32[offset] = u;
|
|
608
|
+
f32[offset + 1] = v;
|
|
591
609
|
}
|
|
592
610
|
}
|
|
593
611
|
|
|
594
612
|
function materialCoordinateSchema(
|
|
595
613
|
material: MaterialSnapshot,
|
|
596
614
|
): readonly ParamSchemaEntry[] | undefined {
|
|
597
|
-
if (
|
|
598
|
-
isStandardPbrMaterialShader(material.materialShaderId) &&
|
|
599
|
-
material.paramSnapshot !== undefined
|
|
600
|
-
) {
|
|
601
|
-
return DEFAULT_STANDARD_PBR_PARAM_SCHEMA;
|
|
602
|
-
}
|
|
603
615
|
if (material.materialParamSchema !== undefined && material.materialParamSchema.length > 0) {
|
|
604
616
|
return material.materialParamSchema;
|
|
605
617
|
}
|
|
618
|
+
if (isStandardPbrMaterialShader(material.materialShaderId)) return STANDARD_PIPELINE_PARAM_SCHEMA;
|
|
606
619
|
switch (material.materialShaderId) {
|
|
607
620
|
case 'forgeax::default-unlit':
|
|
608
621
|
return DEFAULT_UNLIT_PARAM_SCHEMA;
|
|
@@ -652,6 +665,13 @@ export function defaultViewForUserRegionField(
|
|
|
652
665
|
}
|
|
653
666
|
if (field === 'normalTexture') return pipelineState.defaultNormalTextureView;
|
|
654
667
|
if (field === 'baseColorTexture') return pipelineState.fallbackTextureView;
|
|
668
|
+
if (field === 'anisotropyTexture') {
|
|
669
|
+
// The flat-normal fallback encodes a zero tangent direction and unit B
|
|
670
|
+
// strength; the shader's zero-vector guard selects its neutral (1, 0)
|
|
671
|
+
// axis. This keeps an absent anisotropy map neutral even on hosts that do
|
|
672
|
+
// not allocate the optional dedicated identity texture.
|
|
673
|
+
return pipelineState.defaultAnisotropyTextureView ?? pipelineState.defaultNormalTextureView;
|
|
674
|
+
}
|
|
655
675
|
return pipelineState.defaultWhiteTextureView;
|
|
656
676
|
}
|
|
657
677
|
|
|
@@ -698,17 +718,7 @@ export function detectNineSliceScaleTooSmall(
|
|
|
698
718
|
}
|
|
699
719
|
}
|
|
700
720
|
|
|
701
|
-
/**
|
|
702
|
-
* Build the 384-byte Material UBO payload for a PBR / unlit material entry
|
|
703
|
-
* (feat-20260527-sprite-nineslice M2 / w11; D-7 regression-net helper).
|
|
704
|
-
*
|
|
705
|
-
* Byte-for-byte equivalent to the legacy hard-coded PBR write path; each
|
|
706
|
-
* deviation is caught by `render-system-record-pbr-ubo-stable.test.ts`.
|
|
707
|
-
* The schema-driven paramSnapshot overlay (feat-20260523 M9-T05; AC-14)
|
|
708
|
-
* positions vec4 / f32 slots onto std140 [slot0 vec4, slot1 f32 metallic,
|
|
709
|
-
* slot1 f32 roughness] mirroring the engine-shipped default-standard-pbr
|
|
710
|
-
* Material struct.
|
|
711
|
-
*/
|
|
721
|
+
/** Build one max-sized, schema-driven Standard material UBO payload. */
|
|
712
722
|
export function buildPbrMaterialUboPayload(material: MaterialSnapshot): Uint8Array {
|
|
713
723
|
const buf = new Uint8Array(STANDARD_PBR_UBO_SIZE);
|
|
714
724
|
writePbrMaterialUboPayload(buf, material);
|
|
@@ -718,11 +728,10 @@ export function buildPbrMaterialUboPayload(material: MaterialSnapshot): Uint8Arr
|
|
|
718
728
|
/**
|
|
719
729
|
* Write the standard material payload into caller-owned storage.
|
|
720
730
|
*
|
|
721
|
-
* The allocating wrapper above remains the
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
* changing GPU-visible bytes.
|
|
731
|
+
* The allocating wrapper above remains the helper/test surface, while the
|
|
732
|
+
* frame recorder reuses one scratch slot for all materials. Field offsets are
|
|
733
|
+
* read from the material's admitted schema; no Standard field offset table is
|
|
734
|
+
* maintained in the record stage.
|
|
726
735
|
*/
|
|
727
736
|
export function writePbrMaterialUboPayload(
|
|
728
737
|
buf: Uint8Array | Float32Array,
|
|
@@ -735,114 +744,52 @@ export function writePbrMaterialUboPayload(
|
|
|
735
744
|
}
|
|
736
745
|
buf.fill(0);
|
|
737
746
|
const f32 = materialUboFloatView(buf);
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
// channelMap default = (B, G, R, _) per AGENTS.md + glTF 2.0
|
|
770
|
-
// KHR_materials_pbrSpecularGlossiness ARM packing -- now 4 independent
|
|
771
|
-
// f32 selectors; fragment casts each to u32 at the pick site.
|
|
772
|
-
f32[6] = 2; // metallicChannel <- B
|
|
773
|
-
f32[7] = 1; // roughnessChannel <- G
|
|
774
|
-
f32[8] = 0; // aoChannel <- R
|
|
775
|
-
f32[9] = 0; // extraChannel <- reserved
|
|
776
|
-
// emissive vec3 + emissiveIntensity (offset 48..63)
|
|
777
|
-
const em = material.emissive;
|
|
778
|
-
f32[12] = em?.[0] ?? 0;
|
|
779
|
-
f32[13] = em?.[1] ?? 0;
|
|
780
|
-
f32[14] = em?.[2] ?? 0;
|
|
781
|
-
f32[15] = material.emissiveIntensity ?? 0;
|
|
782
|
-
// occlusionStrength (offset 64)
|
|
783
|
-
f32[16] = material.occlusionStrength ?? 1;
|
|
784
|
-
// alphaCutoff (offset 68).
|
|
785
|
-
const alphaCutoff = material.paramSnapshot?.alphaCutoff;
|
|
786
|
-
f32[17] = typeof alphaCutoff === 'number' ? alphaCutoff : 0;
|
|
787
|
-
f32[18] = material.clearcoat ?? 0;
|
|
788
|
-
f32[19] = material.clearcoatRoughness ?? 0.5;
|
|
789
|
-
const specularTint = material.specularTint;
|
|
790
|
-
f32[20] = specularTint?.[0] ?? 1;
|
|
791
|
-
f32[21] = specularTint?.[1] ?? 1;
|
|
792
|
-
f32[22] = specularTint?.[2] ?? 1;
|
|
793
|
-
const snapshotNumber = (name: string, fallback: number): number => {
|
|
794
|
-
const value = material.paramSnapshot?.[name];
|
|
795
|
-
return typeof value === 'number' ? value : fallback;
|
|
796
|
-
};
|
|
797
|
-
const snapshotVec3 = (
|
|
798
|
-
name: string,
|
|
799
|
-
fallback: readonly [number, number, number],
|
|
800
|
-
): readonly [number, number, number] => {
|
|
801
|
-
const value = material.paramSnapshot?.[name];
|
|
802
|
-
return Array.isArray(value)
|
|
803
|
-
? [value[0] ?? fallback[0], value[1] ?? fallback[1], value[2] ?? fallback[2]]
|
|
804
|
-
: fallback;
|
|
747
|
+
const schema =
|
|
748
|
+
material.materialParamSchema === undefined || material.materialParamSchema.length === 0
|
|
749
|
+
? STANDARD_PIPELINE_PARAM_SCHEMA
|
|
750
|
+
: material.materialParamSchema;
|
|
751
|
+
const snapshot = material.paramSnapshot;
|
|
752
|
+
const fallback = (name: string, entry: (typeof schema)[number]): unknown => {
|
|
753
|
+
switch (name) {
|
|
754
|
+
case 'baseColor':
|
|
755
|
+
return [
|
|
756
|
+
material.baseColor[0] ?? 1,
|
|
757
|
+
material.baseColor[1] ?? 1,
|
|
758
|
+
material.baseColor[2] ?? 1,
|
|
759
|
+
1,
|
|
760
|
+
];
|
|
761
|
+
case 'metallic':
|
|
762
|
+
return material.metallic;
|
|
763
|
+
case 'roughness':
|
|
764
|
+
return material.roughness;
|
|
765
|
+
case 'specularColor':
|
|
766
|
+
return material.specularColor ?? [1, 1, 1];
|
|
767
|
+
case 'normalScale':
|
|
768
|
+
return material.normalScale ?? 1;
|
|
769
|
+
case 'emissive':
|
|
770
|
+
return material.emissive ?? [0, 0, 0];
|
|
771
|
+
case 'emissiveIntensity':
|
|
772
|
+
return material.emissiveIntensity ?? 0;
|
|
773
|
+
case 'occlusionStrength':
|
|
774
|
+
return material.occlusionStrength ?? 1;
|
|
775
|
+
default:
|
|
776
|
+
return entry.default;
|
|
777
|
+
}
|
|
805
778
|
};
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
// engine's stock PBR shader (charter P4 + R-H regression fence).
|
|
821
|
-
const paramSnap = material.paramSnapshot;
|
|
822
|
-
if (paramSnap !== undefined) {
|
|
823
|
-
const f32Snap = (name: string): number | undefined => {
|
|
824
|
-
const v = paramSnap[name];
|
|
825
|
-
return typeof v === 'number' ? v : undefined;
|
|
826
|
-
};
|
|
827
|
-
const colorSnap = (name: string): readonly number[] | undefined => {
|
|
828
|
-
const v = paramSnap[name];
|
|
829
|
-
return Array.isArray(v) && v.every((x) => typeof x === 'number')
|
|
830
|
-
? (v as readonly number[])
|
|
831
|
-
: undefined;
|
|
832
|
-
};
|
|
833
|
-
// Walk schema in declared order, fill positional UBO slots.
|
|
834
|
-
const materialShaderId = material.materialShaderId;
|
|
835
|
-
// Note: schema lookup is the caller's job (it owns the runtime ref);
|
|
836
|
-
// when the helper has no access to runtime, paramSnap is consumed
|
|
837
|
-
// positionally without a schema walk. The inline caller (recordFrame)
|
|
838
|
-
// still performs the schema-driven walk and writes the overlay before
|
|
839
|
-
// queueing this payload, so this helper is only the byte-stable
|
|
840
|
-
// baseline. To preserve byte-for-byte equivalence in the helper-only
|
|
841
|
-
// unit-test path, the overlay pass is skipped when the helper is
|
|
842
|
-
// called without an external schema reference.
|
|
843
|
-
void materialShaderId;
|
|
844
|
-
void f32Snap;
|
|
845
|
-
void colorSnap;
|
|
779
|
+
for (const entry of derivedParamSchema(schema).uboLayout.entries) {
|
|
780
|
+
const value =
|
|
781
|
+
snapshot?.[entry.name] ??
|
|
782
|
+
fallback(entry.name, schema.find((item) => item.name === entry.name) ?? entry);
|
|
783
|
+
const offset = entry.offset / 4;
|
|
784
|
+
const width = entry.size / 4;
|
|
785
|
+
if (typeof value === 'number') {
|
|
786
|
+
f32[offset] = value;
|
|
787
|
+
} else if (Array.isArray(value)) {
|
|
788
|
+
for (let index = 0; index < Math.min(width, value.length); index += 1) {
|
|
789
|
+
const component = value[index];
|
|
790
|
+
if (typeof component === 'number') f32[offset + index] = component;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
846
793
|
}
|
|
847
794
|
}
|
|
848
795
|
|
|
@@ -891,13 +838,7 @@ export function applyParamSnapshotToUbo(
|
|
|
891
838
|
if (paramSchema === undefined) return;
|
|
892
839
|
if (paramSnapshot === undefined) return;
|
|
893
840
|
const f32 = materialUboFloatView(payload);
|
|
894
|
-
const
|
|
895
|
-
Object.hasOwn(paramSnapshot, 'transmission') &&
|
|
896
|
-
Object.hasOwn(paramSnapshot, 'ior') &&
|
|
897
|
-
Object.hasOwn(paramSnapshot, 'thickness')
|
|
898
|
-
? DEFAULT_STANDARD_PBR_PARAM_SCHEMA
|
|
899
|
-
: paramSchema;
|
|
900
|
-
const { uboLayout } = derivedParamSchema(effectiveSchema);
|
|
841
|
+
const { uboLayout } = derivedParamSchema(paramSchema);
|
|
901
842
|
for (const entry of uboLayout.entries) {
|
|
902
843
|
const value = paramSnapshot[entry.name];
|
|
903
844
|
if (value === undefined) continue;
|
|
@@ -962,6 +903,46 @@ export interface PerSubmeshMaterialBgDeps {
|
|
|
962
903
|
/** Active transmission backdrop; undefined/null means layout placeholder. */
|
|
963
904
|
readonly transmissionBackdropView?: TextureView | null;
|
|
964
905
|
readonly bindGroupCounts: BindGroupCounts;
|
|
906
|
+
readonly frameState: RenderFrameState;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function recordIblMaterialBinding(
|
|
910
|
+
frameState: RenderFrameState,
|
|
911
|
+
materialBgl: BindGroupLayout,
|
|
912
|
+
bindGroup: BindGroup,
|
|
913
|
+
entries: readonly BindGroupEntry[],
|
|
914
|
+
iblStart: number,
|
|
915
|
+
cache: 'hit' | 'miss',
|
|
916
|
+
): void {
|
|
917
|
+
const receipt = frameState.iblBindingInspection;
|
|
918
|
+
if (receipt === undefined) return;
|
|
919
|
+
const skylightBindingStart = entries.findIndex((entry) => entry.binding === iblStart);
|
|
920
|
+
if (skylightBindingStart < 0) return;
|
|
921
|
+
const iblEntries = entries.slice(skylightBindingStart, skylightBindingStart + 7);
|
|
922
|
+
const projected: IblBindingEntryInspection[] = [];
|
|
923
|
+
for (const entry of iblEntries) {
|
|
924
|
+
if (entry.resource.kind === 'externalTexture') continue;
|
|
925
|
+
const value =
|
|
926
|
+
entry.resource.kind === 'buffer' ? entry.resource.value.buffer : entry.resource.value;
|
|
927
|
+
if (typeof value !== 'object' || value === null) continue;
|
|
928
|
+
projected.push({
|
|
929
|
+
binding: entry.binding,
|
|
930
|
+
kind: entry.resource.kind,
|
|
931
|
+
resourceIdentity: getOpaqueResourceIdentity(value as object),
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
frameState.iblBindingInspection = {
|
|
935
|
+
...receipt,
|
|
936
|
+
material: {
|
|
937
|
+
bindGroupIdentity: getOpaqueResourceIdentity(bindGroup as object),
|
|
938
|
+
materialBglIdentity: getOpaqueResourceIdentity(materialBgl as object),
|
|
939
|
+
cache,
|
|
940
|
+
skylightBindingStart: iblEntries[0]?.binding ?? 0,
|
|
941
|
+
entries: projected,
|
|
942
|
+
reflectionBindings: projected.map((entry) => entry.binding),
|
|
943
|
+
},
|
|
944
|
+
errors: [],
|
|
945
|
+
};
|
|
965
946
|
}
|
|
966
947
|
|
|
967
948
|
/** @internal Exported only so buffer-generation invalidation stays regression-tested. */
|
|
@@ -1009,6 +990,7 @@ export function buildPerSubmeshMaterialBg(
|
|
|
1009
990
|
materialBgAssemblyCache,
|
|
1010
991
|
transmissionBackdropView,
|
|
1011
992
|
bindGroupCounts,
|
|
993
|
+
frameState,
|
|
1012
994
|
} = deps;
|
|
1013
995
|
const effectiveSkylightResources =
|
|
1014
996
|
resolveReflectionProbeResources?.(materialWorld, entityKey) ?? skylightResources;
|
|
@@ -1017,10 +999,31 @@ export function buildPerSubmeshMaterialBg(
|
|
|
1017
999
|
smMaterialHandle === undefined ? undefined : `${materialWorld.identity}:${smMaterialHandle}`;
|
|
1018
1000
|
const smHasVideoFields = (submeshMaterial.videoTextureFields?.size ?? 0) > 0;
|
|
1019
1001
|
const smShaderId = submeshMaterial.materialShaderId;
|
|
1002
|
+
// The cooked shader schema owns the resource ABI. A material snapshot may
|
|
1003
|
+
// carry only the authored values (for example a clearcoat-only root omits
|
|
1004
|
+
// the standard emissive/occlusion maps), while the Standard template still
|
|
1005
|
+
// declares those slots and expects neutral fallbacks to be bound.
|
|
1006
|
+
const smSchema =
|
|
1007
|
+
(smShaderId !== undefined ? runtime.getParamSchema?.(smShaderId) : undefined) ??
|
|
1008
|
+
submeshMaterial.materialParamSchema;
|
|
1020
1009
|
const smPerShaderBgl =
|
|
1021
|
-
smShaderId !== undefined
|
|
1010
|
+
smShaderId !== undefined
|
|
1011
|
+
? runtime.getMaterialBindGroupLayout?.(smShaderId, smSchema)
|
|
1012
|
+
: undefined;
|
|
1022
1013
|
const smMaterialBgl = smPerShaderBgl ?? pipelineState.materialBindGroupLayout;
|
|
1014
|
+
const standardMaterial = isStandardPbrMaterialShader(smShaderId);
|
|
1015
|
+
const physicalFields =
|
|
1016
|
+
standardMaterial && smSchema !== undefined ? physicalTextureFields(smSchema) : [];
|
|
1017
|
+
// Standard's built-in shader owns the canonical seven-pair user region even
|
|
1018
|
+
// when the authored root only declares a subset. Physical maps are appended
|
|
1019
|
+
// after the reserved IBL/transmission tail, so compacting this list would
|
|
1020
|
+
// shift the shader's IBL bindings and invalidate the render pipeline.
|
|
1021
|
+
const smUserRegionFields =
|
|
1022
|
+
smPerShaderBgl === undefined || isCanonicalStandardPbrMaterialShader(smShaderId)
|
|
1023
|
+
? BUILTIN_USER_REGION_TEXTURE_FIELDS
|
|
1024
|
+
: userRegionTextureFieldOrder(smSchema).filter((field) => !physicalFields.includes(field));
|
|
1023
1025
|
const hasActiveTransmissionBackdrop = transmissionBackdropView != null;
|
|
1026
|
+
const diagnosticsEnabled = materialDiagnosticsEnabled();
|
|
1024
1027
|
if (smMaterialHandle !== undefined && !smHasVideoFields && !hasActiveTransmissionBackdrop) {
|
|
1025
1028
|
const cached =
|
|
1026
1029
|
materialCacheKey === undefined ? undefined : materialBgAssemblyCache.get(materialCacheKey);
|
|
@@ -1035,21 +1038,48 @@ export function buildPerSubmeshMaterialBg(
|
|
|
1035
1038
|
transmissionBackdropView,
|
|
1036
1039
|
)
|
|
1037
1040
|
) {
|
|
1041
|
+
if (diagnosticsEnabled) {
|
|
1042
|
+
recordIblMaterialBinding(
|
|
1043
|
+
frameState,
|
|
1044
|
+
smMaterialBgl,
|
|
1045
|
+
cached.bindGroup,
|
|
1046
|
+
[
|
|
1047
|
+
{
|
|
1048
|
+
binding: 1 + smUserRegionFields.length * 2,
|
|
1049
|
+
resource: { kind: 'textureView', value: skylightResources.irradianceView },
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
binding: 2 + smUserRegionFields.length * 2,
|
|
1053
|
+
resource: { kind: 'sampler', value: skylightResources.irradianceSampler },
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
binding: 3 + smUserRegionFields.length * 2,
|
|
1057
|
+
resource: { kind: 'textureView', value: skylightResources.prefilterView },
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
binding: 4 + smUserRegionFields.length * 2,
|
|
1061
|
+
resource: { kind: 'sampler', value: skylightResources.prefilterSampler },
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
binding: 5 + smUserRegionFields.length * 2,
|
|
1065
|
+
resource: { kind: 'textureView', value: skylightResources.brdfLutView },
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
binding: 6 + smUserRegionFields.length * 2,
|
|
1069
|
+
resource: { kind: 'sampler', value: skylightResources.brdfLutSampler },
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
binding: 7 + smUserRegionFields.length * 2,
|
|
1073
|
+
resource: { kind: 'buffer', value: { buffer: skylightResources.intensityBuffer } },
|
|
1074
|
+
},
|
|
1075
|
+
],
|
|
1076
|
+
1 + smUserRegionFields.length * 2,
|
|
1077
|
+
'hit',
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1038
1080
|
return cached.bindGroup;
|
|
1039
1081
|
}
|
|
1040
1082
|
}
|
|
1041
|
-
const smSchema =
|
|
1042
|
-
submeshMaterial.materialParamSchema ??
|
|
1043
|
-
(smShaderId !== undefined ? runtime.getParamSchema?.(smShaderId) : undefined);
|
|
1044
|
-
// The shipped PBR shaders always own the eight texture pairs declared by the
|
|
1045
|
-
// standard shader artifact. The runtime PBR BGL exposes those pairs directly;
|
|
1046
|
-
// emissive and occlusion are not duplicated in an engine injection region.
|
|
1047
|
-
// Keep the record projection on the shader-owned layout so IBL starts at
|
|
1048
|
-
// binding 17 rather than colliding with transmissionSampler.
|
|
1049
|
-
const smUserRegionFields =
|
|
1050
|
-
smPerShaderBgl === undefined || isStandardPbrMaterialShader(smShaderId)
|
|
1051
|
-
? BUILTIN_USER_REGION_TEXTURE_FIELDS
|
|
1052
|
-
: userRegionTextureFieldOrder(smSchema);
|
|
1053
1083
|
let materialResourcesResident = true;
|
|
1054
1084
|
const smSamplerForField = (field: string | undefined): Sampler => {
|
|
1055
1085
|
const handle = field === undefined ? undefined : submeshMaterial.samplerHandles?.get(field);
|
|
@@ -1132,6 +1162,38 @@ export function buildPerSubmeshMaterialBg(
|
|
|
1132
1162
|
},
|
|
1133
1163
|
);
|
|
1134
1164
|
}
|
|
1165
|
+
// Physical Standard maps are deliberately appended after the stable IBL /
|
|
1166
|
+
// transmission tail. Keep their resource projection separate from the
|
|
1167
|
+
// user-region loop so the slot order is the same as
|
|
1168
|
+
// `appendTextureInjection(afterTransmission, physicalFields)` in the
|
|
1169
|
+
// layout owner.
|
|
1170
|
+
const physicalTextureInjections: TextureInjectionResource[] = [];
|
|
1171
|
+
for (let slot = 0; slot < physicalFields.length; slot += 1) {
|
|
1172
|
+
const field = physicalFields[slot];
|
|
1173
|
+
if (field === undefined) continue;
|
|
1174
|
+
let view = defaultViewForUserRegionField(field, pipelineState, smSchema);
|
|
1175
|
+
const targetSource = materialRenderTargetSourceForField(
|
|
1176
|
+
submeshMaterial,
|
|
1177
|
+
field,
|
|
1178
|
+
resolveRenderTargetTextureSource,
|
|
1179
|
+
);
|
|
1180
|
+
if (targetSource !== undefined) {
|
|
1181
|
+
if (targetSource.textureView !== undefined) view = targetSource.textureView;
|
|
1182
|
+
else materialResourcesResident = false;
|
|
1183
|
+
} else {
|
|
1184
|
+
const handle = submeshMaterial.textureHandles?.get(field);
|
|
1185
|
+
if (handle !== undefined) {
|
|
1186
|
+
const resident = residentTextureView(materialWorld, store, runtime, handle);
|
|
1187
|
+
if (resident !== undefined) view = resident;
|
|
1188
|
+
else materialResourcesResident = false;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
physicalTextureInjections.push({
|
|
1192
|
+
slot,
|
|
1193
|
+
sampler: smSamplerForField(field),
|
|
1194
|
+
view,
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1135
1197
|
// The backdrop is a full mip-chain view owned by the pipeline. Reuse the
|
|
1136
1198
|
// pipeline's existing filtering/clamp sampler for screen-space sampling;
|
|
1137
1199
|
// the material default sampler intentionally keeps repeat addressing for
|
|
@@ -1147,6 +1209,7 @@ export function buildPerSubmeshMaterialBg(
|
|
|
1147
1209
|
sampler: transmissionSampler,
|
|
1148
1210
|
...(transmissionBackdropView === undefined ? {} : { backdropView: transmissionBackdropView }),
|
|
1149
1211
|
} satisfies TransmissionBindGroupResources,
|
|
1212
|
+
physicalTextureInjections,
|
|
1150
1213
|
);
|
|
1151
1214
|
const createBindGroup = (): BindGroup => {
|
|
1152
1215
|
const result = runtime.device.createBindGroup({
|
|
@@ -1157,16 +1220,60 @@ export function buildPerSubmeshMaterialBg(
|
|
|
1157
1220
|
if (!result.ok) throw result.error;
|
|
1158
1221
|
return result.value;
|
|
1159
1222
|
};
|
|
1223
|
+
const materialCacheHandles = smMergedEntries.map((entry) => extractEntryResourceHandle(entry));
|
|
1224
|
+
if (
|
|
1225
|
+
diagnosticsEnabled &&
|
|
1226
|
+
submeshMaterial.textureHandles === undefined &&
|
|
1227
|
+
smMaterialHandle !== undefined
|
|
1228
|
+
) {
|
|
1229
|
+
console.error(
|
|
1230
|
+
`[render-material] scalar material cache handles: ${JSON.stringify({
|
|
1231
|
+
entityKey,
|
|
1232
|
+
materialHandle: smMaterialHandle,
|
|
1233
|
+
shader: smShaderId,
|
|
1234
|
+
paramSnapshot: submeshMaterial.paramSnapshot,
|
|
1235
|
+
handleTypes: materialCacheHandles.map((handle) => typeof handle),
|
|
1236
|
+
})}`,
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
if (
|
|
1240
|
+
diagnosticsEnabled &&
|
|
1241
|
+
materialCacheHandles.some((handle) => typeof handle !== 'object' || handle === null)
|
|
1242
|
+
) {
|
|
1243
|
+
console.error(
|
|
1244
|
+
`[render-material] invalid material cache handle: ${JSON.stringify({
|
|
1245
|
+
entityKey,
|
|
1246
|
+
materialHandle: smMaterialHandle,
|
|
1247
|
+
shader: smShaderId,
|
|
1248
|
+
entries: smMergedEntries.map((entry, index) => ({
|
|
1249
|
+
index,
|
|
1250
|
+
binding: entry.binding,
|
|
1251
|
+
kind: entry.resource.kind,
|
|
1252
|
+
handleType: typeof materialCacheHandles[index],
|
|
1253
|
+
})),
|
|
1254
|
+
})}`,
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1160
1257
|
const smBindGroup = hasActiveTransmissionBackdrop
|
|
1161
1258
|
? createBindGroup()
|
|
1162
1259
|
: getOrCreatePerEntity(
|
|
1163
1260
|
materialBgShared,
|
|
1164
1261
|
smShaderId ?? '',
|
|
1165
|
-
|
|
1262
|
+
materialCacheHandles,
|
|
1166
1263
|
'material-shared',
|
|
1167
1264
|
createBindGroup,
|
|
1168
1265
|
bindGroupCounts,
|
|
1169
1266
|
);
|
|
1267
|
+
if (diagnosticsEnabled) {
|
|
1268
|
+
recordIblMaterialBinding(
|
|
1269
|
+
frameState,
|
|
1270
|
+
smMaterialBgl,
|
|
1271
|
+
smBindGroup,
|
|
1272
|
+
smMergedEntries,
|
|
1273
|
+
smBaseEntries.length,
|
|
1274
|
+
'miss',
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1170
1277
|
if (
|
|
1171
1278
|
smMaterialHandle !== undefined &&
|
|
1172
1279
|
!smHasVideoFields &&
|