@forgeax/engine-render 0.1.32 → 0.1.33
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 +379 -33
- package/dist/assembly/analytic-fog-registration.d.ts +3 -0
- package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
- package/dist/assembly/fxaa-registration.d.ts +4 -0
- package/dist/assembly/fxaa-registration.d.ts.map +1 -0
- package/dist/assembly/lod-observation.d.ts +4 -0
- package/dist/assembly/lod-observation.d.ts.map +1 -0
- package/dist/assembly/motion-blur-registration.d.ts +3 -0
- package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
- package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/bloom-admission.d.ts +13 -0
- package/dist/bloom-admission.d.ts.map +1 -0
- package/dist/{chunk-DRR7D3KX.mjs → chunk-3RTCJTH4.mjs} +12 -9
- package/dist/chunk-3RTCJTH4.mjs.map +1 -0
- package/dist/{chunk-S665NJNF.mjs → chunk-LEAXOMYJ.mjs} +3 -3
- package/dist/{chunk-S665NJNF.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
- package/dist/{chunk-6TFBXLRJ.mjs → chunk-NB2S7ZRX.mjs} +155 -11
- package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
- package/dist/{chunk-LULCA3OF.mjs → chunk-NNQL7CB3.mjs} +4 -4
- package/dist/{chunk-LULCA3OF.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
- package/dist/{chunk-4NNZQCL3.mjs → chunk-OO3DRYK7.mjs} +66 -200
- package/dist/chunk-OO3DRYK7.mjs.map +1 -0
- package/dist/{chunk-GWCEMDVR.mjs → chunk-PLAXOOX5.mjs} +232 -7
- package/dist/chunk-PLAXOOX5.mjs.map +1 -0
- package/dist/{chunk-JRIPCMYC.mjs → chunk-VOZWDM3L.mjs} +283 -193
- package/dist/chunk-VOZWDM3L.mjs.map +1 -0
- package/dist/{chunk-JCIRYQ4V.mjs → chunk-ZDQWGQW7.mjs} +10 -4
- package/dist/chunk-ZDQWGQW7.mjs.map +1 -0
- package/dist/components/atmosphere.d.ts +2 -0
- package/dist/components/atmosphere.d.ts.map +1 -1
- package/dist/components/camera.d.ts +20 -4
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/mesh-filter.d.ts +6 -5
- package/dist/components/mesh-filter.d.ts.map +1 -1
- package/dist/components/motion-blur.d.ts +1 -0
- package/dist/components/motion-blur.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +2865 -526
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +6 -0
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/analytic-fog.d.ts +6 -0
- package/dist/environment/analytic-fog.d.ts.map +1 -0
- package/dist/errors/render.d.ts +6 -2
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +7 -0
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/environment.d.ts +2 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/features/error-reporting.d.ts +13 -0
- package/dist/features/error-reporting.d.ts.map +1 -0
- package/dist/features/host.d.ts +5 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
- package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
- package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
- package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
- package/dist/features/plan.d.ts +7 -4
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +9 -5
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +7 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +3 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +9 -0
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +6 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/inspection-types.d.ts +43 -2
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances.d.ts +8 -1
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- package/dist/pipeline/standard-post.d.ts +28 -19
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +2 -2
- package/dist/record/frame-snapshot.d.ts +3 -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 +5 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +6 -5
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +12 -28
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +5 -3
- package/dist/record/skybox-post-pass.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.map +1 -1
- package/dist/render-contract.d.ts +24 -4
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +3 -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 +8 -1
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +7 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +2 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +7 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/temporal/view.d.ts +6 -1
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +10 -7
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +0 -1
- package/dist/volume/component.d.ts.map +1 -1
- package/dist/volume/passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +3 -1
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
- package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
- package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
- package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
- package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
- package/src/__tests__/environment-selection.unit.test.ts +12 -0
- package/src/__tests__/factory-contract.integration.test.ts +331 -68
- package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
- package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +1 -1
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +11 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
- package/src/__tests__/gpu-scene.unit.test.ts +91 -0
- package/src/__tests__/instances-store.unit.test.ts +75 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
- package/src/__tests__/motion-blur-compute.dawn.test.ts +583 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
- package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
- package/src/__tests__/public-surface.test-d.ts +8 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +2 -0
- package/src/__tests__/render-feature-error-reporting.unit.test.ts +24 -0
- package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +31 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +7 -1
- package/src/__tests__/shader-manifest-fixture.ts +4 -4
- package/src/__tests__/skinned-shadow-caster.test.ts +2 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
- package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
- package/src/__tests__/standard-pipeline.integration.test.ts +18 -8
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
- package/src/__tests__/temporal-view.unit.test.ts +2 -1
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
- package/src/__tests__/volume-optics-oracle.unit.test.ts +3 -1
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +177 -13
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +447 -61
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +19 -3
- package/src/assembly/analytic-fog-registration.ts +14 -0
- package/src/assembly/fxaa-registration.ts +17 -0
- package/src/assembly/lod-observation.ts +12 -0
- package/src/assembly/motion-blur-registration.ts +17 -0
- package/src/assembly/renderer-facade.ts +2 -0
- package/src/assembly/temporal-post-process-prewarm.ts +40 -0
- package/src/assembly/webgpu-ready-contract.ts +8 -3
- package/src/assembly/webgpu-ready.ts +181 -245
- package/src/assembly/webgpu-renderer.ts +16 -31
- package/src/bloom-admission.ts +21 -0
- package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
- package/src/components/atmosphere.ts +4 -0
- package/src/components/camera.ts +66 -10
- package/src/components/mesh-filter.ts +11 -9
- package/src/components/motion-blur.ts +1 -0
- package/src/device/gpu-residency.ts +56 -12
- package/src/environment/analytic-fog.ts +44 -0
- package/src/environment/background.ts +2 -2
- package/src/errors/render.ts +14 -1
- package/src/extract/camera.ts +20 -7
- package/src/extract/contracts.ts +7 -0
- package/src/extract/environment.ts +4 -0
- package/src/features/error-reporting.ts +78 -0
- package/src/features/host.ts +25 -12
- package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
- package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
- package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
- package/src/features/motion-blur/motion-blur-params.ts +70 -2
- package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
- package/src/features/plan.ts +25 -8
- package/src/features/prepared-gpu-work.ts +86 -35
- package/src/features/render-graph-compute.ts +13 -5
- package/src/features/render-graph-contribution.ts +2 -0
- package/src/gpu-driven/material-artifacts.ts +2 -7
- package/src/gpu-driven/prepared-draw.ts +15 -10
- package/src/gpu-driven/production-raster.ts +83 -7
- package/src/gpu-driven/view-gpu.ts +17 -0
- package/src/gpu-scene.ts +188 -21
- package/src/index.ts +32 -0
- package/src/inspection-types.ts +60 -2
- package/src/instances.ts +80 -1
- package/src/pipeline/standard-post.ts +307 -78
- package/src/pipeline-spec.ts +5 -5
- package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
- package/src/record/__tests__/temporal-payload.unit.test.ts +2 -1
- package/src/record/frame-snapshot.ts +5 -1
- package/src/record/frame-targets.ts +10 -6
- package/src/record/frame.ts +163 -22
- package/src/record/main-pass-geometry.ts +30 -1
- package/src/record/main-pass-sprite-draws.ts +6 -1
- package/src/record/mesh-ssbo.ts +43 -6
- package/src/record/render-context.ts +12 -28
- package/src/record/shadow-pass.ts +6 -1
- package/src/record/skybox-post-pass.ts +122 -196
- package/src/record/typed-frame-graph.ts +153 -9
- package/src/recovery/render-system-candidate.ts +22 -10
- package/src/render-contract.ts +23 -4
- package/src/render-pipeline.ts +6 -1
- package/src/render-system-extract-tail.ts +16 -0
- package/src/render-system-extract.ts +45 -13
- package/src/render-system.ts +255 -120
- package/src/scene/render-scene.ts +85 -4
- package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
- package/src/temporal/frame-coordinator.ts +8 -1
- package/src/temporal/frame.ts +20 -0
- package/src/temporal/view.ts +13 -2
- package/src/typed-render-graph-primitives.ts +121 -48
- package/src/volume/component.ts +0 -1
- package/src/volume/passes.ts +8 -4
- package/src/volume/resources.ts +1 -1
- package/src/volume/temporal.ts +11 -0
- package/dist/chunk-4NNZQCL3.mjs.map +0 -1
- package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
- package/dist/chunk-DRR7D3KX.mjs.map +0 -1
- package/dist/chunk-GWCEMDVR.mjs.map +0 -1
- package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
- package/dist/chunk-JRIPCMYC.mjs.map +0 -1
|
@@ -4,12 +4,21 @@ export interface MotionBlurParams {
|
|
|
4
4
|
readonly shutterAngle: number;
|
|
5
5
|
readonly maxRadiusPixels: number;
|
|
6
6
|
readonly sampleCount: number;
|
|
7
|
+
/** Target presentation rate used to convert frame displacement to exposure. 0 follows frame time. */
|
|
8
|
+
readonly targetFps: number;
|
|
7
9
|
}
|
|
8
10
|
|
|
11
|
+
/** WGSL uniform size for the renderer-owned Motion Blur parameter block. */
|
|
12
|
+
export const MOTION_BLUR_PARAMS_BYTE_SIZE = 32;
|
|
13
|
+
|
|
14
|
+
/** The only quality tiers the shader is allowed to execute. */
|
|
15
|
+
export type MotionBlurSampleTier = 0 | 4 | 8 | 16;
|
|
16
|
+
|
|
9
17
|
export const DEFAULT_MOTION_BLUR_PARAMS: MotionBlurParams = Object.freeze({
|
|
10
18
|
shutterAngle: 180,
|
|
11
19
|
maxRadiusPixels: 32,
|
|
12
20
|
sampleCount: 8,
|
|
21
|
+
targetFps: 60,
|
|
13
22
|
});
|
|
14
23
|
|
|
15
24
|
export interface MotionBlurInvalidParamsDetail {
|
|
@@ -68,17 +77,76 @@ export function validateMotionBlurParams(
|
|
|
68
77
|
const shutterAngle = value.shutterAngle ?? DEFAULT_MOTION_BLUR_PARAMS.shutterAngle;
|
|
69
78
|
const maxRadiusPixels = value.maxRadiusPixels ?? DEFAULT_MOTION_BLUR_PARAMS.maxRadiusPixels;
|
|
70
79
|
const sampleCount = value.sampleCount ?? DEFAULT_MOTION_BLUR_PARAMS.sampleCount;
|
|
80
|
+
const targetFps = value.targetFps ?? DEFAULT_MOTION_BLUR_PARAMS.targetFps;
|
|
71
81
|
const shutterError = validateField('shutterAngle', shutterAngle, 0, 360);
|
|
72
82
|
if (shutterError !== undefined) return err(shutterError);
|
|
73
83
|
const radiusError = validateField('maxRadiusPixels', maxRadiusPixels, 0, 64);
|
|
74
84
|
if (radiusError !== undefined) return err(radiusError);
|
|
75
85
|
const sampleError = validateField('sampleCount', sampleCount, 4, 16, true);
|
|
76
86
|
if (sampleError !== undefined) return err(sampleError);
|
|
77
|
-
|
|
87
|
+
const targetFpsError = validateField('targetFps', targetFps, 0, 240, true);
|
|
88
|
+
if (targetFpsError !== undefined) return err(targetFpsError);
|
|
89
|
+
return ok(Object.freeze({ shutterAngle, maxRadiusPixels, sampleCount, targetFps }));
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
export function motionBlurTemporalDemand(params: MotionBlurParams | undefined): boolean {
|
|
81
|
-
return params !== undefined && params.shutterAngle > 0;
|
|
93
|
+
return params !== undefined && params.shutterAngle > 0 && params.maxRadiusPixels > 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Round authored work down to the bounded shader tiers without changing exposure length. */
|
|
97
|
+
export function effectiveMotionBlurSampleCount(sampleCount: number): MotionBlurSampleTier {
|
|
98
|
+
if (!Number.isFinite(sampleCount) || sampleCount < 4) return 0;
|
|
99
|
+
if (sampleCount < 8) return 4;
|
|
100
|
+
if (sampleCount < 16) return 8;
|
|
101
|
+
return 16;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Reject a frame interval that cannot represent a stable temporal pair. */
|
|
105
|
+
export function isMotionBlurIntervalValid(frameDeltaSeconds: number): boolean {
|
|
106
|
+
return Number.isFinite(frameDeltaSeconds) && frameDeltaSeconds > 0 && frameDeltaSeconds <= 0.1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Convert current-to-previous frame displacement into the authored target-rate exposure.
|
|
111
|
+
* Invalid intervals return zero so callers can commit a fresh baseline without blurring it.
|
|
112
|
+
*/
|
|
113
|
+
export function motionBlurExposureScale(frameDeltaSeconds: number, targetFps: number): number {
|
|
114
|
+
if (!isMotionBlurIntervalValid(frameDeltaSeconds)) return 0;
|
|
115
|
+
if (
|
|
116
|
+
!Number.isFinite(targetFps) ||
|
|
117
|
+
targetFps < 0 ||
|
|
118
|
+
targetFps > 240 ||
|
|
119
|
+
!Number.isInteger(targetFps)
|
|
120
|
+
) {
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
if (targetFps === 0) return 1;
|
|
124
|
+
return 1 / (targetFps * frameDeltaSeconds);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Derive a render interval from the host sample clock before ECS time policy
|
|
129
|
+
* clamps it. A finite raw pair is never clamped here, so a hitch remains
|
|
130
|
+
* visible to temporal admission.
|
|
131
|
+
*/
|
|
132
|
+
export function motionBlurSampleDelta(
|
|
133
|
+
sampleTimeSeconds: number | undefined,
|
|
134
|
+
previousSampleTimeSeconds: number | undefined,
|
|
135
|
+
fallbackDeltaSeconds: number,
|
|
136
|
+
): number {
|
|
137
|
+
// An explicitly supplied non-finite sample is a temporal discontinuity. Do
|
|
138
|
+
// not silently replace it with ECS delta: that would turn a corrupt host or
|
|
139
|
+
// replay clock into an apparently valid blur interval.
|
|
140
|
+
if (
|
|
141
|
+
(sampleTimeSeconds !== undefined && !Number.isFinite(sampleTimeSeconds)) ||
|
|
142
|
+
(previousSampleTimeSeconds !== undefined && !Number.isFinite(previousSampleTimeSeconds))
|
|
143
|
+
) {
|
|
144
|
+
return Number.NaN;
|
|
145
|
+
}
|
|
146
|
+
if (Number.isFinite(sampleTimeSeconds) && Number.isFinite(previousSampleTimeSeconds)) {
|
|
147
|
+
return (sampleTimeSeconds as number) - (previousSampleTimeSeconds as number);
|
|
148
|
+
}
|
|
149
|
+
return fallbackDeltaSeconds;
|
|
82
150
|
}
|
|
83
151
|
|
|
84
152
|
export function resolveMotionBlurParams(
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { MotionBlurExecutionReceipt, MotionBlurInspection } from '../../inspection-types';
|
|
2
|
+
import type { CameraSnapshot } from '../../render-contract';
|
|
3
|
+
import type { TemporalFrameTransactionInspection } from '../../temporal/frame';
|
|
4
|
+
import type { TemporalView } from '../../temporal/view';
|
|
5
|
+
import type { MotionBlurFeatureInput } from './motion-blur-feature';
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_MOTION_BLUR_PARAMS,
|
|
8
|
+
effectiveMotionBlurSampleCount,
|
|
9
|
+
isMotionBlurIntervalValid,
|
|
10
|
+
type MotionBlurValidationError,
|
|
11
|
+
motionBlurSampleDelta,
|
|
12
|
+
motionBlurTemporalDemand,
|
|
13
|
+
} from './motion-blur-params';
|
|
14
|
+
|
|
15
|
+
export interface MotionBlurRuntimeCapabilities {
|
|
16
|
+
readonly compute: boolean;
|
|
17
|
+
readonly storageBuffer: boolean;
|
|
18
|
+
readonly storageTexture: boolean;
|
|
19
|
+
readonly rgba16floatRenderable: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MotionBlurFramePlan {
|
|
23
|
+
readonly rawSampleDelta: number;
|
|
24
|
+
readonly demanded: boolean;
|
|
25
|
+
readonly featureInput: MotionBlurFeatureInput | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MotionBlurDrawOptions {
|
|
29
|
+
readonly sampleTimeSeconds?: number;
|
|
30
|
+
readonly temporalReset?: true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function motionBlurDrawOptions(input: {
|
|
34
|
+
readonly sampleTimeSeconds?: number;
|
|
35
|
+
readonly temporalReset?: boolean;
|
|
36
|
+
}): MotionBlurDrawOptions {
|
|
37
|
+
return {
|
|
38
|
+
...(input.sampleTimeSeconds === undefined
|
|
39
|
+
? {}
|
|
40
|
+
: { sampleTimeSeconds: input.sampleTimeSeconds }),
|
|
41
|
+
...(input.temporalReset === true ? { temporalReset: true as const } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Derive one motion-blur temporal plan before the record transaction starts. */
|
|
46
|
+
export function planMotionBlurFrame(input: {
|
|
47
|
+
readonly params: CameraSnapshot['motionBlur'];
|
|
48
|
+
readonly sampleTimeSeconds: number | undefined;
|
|
49
|
+
readonly acceptedSampleTimeSeconds: number | undefined;
|
|
50
|
+
readonly fallbackDeltaSeconds: number;
|
|
51
|
+
readonly acceptedTemporal: TemporalView | undefined;
|
|
52
|
+
readonly temporalReset: boolean;
|
|
53
|
+
readonly width: number;
|
|
54
|
+
readonly height: number;
|
|
55
|
+
readonly cameraViewIdentity: string;
|
|
56
|
+
readonly historyVersion: number;
|
|
57
|
+
readonly environmentSignature: string;
|
|
58
|
+
readonly fogSignature: string;
|
|
59
|
+
readonly deviceGeneration: number;
|
|
60
|
+
}): MotionBlurFramePlan {
|
|
61
|
+
const rawSampleDelta = motionBlurSampleDelta(
|
|
62
|
+
input.sampleTimeSeconds,
|
|
63
|
+
input.acceptedSampleTimeSeconds,
|
|
64
|
+
input.fallbackDeltaSeconds,
|
|
65
|
+
);
|
|
66
|
+
const accepted = input.acceptedTemporal;
|
|
67
|
+
const reset =
|
|
68
|
+
input.temporalReset ||
|
|
69
|
+
accepted === undefined ||
|
|
70
|
+
(accepted.historyValid === false && accepted.resetReason === undefined) ||
|
|
71
|
+
!isMotionBlurIntervalValid(rawSampleDelta) ||
|
|
72
|
+
accepted.input.width !== input.width ||
|
|
73
|
+
accepted.input.height !== input.height ||
|
|
74
|
+
accepted.input.viewIdentity !== input.cameraViewIdentity ||
|
|
75
|
+
accepted.input.historyVersion !== input.historyVersion ||
|
|
76
|
+
accepted.input.environmentSignature !== input.environmentSignature ||
|
|
77
|
+
accepted.input.fogSignature !== input.fogSignature ||
|
|
78
|
+
accepted.input.deviceGeneration !== input.deviceGeneration;
|
|
79
|
+
const params =
|
|
80
|
+
input.params === undefined
|
|
81
|
+
? undefined
|
|
82
|
+
: { ...input.params, targetFps: input.params.targetFps ?? 60 };
|
|
83
|
+
const demanded = motionBlurTemporalDemand(params);
|
|
84
|
+
return {
|
|
85
|
+
rawSampleDelta,
|
|
86
|
+
demanded,
|
|
87
|
+
featureInput:
|
|
88
|
+
params === undefined
|
|
89
|
+
? undefined
|
|
90
|
+
: {
|
|
91
|
+
params,
|
|
92
|
+
frameDeltaSeconds: rawSampleDelta,
|
|
93
|
+
reset: demanded && reset,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Project the bounded inspection record without exposing renderer internals. */
|
|
99
|
+
export function projectMotionBlurInspection(input: {
|
|
100
|
+
readonly camera: CameraSnapshot | undefined;
|
|
101
|
+
readonly invalidParams: MotionBlurValidationError | undefined;
|
|
102
|
+
readonly capabilities: MotionBlurRuntimeCapabilities;
|
|
103
|
+
readonly acceptedTemporal: TemporalView | undefined;
|
|
104
|
+
readonly transaction: TemporalFrameTransactionInspection;
|
|
105
|
+
readonly execution: MotionBlurExecutionReceipt | undefined;
|
|
106
|
+
readonly temporalFrameId: number | undefined;
|
|
107
|
+
readonly deviceGeneration: number;
|
|
108
|
+
readonly graphGeneration: number;
|
|
109
|
+
}): MotionBlurInspection | undefined {
|
|
110
|
+
if (input.camera?.motionBlur === undefined && input.invalidParams === undefined) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const motionBlur =
|
|
114
|
+
input.camera?.motionBlur === undefined
|
|
115
|
+
? DEFAULT_MOTION_BLUR_PARAMS
|
|
116
|
+
: { ...input.camera.motionBlur, targetFps: input.camera.motionBlur.targetFps ?? 60 };
|
|
117
|
+
const demanded = motionBlurTemporalDemand(motionBlur);
|
|
118
|
+
const unavailable = demanded && !input.capabilities.rgba16floatRenderable;
|
|
119
|
+
const lane =
|
|
120
|
+
input.capabilities.compute &&
|
|
121
|
+
input.capabilities.storageBuffer &&
|
|
122
|
+
input.capabilities.storageTexture &&
|
|
123
|
+
input.capabilities.rgba16floatRenderable
|
|
124
|
+
? ('compute' as const)
|
|
125
|
+
: ('raster-limited' as const);
|
|
126
|
+
const accepted = input.acceptedTemporal;
|
|
127
|
+
const submitFailure =
|
|
128
|
+
input.transaction.lastFailure === 'queue-submit-failed' ||
|
|
129
|
+
accepted?.resetReason === 'submit-failure';
|
|
130
|
+
const execution = input.execution;
|
|
131
|
+
const executionAccepted =
|
|
132
|
+
execution !== undefined &&
|
|
133
|
+
input.temporalFrameId === execution.frameId &&
|
|
134
|
+
execution.deviceGeneration === input.deviceGeneration &&
|
|
135
|
+
execution.graphGeneration === input.graphGeneration;
|
|
136
|
+
const active =
|
|
137
|
+
demanded &&
|
|
138
|
+
!unavailable &&
|
|
139
|
+
lane === 'compute' &&
|
|
140
|
+
executionAccepted &&
|
|
141
|
+
accepted?.historyValid === true &&
|
|
142
|
+
accepted.resetReason === undefined;
|
|
143
|
+
return {
|
|
144
|
+
enabled: demanded,
|
|
145
|
+
status:
|
|
146
|
+
input.invalidParams !== undefined
|
|
147
|
+
? 'invalid'
|
|
148
|
+
: !demanded
|
|
149
|
+
? 'off'
|
|
150
|
+
: active
|
|
151
|
+
? 'active'
|
|
152
|
+
: !unavailable && lane === 'raster-limited'
|
|
153
|
+
? 'limited'
|
|
154
|
+
: 'reset',
|
|
155
|
+
lane,
|
|
156
|
+
shutterAngle: motionBlur.shutterAngle,
|
|
157
|
+
maxRadiusPixels: motionBlur.maxRadiusPixels,
|
|
158
|
+
sampleCount: motionBlur.sampleCount,
|
|
159
|
+
targetFps: motionBlur.targetFps ?? 60,
|
|
160
|
+
effectiveSampleCount: !demanded ? 0 : effectiveMotionBlurSampleCount(motionBlur.sampleCount),
|
|
161
|
+
temporalDemand: demanded ? 'scene-data-temporal-v1' : 'none',
|
|
162
|
+
passName: 'motion-blur',
|
|
163
|
+
historyWrites: 0,
|
|
164
|
+
passCount: !demanded ? 0 : executionAccepted ? 2 : lane === 'raster-limited' ? 1 : 0,
|
|
165
|
+
tapBudget: !demanded ? 0 : effectiveMotionBlurSampleCount(motionBlur.sampleCount),
|
|
166
|
+
...(executionAccepted ? { execution } : {}),
|
|
167
|
+
...(accepted?.resetReason === undefined ? {} : { resetReason: accepted.resetReason }),
|
|
168
|
+
...(input.invalidParams !== undefined
|
|
169
|
+
? { lastFailure: 'invalid-params' as const }
|
|
170
|
+
: unavailable
|
|
171
|
+
? { lastFailure: 'scene-data-unavailable' as const }
|
|
172
|
+
: submitFailure
|
|
173
|
+
? { lastFailure: 'submit-failure' as const }
|
|
174
|
+
: {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
package/src/features/plan.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { GraphBufferAccess, GraphTextureAccess } from '@forgeax/engine-rend
|
|
|
2
2
|
import type { RhiCaps, TextureFormat } from '@forgeax/engine-rhi';
|
|
3
3
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
4
4
|
import type { RenderError } from '../errors/render';
|
|
5
|
-
import type
|
|
5
|
+
import { isSceneDataTarget, type SceneDataTarget } from '../temporal/scene-data';
|
|
6
6
|
import type {
|
|
7
7
|
RenderFeatureGpuBufferUsage,
|
|
8
8
|
RenderFeatureGpuProgramDescriptor,
|
|
@@ -100,8 +100,8 @@ export type RenderFeatureResourceDeclaration =
|
|
|
100
100
|
}
|
|
101
101
|
| { readonly kind: 'texture-view'; readonly value: unknown }
|
|
102
102
|
| { readonly kind: 'sampler'; readonly value: unknown };
|
|
103
|
-
/** Optional graph target
|
|
104
|
-
readonly logicalTarget?:
|
|
103
|
+
/** Optional graph target or semantic scene-data target backing the resource. */
|
|
104
|
+
readonly logicalTarget?: RenderFeatureSampledTarget;
|
|
105
105
|
}
|
|
106
106
|
| {
|
|
107
107
|
readonly kind: 'compute-bindings';
|
|
@@ -244,7 +244,12 @@ export interface RenderFeatureLogicalTarget {
|
|
|
244
244
|
|
|
245
245
|
export interface RenderFeaturePlanContext {
|
|
246
246
|
readonly caps: Readonly<RhiCaps>;
|
|
247
|
-
readonly frame: {
|
|
247
|
+
readonly frame: {
|
|
248
|
+
readonly frameNumber: number;
|
|
249
|
+
/** Physical render extent used for bounded compute dispatch sizing. */
|
|
250
|
+
readonly width?: number;
|
|
251
|
+
readonly height?: number;
|
|
252
|
+
};
|
|
248
253
|
readonly generation: number;
|
|
249
254
|
readonly targets: readonly RenderFeatureLogicalTarget[];
|
|
250
255
|
/** Read-only semantic scene-data resolver bound to this plan generation. */
|
|
@@ -273,7 +278,7 @@ export interface RenderFeaturePlannedFrame {
|
|
|
273
278
|
}
|
|
274
279
|
|
|
275
280
|
export interface RenderFeatureDerivedAccess {
|
|
276
|
-
readonly resource: string;
|
|
281
|
+
readonly resource: string | SceneDataTarget;
|
|
277
282
|
readonly usage: RenderFeatureResourceUsage;
|
|
278
283
|
}
|
|
279
284
|
|
|
@@ -821,7 +826,16 @@ export function deriveRenderFeaturePassAccess(
|
|
|
821
826
|
declaration?.kind === 'prepared-gpu-resource' &&
|
|
822
827
|
declaration.logicalTarget !== undefined
|
|
823
828
|
) {
|
|
824
|
-
|
|
829
|
+
const layout = layoutEntries.find((candidate) => candidate.binding === entry.binding);
|
|
830
|
+
const usage =
|
|
831
|
+
layout?.storageTexture?.access === 'write-only'
|
|
832
|
+
? 'storage-write'
|
|
833
|
+
: layout?.storageTexture?.access === 'read-only'
|
|
834
|
+
? 'storage-read'
|
|
835
|
+
: layout?.storageTexture?.access === 'read-write'
|
|
836
|
+
? 'sampled-storage-read-write'
|
|
837
|
+
: 'sampled-read';
|
|
838
|
+
accesses.push({ resource: declaration.logicalTarget, usage });
|
|
825
839
|
}
|
|
826
840
|
}
|
|
827
841
|
}
|
|
@@ -895,7 +909,9 @@ export function freezeRenderFeaturePlan(
|
|
|
895
909
|
if (
|
|
896
910
|
resource.kind === 'prepared-gpu-resource' &&
|
|
897
911
|
resource.logicalTarget !== undefined &&
|
|
898
|
-
!
|
|
912
|
+
!(typeof resource.logicalTarget === 'string'
|
|
913
|
+
? targets.has(resource.logicalTarget)
|
|
914
|
+
: isSceneDataTarget(resource.logicalTarget))
|
|
899
915
|
) {
|
|
900
916
|
return err(stageFailure(identity));
|
|
901
917
|
}
|
|
@@ -926,7 +942,8 @@ export function freezeRenderFeaturePlan(
|
|
|
926
942
|
const validPrepared =
|
|
927
943
|
resource?.kind === 'prepared-gpu-resource' &&
|
|
928
944
|
((resource.resource.kind === 'buffer' && layout?.buffer !== undefined) ||
|
|
929
|
-
(resource.resource.kind === 'texture-view' &&
|
|
945
|
+
(resource.resource.kind === 'texture-view' &&
|
|
946
|
+
(layout?.texture !== undefined || layout?.storageTexture !== undefined)) ||
|
|
930
947
|
(resource.resource.kind === 'sampler' && layout?.sampler !== undefined));
|
|
931
948
|
if (!validBuffer && !validPrepared) {
|
|
932
949
|
return err(stageFailure(identity));
|
|
@@ -17,9 +17,13 @@ import { type BindGroupLayoutDescriptor, err, ok, type Result } from '@forgeax/e
|
|
|
17
17
|
import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
|
|
18
18
|
import { type RenderError, RenderFeaturePreparationFailedError } from '../errors/render';
|
|
19
19
|
import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
|
|
20
|
+
import type { SceneDataTarget } from '../temporal/scene-data';
|
|
20
21
|
import type { RenderFeatureTargetHandle } from './targets';
|
|
21
22
|
import type { RenderFeatureShaderModuleMode } from './types';
|
|
22
23
|
|
|
24
|
+
/** A graph target that can be late-bound at encode time. */
|
|
25
|
+
export type RenderFeatureComputeTarget = string | RenderFeatureTargetHandle | SceneDataTarget;
|
|
26
|
+
|
|
23
27
|
declare const RenderFeatureGpuProgramBrand: unique symbol;
|
|
24
28
|
export interface RenderFeatureGpuProgramRef {
|
|
25
29
|
readonly [RenderFeatureGpuProgramBrand]: void;
|
|
@@ -104,8 +108,8 @@ export interface RenderFeatureGpuPrepare {
|
|
|
104
108
|
): Result<RenderFeatureGpuBufferRef, RenderError>;
|
|
105
109
|
prepareTextureView(
|
|
106
110
|
name: string,
|
|
107
|
-
resource: TextureView,
|
|
108
|
-
logicalTarget?:
|
|
111
|
+
resource: TextureView | undefined,
|
|
112
|
+
logicalTarget?: RenderFeatureComputeTarget,
|
|
109
113
|
): Result<RenderFeatureGpuTextureViewRef, RenderError>;
|
|
110
114
|
prepareSampler(name: string, resource: Sampler): Result<RenderFeatureGpuSamplerRef, RenderError>;
|
|
111
115
|
prepareBindings(
|
|
@@ -141,7 +145,8 @@ export interface RenderFeatureGpuComputePassDescriptor {
|
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
export interface RenderFeatureResolvedGpuComputePass {
|
|
144
|
-
readonly sampledTargets?: readonly
|
|
148
|
+
readonly sampledTargets?: readonly RenderFeatureComputeTarget[];
|
|
149
|
+
readonly storageTargets?: readonly RenderFeatureComputeTarget[];
|
|
145
150
|
readonly buffers: readonly {
|
|
146
151
|
readonly name: string;
|
|
147
152
|
readonly buffer: Buffer;
|
|
@@ -154,7 +159,7 @@ export interface RenderFeatureResolvedGpuComputePass {
|
|
|
154
159
|
readonly pipeline: ComputePipeline;
|
|
155
160
|
readonly bindGroup: BindGroup;
|
|
156
161
|
readonly resolveBindGroup?: (
|
|
157
|
-
resolve: (target:
|
|
162
|
+
resolve: (target: RenderFeatureComputeTarget) => TextureView,
|
|
158
163
|
) => BindGroup;
|
|
159
164
|
readonly workgroups: readonly [number, number, number];
|
|
160
165
|
}
|
|
@@ -162,7 +167,7 @@ export interface RenderFeatureResolvedGpuComputePass {
|
|
|
162
167
|
readonly pipeline: ComputePipeline;
|
|
163
168
|
readonly bindGroup: BindGroup;
|
|
164
169
|
readonly resolveBindGroup?: (
|
|
165
|
-
resolve: (target:
|
|
170
|
+
resolve: (target: RenderFeatureComputeTarget) => TextureView,
|
|
166
171
|
) => BindGroup;
|
|
167
172
|
readonly indirectBuffer: Buffer;
|
|
168
173
|
readonly indirectOffset: number;
|
|
@@ -179,7 +184,7 @@ export interface RenderFeatureResolvedGpuBuffer {
|
|
|
179
184
|
export function encodeRenderFeatureGpuComputePass(
|
|
180
185
|
pass: RhiComputePassEncoder,
|
|
181
186
|
work: RenderFeatureResolvedGpuComputePass,
|
|
182
|
-
resolveTarget?: (target:
|
|
187
|
+
resolveTarget?: (target: RenderFeatureComputeTarget) => TextureView,
|
|
183
188
|
): void {
|
|
184
189
|
for (const dispatch of work.dispatches) {
|
|
185
190
|
pass.setPipeline(dispatch.pipeline);
|
|
@@ -221,6 +226,10 @@ interface ProgramItem {
|
|
|
221
226
|
readonly pipelines: ReadonlyMap<string, ComputePipeline>;
|
|
222
227
|
readonly bindGroupLayout?: BindGroupLayout;
|
|
223
228
|
readonly bindingLayouts: ReadonlyMap<number, GPUBufferBindingType>;
|
|
229
|
+
readonly textureBindings: ReadonlyMap<
|
|
230
|
+
number,
|
|
231
|
+
'sampled' | 'storage-write' | 'storage-read' | 'storage-read-write'
|
|
232
|
+
>;
|
|
224
233
|
}
|
|
225
234
|
|
|
226
235
|
interface BufferItem {
|
|
@@ -234,9 +243,9 @@ interface BufferItem {
|
|
|
234
243
|
}
|
|
235
244
|
|
|
236
245
|
interface TextureViewItem {
|
|
237
|
-
readonly logicalTarget?:
|
|
246
|
+
readonly logicalTarget?: RenderFeatureComputeTarget;
|
|
238
247
|
readonly name: string;
|
|
239
|
-
readonly resource: TextureView;
|
|
248
|
+
readonly resource: TextureView | undefined;
|
|
240
249
|
readonly reference: RenderFeatureGpuTextureViewRef;
|
|
241
250
|
}
|
|
242
251
|
|
|
@@ -248,7 +257,7 @@ interface SamplerItem {
|
|
|
248
257
|
|
|
249
258
|
interface BindingResourceItem {
|
|
250
259
|
readonly binding: number;
|
|
251
|
-
readonly resource
|
|
260
|
+
readonly resource?: RhiBindingResource;
|
|
252
261
|
readonly buffer?: BufferItem;
|
|
253
262
|
readonly textureView?: TextureViewItem;
|
|
254
263
|
readonly sampler?: SamplerItem;
|
|
@@ -623,6 +632,24 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
623
632
|
pipelineMap.set(entryPoint, pipeline.value);
|
|
624
633
|
}
|
|
625
634
|
const reference = Object.freeze({ generation: input.generation }) as RenderFeatureGpuProgramRef;
|
|
635
|
+
const textureBindings = new Map<
|
|
636
|
+
number,
|
|
637
|
+
'sampled' | 'storage-write' | 'storage-read' | 'storage-read-write'
|
|
638
|
+
>();
|
|
639
|
+
for (const entry of descriptor.bindings?.[0]?.entries ?? []) {
|
|
640
|
+
if (entry.texture !== undefined) {
|
|
641
|
+
textureBindings.set(entry.binding, 'sampled');
|
|
642
|
+
} else if (entry.storageTexture !== undefined) {
|
|
643
|
+
textureBindings.set(
|
|
644
|
+
entry.binding,
|
|
645
|
+
entry.storageTexture.access === 'write-only'
|
|
646
|
+
? 'storage-write'
|
|
647
|
+
: entry.storageTexture.access === 'read-only'
|
|
648
|
+
? 'storage-read'
|
|
649
|
+
: 'storage-read-write',
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
626
653
|
const item = {
|
|
627
654
|
name,
|
|
628
655
|
descriptor: cloneProgramDescriptor(descriptor),
|
|
@@ -636,6 +663,7 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
636
663
|
: [[entry.binding, entry.buffer.type ?? 'uniform'] as const],
|
|
637
664
|
),
|
|
638
665
|
),
|
|
666
|
+
textureBindings,
|
|
639
667
|
...(bindGroupLayout === undefined ? {} : { bindGroupLayout }),
|
|
640
668
|
};
|
|
641
669
|
programs.set(name, item);
|
|
@@ -964,7 +992,9 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
964
992
|
}
|
|
965
993
|
entryItems.push({
|
|
966
994
|
binding: entry.binding,
|
|
967
|
-
|
|
995
|
+
...(item.resource === undefined
|
|
996
|
+
? {}
|
|
997
|
+
: { resource: { kind: 'textureView' as const, value: item.resource } }),
|
|
968
998
|
textureView: item,
|
|
969
999
|
});
|
|
970
1000
|
continue;
|
|
@@ -992,7 +1022,7 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
992
1022
|
entries: entryItems.map((entry) => [
|
|
993
1023
|
entry.binding,
|
|
994
1024
|
entry.buffer?.name ?? entry.textureView?.name ?? entry.sampler?.name,
|
|
995
|
-
entry.resource
|
|
1025
|
+
entry.resource?.kind ?? 'late-texture',
|
|
996
1026
|
]),
|
|
997
1027
|
});
|
|
998
1028
|
const prior = bindings.get(name) ?? parkedBindings.get(name)?.item;
|
|
@@ -1026,15 +1056,32 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
1026
1056
|
),
|
|
1027
1057
|
);
|
|
1028
1058
|
}
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1059
|
+
const hasLateTextures = entryItems.some(
|
|
1060
|
+
(entry) => entry.textureView?.logicalTarget !== undefined,
|
|
1061
|
+
);
|
|
1062
|
+
const hasMissingResource = entryItems.some((entry) => entry.resource === undefined);
|
|
1063
|
+
if (hasMissingResource && !hasLateTextures) {
|
|
1064
|
+
return err(
|
|
1065
|
+
failure(
|
|
1066
|
+
input.featureIdentity,
|
|
1067
|
+
'prepare-gpu-bindings',
|
|
1068
|
+
'bindings',
|
|
1069
|
+
name,
|
|
1070
|
+
'texture-view-unavailable',
|
|
1071
|
+
),
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
const created = hasLateTextures
|
|
1075
|
+
? undefined
|
|
1076
|
+
: input.device.createBindGroup({
|
|
1077
|
+
label: name,
|
|
1078
|
+
layout,
|
|
1079
|
+
entries: entryItems.map((entry) => ({
|
|
1080
|
+
binding: entry.binding,
|
|
1081
|
+
resource: entry.resource as RhiBindingResource,
|
|
1082
|
+
})),
|
|
1083
|
+
});
|
|
1084
|
+
if (created !== undefined && !created.ok) {
|
|
1038
1085
|
return err(
|
|
1039
1086
|
failure(
|
|
1040
1087
|
input.featureIdentity,
|
|
@@ -1057,7 +1104,10 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
1057
1104
|
buffers: entryItems.flatMap((entry) =>
|
|
1058
1105
|
entry.buffer === undefined ? [] : [{ binding: entry.binding, item: entry.buffer }],
|
|
1059
1106
|
),
|
|
1060
|
-
|
|
1107
|
+
// A bind group with semantic targets is created once the graph resolves
|
|
1108
|
+
// the current generation's physical views. The placeholder is never
|
|
1109
|
+
// encoded because resolveComputePass installs resolveBindGroup.
|
|
1110
|
+
bindGroup: created?.value as BindGroup,
|
|
1061
1111
|
};
|
|
1062
1112
|
bindings.set(name, item);
|
|
1063
1113
|
bindingRefs.set(reference, item);
|
|
@@ -1188,7 +1238,7 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
1188
1238
|
(entry) => entry.textureView?.logicalTarget !== undefined,
|
|
1189
1239
|
)
|
|
1190
1240
|
? {
|
|
1191
|
-
resolveBindGroup: (resolve: (target:
|
|
1241
|
+
resolveBindGroup: (resolve: (target: RenderFeatureComputeTarget) => TextureView) => {
|
|
1192
1242
|
const layout =
|
|
1193
1243
|
program.bindGroupLayout ??
|
|
1194
1244
|
(pipeline as ComputePipeline & RhiComputePipelineOps).getBindGroupLayout(0);
|
|
@@ -1199,7 +1249,7 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
1199
1249
|
binding: entry.binding,
|
|
1200
1250
|
resource:
|
|
1201
1251
|
entry.textureView?.logicalTarget === undefined
|
|
1202
|
-
? entry.resource
|
|
1252
|
+
? (entry.resource as RhiBindingResource)
|
|
1203
1253
|
: {
|
|
1204
1254
|
kind: 'textureView' as const,
|
|
1205
1255
|
value: resolve(entry.textureView.logicalTarget),
|
|
@@ -1289,19 +1339,20 @@ function createRenderFeatureGpuWorkSession(input: {
|
|
|
1289
1339
|
accesses.add('indirect-read');
|
|
1290
1340
|
resolvedBuffers.set(item, accesses);
|
|
1291
1341
|
}
|
|
1342
|
+
const sampledTargets = new Set<RenderFeatureComputeTarget>();
|
|
1343
|
+
const storageTargets = new Set<RenderFeatureComputeTarget>();
|
|
1344
|
+
for (const item of dispatchBindings) {
|
|
1345
|
+
for (const entry of item?.entries ?? []) {
|
|
1346
|
+
const target = entry.textureView?.logicalTarget;
|
|
1347
|
+
if (target === undefined) continue;
|
|
1348
|
+
const role = program.textureBindings.get(entry.binding) ?? 'sampled';
|
|
1349
|
+
if (role === 'sampled') sampledTargets.add(target);
|
|
1350
|
+
else storageTargets.add(target);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1292
1353
|
return ok({
|
|
1293
|
-
sampledTargets: [
|
|
1294
|
-
|
|
1295
|
-
dispatchBindings.flatMap(
|
|
1296
|
-
(item) =>
|
|
1297
|
-
item?.entries.flatMap((entry) =>
|
|
1298
|
-
entry.textureView?.logicalTarget === undefined
|
|
1299
|
-
? []
|
|
1300
|
-
: [entry.textureView.logicalTarget],
|
|
1301
|
-
) ?? [],
|
|
1302
|
-
),
|
|
1303
|
-
),
|
|
1304
|
-
],
|
|
1354
|
+
sampledTargets: [...sampledTargets],
|
|
1355
|
+
storageTargets: [...storageTargets],
|
|
1305
1356
|
buffers: [...resolvedBuffers].flatMap(([item, accesses]) =>
|
|
1306
1357
|
[...accesses].map((access) => ({
|
|
1307
1358
|
name: item.name,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
RenderFeatureDrawRecordingFailedError,
|
|
10
10
|
RenderFeatureStageFailedError,
|
|
11
11
|
} from '../errors/render';
|
|
12
|
+
import type { RenderFeatureComputeTarget } from './prepared-gpu-work';
|
|
12
13
|
import {
|
|
13
14
|
encodeRenderFeatureGpuComputePass,
|
|
14
15
|
type RenderFeatureResolvedGpuComputePass,
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
importRenderFeatureGraphBuffer,
|
|
20
21
|
type RenderFeatureGraphBufferState,
|
|
21
22
|
} from './render-graph-resources';
|
|
22
|
-
import type { RenderFeatureTargetHandle } from './targets';
|
|
23
23
|
|
|
24
24
|
export class RenderFeatureComputeGraphProjection<FrameCtx extends RenderGraphFrame> {
|
|
25
25
|
private readonly resources: RenderFeatureGraphBufferState;
|
|
@@ -41,22 +41,30 @@ export class RenderFeatureComputeGraphProjection<FrameCtx extends RenderGraphFra
|
|
|
41
41
|
): Result<void, RenderGraphError | RenderError> {
|
|
42
42
|
const accesses = [];
|
|
43
43
|
const targets = new Map<
|
|
44
|
-
|
|
44
|
+
RenderFeatureComputeTarget,
|
|
45
45
|
NonNullable<ReturnType<RenderFeatureGraphTargetResolver>>
|
|
46
46
|
>();
|
|
47
|
-
|
|
47
|
+
const sampledTargets = new Set(work.sampledTargets ?? []);
|
|
48
|
+
const storageTargets = new Set(work.storageTargets ?? []);
|
|
49
|
+
for (const handle of [...sampledTargets, ...storageTargets]) {
|
|
48
50
|
let target = this.resolveTarget?.(handle);
|
|
49
51
|
if (target === undefined)
|
|
50
52
|
return err(
|
|
51
53
|
new RenderFeatureStageFailedError(featureIdentity, order, 'record', 'renderer-recover'),
|
|
52
54
|
);
|
|
53
|
-
if (handle.kind === 'scene-depth') {
|
|
55
|
+
if (typeof handle !== 'string' && handle.kind === 'scene-depth') {
|
|
54
56
|
const view = this.builder.view(target.texture, { aspect: 'depth-only' });
|
|
55
57
|
if (!view.ok) return view;
|
|
56
58
|
target = { ...target, view: view.value };
|
|
57
59
|
}
|
|
58
60
|
targets.set(handle, target);
|
|
59
|
-
|
|
61
|
+
if (sampledTargets.has(handle) && storageTargets.has(handle)) {
|
|
62
|
+
accesses.push({ resource: target.view, usage: 'sampled-storage-read-write' as const });
|
|
63
|
+
} else if (storageTargets.has(handle)) {
|
|
64
|
+
accesses.push({ resource: target.view, usage: 'storage-write' as const });
|
|
65
|
+
} else {
|
|
66
|
+
accesses.push({ resource: target.view, usage: 'sampled-read' as const });
|
|
67
|
+
}
|
|
60
68
|
}
|
|
61
69
|
for (const resource of work.buffers) {
|
|
62
70
|
const imported = importRenderFeatureGraphBuffer(
|
|
@@ -181,6 +181,7 @@ export function projectRenderFeaturePlans<FrameCtx extends RenderGraphFrame>(
|
|
|
181
181
|
pass.resolvedGpuCompute,
|
|
182
182
|
);
|
|
183
183
|
if (!added.ok) return added;
|
|
184
|
+
state.projected.add(pass);
|
|
184
185
|
continue;
|
|
185
186
|
}
|
|
186
187
|
if (
|
|
@@ -197,6 +198,7 @@ export function projectRenderFeaturePlans<FrameCtx extends RenderGraphFrame>(
|
|
|
197
198
|
pass.resolvedGraphics,
|
|
198
199
|
);
|
|
199
200
|
if (!added.ok) return added;
|
|
201
|
+
state.projected.add(pass);
|
|
200
202
|
continue;
|
|
201
203
|
}
|
|
202
204
|
return err(
|