@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/materials.ts
CHANGED
|
@@ -8,7 +8,11 @@ import type {
|
|
|
8
8
|
MaterialRenderState,
|
|
9
9
|
MaterialValue,
|
|
10
10
|
} from '@forgeax/engine-types';
|
|
11
|
-
import { deriveStandardLayerPlan } from '@forgeax/engine-types';
|
|
11
|
+
import { deriveStandardLayerPlan, standardSurfaceParameters } from '@forgeax/engine-types';
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_STANDARD_SURFACE_MODULE,
|
|
14
|
+
projectStandardSurfacePasses,
|
|
15
|
+
} from './assembly/material/surface-projection';
|
|
12
16
|
|
|
13
17
|
export type MaterialTransmissionValidationReason =
|
|
14
18
|
| 'non-finite'
|
|
@@ -58,7 +62,6 @@ export const SPRITE_PREMULTIPLIED_ALPHA_BLEND: GPUBlendState = {
|
|
|
58
62
|
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
59
63
|
};
|
|
60
64
|
|
|
61
|
-
const STANDARD_MODULE = 'forgeax_material::standard';
|
|
62
65
|
const UNLIT_MODULE = 'forgeax_material::unlit';
|
|
63
66
|
const SPRITE_MODULE = 'forgeax_material::sprite';
|
|
64
67
|
|
|
@@ -389,6 +392,70 @@ interface StandardOpts {
|
|
|
389
392
|
readonly queue?: number;
|
|
390
393
|
}
|
|
391
394
|
|
|
395
|
+
/** Built-in Standard authoring surface with engine-owned parameter defaults. */
|
|
396
|
+
export interface DefaultStandardOptions extends StandardOpts {
|
|
397
|
+
readonly surfaceModule?: never;
|
|
398
|
+
readonly parameters?: never;
|
|
399
|
+
readonly values?: never;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Import-first Standard Surface authoring; the root parameter contract stays caller-owned. */
|
|
403
|
+
export interface CustomStandardSurfaceOptions {
|
|
404
|
+
readonly surfaceModule: string;
|
|
405
|
+
readonly parameters: readonly MaterialParameter[];
|
|
406
|
+
readonly values: Readonly<Record<string, MaterialValue>>;
|
|
407
|
+
readonly colorSpace?: MaterialColorSpace;
|
|
408
|
+
readonly renderState?: MaterialRenderState;
|
|
409
|
+
readonly castShadow?: boolean;
|
|
410
|
+
readonly queue?: number;
|
|
411
|
+
readonly baseColor?: never;
|
|
412
|
+
readonly metallic?: never;
|
|
413
|
+
readonly roughness?: never;
|
|
414
|
+
readonly metallicChannel?: never;
|
|
415
|
+
readonly roughnessChannel?: never;
|
|
416
|
+
readonly clearcoat?: never;
|
|
417
|
+
readonly clearcoatRoughness?: never;
|
|
418
|
+
readonly clearcoatTexture?: never;
|
|
419
|
+
readonly clearcoatRoughnessTexture?: never;
|
|
420
|
+
readonly clearcoatNormalTexture?: never;
|
|
421
|
+
readonly clearcoatNormalScale?: never;
|
|
422
|
+
readonly anisotropyStrength?: never;
|
|
423
|
+
readonly anisotropyRotation?: never;
|
|
424
|
+
readonly anisotropyTexture?: never;
|
|
425
|
+
readonly sheenColor?: never;
|
|
426
|
+
readonly sheenRoughness?: never;
|
|
427
|
+
readonly sheenColorTexture?: never;
|
|
428
|
+
readonly sheenRoughnessTexture?: never;
|
|
429
|
+
readonly iridescence?: never;
|
|
430
|
+
readonly iridescenceIor?: never;
|
|
431
|
+
readonly iridescenceThicknessMinimum?: never;
|
|
432
|
+
readonly iridescenceThicknessMaximum?: never;
|
|
433
|
+
readonly iridescenceTexture?: never;
|
|
434
|
+
readonly iridescenceThicknessTexture?: never;
|
|
435
|
+
readonly specular?: never;
|
|
436
|
+
readonly specularColor?: never;
|
|
437
|
+
readonly specularTexture?: never;
|
|
438
|
+
readonly specularColorTexture?: never;
|
|
439
|
+
readonly emissive?: never;
|
|
440
|
+
readonly emissiveIntensity?: never;
|
|
441
|
+
readonly emissiveTexture?: never;
|
|
442
|
+
readonly baseColorTexture?: never;
|
|
443
|
+
readonly metallicRoughnessTexture?: never;
|
|
444
|
+
readonly normalTexture?: never;
|
|
445
|
+
readonly occlusionTexture?: never;
|
|
446
|
+
readonly occlusionStrength?: never;
|
|
447
|
+
readonly alphaCutoff?: never;
|
|
448
|
+
readonly transmission?: never;
|
|
449
|
+
readonly transmissionTexture?: never;
|
|
450
|
+
readonly ior?: never;
|
|
451
|
+
readonly thickness?: never;
|
|
452
|
+
readonly thicknessTexture?: never;
|
|
453
|
+
readonly attenuationColor?: never;
|
|
454
|
+
readonly attenuationDistance?: never;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export type StandardOptions = DefaultStandardOptions | CustomStandardSurfaceOptions;
|
|
458
|
+
|
|
392
459
|
function validateChannel(
|
|
393
460
|
name: 'metallicChannel' | 'roughnessChannel',
|
|
394
461
|
value: number | undefined,
|
|
@@ -413,7 +480,7 @@ function validateFiniteRange(
|
|
|
413
480
|
}
|
|
414
481
|
}
|
|
415
482
|
|
|
416
|
-
function
|
|
483
|
+
function standardDefault(opts: DefaultStandardOptions): MaterialAsset {
|
|
417
484
|
const occlusionStrength = opts.occlusionStrength ?? 1;
|
|
418
485
|
if (occlusionStrength < 0 || occlusionStrength > 1) {
|
|
419
486
|
throw new Error(
|
|
@@ -511,21 +578,23 @@ function standard(opts: StandardOpts): MaterialAsset {
|
|
|
511
578
|
if (opts.attenuationDistance !== undefined) values.attenuationDistance = opts.attenuationDistance;
|
|
512
579
|
const forwardRenderState =
|
|
513
580
|
transmission > 0 ? { ...(opts.renderState ?? {}), depthWriteEnabled: false } : opts.renderState;
|
|
514
|
-
const parameters = standardParameters('linear', opts);
|
|
581
|
+
const parameters = standardSurfaceParameters(standardParameters('linear', opts));
|
|
515
582
|
const declaredNames = new Set(parameters.map((parameter) => parameter.name));
|
|
516
583
|
for (const name of Object.keys(values)) {
|
|
517
584
|
if (!declaredNames.has(name)) delete values[name];
|
|
518
585
|
}
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
586
|
+
const layerPlan = deriveStandardLayerPlan(parameters);
|
|
587
|
+
const projected = projectStandardSurfacePasses({
|
|
588
|
+
surfaceModule: DEFAULT_STANDARD_SURFACE_MODULE,
|
|
589
|
+
values,
|
|
590
|
+
...(forwardRenderState === undefined ? {} : { renderState: forwardRenderState }),
|
|
591
|
+
...(opts.castShadow === undefined ? {} : { castShadow: opts.castShadow }),
|
|
592
|
+
...(opts.queue === undefined ? {} : { queue: opts.queue }),
|
|
593
|
+
layerPlan,
|
|
594
|
+
});
|
|
595
|
+
const passes = (
|
|
596
|
+
transmission > 0 ? projected.filter((entry) => entry.name !== 'deferred') : projected
|
|
597
|
+
) as [MaterialPass, ...MaterialPass[]];
|
|
529
598
|
return {
|
|
530
599
|
kind: 'material',
|
|
531
600
|
colorSpace: 'linear',
|
|
@@ -535,6 +604,32 @@ function standard(opts: StandardOpts): MaterialAsset {
|
|
|
535
604
|
};
|
|
536
605
|
}
|
|
537
606
|
|
|
607
|
+
function standardCustom(opts: CustomStandardSurfaceOptions): MaterialAsset {
|
|
608
|
+
const layerPlan = deriveStandardLayerPlan(
|
|
609
|
+
opts.parameters.filter((parameter) => parameter.optional !== true),
|
|
610
|
+
);
|
|
611
|
+
return {
|
|
612
|
+
kind: 'material',
|
|
613
|
+
...(opts.colorSpace === undefined || opts.colorSpace === 'srgb'
|
|
614
|
+
? {}
|
|
615
|
+
: { colorSpace: opts.colorSpace }),
|
|
616
|
+
passes: projectStandardSurfacePasses({
|
|
617
|
+
surfaceModule: opts.surfaceModule,
|
|
618
|
+
values: opts.values,
|
|
619
|
+
...(opts.renderState === undefined ? {} : { renderState: opts.renderState }),
|
|
620
|
+
...(opts.castShadow === undefined ? {} : { castShadow: opts.castShadow }),
|
|
621
|
+
...(opts.queue === undefined ? {} : { queue: opts.queue }),
|
|
622
|
+
layerPlan,
|
|
623
|
+
}),
|
|
624
|
+
parameters: standardSurfaceParameters(opts.parameters),
|
|
625
|
+
values: opts.values,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export function standard(options: StandardOptions): MaterialAsset {
|
|
630
|
+
return options.surfaceModule === undefined ? standardDefault(options) : standardCustom(options);
|
|
631
|
+
}
|
|
632
|
+
|
|
538
633
|
export const Materials = { unlit, standard, srgb } as const;
|
|
539
634
|
|
|
540
635
|
export { SPRITE_MODULE };
|
package/src/pipeline-builder.ts
CHANGED
|
@@ -277,7 +277,13 @@ export function buildPipelineForMaterialShader(
|
|
|
277
277
|
const vsEntry = vertexEntry ?? (passKind === 'temporal' ? 'vs_temporal' : 'vs_main');
|
|
278
278
|
const fsEntry =
|
|
279
279
|
fragmentEntry ??
|
|
280
|
-
(passKind === 'temporal'
|
|
280
|
+
(passKind === 'temporal'
|
|
281
|
+
? 'fs_temporal'
|
|
282
|
+
: passKind === 'deferred'
|
|
283
|
+
? 'fs_gbuffer'
|
|
284
|
+
: passKind === 'shadow-caster'
|
|
285
|
+
? 'fs_shadow'
|
|
286
|
+
: 'fs_main');
|
|
281
287
|
|
|
282
288
|
let source = entry.source;
|
|
283
289
|
if (defines !== undefined && Object.keys(defines).length > 0) {
|
|
@@ -345,15 +351,13 @@ export function buildPipelineForMaterialShader(
|
|
|
345
351
|
vertex: {
|
|
346
352
|
module: shaderModule,
|
|
347
353
|
entryPoint: vsEntry,
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
},
|
|
356
|
-
],
|
|
354
|
+
// Surface-backed Standard shadow entries evaluate the same material
|
|
355
|
+
// surface as Forward/Deferred, so their vertex stage consumes normal,
|
|
356
|
+
// UV, and tangent inputs in addition to position. Keep the complete
|
|
357
|
+
// derived mesh layout here; extra attributes remain legal for legacy
|
|
358
|
+
// position-only custom casters, while trimming to position would make
|
|
359
|
+
// the built-in Surface shadow shader fail WebGPU validation.
|
|
360
|
+
buffers: [...ctx.vertexBuffers],
|
|
357
361
|
},
|
|
358
362
|
// Vertex-only depth pass writes `fragment: undefined` (GPU derives
|
|
359
363
|
// depth from gl_Position.z). A custom ShadowCaster shader with a
|
|
@@ -10,6 +10,8 @@ export interface PipelineSpec {
|
|
|
10
10
|
readonly id: string;
|
|
11
11
|
readonly passKind: string;
|
|
12
12
|
readonly variantSet: string | undefined;
|
|
13
|
+
readonly vertexEntry?: string;
|
|
14
|
+
readonly fragmentEntry?: string;
|
|
13
15
|
};
|
|
14
16
|
readonly attachments: {
|
|
15
17
|
readonly colorFormats: readonly GPUTextureFormat[];
|
package/src/pipeline-spec.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import {
|
|
21
21
|
deriveVertexBufferLayout,
|
|
22
|
+
deriveVertexBufferLayoutFromProjection,
|
|
22
23
|
deriveVertexLayoutProjection,
|
|
23
24
|
type VertexLayoutProjection,
|
|
24
25
|
} from '@forgeax/engine-geometry';
|
|
@@ -247,7 +248,11 @@ export function cacheKeyOf(spec: PipelineSpec): string {
|
|
|
247
248
|
stripSegment,
|
|
248
249
|
`vl:${vlDigest}`,
|
|
249
250
|
renderStateHash(renderState),
|
|
250
|
-
].join(':') +
|
|
251
|
+
].join(':') +
|
|
252
|
+
uvSetCountSegment +
|
|
253
|
+
(shader.vertexEntry === undefined && shader.fragmentEntry === undefined
|
|
254
|
+
? ''
|
|
255
|
+
: `:entries:${JSON.stringify([shader.vertexEntry ?? null, shader.fragmentEntry ?? null])}`)
|
|
251
256
|
);
|
|
252
257
|
}
|
|
253
258
|
|
|
@@ -407,25 +412,16 @@ export function buildPipelineDescriptor(
|
|
|
407
412
|
? { shaderUvSetCount: geometry.shaderUvSetCount }
|
|
408
413
|
: {}),
|
|
409
414
|
})
|
|
410
|
-
: geometry.vertexLayoutProjection
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
{
|
|
414
|
-
|
|
415
|
-
attributes: geometry.vertexLayoutProjection.attributes.map(
|
|
416
|
-
({ shaderLocation, offset, format }) => ({
|
|
417
|
-
shaderLocation,
|
|
418
|
-
offset,
|
|
419
|
-
format,
|
|
420
|
-
}),
|
|
421
|
-
),
|
|
422
|
-
},
|
|
423
|
-
];
|
|
415
|
+
: deriveVertexBufferLayoutFromProjection(geometry.vertexLayoutProjection, {
|
|
416
|
+
...(geometry.shaderUvSetCount !== undefined
|
|
417
|
+
? { shaderUvSetCount: geometry.shaderUvSetCount }
|
|
418
|
+
: {}),
|
|
419
|
+
});
|
|
424
420
|
|
|
425
421
|
const descriptor: Record<string, unknown> = {
|
|
426
422
|
vertex: {
|
|
427
423
|
module: modules.vertex,
|
|
428
|
-
entryPoint: modules.vertexEntryPoint ?? 'vs_main',
|
|
424
|
+
entryPoint: spec.shader.vertexEntry ?? modules.vertexEntryPoint ?? 'vs_main',
|
|
429
425
|
buffers: vertexBuffers,
|
|
430
426
|
},
|
|
431
427
|
};
|
|
@@ -434,7 +430,7 @@ export function buildPipelineDescriptor(
|
|
|
434
430
|
if (attachments.colorFormats.length > 0) {
|
|
435
431
|
descriptor.fragment = {
|
|
436
432
|
module: modules.fragment,
|
|
437
|
-
entryPoint: modules.fragmentEntryPoint ?? 'fs_main',
|
|
433
|
+
entryPoint: spec.shader.fragmentEntry ?? modules.fragmentEntryPoint ?? 'fs_main',
|
|
438
434
|
targets: attachments.colorFormats.map((fmt) => {
|
|
439
435
|
const target: Record<string, unknown> = { format: fmt };
|
|
440
436
|
if (renderState?.blend !== undefined) {
|
|
@@ -18,6 +18,7 @@ import type { DirectionalShadowQuality } from '../components/directional-shadow-
|
|
|
18
18
|
import type { EnvironmentGeneration } from '../environment/generation';
|
|
19
19
|
import type { EnvironmentLifecycle } from '../environment/lifecycle';
|
|
20
20
|
import type { GpuBuffer } from '../gpu-resource';
|
|
21
|
+
import type { InstanceCollectionId, InstanceResidency } from '../instances';
|
|
21
22
|
import type { IblBindingInspection } from '../mesh-material-bindings';
|
|
22
23
|
import type { StandardLightingInspection } from '../pipeline/standard-lighting/inspection';
|
|
23
24
|
import type { PointShadowInspection } from '../point-shadow-inspection';
|
|
@@ -52,6 +53,17 @@ export function worldEntityKey(worldId: number, entityKey: number): number {
|
|
|
52
53
|
return worldId * 4294967296 + entityKey; // 2^32 = 4294967296
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/** Per-renderer projection identity, with entity keys for sprite/fold snapshots. */
|
|
57
|
+
export function instanceCollectionCacheKey(
|
|
58
|
+
worldId: number,
|
|
59
|
+
instance: { readonly cacheKey: number; readonly collectionId?: number },
|
|
60
|
+
): number {
|
|
61
|
+
// Keep collection entries in a disjoint numeric namespace from the shared
|
|
62
|
+
// legacy sprite/fold cache. Fold keys occupy -1..-(2^32), while ordinary
|
|
63
|
+
// world/entity keys are non-negative; this range starts below both.
|
|
64
|
+
return -(0x1_0000_0000 + worldEntityKey(worldId, instance.collectionId ?? instance.cacheKey));
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
/**
|
|
56
68
|
* Cross-frame cache entry for a material bind group whose source material and
|
|
57
69
|
* all resolved GPU resources are stable. The material snapshot identity is
|
|
@@ -289,8 +301,9 @@ export function makeZeroCameraFallbackSnapshot(): CameraSnapshot {
|
|
|
289
301
|
* Owned by the `createRenderSystem` closure; advanced once per
|
|
290
302
|
* `recordFrame` invocation. The `instanceBuffers` map holds the per-entity
|
|
291
303
|
* GPU storage buffers for Instances-bearing entities (cache key = the
|
|
292
|
-
* packed Entity u32
|
|
293
|
-
* are recreated when the `archVersion` bumps or
|
|
304
|
+
* renderer-owned collection id, with the packed Entity u32 retained only for
|
|
305
|
+
* legacy snapshots); entries are recreated when the `archVersion` bumps or
|
|
306
|
+
* `byteLength` changes.
|
|
294
307
|
*
|
|
295
308
|
* feat-20260518-pbr-direct-lighting-mvp M3 / w14 (AC-17 a): the
|
|
296
309
|
* `warnedZeroLightStandard` flag latches the first-frame warning that
|
|
@@ -390,6 +403,10 @@ export interface RenderFrameState {
|
|
|
390
403
|
currentDirectionalShadowView: TextureView | null;
|
|
391
404
|
currentSpotShadowView: TextureView | null;
|
|
392
405
|
readonly instanceBuffers: Map<number, InstanceBufferCacheEntry>;
|
|
406
|
+
/** Renderer-owned collection owner used for record-stage residency facts. */
|
|
407
|
+
readonly instanceResidency?: Map<InstanceCollectionId, InstanceResidency>;
|
|
408
|
+
/** Stable buffers for renderer-owned collections split by device cap. */
|
|
409
|
+
readonly instanceBufferChunks?: Map<string, InstanceBufferCacheEntry>;
|
|
393
410
|
/** Per-object ProbeBlendRecord buffers; upload is one atomic 160B write. */
|
|
394
411
|
readonly probeBlendBuffers: Map<number, ProbeBlendBufferCacheEntry>;
|
|
395
412
|
/** One renderer-owned record array; each object selects its 256B-aligned lane. */
|
package/src/record/frame.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { uploadMaterialUniforms } from './material-uniforms';
|
|
1
2
|
// @forgeax/engine-runtime - RenderSystem record stage: frame.
|
|
2
3
|
// Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
|
|
3
4
|
|
|
@@ -86,6 +87,7 @@ import {
|
|
|
86
87
|
type DispatchCounts,
|
|
87
88
|
type FrameObservationSource,
|
|
88
89
|
getTextureIdentity,
|
|
90
|
+
instanceCollectionCacheKey,
|
|
89
91
|
makeZeroCameraFallbackSnapshot,
|
|
90
92
|
type ReflectionFallbackReadbackRequest,
|
|
91
93
|
type RenderFrameState,
|
|
@@ -1924,7 +1926,11 @@ export function recordFrame(
|
|
|
1924
1926
|
// the swap-chain fallback uses its raw storage view. The encoder and
|
|
1925
1927
|
// sprite PSO must use the same resolved format because WebGPU requires
|
|
1926
1928
|
// attachment and pipeline target formats to match.
|
|
1927
|
-
const splitLdrSprite = computeSplitLdrSprite(
|
|
1929
|
+
const splitLdrSprite = computeSplitLdrSprite(
|
|
1930
|
+
validatedOrdered,
|
|
1931
|
+
tonemapActive,
|
|
1932
|
+
transparentDispatch,
|
|
1933
|
+
);
|
|
1928
1934
|
let ldrSpritePassView: TextureView | null = null;
|
|
1929
1935
|
if (splitLdrSprite) {
|
|
1930
1936
|
const unormViewRes = internals.device.createTextureView(currentTexture, {});
|
|
@@ -2154,6 +2160,7 @@ export function recordFrame(
|
|
|
2154
2160
|
directionalShadowCacheReuse: directionalShadowCache.reuse,
|
|
2155
2161
|
...(profilePhase !== undefined ? { profilePhase } : {}),
|
|
2156
2162
|
};
|
|
2163
|
+
uploadMaterialUniforms(passCtx);
|
|
2157
2164
|
const fallbackReadbackRequest = createReflectionFallbackReadbackRequest(internals, frameState);
|
|
2158
2165
|
if (fallbackReadbackRequest !== undefined) {
|
|
2159
2166
|
frameState.reflectionFallbackReadback = fallbackReadbackRequest;
|
|
@@ -2993,8 +3000,14 @@ function cleanPerFrameCaches(
|
|
|
2993
3000
|
// composites matching the write-side keys of #1-#3 — cross-world false eviction
|
|
2994
3001
|
// is prevented because worldEntityKey(0, k) !== worldEntityKey(1, k).
|
|
2995
3002
|
const validatedEntityKeys = new Set<number>(retainedEntityKeys);
|
|
3003
|
+
const validatedInstanceKeys = new Set<number>(retainedEntityKeys);
|
|
2996
3004
|
for (const v of validated) {
|
|
2997
|
-
|
|
3005
|
+
const entityKey = worldEntityKey(v.source.worldId, v.source.entityKey);
|
|
3006
|
+
validatedEntityKeys.add(entityKey);
|
|
3007
|
+
validatedInstanceKeys.add(entityKey);
|
|
3008
|
+
if (v.source.instances !== undefined) {
|
|
3009
|
+
validatedInstanceKeys.add(instanceCollectionCacheKey(v.source.worldId, v.source.instances));
|
|
3010
|
+
}
|
|
2998
3011
|
}
|
|
2999
3012
|
|
|
3000
3013
|
// Clean per-entity material BG cache: drop outer-Map entries whose
|
|
@@ -3018,7 +3031,7 @@ function cleanPerFrameCaches(
|
|
|
3018
3031
|
// material-handle-based and cross-world collision is semantically
|
|
3019
3032
|
// correct (same material renders in same fold bucket).
|
|
3020
3033
|
for (const [key, entry] of frameState.instanceBuffers.entries()) {
|
|
3021
|
-
if (!
|
|
3034
|
+
if (!validatedInstanceKeys.has(key)) {
|
|
3022
3035
|
if (!entry.buffer.isDestroyed) {
|
|
3023
3036
|
const r = entry.buffer.destroy();
|
|
3024
3037
|
if (!r.ok) internals.errorRegistry.fire(r.error);
|
|
@@ -3027,6 +3040,28 @@ function cleanPerFrameCaches(
|
|
|
3027
3040
|
}
|
|
3028
3041
|
}
|
|
3029
3042
|
|
|
3043
|
+
if (frameState.instanceResidency !== undefined) {
|
|
3044
|
+
const liveCollections = new Set(validated.map((entry) => entry.source.instances?.collectionId));
|
|
3045
|
+
for (const id of frameState.instanceResidency.keys()) {
|
|
3046
|
+
if (!liveCollections.has(id)) frameState.instanceResidency.delete(id);
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
// Chunk keys are `${worldEntityKey}:${chunkStart}`. Keep the key opaque to
|
|
3051
|
+
// the authoring API, but retain the numeric owner prefix for eviction.
|
|
3052
|
+
if (frameState.instanceBufferChunks !== undefined) {
|
|
3053
|
+
for (const [key, entry] of frameState.instanceBufferChunks.entries()) {
|
|
3054
|
+
const separator = key.indexOf(':');
|
|
3055
|
+
const ownerKey = separator < 0 ? Number.NaN : Number(key.slice(0, separator));
|
|
3056
|
+
if (validatedInstanceKeys.has(ownerKey)) continue;
|
|
3057
|
+
if (!entry.buffer.isDestroyed) {
|
|
3058
|
+
const r = entry.buffer.destroy();
|
|
3059
|
+
if (!r.ok) internals.errorRegistry.fire(r.error);
|
|
3060
|
+
}
|
|
3061
|
+
frameState.instanceBufferChunks.delete(key);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3030
3065
|
if (frameState.morphBuffers !== undefined) {
|
|
3031
3066
|
for (const [key, entry] of frameState.morphBuffers.entries()) {
|
|
3032
3067
|
if (validatedEntityKeys.has(key)) continue;
|