@forgeax/engine-render 0.1.31 → 0.1.32
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 +68 -1
- package/dist/assembly/webgpu-ready.d.ts +2 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/{chunk-C3DDSXYK.mjs → chunk-4NNZQCL3.mjs} +110 -35
- package/dist/chunk-4NNZQCL3.mjs.map +1 -0
- package/dist/{chunk-JRBDO2KB.mjs → chunk-6TFBXLRJ.mjs} +19 -7
- package/dist/chunk-6TFBXLRJ.mjs.map +1 -0
- package/dist/{chunk-FUUO4UND.mjs → chunk-DRR7D3KX.mjs} +3 -3
- package/dist/chunk-DRR7D3KX.mjs.map +1 -0
- package/dist/{chunk-S5UE2T5P.mjs → chunk-GWCEMDVR.mjs} +380 -4
- package/dist/chunk-GWCEMDVR.mjs.map +1 -0
- package/dist/{chunk-MDQJPY3T.mjs → chunk-JCIRYQ4V.mjs} +3 -3
- package/dist/{chunk-MDQJPY3T.mjs.map → chunk-JCIRYQ4V.mjs.map} +1 -1
- package/dist/{chunk-PSEQ6QPH.mjs → chunk-JRIPCMYC.mjs} +17 -10
- package/dist/chunk-JRIPCMYC.mjs.map +1 -0
- package/dist/{chunk-7MQ3XBSP.mjs → chunk-LULCA3OF.mjs} +4 -4
- package/dist/{chunk-7MQ3XBSP.mjs.map → chunk-LULCA3OF.mjs.map} +1 -1
- package/dist/{chunk-NO4WZCAH.mjs → chunk-S665NJNF.mjs} +3 -3
- package/dist/{chunk-NO4WZCAH.mjs.map → chunk-S665NJNF.mjs.map} +1 -1
- package/dist/components/depth-of-field.d.ts +28 -0
- package/dist/components/depth-of-field.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +2 -1
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +898 -328
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/features/depth-of-field/depth-of-field-assembly.d.ts +19 -0
- package/dist/features/depth-of-field/depth-of-field-assembly.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-feature.d.ts +43 -0
- package/dist/features/depth-of-field/depth-of-field-feature.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-inspection.d.ts +34 -0
- package/dist/features/depth-of-field/depth-of-field-inspection.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-params.d.ts +83 -0
- package/dist/features/depth-of-field/depth-of-field-params.d.ts.map +1 -0
- package/dist/features/types.d.ts +19 -1
- package/dist/features/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +6 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +35 -0
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances.d.ts +7 -0
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- package/dist/mesh-material-bindings.d.ts +3 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-output/types.d.ts +4 -2
- package/dist/pipeline/standard-output/types.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +21 -1
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +7 -0
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +8 -1
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +2 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +13 -1
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +18 -4
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +2 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +12 -0
- package/src/__tests__/gpu-driven-production.integration.test.ts +27 -1
- package/src/__tests__/instances-store.unit.test.ts +15 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +28 -0
- package/src/assembly/webgpu-ready.ts +5 -5
- package/src/assembly/webgpu-renderer.ts +14 -14
- package/src/components/depth-of-field.ts +45 -0
- package/src/components/directional-light.ts +2 -1
- package/src/components/index.ts +1 -0
- package/src/extract/camera.ts +22 -2
- package/src/features/depth-of-field/__tests__/depth-of-field-params.unit.test.ts +943 -0
- package/src/features/depth-of-field/depth-of-field-assembly.ts +100 -0
- package/src/features/depth-of-field/depth-of-field-feature.ts +295 -0
- package/src/features/depth-of-field/depth-of-field-inspection.ts +237 -0
- package/src/features/depth-of-field/depth-of-field-params.ts +556 -0
- package/src/features/host.ts +1 -1
- package/src/features/types.ts +18 -1
- package/src/index.ts +32 -0
- package/src/inspection-types.ts +38 -0
- package/src/instances.ts +19 -5
- package/src/mesh-material-bindings.ts +4 -0
- package/src/pipeline/standard-output/graph.ts +3 -0
- package/src/pipeline/standard-output/types.ts +4 -1
- package/src/pipeline/standard-post.ts +21 -2
- package/src/plugin.ts +2 -0
- package/src/record/frame-snapshot.ts +26 -1
- package/src/record/frame.ts +39 -6
- package/src/record/main-pass.ts +1 -5
- package/src/record/typed-frame-graph.ts +106 -6
- package/src/recovery/render-system-candidate.ts +6 -0
- package/src/render-contract.ts +11 -0
- package/src/render-graph-primitives.ts +8 -1
- package/src/render-pipeline.ts +17 -1
- package/src/render-system-extract-tail.ts +87 -13
- package/src/render-system-extract.ts +103 -25
- package/src/render-system.ts +78 -0
- package/src/typed-render-graph-primitives.ts +3 -0
- package/dist/chunk-C3DDSXYK.mjs.map +0 -1
- package/dist/chunk-FUUO4UND.mjs.map +0 -1
- package/dist/chunk-JRBDO2KB.mjs.map +0 -1
- package/dist/chunk-PSEQ6QPH.mjs.map +0 -1
- package/dist/chunk-S5UE2T5P.mjs.map +0 -1
package/src/instances.ts
CHANGED
|
@@ -502,17 +502,23 @@ export class InstanceCollectionStore {
|
|
|
502
502
|
if (!record.ok) return record;
|
|
503
503
|
const value = record.value;
|
|
504
504
|
value.lastSnapshotRevision = value.revision;
|
|
505
|
-
const result =
|
|
506
|
-
...this.info(value),
|
|
507
|
-
transforms: new Float32Array(value.transforms),
|
|
508
|
-
dirtyRanges: value.dirtyRanges.map((range) => ({ ...range })),
|
|
509
|
-
};
|
|
505
|
+
const result = this.detachedSnapshot(value);
|
|
510
506
|
// Keep the current ranges stable for repeated reads by entities sharing
|
|
511
507
|
// this collection. The next authoring mutation starts a fresh interval
|
|
512
508
|
// set once this revision has been observed.
|
|
513
509
|
return ok(result);
|
|
514
510
|
}
|
|
515
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Read a detached matrix view without advancing the render-consumption
|
|
514
|
+
* cursor. Inspection and picking may call this between authoring updates;
|
|
515
|
+
* the next renderer snapshot must still receive the complete dirty union.
|
|
516
|
+
*/
|
|
517
|
+
peek(id: InstanceCollectionId): InstanceCollectionResult<InstanceCollectionSnapshot> {
|
|
518
|
+
const record = this.live('inspect', id);
|
|
519
|
+
return record.ok ? ok(this.detachedSnapshot(record.value)) : record;
|
|
520
|
+
}
|
|
521
|
+
|
|
516
522
|
release(id: InstanceCollectionId): InstanceCollectionResult<void> {
|
|
517
523
|
const record = this.records.get(id);
|
|
518
524
|
if (record === undefined) return err(missing('release', id));
|
|
@@ -569,6 +575,14 @@ export class InstanceCollectionStore {
|
|
|
569
575
|
return record === undefined ? err(missing(operation, id)) : ok(record);
|
|
570
576
|
}
|
|
571
577
|
|
|
578
|
+
private detachedSnapshot(record: CollectionRecord): InstanceCollectionSnapshot {
|
|
579
|
+
return {
|
|
580
|
+
...this.info(record),
|
|
581
|
+
transforms: new Float32Array(record.transforms),
|
|
582
|
+
dirtyRanges: record.dirtyRanges.map((range) => ({ ...range })),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
572
586
|
private info(record: CollectionRecord): InstanceCollectionInfo {
|
|
573
587
|
return Object.freeze({
|
|
574
588
|
collectionId: record.id,
|
|
@@ -112,6 +112,8 @@ export interface MeshMaterialBindingResidency {
|
|
|
112
112
|
/** Public read-only observation of the bindings used by the last frame. */
|
|
113
113
|
export interface MeshMaterialBindingObservation {
|
|
114
114
|
readonly worldId: number;
|
|
115
|
+
/** Stable World identity for cross-world inspection; worldId is only a frame slot. */
|
|
116
|
+
readonly worldIdentity?: string;
|
|
115
117
|
readonly entityKey: number;
|
|
116
118
|
readonly bindings: readonly ResolvedMeshMaterialBinding[];
|
|
117
119
|
readonly diagnostics: readonly MeshMaterialBindingDiagnostic[];
|
|
@@ -143,6 +145,7 @@ const EMPTY_RESIDENCY: MeshMaterialBindingResidency = Object.freeze({
|
|
|
143
145
|
*/
|
|
144
146
|
export function projectMeshMaterialBindingObservation(input: {
|
|
145
147
|
readonly worldId: number;
|
|
148
|
+
readonly worldIdentity?: string;
|
|
146
149
|
readonly entityKey: number;
|
|
147
150
|
readonly bindings: readonly ResolvedMeshMaterialBinding[];
|
|
148
151
|
readonly diagnostics: readonly MeshMaterialBindingDiagnostic[];
|
|
@@ -168,6 +171,7 @@ export function projectMeshMaterialBindingObservation(input: {
|
|
|
168
171
|
});
|
|
169
172
|
return {
|
|
170
173
|
worldId: input.worldId,
|
|
174
|
+
...(input.worldIdentity === undefined ? {} : { worldIdentity: input.worldIdentity }),
|
|
171
175
|
entityKey: input.entityKey,
|
|
172
176
|
bindings: Object.freeze([...input.bindings]),
|
|
173
177
|
diagnostics: Object.freeze([...input.diagnostics]),
|
|
@@ -26,6 +26,7 @@ function requestedStages(input: StandardOutputRequest): StandardOutputLogicalSta
|
|
|
26
26
|
const stages: StandardOutputLogicalStage[] = [];
|
|
27
27
|
if (input.temporal) stages.push('temporal');
|
|
28
28
|
if (input.meter) stages.push('meter');
|
|
29
|
+
if (input.dof === true) stages.push('dof');
|
|
29
30
|
if (input.bloom) stages.push('bloom');
|
|
30
31
|
if (input.exposure || input.whiteBalance) stages.push('exposure-white-balance');
|
|
31
32
|
stages.push('tone');
|
|
@@ -43,6 +44,7 @@ function physicalStages(
|
|
|
43
44
|
case 'temporal':
|
|
44
45
|
case 'meter':
|
|
45
46
|
case 'bloom':
|
|
47
|
+
case 'dof':
|
|
46
48
|
case 'exposure-white-balance':
|
|
47
49
|
return { name, input: LINEAR_HDR_DOMAIN, output: LINEAR_HDR_DOMAIN };
|
|
48
50
|
case 'tone':
|
|
@@ -68,6 +70,7 @@ export function createStandardOutputPlan(input: StandardOutputRequest): Standard
|
|
|
68
70
|
temporal: input.temporal,
|
|
69
71
|
meter: input.meter,
|
|
70
72
|
bloom: input.bloom,
|
|
73
|
+
dof: input.dof === true,
|
|
71
74
|
exposure: input.exposure,
|
|
72
75
|
whiteBalance: input.whiteBalance,
|
|
73
76
|
lut: input.lut,
|
|
@@ -3,6 +3,7 @@ import type { ColorValueDomain } from '@forgeax/engine-render-graph';
|
|
|
3
3
|
export const STANDARD_OUTPUT_LOGICAL_STAGES = [
|
|
4
4
|
'temporal',
|
|
5
5
|
'meter',
|
|
6
|
+
'dof',
|
|
6
7
|
'bloom',
|
|
7
8
|
'exposure-white-balance',
|
|
8
9
|
'tone',
|
|
@@ -26,6 +27,8 @@ export interface StandardOutputRequest {
|
|
|
26
27
|
readonly temporal: boolean;
|
|
27
28
|
readonly meter: boolean;
|
|
28
29
|
readonly bloom: boolean;
|
|
30
|
+
/** Built-in spatial DoF stage; false keeps the path exact-zero. */
|
|
31
|
+
readonly dof?: boolean;
|
|
29
32
|
readonly exposure: boolean;
|
|
30
33
|
readonly whiteBalance: boolean;
|
|
31
34
|
readonly lut: boolean;
|
|
@@ -44,7 +47,7 @@ export interface StandardOutputPlan {
|
|
|
44
47
|
readonly features: Readonly<
|
|
45
48
|
Pick<
|
|
46
49
|
StandardOutputRequest,
|
|
47
|
-
'temporal' | 'meter' | 'bloom' | 'exposure' | 'whiteBalance' | 'lut' | 'fxaa'
|
|
50
|
+
'temporal' | 'meter' | 'bloom' | 'dof' | 'exposure' | 'whiteBalance' | 'lut' | 'fxaa'
|
|
48
51
|
>
|
|
49
52
|
>;
|
|
50
53
|
readonly outputEncoding: StandardOutputEncoding;
|
|
@@ -7,6 +7,7 @@ import { RenderGraphError } from '@forgeax/engine-render-graph';
|
|
|
7
7
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
8
8
|
import { addTypedDebugOverlayPass } from '../debug-draw-glue';
|
|
9
9
|
import { SceneDataUnavailableError } from '../errors/render';
|
|
10
|
+
import { addDepthOfFieldPasses } from '../features/depth-of-field/depth-of-field-feature';
|
|
10
11
|
import { addMotionBlurPass } from '../features/motion-blur/motion-blur-feature';
|
|
11
12
|
import { GPU_TEXTURE_USAGE_COPY_SRC } from '../gpu-texture-usage';
|
|
12
13
|
import type {
|
|
@@ -179,6 +180,7 @@ export function addStandardPost(
|
|
|
179
180
|
temporal: !clearOnly && topology.camera.antialias === 'taa',
|
|
180
181
|
meter: topology.output?.autoExposure === true,
|
|
181
182
|
bloom: !clearOnly && topology.camera.bloom === 'on' && topology.camera.tonemap !== 'none',
|
|
183
|
+
dof: !clearOnly && topology.camera.depthOfField !== undefined,
|
|
182
184
|
exposure: topology.output?.autoExposure === true,
|
|
183
185
|
whiteBalance: topology.output?.whiteBalance === true,
|
|
184
186
|
lut: topology.output?.colorLut === true,
|
|
@@ -313,8 +315,8 @@ export function addStandardPost(
|
|
|
313
315
|
}
|
|
314
316
|
}
|
|
315
317
|
|
|
316
|
-
// Meter the temporally resolved color before Bloom. This preserves the
|
|
317
|
-
// Standard order: temporal ->
|
|
318
|
+
// Meter the temporally resolved color before DoF/Bloom. This preserves the
|
|
319
|
+
// Standard order: temporal -> motion -> meter -> DoF -> bloom -> output.
|
|
318
320
|
if (outputPlan.features.exposure && autoExposure !== undefined) {
|
|
319
321
|
const added = addAutoExposureGraphPasses(context, {
|
|
320
322
|
source: postInput,
|
|
@@ -325,6 +327,23 @@ export function addStandardPost(
|
|
|
325
327
|
if (!added.ok) return added;
|
|
326
328
|
}
|
|
327
329
|
|
|
330
|
+
if (outputPlan.features.dof) {
|
|
331
|
+
const dof = addDepthOfFieldPasses(
|
|
332
|
+
{
|
|
333
|
+
graph: context.graph,
|
|
334
|
+
camera: context.camera ?? {},
|
|
335
|
+
temporalTaa: topology.temporal?.taa === true,
|
|
336
|
+
msaaActive: depth.sampleCount === 4,
|
|
337
|
+
...(topology.extent === undefined ? {} : { extent: topology.extent }),
|
|
338
|
+
},
|
|
339
|
+
postInput,
|
|
340
|
+
depth,
|
|
341
|
+
topology.temporal?.taa === true ? postTemporal : undefined,
|
|
342
|
+
);
|
|
343
|
+
if (!dof.ok) return dof;
|
|
344
|
+
postInput = dof.value;
|
|
345
|
+
}
|
|
346
|
+
|
|
328
347
|
if (bloomActive) {
|
|
329
348
|
const composited = target(
|
|
330
349
|
context,
|
package/src/plugin.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Component, World } from '@forgeax/engine-ecs';
|
|
|
2
2
|
import type { Plugin } from '@forgeax/engine-plugin';
|
|
3
3
|
import { Atmosphere } from './components/atmosphere';
|
|
4
4
|
import { Camera } from './components/camera';
|
|
5
|
+
import { DepthOfField } from './components/depth-of-field';
|
|
5
6
|
import { DirectionalLight } from './components/directional-light';
|
|
6
7
|
import { DynamicResolution } from './components/dynamic-resolution';
|
|
7
8
|
import { Instances } from './components/instances';
|
|
@@ -33,6 +34,7 @@ const RENDER_COMPONENTS: readonly Component[] = [
|
|
|
33
34
|
Camera,
|
|
34
35
|
DirectionalLight,
|
|
35
36
|
DynamicResolution,
|
|
37
|
+
DepthOfField,
|
|
36
38
|
Instances,
|
|
37
39
|
Layer,
|
|
38
40
|
LightProbe,
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
import type { World } from '@forgeax/engine-ecs';
|
|
5
5
|
import type { RenderReadVersion } from '@forgeax/engine-ecs/projection';
|
|
6
6
|
import { vec3 } from '@forgeax/engine-math';
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
CompiledRenderGraphInfo,
|
|
9
|
+
ResolvedColorTargetDescriptor,
|
|
10
|
+
} from '@forgeax/engine-render-graph';
|
|
8
11
|
import type {
|
|
9
12
|
BindGroup,
|
|
10
13
|
BindGroupLayout,
|
|
@@ -17,6 +20,7 @@ import type { ClusterBinScratch } from '../cluster-binner';
|
|
|
17
20
|
import type { DirectionalShadowQuality } from '../components/directional-shadow-filter';
|
|
18
21
|
import type { EnvironmentGeneration } from '../environment/generation';
|
|
19
22
|
import type { EnvironmentLifecycle } from '../environment/lifecycle';
|
|
23
|
+
import type { DepthOfFieldParams } from '../features/depth-of-field/depth-of-field-params';
|
|
20
24
|
import type { GpuBuffer } from '../gpu-resource';
|
|
21
25
|
import type { InstanceCollectionStore } from '../instances';
|
|
22
26
|
import type { IblBindingInspection } from '../mesh-material-bindings';
|
|
@@ -452,6 +456,27 @@ export interface RenderFrameState {
|
|
|
452
456
|
compiledFrameGraphTopologyKey: string | null;
|
|
453
457
|
/** Monotonic generation of successfully promoted compiled graphs. */
|
|
454
458
|
compiledFrameGraphGeneration: number;
|
|
459
|
+
/**
|
|
460
|
+
* DoF graph candidate staged until the surrounding frame transaction
|
|
461
|
+
* submits. The previous graph, access projection, generation, and lighting
|
|
462
|
+
* signature are restored together when encoding or submission aborts.
|
|
463
|
+
*/
|
|
464
|
+
depthOfFieldPreviousGraph: CompiledRenderGraph<RenderPipelineFrame> | null;
|
|
465
|
+
depthOfFieldPreviousGraphKey: string | null;
|
|
466
|
+
depthOfFieldPreviousPerFrameGraph?: RenderFrameState['perFrameGraph'];
|
|
467
|
+
depthOfFieldPreviousGraphGeneration: number;
|
|
468
|
+
depthOfFieldPreviousStandardLightingGraphSignature: string;
|
|
469
|
+
depthOfFieldCandidateGraph: CompiledRenderGraph<RenderPipelineFrame> | null;
|
|
470
|
+
/** DoF graph/params detached at the same successful-submit boundary. */
|
|
471
|
+
depthOfFieldAccepted?:
|
|
472
|
+
| {
|
|
473
|
+
readonly params: DepthOfFieldParams;
|
|
474
|
+
readonly graph: CompiledRenderGraphInfo;
|
|
475
|
+
readonly deviceGeneration: number;
|
|
476
|
+
}
|
|
477
|
+
| undefined;
|
|
478
|
+
/** A DoF candidate was present when the frame failed to submit. */
|
|
479
|
+
depthOfFieldLastSubmitFailed?: boolean | undefined;
|
|
455
480
|
/**
|
|
456
481
|
* Last-known-good graph held while a volume topology candidate is tested.
|
|
457
482
|
* The candidate is promoted only after its command buffer is submitted;
|
package/src/record/frame.ts
CHANGED
|
@@ -34,6 +34,14 @@ import {
|
|
|
34
34
|
RendererOperationError,
|
|
35
35
|
} from '../errors/render';
|
|
36
36
|
import { gpuDrivenDrawKey, gpuDrivenSourceDrawItemIndex } from '../extract/gpu-driven';
|
|
37
|
+
import {
|
|
38
|
+
DEPTH_OF_FIELD_MSAA_POST_PROCESS_IDS,
|
|
39
|
+
DEPTH_OF_FIELD_POST_PROCESS_IDS,
|
|
40
|
+
} from '../features/depth-of-field/depth-of-field-feature';
|
|
41
|
+
import {
|
|
42
|
+
packDepthOfFieldParams,
|
|
43
|
+
resolveDepthOfFieldFrameParams,
|
|
44
|
+
} from '../features/depth-of-field/depth-of-field-params';
|
|
37
45
|
import type { GpuDrivenProduction, PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
|
|
38
46
|
import type { ShadowViewIdentity } from '../gpu-driven/shadow-views';
|
|
39
47
|
import { GpuBuffer } from '../gpu-resource';
|
|
@@ -323,6 +331,7 @@ import {
|
|
|
323
331
|
executeCompiledFrameGraph,
|
|
324
332
|
getRenderFeatureGraphState,
|
|
325
333
|
type RenderFeatureGraphCandidate,
|
|
334
|
+
settleDepthOfFieldGraphCandidate,
|
|
326
335
|
settleVolumetricFogGraphCandidate,
|
|
327
336
|
} from './typed-frame-graph';
|
|
328
337
|
import {
|
|
@@ -1671,8 +1680,17 @@ export function recordFrame(
|
|
|
1671
1680
|
}),
|
|
1672
1681
|
);
|
|
1673
1682
|
}
|
|
1674
|
-
const
|
|
1675
|
-
if (!
|
|
1683
|
+
const requestedCamera = activeCameras[0];
|
|
1684
|
+
if (!requestedCamera) return false;
|
|
1685
|
+
const frameDepthOfField = resolveDepthOfFieldFrameParams(
|
|
1686
|
+
requestedCamera.depthOfField,
|
|
1687
|
+
requestedCamera.depthOfFieldError,
|
|
1688
|
+
frameState.depthOfFieldAccepted?.params,
|
|
1689
|
+
);
|
|
1690
|
+
const camera: CameraSnapshot =
|
|
1691
|
+
frameDepthOfField === undefined || frameDepthOfField === requestedCamera.depthOfField
|
|
1692
|
+
? requestedCamera
|
|
1693
|
+
: { ...requestedCamera, depthOfField: frameDepthOfField };
|
|
1676
1694
|
frameState.ssrRequested = camera.screenSpaceReflection !== undefined;
|
|
1677
1695
|
const pipelineState = internals.getPipelineState();
|
|
1678
1696
|
if (pipelineState === null) return false;
|
|
@@ -2643,6 +2661,20 @@ export function recordFrame(
|
|
|
2643
2661
|
motionBlurView.setUint32(12, temporalView?.historyValid === true ? 0 : 1, true);
|
|
2644
2662
|
framePostProcessParams.set('forgeax.motion-blur', motionBlurParams);
|
|
2645
2663
|
}
|
|
2664
|
+
if (camera.depthOfField !== undefined) {
|
|
2665
|
+
const dofParams = packDepthOfFieldParams(camera.depthOfField, {
|
|
2666
|
+
outputHeight: targetH,
|
|
2667
|
+
near: camera.near,
|
|
2668
|
+
far: camera.far,
|
|
2669
|
+
useTemporalDepth: camera.antialias === 'taa' && temporalView !== undefined,
|
|
2670
|
+
});
|
|
2671
|
+
for (const id of [
|
|
2672
|
+
...DEPTH_OF_FIELD_POST_PROCESS_IDS,
|
|
2673
|
+
...DEPTH_OF_FIELD_MSAA_POST_PROCESS_IDS,
|
|
2674
|
+
]) {
|
|
2675
|
+
framePostProcessParams.set(id, dofParams);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2646
2678
|
|
|
2647
2679
|
const passCtx: _InternalRenderPipelineContext = {
|
|
2648
2680
|
assets: internals.assets,
|
|
@@ -2925,11 +2957,12 @@ export function recordFrame(
|
|
|
2925
2957
|
frameState.ssrHistoryOwner?.abortFrame(frameState.ssrHistoryCandidate, 'submit');
|
|
2926
2958
|
frameState.ssrHistoryCandidate = undefined;
|
|
2927
2959
|
}
|
|
2928
|
-
//
|
|
2929
|
-
// succeeds. This runs for every early-return path as well, so a
|
|
2930
|
-
// encoder, or graph failure cannot leave a non-submitted
|
|
2931
|
-
// next frame's accepted graph.
|
|
2960
|
+
// DoF and volume topology replacements are provisional until the queue
|
|
2961
|
+
// submission succeeds. This runs for every early-return path as well, so a
|
|
2962
|
+
// swapchain, encoder, or graph failure cannot leave a non-submitted
|
|
2963
|
+
// candidate as the next frame's accepted graph.
|
|
2932
2964
|
settleVolumetricFogGraphCandidate(frameState, submitted);
|
|
2965
|
+
settleDepthOfFieldGraphCandidate(frameState, submitted);
|
|
2933
2966
|
if (volumeInspectionReady && (submitted || volumeSubmissionAttempted)) {
|
|
2934
2967
|
updateVolumetricFogInspection(
|
|
2935
2968
|
frameState,
|
package/src/record/main-pass.ts
CHANGED
|
@@ -676,11 +676,7 @@ export function recordMainPass(
|
|
|
676
676
|
hint: 'publish the current frame view/light/shadow bind group before GPU raster',
|
|
677
677
|
});
|
|
678
678
|
}
|
|
679
|
-
if (
|
|
680
|
-
gpuDrivenStandardPbrFrameResources === undefined ||
|
|
681
|
-
gpuDrivenStandardPbrFrameResources.materialBindGroups.length === 0 ||
|
|
682
|
-
gpuDrivenStandardPbrFrameResources.materialBindGroups.some((group) => group === undefined)
|
|
683
|
-
) {
|
|
679
|
+
if (gpuDrivenStandardPbrFrameResources === undefined) {
|
|
684
680
|
throw new RhiError({
|
|
685
681
|
code: 'rhi-not-available',
|
|
686
682
|
expected: 'complete main-pass Standard PBR material bind-group receipt',
|
|
@@ -23,6 +23,8 @@ import { RhiError } from '@forgeax/engine-rhi';
|
|
|
23
23
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
24
24
|
import { executeRendererFrameTransaction } from '../assembly/renderer-frame-transaction';
|
|
25
25
|
import { type RenderError, RenderFeatureStageFailedError } from '../errors/render';
|
|
26
|
+
import { depthOfFieldTopology } from '../features/depth-of-field/depth-of-field-feature';
|
|
27
|
+
import { resolveDepthOfFieldFrameParams } from '../features/depth-of-field/depth-of-field-params';
|
|
26
28
|
import {
|
|
27
29
|
getRenderFeaturePlanExecutionProjection,
|
|
28
30
|
hasSubmissionSensitiveFeatures,
|
|
@@ -1419,6 +1421,7 @@ function topologyOf(
|
|
|
1419
1421
|
: undefined;
|
|
1420
1422
|
const preparedLut =
|
|
1421
1423
|
frameState.pendingStandardLutGpuResources ?? frameState.standardLutGpuResources;
|
|
1424
|
+
const depthOfField = depthOfFieldTopology(camera.depthOfField);
|
|
1422
1425
|
return ok({
|
|
1423
1426
|
pipelineId: STANDARD_PIPELINE_ID,
|
|
1424
1427
|
standardProfile: internals.standardProfile,
|
|
@@ -1438,6 +1441,7 @@ function topologyOf(
|
|
|
1438
1441
|
tonemap: camera.tonemap,
|
|
1439
1442
|
antialias: camera.antialias,
|
|
1440
1443
|
bloom: camera.bloom,
|
|
1444
|
+
...(depthOfField === undefined ? {} : { depthOfField }),
|
|
1441
1445
|
},
|
|
1442
1446
|
atmosphere: !clearOnly && frameState.environmentFrame?.source.kind === 'atmosphere',
|
|
1443
1447
|
output: {
|
|
@@ -1754,18 +1758,66 @@ export function settleVolumetricFogGraphCandidate(
|
|
|
1754
1758
|
if (candidate === null) return;
|
|
1755
1759
|
const previous = frameState.volumetricFogPreviousGraph ?? null;
|
|
1756
1760
|
const previousKey = frameState.volumetricFogPreviousGraphKey ?? null;
|
|
1761
|
+
// A frame can replace both the volume and DoF topology at once. DoF owns
|
|
1762
|
+
// the shared compiled graph's full projection (per-frame resolver,
|
|
1763
|
+
// generation, and lighting signature), so let it settle the graph exactly
|
|
1764
|
+
// once while volume clears only its own candidate marker.
|
|
1765
|
+
const sharedWithDepthOfField = frameState.depthOfFieldCandidateGraph === candidate;
|
|
1757
1766
|
if (submitted) {
|
|
1758
|
-
if (previous !== null && previous !== candidate)
|
|
1767
|
+
if (!sharedWithDepthOfField && previous !== null && previous !== candidate) {
|
|
1768
|
+
retire(frameState, previous);
|
|
1769
|
+
}
|
|
1759
1770
|
} else {
|
|
1760
|
-
if (
|
|
1761
|
-
|
|
1762
|
-
|
|
1771
|
+
if (!sharedWithDepthOfField) {
|
|
1772
|
+
if (candidate !== previous) retire(frameState, candidate);
|
|
1773
|
+
frameState.compiledFrameGraph = previous;
|
|
1774
|
+
frameState.compiledFrameGraphTopologyKey = previousKey;
|
|
1775
|
+
}
|
|
1763
1776
|
}
|
|
1764
1777
|
frameState.volumetricFogPreviousGraph = null;
|
|
1765
1778
|
frameState.volumetricFogPreviousGraphKey = null;
|
|
1766
1779
|
frameState.volumetricFogCandidateGraph = null;
|
|
1767
1780
|
}
|
|
1768
1781
|
|
|
1782
|
+
/**
|
|
1783
|
+
* Settle a DoF graph replacement at the same submit barrier as the frame.
|
|
1784
|
+
* Graph compilation publishes a candidate for the current record pass, but a
|
|
1785
|
+
* failed encode/finish/submit must restore every graph-owned LKG projection
|
|
1786
|
+
* before the next frame can inspect or reuse it.
|
|
1787
|
+
*/
|
|
1788
|
+
export function settleDepthOfFieldGraphCandidate(
|
|
1789
|
+
frameState: RenderFrameState,
|
|
1790
|
+
submitted: boolean,
|
|
1791
|
+
): void {
|
|
1792
|
+
const candidate = frameState.depthOfFieldCandidateGraph;
|
|
1793
|
+
if (candidate === null || candidate === undefined) return;
|
|
1794
|
+
// Sparse synthetic frame states used by graph tests predate the explicit
|
|
1795
|
+
// transaction fields. Treat omitted pending values as the empty LKG rather
|
|
1796
|
+
// than passing undefined into retirement or restoring an invalid key.
|
|
1797
|
+
const previous = frameState.depthOfFieldPreviousGraph ?? null;
|
|
1798
|
+
if (submitted) {
|
|
1799
|
+
if (previous !== null && previous !== candidate) retire(frameState, previous);
|
|
1800
|
+
} else {
|
|
1801
|
+
if (candidate !== previous) retire(frameState, candidate);
|
|
1802
|
+
frameState.compiledFrameGraph = previous;
|
|
1803
|
+
frameState.compiledFrameGraphTopologyKey = frameState.depthOfFieldPreviousGraphKey ?? null;
|
|
1804
|
+
if (frameState.depthOfFieldPreviousPerFrameGraph === undefined) {
|
|
1805
|
+
delete frameState.perFrameGraph;
|
|
1806
|
+
} else {
|
|
1807
|
+
frameState.perFrameGraph = frameState.depthOfFieldPreviousPerFrameGraph;
|
|
1808
|
+
}
|
|
1809
|
+
frameState.graphGeneration = frameState.depthOfFieldPreviousGraphGeneration ?? 0;
|
|
1810
|
+
frameState.standardLightingGraphSignature =
|
|
1811
|
+
frameState.depthOfFieldPreviousStandardLightingGraphSignature ?? '';
|
|
1812
|
+
}
|
|
1813
|
+
frameState.depthOfFieldPreviousGraph = null;
|
|
1814
|
+
frameState.depthOfFieldPreviousGraphKey = null;
|
|
1815
|
+
frameState.depthOfFieldPreviousPerFrameGraph = undefined;
|
|
1816
|
+
frameState.depthOfFieldPreviousGraphGeneration = 0;
|
|
1817
|
+
frameState.depthOfFieldPreviousStandardLightingGraphSignature = '';
|
|
1818
|
+
frameState.depthOfFieldCandidateGraph = null;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1769
1821
|
export function ensureCompiledFrameGraph(
|
|
1770
1822
|
internals: RenderSystemInternals,
|
|
1771
1823
|
frameState: RenderFrameState,
|
|
@@ -1791,6 +1843,18 @@ export function ensureCompiledFrameGraph(
|
|
|
1791
1843
|
const featureMetrics = mutableFeatureGraphInspection(internals);
|
|
1792
1844
|
featureMetrics.ensureCalls += 1;
|
|
1793
1845
|
const requestedStandardLightingSignature = standardLightingTopologySignature(standardLighting);
|
|
1846
|
+
const frameDepthOfField = resolveDepthOfFieldFrameParams(
|
|
1847
|
+
camera.depthOfField,
|
|
1848
|
+
camera.depthOfFieldError,
|
|
1849
|
+
frameState.depthOfFieldAccepted?.params,
|
|
1850
|
+
);
|
|
1851
|
+
// A rejected authoring request remains visible on the source camera, while
|
|
1852
|
+
// an accepted graph continues to receive its own params until a valid
|
|
1853
|
+
// replacement or explicit component removal is submitted.
|
|
1854
|
+
const graphCamera: CameraSnapshot =
|
|
1855
|
+
frameDepthOfField === undefined || frameDepthOfField === camera.depthOfField
|
|
1856
|
+
? camera
|
|
1857
|
+
: { ...camera, depthOfField: frameDepthOfField };
|
|
1794
1858
|
const lastKnownGood = (): CompiledRenderGraph<RenderPipelineFrame> | null =>
|
|
1795
1859
|
frameState.compiledFrameGraph !== null &&
|
|
1796
1860
|
!hasSubmissionSensitiveFeatures(getRenderFeatureGraphState(internals).plans) &&
|
|
@@ -1825,7 +1889,7 @@ export function ensureCompiledFrameGraph(
|
|
|
1825
1889
|
internals,
|
|
1826
1890
|
frameState,
|
|
1827
1891
|
pipelineState,
|
|
1828
|
-
|
|
1892
|
+
graphCamera,
|
|
1829
1893
|
clearOnly,
|
|
1830
1894
|
lights,
|
|
1831
1895
|
surfaceWidth,
|
|
@@ -2032,6 +2096,7 @@ export function ensureCompiledFrameGraph(
|
|
|
2032
2096
|
const built = frameState.activePipeline.build(
|
|
2033
2097
|
{
|
|
2034
2098
|
graph: builder,
|
|
2099
|
+
camera: { depthOfField: graphCamera.depthOfField },
|
|
2035
2100
|
observationCaptureDomains: internals.observationCaptureDomains,
|
|
2036
2101
|
...(autoExposureResources?.ok === true
|
|
2037
2102
|
? { standardOutput: { autoExposure: autoExposureResources.value } }
|
|
@@ -2172,10 +2237,32 @@ export function ensureCompiledFrameGraph(
|
|
|
2172
2237
|
return lastKnownGood();
|
|
2173
2238
|
}
|
|
2174
2239
|
const previous = frameState.compiledFrameGraph;
|
|
2240
|
+
const depthOfFieldReplacement =
|
|
2241
|
+
(frameState.depthOfFieldCandidateGraph === null ||
|
|
2242
|
+
frameState.depthOfFieldCandidateGraph === undefined) &&
|
|
2243
|
+
(topologyResult.value.camera.depthOfField !== undefined ||
|
|
2244
|
+
frameState.depthOfFieldAccepted !== undefined);
|
|
2175
2245
|
if (volumeTopologyCandidate) {
|
|
2176
2246
|
frameState.volumetricFogPreviousGraph = previous;
|
|
2177
2247
|
frameState.volumetricFogPreviousGraphKey = frameState.compiledFrameGraphTopologyKey;
|
|
2178
2248
|
frameState.volumetricFogCandidateGraph = compiled.value;
|
|
2249
|
+
if (depthOfFieldReplacement) {
|
|
2250
|
+
frameState.depthOfFieldPreviousGraph = previous;
|
|
2251
|
+
frameState.depthOfFieldPreviousGraphKey = frameState.compiledFrameGraphTopologyKey;
|
|
2252
|
+
frameState.depthOfFieldPreviousPerFrameGraph = frameState.perFrameGraph;
|
|
2253
|
+
frameState.depthOfFieldPreviousGraphGeneration = frameState.graphGeneration;
|
|
2254
|
+
frameState.depthOfFieldPreviousStandardLightingGraphSignature =
|
|
2255
|
+
frameState.standardLightingGraphSignature;
|
|
2256
|
+
frameState.depthOfFieldCandidateGraph = compiled.value;
|
|
2257
|
+
}
|
|
2258
|
+
} else if (depthOfFieldReplacement) {
|
|
2259
|
+
frameState.depthOfFieldPreviousGraph = previous;
|
|
2260
|
+
frameState.depthOfFieldPreviousGraphKey = frameState.compiledFrameGraphTopologyKey;
|
|
2261
|
+
frameState.depthOfFieldPreviousPerFrameGraph = frameState.perFrameGraph;
|
|
2262
|
+
frameState.depthOfFieldPreviousGraphGeneration = frameState.graphGeneration;
|
|
2263
|
+
frameState.depthOfFieldPreviousStandardLightingGraphSignature =
|
|
2264
|
+
frameState.standardLightingGraphSignature;
|
|
2265
|
+
frameState.depthOfFieldCandidateGraph = compiled.value;
|
|
2179
2266
|
}
|
|
2180
2267
|
frameState.compiledFrameGraph = compiled.value;
|
|
2181
2268
|
frameState.compiledFrameGraphTopologyKey = key;
|
|
@@ -2209,7 +2296,9 @@ export function ensureCompiledFrameGraph(
|
|
|
2209
2296
|
} else if (featureGraphCandidate !== undefined) {
|
|
2210
2297
|
recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'abandoned');
|
|
2211
2298
|
}
|
|
2212
|
-
if (!volumeTopologyCandidate && previous !== null)
|
|
2299
|
+
if (!volumeTopologyCandidate && !depthOfFieldReplacement && previous !== null) {
|
|
2300
|
+
retire(frameState, previous);
|
|
2301
|
+
}
|
|
2213
2302
|
return compiled.value;
|
|
2214
2303
|
}
|
|
2215
2304
|
|
|
@@ -2245,6 +2334,7 @@ export function executeCompiledFrameGraph(
|
|
|
2245
2334
|
frameState.pendingAutoExposureState = undefined;
|
|
2246
2335
|
frameState.pendingStandardLutState = undefined;
|
|
2247
2336
|
settleVolumetricFogGraphCandidate(frameState, false);
|
|
2337
|
+
settleDepthOfFieldGraphCandidate(frameState, false);
|
|
2248
2338
|
rejectTemporalFrame();
|
|
2249
2339
|
return false;
|
|
2250
2340
|
}
|
|
@@ -2308,6 +2398,8 @@ export function executeCompiledFrameGraph(
|
|
|
2308
2398
|
},
|
|
2309
2399
|
}),
|
|
2310
2400
|
);
|
|
2401
|
+
settleVolumetricFogGraphCandidate(frameState, false);
|
|
2402
|
+
settleDepthOfFieldGraphCandidate(frameState, false);
|
|
2311
2403
|
return false;
|
|
2312
2404
|
}
|
|
2313
2405
|
const capturedGeneration = deviceScope?.generation;
|
|
@@ -3000,6 +3092,8 @@ export function executeCompiledFrameGraph(
|
|
|
3000
3092
|
frameState.pendingAtmospherePublish?.();
|
|
3001
3093
|
frameState.pendingAtmospherePublish = undefined;
|
|
3002
3094
|
onSubmitted?.(internals.device.queue.onSubmittedWorkDone());
|
|
3095
|
+
settleVolumetricFogGraphCandidate(frameState, true);
|
|
3096
|
+
settleDepthOfFieldGraphCandidate(frameState, true);
|
|
3003
3097
|
},
|
|
3004
3098
|
abort: (failure) => {
|
|
3005
3099
|
frameState.pendingAtmospherePublish = undefined;
|
|
@@ -3038,9 +3132,15 @@ export function executeCompiledFrameGraph(
|
|
|
3038
3132
|
frameState.environmentLifecycle?.discard(frameState.environmentGeneration);
|
|
3039
3133
|
frameState.environmentGeneration = undefined;
|
|
3040
3134
|
}
|
|
3135
|
+
settleVolumetricFogGraphCandidate(frameState, false);
|
|
3136
|
+
settleDepthOfFieldGraphCandidate(frameState, false);
|
|
3041
3137
|
},
|
|
3042
3138
|
});
|
|
3139
|
+
// The callbacks above settle the normal transaction path. Keep this guard
|
|
3140
|
+
// for synthetic callers and any future early transaction result that does
|
|
3141
|
+
// not enter a callback.
|
|
3043
3142
|
settleVolumetricFogGraphCandidate(frameState, transaction.ok);
|
|
3143
|
+
settleDepthOfFieldGraphCandidate(frameState, transaction.ok);
|
|
3044
3144
|
if (!transaction.ok) rejectTemporalFrame();
|
|
3045
3145
|
return transaction.ok;
|
|
3046
3146
|
}
|
|
@@ -232,6 +232,12 @@ export function createRenderSystemRecovery(
|
|
|
232
232
|
directionalShadowCacheRecorded: false,
|
|
233
233
|
compiledFrameGraph: null,
|
|
234
234
|
compiledFrameGraphTopologyKey: null,
|
|
235
|
+
depthOfFieldPreviousGraph: null,
|
|
236
|
+
depthOfFieldPreviousGraphKey: null,
|
|
237
|
+
depthOfFieldPreviousPerFrameGraph: undefined,
|
|
238
|
+
depthOfFieldPreviousGraphGeneration: 0,
|
|
239
|
+
depthOfFieldPreviousStandardLightingGraphSignature: '',
|
|
240
|
+
depthOfFieldCandidateGraph: null,
|
|
235
241
|
volumetricFogPreviousGraph: null,
|
|
236
242
|
volumetricFogPreviousGraphKey: null,
|
|
237
243
|
volumetricFogCandidateGraph: null,
|
package/src/render-contract.ts
CHANGED
|
@@ -31,10 +31,15 @@ import type { EnvironmentInspection } from './environment/inspection.js';
|
|
|
31
31
|
import type { GpuDrivenPreparationError } from './errors/gpu-driven.js';
|
|
32
32
|
import type { RecoveryOutcome, RecoveryPhase } from './errors/recover.js';
|
|
33
33
|
import type { RenderError } from './errors/render.js';
|
|
34
|
+
import type {
|
|
35
|
+
DepthOfFieldParams,
|
|
36
|
+
DepthOfFieldRequestFailure,
|
|
37
|
+
} from './features/depth-of-field/depth-of-field-params.js';
|
|
34
38
|
import type { RenderFeature, RenderFeatureDiagnostics } from './features/types.js';
|
|
35
39
|
import type { PostProcessShaderEntry } from './fullscreen-post-process-pass.js';
|
|
36
40
|
import type {
|
|
37
41
|
BloomInspection,
|
|
42
|
+
DepthOfFieldInspection,
|
|
38
43
|
DirectionalShadowInspection,
|
|
39
44
|
LightInspection,
|
|
40
45
|
LodOcclusionInspection,
|
|
@@ -171,6 +176,10 @@ export interface CameraSnapshot {
|
|
|
171
176
|
readonly screenSpaceReflection?: ScreenSpaceReflectionData;
|
|
172
177
|
/** Renderer-owned temporal projection used by Standard's TAA producer. */
|
|
173
178
|
readonly temporal?: TemporalView;
|
|
179
|
+
/** Active-camera depth-of-field parameters, present only when demanded. */
|
|
180
|
+
readonly depthOfField?: DepthOfFieldParams;
|
|
181
|
+
/** Structured DoF validation facts; invalid requests remain inspectable. */
|
|
182
|
+
readonly depthOfFieldError?: DepthOfFieldRequestFailure;
|
|
174
183
|
/** Active-camera motion blur parameters, present only when demanded. */
|
|
175
184
|
readonly motionBlur?: {
|
|
176
185
|
readonly shutterAngle: number;
|
|
@@ -776,6 +785,8 @@ export interface RenderInspection {
|
|
|
776
785
|
readonly temporalTarget?: TemporalTargetInspection;
|
|
777
786
|
/** Renderer-owned Motion Blur facts; absent when no component is active. */
|
|
778
787
|
readonly motionBlur?: MotionBlurInspection;
|
|
788
|
+
/** Renderer-owned Depth of Field facts; absent when no component is active. */
|
|
789
|
+
readonly depthOfField?: DepthOfFieldInspection;
|
|
779
790
|
/** Bounded renderer-owned LOD/occlusion facts; absent before a candidate frame. */
|
|
780
791
|
readonly lodOcclusion?: LodOcclusionInspection;
|
|
781
792
|
}
|
|
@@ -577,6 +577,7 @@ function dispatchFullscreenPass(
|
|
|
577
577
|
graphDepthView?: TextureView,
|
|
578
578
|
paramsOverride?: Uint8Array,
|
|
579
579
|
fragmentEntryPoint?: string,
|
|
580
|
+
msaaActive = false,
|
|
580
581
|
): void {
|
|
581
582
|
if (shader === 'fxaa') {
|
|
582
583
|
if (resolveCtx === undefined) {
|
|
@@ -643,7 +644,7 @@ function dispatchFullscreenPass(
|
|
|
643
644
|
const built = buildFullscreenPostProcessPass(
|
|
644
645
|
{ device: ctx.runtime.device, errorRegistry: ctx.runtime.errorRegistry },
|
|
645
646
|
entry,
|
|
646
|
-
|
|
647
|
+
msaaActive,
|
|
647
648
|
);
|
|
648
649
|
if (built === null) return;
|
|
649
650
|
|
|
@@ -842,6 +843,8 @@ export function encodeFullscreenPass(
|
|
|
842
843
|
readonly depthView?: TextureView | undefined;
|
|
843
844
|
readonly paramsOverride?: Uint8Array | undefined;
|
|
844
845
|
readonly fragmentEntryPoint?: string | undefined;
|
|
846
|
+
/** The depth target's actual sample count, when this typed pass reads depth. */
|
|
847
|
+
readonly msaaActive?: boolean | undefined;
|
|
845
848
|
},
|
|
846
849
|
): void {
|
|
847
850
|
dispatchFullscreenPass(
|
|
@@ -858,5 +861,9 @@ export function encodeFullscreenPass(
|
|
|
858
861
|
input.depthView,
|
|
859
862
|
input.paramsOverride,
|
|
860
863
|
input.fragmentEntryPoint,
|
|
864
|
+
// Legacy callers rely on the frame context's MSAA state. Typed callers
|
|
865
|
+
// pass the actual depth target sample count explicitly so a single-sample
|
|
866
|
+
// view can never select the multisampled shader by camera policy alone.
|
|
867
|
+
input.msaaActive ?? ctx.msaaActive,
|
|
861
868
|
);
|
|
862
869
|
}
|
package/src/render-pipeline.ts
CHANGED
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
} from '@forgeax/engine-rhi';
|
|
38
38
|
import { ok, type Result } from '@forgeax/engine-types';
|
|
39
39
|
import type { Tonemap } from './components/camera';
|
|
40
|
+
import type { DepthOfFieldSide } from './components/depth-of-field';
|
|
40
41
|
import type { DirectionalShadowFilterLabel } from './components/directional-shadow-filter';
|
|
41
42
|
import type { RenderError } from './errors/render';
|
|
42
43
|
import type { RenderFeatureTargetKind } from './features/targets';
|
|
@@ -234,7 +235,16 @@ export interface RenderPipelineTopology {
|
|
|
234
235
|
};
|
|
235
236
|
/** Renderer-owned immutable dimensions for Standard graph consumers. */
|
|
236
237
|
readonly extent?: RenderExtent;
|
|
237
|
-
readonly camera: Pick<RenderPipelineContext['camera'], 'tonemap' | 'antialias' | 'bloom'
|
|
238
|
+
readonly camera: Pick<RenderPipelineContext['camera'], 'tonemap' | 'antialias' | 'bloom'> & {
|
|
239
|
+
/** Shape-only DoF admission; optical values remain per-frame UBO data. */
|
|
240
|
+
readonly depthOfField?:
|
|
241
|
+
| {
|
|
242
|
+
readonly blurSide: DepthOfFieldSide;
|
|
243
|
+
readonly useNear: boolean;
|
|
244
|
+
readonly useFar: boolean;
|
|
245
|
+
}
|
|
246
|
+
| undefined;
|
|
247
|
+
};
|
|
238
248
|
/** Frame-stable Standard output policy; no raw RHI handles enter topology. */
|
|
239
249
|
readonly output?: {
|
|
240
250
|
readonly autoExposure: boolean;
|
|
@@ -475,6 +485,12 @@ export interface GpuDrivenStandardPbrFrameResources {
|
|
|
475
485
|
|
|
476
486
|
export interface RenderPipelineBuildContext<FrameCtx extends RenderPipelineFrame> {
|
|
477
487
|
readonly graph: RenderGraphBuilder<FrameCtx>;
|
|
488
|
+
/** Per-frame camera values consumed by optical fullscreen features. */
|
|
489
|
+
readonly camera?: {
|
|
490
|
+
readonly depthOfField?:
|
|
491
|
+
| import('./features/depth-of-field/depth-of-field-params').DepthOfFieldParams
|
|
492
|
+
| undefined;
|
|
493
|
+
};
|
|
478
494
|
/** Optional Standard-only output-domain coverage producer input. */
|
|
479
495
|
readonly targetCoverage?: import('./temporal/target-coverage-attachment').TargetCoverageAttachment;
|
|
480
496
|
/** Explicit receipt-bound color domains requested for this frame. */
|