@forgeax/engine-render 0.1.19 → 0.1.20
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/dist/.tsbuildinfo +1 -1
- package/dist/assembly/factory.d.ts +16 -5
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/{chunk-RJRUS55L.mjs → chunk-4XINDLAY.mjs} +267 -186
- package/dist/chunk-4XINDLAY.mjs.map +1 -0
- package/dist/{chunk-YTQ5C7YQ.mjs → chunk-CDTWTHZR.mjs} +176 -8
- package/dist/chunk-CDTWTHZR.mjs.map +1 -0
- package/dist/{chunk-IPXCABEP.mjs → chunk-M2MP4ZOU.mjs} +2 -2
- package/dist/chunk-M2MP4ZOU.mjs.map +1 -0
- package/dist/{chunk-LOSO4CTV.mjs → chunk-TBQJOK2L.mjs} +67 -3
- package/dist/chunk-TBQJOK2L.mjs.map +1 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +12 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +2281 -164
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -2
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/errors/render.d.ts +69 -1
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +2 -0
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/internal.mjs +1 -1
- package/dist/light-buffer-layout.d.ts +20 -12
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +6 -3
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +13 -0
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/record/frame-lighting.d.ts +2 -0
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +37 -1
- 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 +3 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-timing.d.ts +32 -0
- package/dist/record/gpu-timing.d.ts.map +1 -0
- package/dist/record/main-pass-material.d.ts +1 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +5 -1
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +11 -3
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/render-contract.d.ts +66 -1
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-data.d.ts +10 -6
- package/dist/render-data.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +36 -0
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +55 -3
- 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/renderer-inspect.d.ts +29 -0
- package/dist/renderer-inspect.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +44 -0
- package/dist/temporal/frame.d.ts.map +1 -0
- package/dist/temporal/index.mjs +3 -3
- package/dist/typed-render-graph-primitives.d.ts +6 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-atlas.d.ts +10 -0
- package/dist/typed-shadow-atlas.d.ts.map +1 -0
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/capability.d.ts +57 -0
- package/dist/volume/capability.d.ts.map +1 -0
- package/dist/volume/component.d.ts +61 -0
- package/dist/volume/component.d.ts.map +1 -0
- package/dist/volume/extract.d.ts +11 -0
- package/dist/volume/extract.d.ts.map +1 -0
- package/dist/volume/inspection.d.ts +38 -0
- package/dist/volume/inspection.d.ts.map +1 -0
- package/dist/volume/passes.d.ts +38 -0
- package/dist/volume/passes.d.ts.map +1 -0
- package/dist/volume/recovery.d.ts +38 -0
- package/dist/volume/recovery.d.ts.map +1 -0
- package/dist/volume/resources.d.ts +57 -0
- package/dist/volume/resources.d.ts.map +1 -0
- package/dist/volume/temporal.d.ts +35 -0
- package/dist/volume/temporal.d.ts.map +1 -0
- package/package.json +20 -20
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- package/src/__tests__/render-error-code-owner.test-d.ts +5 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +28 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
- package/src/__tests__/skinned-shadow-caster.test.ts +21 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +7 -5
- package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
- package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
- package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
- package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
- package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
- package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
- package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
- package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
- package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
- package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
- package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
- package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
- package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
- package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
- package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
- package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
- package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
- package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
- package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
- package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
- package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
- package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
- package/src/assembly/factory.ts +271 -15
- package/src/components/light-helpers.ts +4 -0
- package/src/components/spot-light.ts +22 -3
- package/src/device/gpu-residency.ts +168 -58
- package/src/errors/render.ts +119 -1
- package/src/gpu-usage.ts +2 -0
- package/src/index.ts +38 -1
- package/src/light-buffer-layout.ts +59 -26
- package/src/pbr-pipeline.ts +33 -10
- package/src/pipeline/standard-forward-lane.ts +20 -0
- package/src/pipeline/standard-pipeline.ts +20 -0
- package/src/pipeline/standard-profile.ts +23 -0
- package/src/record/frame-lighting.ts +103 -89
- package/src/record/frame-snapshot.ts +41 -1
- package/src/record/frame-targets.ts +6 -2
- package/src/record/frame.ts +908 -5
- package/src/record/gpu-timing.ts +224 -0
- package/src/record/main-pass-material.ts +6 -2
- package/src/record/render-context.ts +5 -0
- package/src/record/shadow-pass.ts +87 -38
- package/src/record/typed-frame-graph.ts +143 -8
- package/src/render-contract.ts +72 -0
- package/src/render-data.ts +50 -22
- package/src/render-pipeline.ts +44 -0
- package/src/render-system-extract.ts +329 -3
- package/src/render-system.ts +79 -0
- package/src/renderer-inspect.ts +66 -0
- package/src/scene/render-scene.ts +2 -0
- package/src/temporal/frame.ts +128 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +2 -3
- package/src/typed-render-graph-primitives.ts +32 -2
- package/src/typed-shadow-atlas.ts +17 -0
- package/src/typed-shadow-passes.ts +6 -5
- package/src/volume/capability.ts +173 -0
- package/src/volume/component.ts +110 -0
- package/src/volume/extract.ts +21 -0
- package/src/volume/inspection.ts +78 -0
- package/src/volume/passes.ts +680 -0
- package/src/volume/recovery.ts +86 -0
- package/src/volume/resources.ts +236 -0
- package/src/volume/temporal.ts +89 -0
- package/dist/chunk-IPXCABEP.mjs.map +0 -1
- package/dist/chunk-LOSO4CTV.mjs.map +0 -1
- package/dist/chunk-RJRUS55L.mjs.map +0 -1
- package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GraphBuffer,
|
|
3
|
+
GraphTextureView,
|
|
4
|
+
RenderGraphBuilder,
|
|
5
|
+
RenderGraphError,
|
|
6
|
+
} from '@forgeax/engine-render-graph';
|
|
7
|
+
import type {
|
|
8
|
+
BindGroup,
|
|
9
|
+
BindGroupLayout,
|
|
10
|
+
Buffer,
|
|
11
|
+
ComputePipeline,
|
|
12
|
+
RenderPipeline,
|
|
13
|
+
Sampler,
|
|
14
|
+
TextureView,
|
|
15
|
+
} from '@forgeax/engine-rhi';
|
|
16
|
+
import { RhiError } from '@forgeax/engine-rhi';
|
|
17
|
+
import { ok, type Result } from '@forgeax/engine-types';
|
|
18
|
+
import { GPU_TEXTURE_USAGE_TEXTURE_BINDING } from '../gpu-texture-usage';
|
|
19
|
+
import {
|
|
20
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
21
|
+
GPU_BUFFER_USAGE_STORAGE,
|
|
22
|
+
GPU_BUFFER_USAGE_UNIFORM,
|
|
23
|
+
} from '../gpu-usage';
|
|
24
|
+
import {
|
|
25
|
+
LIGHT_ARRAY_HEADER_BYTES,
|
|
26
|
+
LIGHT_ARRAY_MAX_SLOTS,
|
|
27
|
+
SPOT_LIGHT_STD430_BYTES,
|
|
28
|
+
} from '../light-buffer-layout';
|
|
29
|
+
import { VIEW_UNIFORM_BYTES } from '../record/view-ubo';
|
|
30
|
+
import type { VolumetricFogFrameContext, VolumetricFogShaderSources } from '../render-contract';
|
|
31
|
+
import type {
|
|
32
|
+
RenderPipelineFrame,
|
|
33
|
+
RenderPipelineTarget,
|
|
34
|
+
RenderPipelineTopology,
|
|
35
|
+
} from '../render-pipeline';
|
|
36
|
+
import type { VolumetricFogResources } from './resources';
|
|
37
|
+
import {
|
|
38
|
+
createVolumetricFogResources,
|
|
39
|
+
VOLUME_FROXEL_FORMAT,
|
|
40
|
+
VOLUME_HISTORY_FORMAT,
|
|
41
|
+
VOLUME_PACKING_Z,
|
|
42
|
+
VOLUME_RESOLVED_FORMAT,
|
|
43
|
+
} from './resources';
|
|
44
|
+
import type { VolumeProjectorTuple } from './temporal';
|
|
45
|
+
|
|
46
|
+
export const VOLUMETRIC_FOG_PASS_ORDER = [
|
|
47
|
+
'volume-inject',
|
|
48
|
+
'volume-integrate',
|
|
49
|
+
'volume-temporal',
|
|
50
|
+
'volume-composite',
|
|
51
|
+
] as const;
|
|
52
|
+
export interface VolumetricFogTopology {
|
|
53
|
+
readonly passes: readonly string[];
|
|
54
|
+
readonly resources: readonly string[];
|
|
55
|
+
readonly colorInput: 'linear-hdr';
|
|
56
|
+
readonly depthInput: 'scene-depth';
|
|
57
|
+
readonly lightKind?: 'directional' | 'point' | 'spot';
|
|
58
|
+
readonly lightEntity?: number;
|
|
59
|
+
readonly lightRevision?: number;
|
|
60
|
+
readonly projector?: Pick<VolumeProjectorTuple, 'guid' | 'generation' | 'revision'>;
|
|
61
|
+
}
|
|
62
|
+
export function volumetricFogTopology(
|
|
63
|
+
enabled: boolean,
|
|
64
|
+
selectedLight?: {
|
|
65
|
+
readonly lightKind: 'directional' | 'point' | 'spot';
|
|
66
|
+
readonly lightEntity: number;
|
|
67
|
+
readonly lightRevision: number;
|
|
68
|
+
},
|
|
69
|
+
): VolumetricFogTopology {
|
|
70
|
+
return enabled
|
|
71
|
+
? {
|
|
72
|
+
passes: [...VOLUMETRIC_FOG_PASS_ORDER],
|
|
73
|
+
resources: [
|
|
74
|
+
'volume-froxel',
|
|
75
|
+
'volume-resolved-current',
|
|
76
|
+
'volume-history',
|
|
77
|
+
'volume-temporal',
|
|
78
|
+
],
|
|
79
|
+
colorInput: 'linear-hdr',
|
|
80
|
+
depthInput: 'scene-depth',
|
|
81
|
+
...(selectedLight ?? {}),
|
|
82
|
+
}
|
|
83
|
+
: { passes: [], resources: [], colorInput: 'linear-hdr', depthInput: 'scene-depth' };
|
|
84
|
+
}
|
|
85
|
+
export interface VolumetricFogPassInputs {
|
|
86
|
+
readonly resources: VolumetricFogResources;
|
|
87
|
+
readonly linearHdr: RenderPipelineTarget;
|
|
88
|
+
readonly sceneDepth: GraphTextureView;
|
|
89
|
+
readonly density: GraphTextureView;
|
|
90
|
+
readonly params: GraphBuffer;
|
|
91
|
+
readonly view: GraphBuffer;
|
|
92
|
+
readonly directionalShadow: GraphTextureView;
|
|
93
|
+
readonly spotShadow: GraphTextureView;
|
|
94
|
+
readonly pointLights: GraphBuffer;
|
|
95
|
+
readonly spotLights: GraphBuffer;
|
|
96
|
+
readonly selectedLightKind: 'directional' | 'point' | 'spot';
|
|
97
|
+
readonly projector?: GraphTextureView;
|
|
98
|
+
}
|
|
99
|
+
function pipelineError(stage: string): RhiError {
|
|
100
|
+
return new RhiError({
|
|
101
|
+
code: 'rhi-not-available',
|
|
102
|
+
expected: `volumetric fog ${stage} pipeline to be available`,
|
|
103
|
+
hint: 'construct the renderer through a backend pack with shader module support',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function shaderSource(frame: RenderPipelineFrame, stage: keyof VolumetricFogShaderSources): string {
|
|
107
|
+
const source = frame.volumetricFogShaders?.[stage];
|
|
108
|
+
if (source === undefined) throw pipelineError(`${stage} shader source`);
|
|
109
|
+
return source;
|
|
110
|
+
}
|
|
111
|
+
interface ComputeState {
|
|
112
|
+
readonly pipeline: ComputePipeline;
|
|
113
|
+
readonly layout: BindGroupLayout;
|
|
114
|
+
}
|
|
115
|
+
function computeState(
|
|
116
|
+
frame: RenderPipelineFrame,
|
|
117
|
+
source: string,
|
|
118
|
+
label: string,
|
|
119
|
+
entries: readonly unknown[],
|
|
120
|
+
entryPoint = label,
|
|
121
|
+
): ComputeState {
|
|
122
|
+
const factory = frame.runtime.shaderModuleFactory;
|
|
123
|
+
if (factory === undefined) throw pipelineError(label);
|
|
124
|
+
const module = factory.createShaderModule({ code: source, label });
|
|
125
|
+
if (!module.ok) throw module.error;
|
|
126
|
+
const layout = frame.runtime.device.createBindGroupLayout({ entries: entries as never });
|
|
127
|
+
if (!layout.ok) throw layout.error;
|
|
128
|
+
const pipelineLayout = frame.runtime.device.createPipelineLayout({
|
|
129
|
+
label: `${label}.layout`,
|
|
130
|
+
bindGroupLayouts: [layout.value],
|
|
131
|
+
});
|
|
132
|
+
if (!pipelineLayout.ok) throw pipelineLayout.error;
|
|
133
|
+
const pipeline = frame.runtime.device.createComputePipeline({
|
|
134
|
+
label,
|
|
135
|
+
layout: pipelineLayout.value,
|
|
136
|
+
compute: { module: module.value, entryPoint },
|
|
137
|
+
});
|
|
138
|
+
if (!pipeline.ok) throw pipeline.error;
|
|
139
|
+
return { pipeline: pipeline.value, layout: layout.value };
|
|
140
|
+
}
|
|
141
|
+
type VolumeBinding =
|
|
142
|
+
| {
|
|
143
|
+
readonly binding: number;
|
|
144
|
+
readonly resource: { readonly kind: 'textureView'; readonly value: TextureView };
|
|
145
|
+
}
|
|
146
|
+
| {
|
|
147
|
+
readonly binding: number;
|
|
148
|
+
readonly resource: { readonly kind: 'sampler'; readonly value: Sampler };
|
|
149
|
+
}
|
|
150
|
+
| {
|
|
151
|
+
readonly binding: number;
|
|
152
|
+
readonly resource: { readonly kind: 'buffer'; readonly value: { readonly buffer: Buffer } };
|
|
153
|
+
};
|
|
154
|
+
function volumeBindGroup(
|
|
155
|
+
frame: RenderPipelineFrame,
|
|
156
|
+
layout: BindGroupLayout,
|
|
157
|
+
entries: readonly VolumeBinding[],
|
|
158
|
+
): BindGroup {
|
|
159
|
+
const group = frame.runtime.device.createBindGroup({ layout, entries });
|
|
160
|
+
if (!group.ok) throw group.error;
|
|
161
|
+
return group.value;
|
|
162
|
+
}
|
|
163
|
+
type ImportedVolumeInputs = Omit<VolumetricFogPassInputs, 'resources'>;
|
|
164
|
+
function importVolumeInputs(
|
|
165
|
+
graph: RenderGraphBuilder<RenderPipelineFrame>,
|
|
166
|
+
topology: RenderPipelineTopology,
|
|
167
|
+
linearHdr: RenderPipelineTarget,
|
|
168
|
+
sceneDepth: GraphTextureView,
|
|
169
|
+
directionalShadow: GraphTextureView,
|
|
170
|
+
spotShadow: GraphTextureView,
|
|
171
|
+
selectedLightKind: 'directional' | 'point' | 'spot' | undefined,
|
|
172
|
+
): Result<ImportedVolumeInputs | undefined, RenderGraphError> {
|
|
173
|
+
const volume = topology.volumetricFog;
|
|
174
|
+
if (volume?.enabled !== true || volume.format === undefined || volume.extent === undefined)
|
|
175
|
+
return ok(undefined);
|
|
176
|
+
const texture = graph.importTexture(
|
|
177
|
+
'volume-density',
|
|
178
|
+
{
|
|
179
|
+
format: volume.format,
|
|
180
|
+
size: {
|
|
181
|
+
width: volume.extent.width,
|
|
182
|
+
height: volume.extent.height,
|
|
183
|
+
depthOrArrayLayers: volume.extent.depth,
|
|
184
|
+
},
|
|
185
|
+
dimension: '3d',
|
|
186
|
+
usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING,
|
|
187
|
+
},
|
|
188
|
+
(frame) => {
|
|
189
|
+
const context = frame.volumetricFog as VolumetricFogFrameContext | undefined;
|
|
190
|
+
if (context === undefined) throw pipelineError('density residency');
|
|
191
|
+
return context.densityTexture;
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
if (!texture.ok) return texture;
|
|
195
|
+
const density = graph.importView(
|
|
196
|
+
texture.value,
|
|
197
|
+
{ label: 'volume-density.view', dimension: '3d' },
|
|
198
|
+
(frame) => {
|
|
199
|
+
const context = frame.volumetricFog as VolumetricFogFrameContext | undefined;
|
|
200
|
+
if (context === undefined) throw pipelineError('density view residency');
|
|
201
|
+
return context.densityView;
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
if (!density.ok) return density;
|
|
205
|
+
const params = graph.importBuffer(
|
|
206
|
+
'volume-params',
|
|
207
|
+
{ size: 128, usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST },
|
|
208
|
+
(frame) => {
|
|
209
|
+
const context = frame.volumetricFog as VolumetricFogFrameContext | undefined;
|
|
210
|
+
if (context === undefined) throw pipelineError('parameter residency');
|
|
211
|
+
return context.paramsBuffer;
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
if (!params.ok) return params;
|
|
215
|
+
const view = graph.importBuffer(
|
|
216
|
+
'volume-view',
|
|
217
|
+
{ size: VIEW_UNIFORM_BYTES, usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST },
|
|
218
|
+
(frame) => frame.pipelineState.viewUniformBuffer,
|
|
219
|
+
);
|
|
220
|
+
if (!view.ok) return view;
|
|
221
|
+
const spotLights = graph.importBuffer(
|
|
222
|
+
'volume-spot-lights',
|
|
223
|
+
{
|
|
224
|
+
size: LIGHT_ARRAY_HEADER_BYTES + LIGHT_ARRAY_MAX_SLOTS * SPOT_LIGHT_STD430_BYTES,
|
|
225
|
+
usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
|
|
226
|
+
},
|
|
227
|
+
(frame) => frame.pipelineState.spotLightsBuffer,
|
|
228
|
+
);
|
|
229
|
+
if (!spotLights.ok) return spotLights;
|
|
230
|
+
const pointLights = graph.importBuffer(
|
|
231
|
+
'volume-point-lights',
|
|
232
|
+
{ size: 16 + 4 * 32, usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST },
|
|
233
|
+
(frame) => frame.pipelineState.pointLightsBuffer,
|
|
234
|
+
);
|
|
235
|
+
if (!pointLights.ok) return pointLights;
|
|
236
|
+
let projector: GraphTextureView | undefined;
|
|
237
|
+
if (volume.projector !== undefined) {
|
|
238
|
+
const projectorTexture = graph.importTexture(
|
|
239
|
+
'spot-projector',
|
|
240
|
+
{
|
|
241
|
+
format: 'rgba8unorm-srgb',
|
|
242
|
+
size: { width: 128, height: 128, depthOrArrayLayers: 1 },
|
|
243
|
+
dimension: '2d',
|
|
244
|
+
usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING,
|
|
245
|
+
},
|
|
246
|
+
(frame) => {
|
|
247
|
+
const context = frame.volumetricFog as VolumetricFogFrameContext | undefined;
|
|
248
|
+
if (context?.projectorTexture === undefined) throw pipelineError('projector residency');
|
|
249
|
+
return context.projectorTexture;
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
if (!projectorTexture.ok) return projectorTexture;
|
|
253
|
+
const projectorView = graph.importView(
|
|
254
|
+
projectorTexture.value,
|
|
255
|
+
{ label: 'spot-projector.view', dimension: '2d' },
|
|
256
|
+
(frame) => {
|
|
257
|
+
const context = frame.volumetricFog as VolumetricFogFrameContext | undefined;
|
|
258
|
+
if (context?.projectorView === undefined) throw pipelineError('projector view residency');
|
|
259
|
+
return context.projectorView;
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
if (!projectorView.ok) return projectorView;
|
|
263
|
+
projector = projectorView.value;
|
|
264
|
+
}
|
|
265
|
+
return ok({
|
|
266
|
+
linearHdr,
|
|
267
|
+
sceneDepth,
|
|
268
|
+
density: density.value,
|
|
269
|
+
params: params.value,
|
|
270
|
+
view: view.value,
|
|
271
|
+
directionalShadow,
|
|
272
|
+
spotShadow,
|
|
273
|
+
pointLights: pointLights.value,
|
|
274
|
+
spotLights: spotLights.value,
|
|
275
|
+
selectedLightKind: selectedLightKind ?? 'directional',
|
|
276
|
+
...(projector === undefined ? {} : { projector }),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
export function addAuthoredVolumetricFogPasses(
|
|
280
|
+
graph: RenderGraphBuilder<RenderPipelineFrame>,
|
|
281
|
+
topology: RenderPipelineTopology,
|
|
282
|
+
linearHdr: RenderPipelineTarget,
|
|
283
|
+
sceneDepth: GraphTextureView,
|
|
284
|
+
directionalShadow: GraphTextureView,
|
|
285
|
+
spotShadow: GraphTextureView,
|
|
286
|
+
): Result<void, RenderGraphError> {
|
|
287
|
+
// A paired Point+Spot volume stores the Spot shadow lane as well. The
|
|
288
|
+
// topology's primary light remains PointLight for radiance selection, but
|
|
289
|
+
// injection must import the spot atlas so the packed visibility can be
|
|
290
|
+
// applied only to the spot term during integration.
|
|
291
|
+
const shadowLightKind =
|
|
292
|
+
topology.volumetricFog?.spotLightEntity === undefined
|
|
293
|
+
? topology.volumetricFog?.lightKind
|
|
294
|
+
: 'spot';
|
|
295
|
+
const imported = importVolumeInputs(
|
|
296
|
+
graph,
|
|
297
|
+
topology,
|
|
298
|
+
linearHdr,
|
|
299
|
+
sceneDepth,
|
|
300
|
+
directionalShadow,
|
|
301
|
+
spotShadow,
|
|
302
|
+
shadowLightKind,
|
|
303
|
+
);
|
|
304
|
+
if (!imported.ok) return imported;
|
|
305
|
+
if (imported.value === undefined) return ok(undefined);
|
|
306
|
+
const resources = createVolumetricFogResources(
|
|
307
|
+
graph,
|
|
308
|
+
topology.volumetricFog?.froxelExtent ?? { width: 64, height: 64, depth: 64 },
|
|
309
|
+
topology.volumetricFog?.resolvedExtent,
|
|
310
|
+
);
|
|
311
|
+
if (!resources.ok) return resources;
|
|
312
|
+
return addVolumetricFogPasses(graph, { ...imported.value, resources: resources.value });
|
|
313
|
+
}
|
|
314
|
+
export function addVolumetricFogPasses(
|
|
315
|
+
graph: RenderGraphBuilder<RenderPipelineFrame>,
|
|
316
|
+
inputs: VolumetricFogPassInputs,
|
|
317
|
+
): Result<void, RenderGraphError> {
|
|
318
|
+
const dispatchX = Math.ceil(inputs.resources.extent.width / 8);
|
|
319
|
+
const dispatchY = Math.ceil(inputs.resources.extent.height / 8);
|
|
320
|
+
const packedLayers = Math.ceil(inputs.resources.extent.depth / VOLUME_PACKING_Z);
|
|
321
|
+
const resolvedDispatchX = Math.ceil(inputs.resources.resolvedExtent.width / 8);
|
|
322
|
+
const resolvedDispatchY = Math.ceil(inputs.resources.resolvedExtent.height / 8);
|
|
323
|
+
let inject: ComputeState | undefined;
|
|
324
|
+
let integrate: ComputeState | undefined;
|
|
325
|
+
let temporal: ComputeState | undefined;
|
|
326
|
+
let composite: RenderPipeline | undefined;
|
|
327
|
+
let compositeLayout: BindGroupLayout | undefined;
|
|
328
|
+
const injected = graph.addComputePass('volume-inject', {
|
|
329
|
+
accesses: [
|
|
330
|
+
{ resource: inputs.params, usage: 'uniform-read' },
|
|
331
|
+
{ resource: inputs.view, usage: 'uniform-read' },
|
|
332
|
+
{
|
|
333
|
+
resource:
|
|
334
|
+
inputs.selectedLightKind === 'spot' ? inputs.spotShadow : inputs.directionalShadow,
|
|
335
|
+
usage: 'sampled-read',
|
|
336
|
+
},
|
|
337
|
+
{ resource: inputs.spotLights, usage: 'storage-read' },
|
|
338
|
+
{ resource: inputs.pointLights, usage: 'storage-read' },
|
|
339
|
+
...(inputs.projector === undefined
|
|
340
|
+
? []
|
|
341
|
+
: [{ resource: inputs.projector, usage: 'sampled-read' as const }]),
|
|
342
|
+
{ resource: inputs.resources.froxelView, usage: 'storage-write' },
|
|
343
|
+
],
|
|
344
|
+
encode: ({ pass, frame, resources }) => {
|
|
345
|
+
inject ??= computeState(frame, shaderSource(frame, 'inject'), 'volume_inject', [
|
|
346
|
+
{ binding: 0, visibility: 4, buffer: { type: 'uniform' } },
|
|
347
|
+
{ binding: 3, visibility: 4, texture: { sampleType: 'depth', viewDimension: '2d' } },
|
|
348
|
+
{ binding: 4, visibility: 4, sampler: { type: 'comparison' } },
|
|
349
|
+
{ binding: 5, visibility: 4, buffer: { type: 'uniform' } },
|
|
350
|
+
{
|
|
351
|
+
binding: 6,
|
|
352
|
+
visibility: 4,
|
|
353
|
+
storageTexture: {
|
|
354
|
+
access: 'write-only',
|
|
355
|
+
format: VOLUME_FROXEL_FORMAT,
|
|
356
|
+
viewDimension: '2d-array',
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
{ binding: 7, visibility: 4, buffer: { type: 'read-only-storage' } },
|
|
360
|
+
{ binding: 8, visibility: 4, buffer: { type: 'read-only-storage' } },
|
|
361
|
+
]);
|
|
362
|
+
const shadow = resources.textureView(
|
|
363
|
+
inputs.selectedLightKind === 'spot' ? inputs.spotShadow : inputs.directionalShadow,
|
|
364
|
+
);
|
|
365
|
+
const params = resources.buffer(inputs.params);
|
|
366
|
+
const view = resources.buffer(inputs.view);
|
|
367
|
+
const spotLights = resources.buffer(inputs.spotLights);
|
|
368
|
+
const pointLights = resources.buffer(inputs.pointLights);
|
|
369
|
+
const froxel = resources.textureView(inputs.resources.froxelView);
|
|
370
|
+
if (!shadow.ok) throw shadow.error;
|
|
371
|
+
if (!params.ok) throw params.error;
|
|
372
|
+
if (!view.ok) throw view.error;
|
|
373
|
+
if (!spotLights.ok) throw spotLights.error;
|
|
374
|
+
if (!pointLights.ok) throw pointLights.error;
|
|
375
|
+
if (!froxel.ok) throw froxel.error;
|
|
376
|
+
const shadowSampler = frame.runtime.device.createSampler({ compare: 'less' });
|
|
377
|
+
if (!shadowSampler.ok) throw shadowSampler.error;
|
|
378
|
+
const bindings = volumeBindGroup(frame, inject.layout, [
|
|
379
|
+
{ binding: 0, resource: { kind: 'buffer', value: { buffer: view.value } } },
|
|
380
|
+
{ binding: 3, resource: { kind: 'textureView', value: shadow.value as never } },
|
|
381
|
+
{ binding: 4, resource: { kind: 'sampler', value: shadowSampler.value } },
|
|
382
|
+
{ binding: 5, resource: { kind: 'buffer', value: { buffer: params.value } } },
|
|
383
|
+
{ binding: 6, resource: { kind: 'textureView', value: froxel.value as never } },
|
|
384
|
+
{ binding: 7, resource: { kind: 'buffer', value: { buffer: spotLights.value } } },
|
|
385
|
+
{ binding: 8, resource: { kind: 'buffer', value: { buffer: pointLights.value } } },
|
|
386
|
+
]);
|
|
387
|
+
pass.setPipeline(inject.pipeline);
|
|
388
|
+
pass.setBindGroup(0, bindings);
|
|
389
|
+
pass.dispatchWorkgroups(dispatchX, dispatchY, packedLayers);
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
if (!injected.ok) return injected;
|
|
393
|
+
const integratePass = graph.addComputePass('volume-integrate', {
|
|
394
|
+
accesses: [
|
|
395
|
+
{ resource: inputs.resources.froxelView, usage: 'sampled-read' },
|
|
396
|
+
{ resource: inputs.sceneDepth, usage: 'sampled-read' },
|
|
397
|
+
{ resource: inputs.density, usage: 'sampled-read' },
|
|
398
|
+
{ resource: inputs.resources.resolvedView, usage: 'storage-write' },
|
|
399
|
+
{ resource: inputs.resources.historyView, usage: 'storage-write' },
|
|
400
|
+
{ resource: inputs.resources.temporalView, usage: 'storage-write' },
|
|
401
|
+
{ resource: inputs.params, usage: 'uniform-read' },
|
|
402
|
+
{ resource: inputs.view, usage: 'uniform-read' },
|
|
403
|
+
{ resource: inputs.spotLights, usage: 'storage-read' },
|
|
404
|
+
{ resource: inputs.pointLights, usage: 'storage-read' },
|
|
405
|
+
...(inputs.projector === undefined
|
|
406
|
+
? []
|
|
407
|
+
: [{ resource: inputs.projector, usage: 'sampled-read' as const }]),
|
|
408
|
+
],
|
|
409
|
+
encode: ({ pass, frame, resources }) => {
|
|
410
|
+
integrate ??= computeState(frame, shaderSource(frame, 'integrate'), 'volume_integrate', [
|
|
411
|
+
{ binding: 0, visibility: 4, texture: { sampleType: 'float', viewDimension: '2d-array' } },
|
|
412
|
+
{ binding: 1, visibility: 4, texture: { sampleType: 'depth', viewDimension: '2d' } },
|
|
413
|
+
{
|
|
414
|
+
binding: 2,
|
|
415
|
+
visibility: 4,
|
|
416
|
+
storageTexture: {
|
|
417
|
+
access: 'write-only',
|
|
418
|
+
format: VOLUME_RESOLVED_FORMAT,
|
|
419
|
+
viewDimension: '2d',
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
{ binding: 3, visibility: 4, buffer: { type: 'uniform' } },
|
|
423
|
+
{ binding: 4, visibility: 4, buffer: { type: 'uniform' } },
|
|
424
|
+
{ binding: 5, visibility: 4, sampler: { type: 'filtering' } },
|
|
425
|
+
{ binding: 6, visibility: 4, texture: { sampleType: 'float', viewDimension: '3d' } },
|
|
426
|
+
{ binding: 7, visibility: 4, sampler: { type: 'filtering' } },
|
|
427
|
+
{
|
|
428
|
+
binding: 8,
|
|
429
|
+
visibility: 4,
|
|
430
|
+
storageTexture: {
|
|
431
|
+
access: 'write-only',
|
|
432
|
+
format: VOLUME_HISTORY_FORMAT,
|
|
433
|
+
viewDimension: '2d',
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
binding: 9,
|
|
438
|
+
visibility: 4,
|
|
439
|
+
storageTexture: {
|
|
440
|
+
access: 'write-only',
|
|
441
|
+
format: VOLUME_HISTORY_FORMAT,
|
|
442
|
+
viewDimension: '2d',
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
{ binding: 10, visibility: 4, buffer: { type: 'read-only-storage' } },
|
|
446
|
+
{ binding: 11, visibility: 4, buffer: { type: 'read-only-storage' } },
|
|
447
|
+
{ binding: 12, visibility: 4, texture: { sampleType: 'float', viewDimension: '2d' } },
|
|
448
|
+
{ binding: 13, visibility: 4, sampler: { type: 'filtering' } },
|
|
449
|
+
]);
|
|
450
|
+
const froxel = resources.textureView(inputs.resources.froxelView);
|
|
451
|
+
const depth = resources.textureView(inputs.sceneDepth);
|
|
452
|
+
const density = resources.textureView(inputs.density);
|
|
453
|
+
const resolved = resources.textureView(inputs.resources.resolvedView);
|
|
454
|
+
const historySeed = resources.textureView(inputs.resources.historyView);
|
|
455
|
+
const temporalSeed = resources.textureView(inputs.resources.temporalView);
|
|
456
|
+
const params = resources.buffer(inputs.params);
|
|
457
|
+
const view = resources.buffer(inputs.view);
|
|
458
|
+
const spotLights = resources.buffer(inputs.spotLights);
|
|
459
|
+
const pointLights = resources.buffer(inputs.pointLights);
|
|
460
|
+
const projector =
|
|
461
|
+
inputs.projector === undefined ? undefined : resources.textureView(inputs.projector);
|
|
462
|
+
if (!froxel.ok) throw froxel.error;
|
|
463
|
+
if (!depth.ok) throw depth.error;
|
|
464
|
+
if (!density.ok) throw density.error;
|
|
465
|
+
if (!resolved.ok) throw resolved.error;
|
|
466
|
+
if (!historySeed.ok) throw historySeed.error;
|
|
467
|
+
if (!temporalSeed.ok) throw temporalSeed.error;
|
|
468
|
+
if (!params.ok) throw params.error;
|
|
469
|
+
if (!view.ok) throw view.error;
|
|
470
|
+
if (!spotLights.ok) throw spotLights.error;
|
|
471
|
+
if (!pointLights.ok) throw pointLights.error;
|
|
472
|
+
if (projector !== undefined && !projector.ok) throw projector.error;
|
|
473
|
+
const sampler = frame.runtime.device.createSampler({
|
|
474
|
+
minFilter: 'linear',
|
|
475
|
+
magFilter: 'linear',
|
|
476
|
+
});
|
|
477
|
+
if (!sampler.ok) throw sampler.error;
|
|
478
|
+
const densitySampler = frame.runtime.device.createSampler({
|
|
479
|
+
minFilter: 'linear',
|
|
480
|
+
magFilter: 'linear',
|
|
481
|
+
addressModeU: 'repeat',
|
|
482
|
+
addressModeV: 'repeat',
|
|
483
|
+
addressModeW: 'repeat',
|
|
484
|
+
});
|
|
485
|
+
if (!densitySampler.ok) throw densitySampler.error;
|
|
486
|
+
const bindings = volumeBindGroup(frame, integrate.layout, [
|
|
487
|
+
{ binding: 0, resource: { kind: 'textureView', value: froxel.value as never } },
|
|
488
|
+
{ binding: 1, resource: { kind: 'textureView', value: depth.value as never } },
|
|
489
|
+
{ binding: 2, resource: { kind: 'textureView', value: resolved.value as never } },
|
|
490
|
+
{ binding: 3, resource: { kind: 'buffer', value: { buffer: params.value } } },
|
|
491
|
+
{ binding: 4, resource: { kind: 'buffer', value: { buffer: view.value } } },
|
|
492
|
+
{ binding: 5, resource: { kind: 'sampler', value: sampler.value } },
|
|
493
|
+
{ binding: 6, resource: { kind: 'textureView', value: density.value as never } },
|
|
494
|
+
{ binding: 7, resource: { kind: 'sampler', value: densitySampler.value } },
|
|
495
|
+
{ binding: 8, resource: { kind: 'textureView', value: historySeed.value as never } },
|
|
496
|
+
{ binding: 9, resource: { kind: 'textureView', value: temporalSeed.value as never } },
|
|
497
|
+
{ binding: 10, resource: { kind: 'buffer', value: { buffer: spotLights.value } } },
|
|
498
|
+
{ binding: 11, resource: { kind: 'buffer', value: { buffer: pointLights.value } } },
|
|
499
|
+
{
|
|
500
|
+
binding: 12,
|
|
501
|
+
resource: {
|
|
502
|
+
kind: 'textureView',
|
|
503
|
+
value:
|
|
504
|
+
projector === undefined
|
|
505
|
+
? frame.pipelineState.defaultWhiteTextureView
|
|
506
|
+
: (projector.value as never),
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
binding: 13,
|
|
511
|
+
resource: {
|
|
512
|
+
kind: 'sampler',
|
|
513
|
+
value: frame.volumetricFog?.projectorSampler ?? frame.pipelineState.defaultSampler,
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
]);
|
|
517
|
+
pass.setPipeline(integrate.pipeline);
|
|
518
|
+
pass.setBindGroup(0, bindings);
|
|
519
|
+
pass.dispatchWorkgroups(resolvedDispatchX, resolvedDispatchY, 1);
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
if (!integratePass.ok) return integratePass;
|
|
523
|
+
const temporalPass = graph.addComputePass('volume-temporal', {
|
|
524
|
+
accesses: [
|
|
525
|
+
{ resource: inputs.resources.resolvedView, usage: 'sampled-read' },
|
|
526
|
+
{ resource: inputs.resources.historyView, usage: 'sampled-storage-read-write' },
|
|
527
|
+
{ resource: inputs.resources.temporalView, usage: 'sampled-storage-read-write' },
|
|
528
|
+
{ resource: inputs.params, usage: 'uniform-read' },
|
|
529
|
+
{ resource: inputs.view, usage: 'uniform-read' },
|
|
530
|
+
],
|
|
531
|
+
encode: ({ pass, frame, resources }) => {
|
|
532
|
+
temporal ??= computeState(frame, shaderSource(frame, 'temporal'), 'volume_temporal', [
|
|
533
|
+
{ binding: 0, visibility: 4, texture: { sampleType: 'float', viewDimension: '2d' } },
|
|
534
|
+
{ binding: 1, visibility: 4, texture: { sampleType: 'float', viewDimension: '2d' } },
|
|
535
|
+
{
|
|
536
|
+
binding: 2,
|
|
537
|
+
visibility: 4,
|
|
538
|
+
storageTexture: {
|
|
539
|
+
access: 'write-only',
|
|
540
|
+
format: VOLUME_HISTORY_FORMAT,
|
|
541
|
+
viewDimension: '2d',
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{ binding: 3, visibility: 4, buffer: { type: 'uniform' } },
|
|
545
|
+
{ binding: 4, visibility: 4, buffer: { type: 'uniform' } },
|
|
546
|
+
]);
|
|
547
|
+
const current = resources.textureView(inputs.resources.resolvedView);
|
|
548
|
+
const history = resources.textureView(
|
|
549
|
+
frame.volumetricFog?.historyReadSlot === 0
|
|
550
|
+
? inputs.resources.historyView
|
|
551
|
+
: inputs.resources.temporalView,
|
|
552
|
+
);
|
|
553
|
+
const pending = resources.textureView(
|
|
554
|
+
frame.volumetricFog?.historyWriteSlot === 0
|
|
555
|
+
? inputs.resources.historyView
|
|
556
|
+
: inputs.resources.temporalView,
|
|
557
|
+
);
|
|
558
|
+
const params = resources.buffer(inputs.params);
|
|
559
|
+
const view = resources.buffer(inputs.view);
|
|
560
|
+
if (!current.ok) throw current.error;
|
|
561
|
+
if (!history.ok) throw history.error;
|
|
562
|
+
if (!pending.ok) throw pending.error;
|
|
563
|
+
if (!params.ok) throw params.error;
|
|
564
|
+
if (!view.ok) throw view.error;
|
|
565
|
+
const bindings = volumeBindGroup(frame, temporal.layout, [
|
|
566
|
+
{ binding: 0, resource: { kind: 'textureView', value: current.value as never } },
|
|
567
|
+
{ binding: 1, resource: { kind: 'textureView', value: history.value as never } },
|
|
568
|
+
{ binding: 2, resource: { kind: 'textureView', value: pending.value as never } },
|
|
569
|
+
{ binding: 3, resource: { kind: 'buffer', value: { buffer: params.value } } },
|
|
570
|
+
{ binding: 4, resource: { kind: 'buffer', value: { buffer: view.value } } },
|
|
571
|
+
]);
|
|
572
|
+
pass.setPipeline(temporal.pipeline);
|
|
573
|
+
pass.setBindGroup(0, bindings);
|
|
574
|
+
pass.dispatchWorkgroups(resolvedDispatchX, resolvedDispatchY, 1);
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
if (!temporalPass.ok) return temporalPass;
|
|
578
|
+
return graph.addRasterPass('volume-composite', {
|
|
579
|
+
accesses: [
|
|
580
|
+
{ resource: inputs.resources.historyView, usage: 'sampled-read' },
|
|
581
|
+
{ resource: inputs.resources.temporalView, usage: 'sampled-read' },
|
|
582
|
+
{ resource: inputs.sceneDepth, usage: 'sampled-read' },
|
|
583
|
+
{ resource: inputs.view, usage: 'uniform-read' },
|
|
584
|
+
{ resource: inputs.linearHdr.view, usage: 'color-attachment' },
|
|
585
|
+
],
|
|
586
|
+
colorAttachments: [
|
|
587
|
+
{
|
|
588
|
+
view: inputs.linearHdr.view,
|
|
589
|
+
loadOp: 'load',
|
|
590
|
+
storeOp: 'store',
|
|
591
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
encode: ({ pass, frame, resources }) => {
|
|
595
|
+
composite ??= (() => {
|
|
596
|
+
const factory = frame.runtime.shaderModuleFactory;
|
|
597
|
+
if (factory === undefined) throw pipelineError('volume_composite');
|
|
598
|
+
const module = factory.createShaderModule({
|
|
599
|
+
code: shaderSource(frame, 'composite'),
|
|
600
|
+
label: 'volume_composite',
|
|
601
|
+
});
|
|
602
|
+
if (!module.ok) throw module.error;
|
|
603
|
+
const layout = frame.runtime.device.createBindGroupLayout({
|
|
604
|
+
label: 'volume_composite.bind-group-layout',
|
|
605
|
+
entries: [
|
|
606
|
+
{
|
|
607
|
+
binding: 0,
|
|
608
|
+
visibility: 2 | 1,
|
|
609
|
+
texture: { sampleType: 'float', viewDimension: '2d' },
|
|
610
|
+
},
|
|
611
|
+
{ binding: 1, visibility: 2 | 1, sampler: { type: 'filtering' } },
|
|
612
|
+
{
|
|
613
|
+
binding: 2,
|
|
614
|
+
visibility: 2 | 1,
|
|
615
|
+
texture: { sampleType: 'depth', viewDimension: '2d' },
|
|
616
|
+
},
|
|
617
|
+
{ binding: 3, visibility: 2 | 1, buffer: { type: 'uniform' } },
|
|
618
|
+
],
|
|
619
|
+
});
|
|
620
|
+
if (!layout.ok) throw layout.error;
|
|
621
|
+
const pipelineLayout = frame.runtime.device.createPipelineLayout({
|
|
622
|
+
label: 'volume_composite.layout',
|
|
623
|
+
bindGroupLayouts: [layout.value],
|
|
624
|
+
});
|
|
625
|
+
if (!pipelineLayout.ok) throw pipelineLayout.error;
|
|
626
|
+
const created = frame.runtime.device.createRenderPipeline({
|
|
627
|
+
label: 'volume_composite',
|
|
628
|
+
layout: pipelineLayout.value,
|
|
629
|
+
vertex: { module: module.value, entryPoint: 'volume_vs', buffers: [] },
|
|
630
|
+
fragment: {
|
|
631
|
+
module: module.value,
|
|
632
|
+
entryPoint: 'volume_fs',
|
|
633
|
+
targets: [
|
|
634
|
+
{
|
|
635
|
+
format: inputs.linearHdr.format,
|
|
636
|
+
blend: {
|
|
637
|
+
color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
638
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
},
|
|
643
|
+
primitive: { topology: 'triangle-list' },
|
|
644
|
+
});
|
|
645
|
+
if (!created.ok) throw created.error;
|
|
646
|
+
compositeLayout = layout.value;
|
|
647
|
+
return created.value;
|
|
648
|
+
})();
|
|
649
|
+
const resolved = resources.textureView(
|
|
650
|
+
frame.volumetricFog?.historyWriteSlot === 0
|
|
651
|
+
? inputs.resources.historyView
|
|
652
|
+
: inputs.resources.temporalView,
|
|
653
|
+
);
|
|
654
|
+
const depth = resources.textureView(inputs.sceneDepth);
|
|
655
|
+
const view = resources.buffer(inputs.view);
|
|
656
|
+
if (!resolved.ok) throw resolved.error;
|
|
657
|
+
if (!depth.ok) throw depth.error;
|
|
658
|
+
if (!view.ok) throw view.error;
|
|
659
|
+
const sampler = frame.runtime.device.createSampler({
|
|
660
|
+
label: 'volume_composite.sampler',
|
|
661
|
+
minFilter: 'linear',
|
|
662
|
+
magFilter: 'linear',
|
|
663
|
+
});
|
|
664
|
+
if (!sampler.ok) throw sampler.error;
|
|
665
|
+
const bindings = frame.runtime.device.createBindGroup({
|
|
666
|
+
layout: compositeLayout as BindGroupLayout,
|
|
667
|
+
entries: [
|
|
668
|
+
{ binding: 0, resource: { kind: 'textureView', value: resolved.value as never } },
|
|
669
|
+
{ binding: 1, resource: { kind: 'sampler', value: sampler.value } },
|
|
670
|
+
{ binding: 2, resource: { kind: 'textureView', value: depth.value as never } },
|
|
671
|
+
{ binding: 3, resource: { kind: 'buffer', value: { buffer: view.value } } },
|
|
672
|
+
],
|
|
673
|
+
});
|
|
674
|
+
if (!bindings.ok) throw bindings.error;
|
|
675
|
+
pass.setPipeline(composite);
|
|
676
|
+
pass.setBindGroup(0, bindings.value);
|
|
677
|
+
pass.draw(3);
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
}
|