refactor: centralize fullscreen graph policy

Amp-Thread-ID: https://ampcode.com/threads/T-019f9d91-77c1-7206-a60f-ed6554ce92ab

Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-07-28 13:45:10 +00:00
co-authored by heaust
parent edc7c8ef29
commit 67cf3a6555
11 changed files with 272 additions and 133 deletions
+22 -32
View File
@@ -946,8 +946,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
}
let transition_sockets: &[(&str, u16)] = match contracts[i].key {
"pipeline" => &[("colorTarget", 0), ("depthTarget", 1)],
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur" | "bloom_composite"
| "luminance_edge" => &[("colorTarget", 0)],
_ if contracts[i].fullscreen_policy.is_some() => &[("colorTarget", 0)],
_ => continue,
};
for &(input_socket, output_ordinal) in transition_sockets {
@@ -1118,12 +1117,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
format!("nodes[{i}].inputs"),
));
}
} else if contracts[i].key != "frame_out"
&& contracts[i]
.inputs
.iter()
.any(|input| matches!(input.role, InputRole::SampledTexture))
{
} else if contracts[i].fullscreen_policy.is_some() {
let hazard = contracts[i].inputs.iter().filter(|input| matches!(input.role, InputRole::SampledTexture)).any(|input| matches!((version_of.get(&bound[i][input.name].producer), version_of.get(&OutputKey(i, 0))), (Some((sf, _, _)), Some((tf, _, _))) if sf == tf));
if hazard {
return Err(error(
@@ -1213,13 +1207,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
}
for i in 0..graph.nodes.len() {
if !live.contains(&i)
|| contracts[i].key == "frame_out"
|| !contracts[i]
.inputs
.iter()
.any(|input| matches!(input.role, InputRole::SampledTexture))
{
if !live.contains(&i) || contracts[i].fullscreen_policy.is_none() {
continue;
}
let source_key = bound[i]["source"].producer;
@@ -1244,7 +1232,9 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
let authored_target_ok = target_descriptor.is_some_and(|descriptor| {
descriptor.format == TextureFormat::Rgba16Float && is_single_view_d2(descriptor)
});
let bloom_input_ok = if contracts[i].key == "bloom_composite" {
let bloom_input_ok = if contracts[i].fullscreen_policy
== Some(FullscreenPolicy::BloomComposite)
{
let bloom_key = bound[i]["bloom"].producer;
let Some(&(bloom_family_id, _, _)) = version_of.get(&bloom_key) else {
return Err(error(
@@ -1264,8 +1254,8 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
let source_is_full_surface = matches!(&source_descriptor.extent, NormalizedTextureExtent::SurfaceRelative { width, height, depth_or_array_layers: 1 } if *width == Ratio { numerator:1, denominator:1 } && *height == Ratio { numerator:1, denominator:1 });
let target_matches_source = target_descriptor
.is_some_and(|descriptor| descriptor.extent == source_descriptor.extent);
let descriptor_ok = match contracts[i].key {
"fullscreen_copy" => {
let descriptor_ok = match contracts[i].fullscreen_policy {
Some(FullscreenPolicy::Copy) => {
target_descriptor.is_none() && source_is_full_surface
|| target_descriptor.is_some_and(|descriptor| {
descriptor.format != TextureFormat::Depth32Float
@@ -1273,15 +1263,17 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
&& descriptor.extent == source_descriptor.extent
})
}
"tone_map" => target_descriptor.is_some_and(|descriptor| {
Some(FullscreenPolicy::ToneMap) => target_descriptor.is_some_and(|descriptor| {
descriptor.format != TextureFormat::Depth32Float
&& descriptor.format != TextureFormat::R32Float
&& is_single_view_d2(descriptor)
&& descriptor.extent == source_descriptor.extent
}),
"bloom_extract" => authored_target_ok,
"bloom_blur" | "luminance_edge" => authored_target_ok && target_matches_source,
"bloom_composite" => authored_target_ok && target_matches_source && bloom_input_ok,
Some(FullscreenPolicy::BloomExtract) => authored_target_ok,
Some(FullscreenPolicy::HdrSameExtent) => authored_target_ok && target_matches_source,
Some(FullscreenPolicy::BloomComposite) => {
authored_target_ok && target_matches_source && bloom_input_ok
}
_ => false,
};
if !source_ok || !descriptor_ok {
@@ -1650,21 +1642,19 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
}),
}
}
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur" | "bloom_composite"
| "luminance_edge" => {
_ if contracts[i].fullscreen_policy.is_some() => {
let color = output_ids[&OutputKey(i, 0)];
let load = NormalizedColorLoad::Clear {
value: [0.0, 0.0, 0.0, 0.0],
};
accesses.push(CompiledAccess {
socket: "source".into(),
resource: input_resource("source"),
mode: AccessMode::SampledTexture,
});
if contracts[i].key == "bloom_composite" {
for input in contracts[i]
.inputs
.iter()
.filter(|input| input.role == InputRole::SampledTexture)
{
accesses.push(CompiledAccess {
socket: "bloom".into(),
resource: input_resource("bloom"),
socket: input.name.into(),
resource: input_resource(input.name),
mode: AccessMode::SampledTexture,
});
}
+24
View File
@@ -22,6 +22,15 @@ pub enum ExecutionClass {
Frame,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FullscreenPolicy {
Copy,
ToneMap,
HdrSameExtent,
BloomExtract,
BloomComposite,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum InputCardinality {
@@ -81,6 +90,8 @@ pub struct Contract {
pub inputs: &'static [InputSocketContract],
pub outputs: &'static [OutputSocketContract],
pub inherently_observable: bool,
#[serde(skip)]
pub fullscreen_policy: Option<FullscreenPolicy>,
}
use SemanticType::*;
@@ -268,6 +279,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: NONE_IN,
outputs: MESH_OUT,
inherently_observable: false,
fullscreen_policy: None,
},
Contract {
key: "texture",
@@ -276,6 +288,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: NONE_IN,
outputs: TEXTURE_OUT,
inherently_observable: false,
fullscreen_policy: None,
},
Contract {
key: "frustum_cull",
@@ -284,6 +297,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: CULL_IN,
outputs: CULLED_OUT,
inherently_observable: false,
fullscreen_policy: None,
},
Contract {
key: "mesh_query",
@@ -292,6 +306,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: QUERY_IN,
outputs: DRAW_OUT,
inherently_observable: false,
fullscreen_policy: None,
},
Contract {
key: "pipeline_registry",
@@ -300,6 +315,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: REGISTRY_IN,
outputs: ACTIVATION_OUT,
inherently_observable: false,
fullscreen_policy: None,
},
Contract {
key: "pipeline",
@@ -308,6 +324,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: PIPELINE_IN,
outputs: PIPELINE_OUT,
inherently_observable: false,
fullscreen_policy: None,
},
Contract {
key: "fullscreen_copy",
@@ -316,6 +333,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: FULLSCREEN_COPY_IN,
outputs: FULLSCREEN_COPY_OUT,
inherently_observable: false,
fullscreen_policy: Some(FullscreenPolicy::Copy),
},
Contract {
key: "tone_map",
@@ -324,6 +342,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: FULLSCREEN_COPY_IN,
outputs: FULLSCREEN_COPY_OUT,
inherently_observable: false,
fullscreen_policy: Some(FullscreenPolicy::ToneMap),
},
Contract {
key: "bloom_extract",
@@ -332,6 +351,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: FULLSCREEN_COPY_IN,
outputs: FULLSCREEN_COPY_OUT,
inherently_observable: false,
fullscreen_policy: Some(FullscreenPolicy::BloomExtract),
},
Contract {
key: "bloom_blur",
@@ -340,6 +360,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: FULLSCREEN_COPY_IN,
outputs: FULLSCREEN_COPY_OUT,
inherently_observable: false,
fullscreen_policy: Some(FullscreenPolicy::HdrSameExtent),
},
Contract {
key: "bloom_composite",
@@ -348,6 +369,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: BLOOM_COMPOSITE_IN,
outputs: FULLSCREEN_COPY_OUT,
inherently_observable: false,
fullscreen_policy: Some(FullscreenPolicy::BloomComposite),
},
Contract {
key: "luminance_edge",
@@ -356,6 +378,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: FULLSCREEN_COPY_IN,
outputs: FULLSCREEN_COPY_OUT,
inherently_observable: false,
fullscreen_policy: Some(FullscreenPolicy::HdrSameExtent),
},
Contract {
key: "frame_out",
@@ -364,6 +387,7 @@ pub static CONTRACTS: &[Contract] = &[
inputs: FRAME_OUT_IN,
outputs: NONE_OUT,
inherently_observable: true,
fullscreen_policy: None,
},
];
+33 -41
View File
@@ -256,20 +256,13 @@ fn valid_pipeline_name(name: &str) -> bool {
}
fn execution_supported(key: &str) -> bool {
matches!(
key,
"frustum_cull"
| "mesh_query"
| "pipeline_registry"
| "pipeline"
| "fullscreen_copy"
| "tone_map"
| "bloom_extract"
| "bloom_blur"
| "bloom_composite"
| "luminance_edge"
| "frame_out"
)
contract(key).is_some_and(|contract| {
contract.fullscreen_policy.is_some()
|| matches!(
key,
"frustum_cull" | "mesh_query" | "pipeline_registry" | "pipeline" | "frame_out"
)
})
}
fn resource_is_mesh(graph: &CompiledGraph, id: u32) -> bool {
@@ -326,19 +319,12 @@ fn validate_fullscreen_execution(
graph: &CompiledGraph,
i: usize,
execution: &CompiledExecution,
contract: &Contract,
) -> Result<(), GraphError> {
let key = execution.executor.key.as_str();
if !matches!(
key,
"fullscreen_copy"
| "tone_map"
| "bloom_extract"
| "bloom_blur"
| "bloom_composite"
| "luminance_edge"
) {
let Some(policy) = contract.fullscreen_policy else {
return Ok(());
}
};
let path = |field| format!("executions[{i}].{field}");
let valid_parameters = match (key, &execution.parameters) {
("fullscreen_copy", NormalizedParameters::FullscreenCopy) => true,
@@ -372,16 +358,12 @@ fn validate_fullscreen_execution(
path("parameters"),
));
}
let expected_inputs = if key == "bloom_composite" {
&["source", "bloom", "colorTarget"][..]
} else {
&["source", "colorTarget"][..]
};
let expected_inputs: Vec<_> = contract.inputs.iter().map(|input| input.name).collect();
if execution.inputs.len() != expected_inputs.len()
|| execution
.inputs
.iter()
.zip(expected_inputs)
.zip(&expected_inputs)
.any(|(v, s)| v.socket != *s)
{
return Err(invalid("fullscreen inputs mismatch", path("inputs")));
@@ -410,7 +392,18 @@ fn validate_fullscreen_execution(
{
return Err(invalid("fullscreen attachment mismatch", path("kind")));
}
let sampled_count = expected_inputs.len() - 1;
let sampled_inputs: Vec<_> = contract
.inputs
.iter()
.filter(|input| input.role == InputRole::SampledTexture)
.collect();
let sampled_count = sampled_inputs.len();
if contract.inputs[..sampled_count]
.iter()
.any(|input| input.role != InputRole::SampledTexture)
{
return Err(invalid("fullscreen inputs mismatch", path("inputs")));
}
if execution.accesses.len() != sampled_count + 1
|| execution.inputs[..sampled_count]
.iter()
@@ -501,13 +494,13 @@ fn validate_fullscreen_execution(
single_view_d2(d) && d.format == TextureFormat::Rgba16Float
};
let descriptors_valid = hdr(source)
&& match key {
"fullscreen_copy" => {
&& match policy {
FullscreenPolicy::Copy => {
single_view_d2(target_d)
&& target_d.format != TextureFormat::Depth32Float
&& target_d.extent == source.extent
}
"tone_map" => {
FullscreenPolicy::ToneMap => {
single_view_d2(target_d)
&& !matches!(
target_d.format,
@@ -515,14 +508,13 @@ fn validate_fullscreen_execution(
)
&& target_d.extent == source.extent
}
"bloom_extract" => hdr(target_d),
"bloom_blur" | "luminance_edge" => hdr(target_d) && target_d.extent == source.extent,
"bloom_composite" => {
FullscreenPolicy::BloomExtract => hdr(target_d),
FullscreenPolicy::HdrSameExtent => hdr(target_d) && target_d.extent == source.extent,
FullscreenPolicy::BloomComposite => {
hdr(target_d)
&& target_d.extent == source.extent
&& texture_descriptor(graph, execution.inputs[1].resource).is_some_and(hdr)
}
_ => false,
};
if !descriptors_valid {
return Err(invalid(
@@ -769,7 +761,7 @@ fn validate_canonical_plan(graph: &CompiledGraph) -> Result<(), GraphError> {
referenced.insert(access.resource);
}
validate_compute_execution(graph, i, execution)?;
validate_fullscreen_execution(graph, i, execution)?;
validate_fullscreen_execution(graph, i, execution, contract)?;
for resource in referenced {
uses.get_mut(resource as usize)
.ok_or_else(|| {
@@ -950,8 +942,8 @@ pub fn prepare_runtime_plan(
}
}
"pipeline_registry" | "pipeline" => {}
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur" | "bloom_composite"
| "luminance_edge" => {}
_ if contract(&execution.executor.key)
.is_some_and(|contract| contract.fullscreen_policy.is_some()) => {}
"frustum_cull" => {}
"frame_out" => {
if frame_out_index.replace(i).is_some() {
+25
View File
@@ -847,6 +847,31 @@ fn exact_phase_four_contract_catalog_and_mesh_metadata() {
("frame_out", 1),
]
);
assert_eq!(
CONTRACTS
.iter()
.map(|contract| (contract.key, contract.fullscreen_policy))
.collect::<Vec<_>>(),
[
("mesh", None),
("texture", None),
("frustum_cull", None),
("mesh_query", None),
("pipeline_registry", None),
("pipeline", None),
("fullscreen_copy", Some(FullscreenPolicy::Copy)),
("tone_map", Some(FullscreenPolicy::ToneMap)),
("bloom_extract", Some(FullscreenPolicy::BloomExtract)),
("bloom_blur", Some(FullscreenPolicy::HdrSameExtent)),
("bloom_composite", Some(FullscreenPolicy::BloomComposite)),
("luminance_edge", Some(FullscreenPolicy::HdrSameExtent)),
("frame_out", None),
]
);
for contract in CONTRACTS {
let serialized = serde_json::to_value(contract).unwrap();
assert!(serialized.get("fullscreenPolicy").is_none());
}
let mesh = contract("mesh").unwrap();
assert_eq!(mesh.execution, ExecutionClass::Source);
assert!(mesh.inputs.is_empty());
+6 -6
View File
@@ -1,7 +1,7 @@
@group(0) @binding(0) var source_texture: texture_2d<f32>;
@group(0) @binding(1) var second_texture: texture_2d<f32>;
@group(0) @binding(2) var linear_clamp: sampler;
struct Parameters { a: vec4<f32>, b: vec4<f32> }
struct Parameters { values: array<vec4<f32>, 8> }
@group(0) @binding(3) var<uniform> parameters: Parameters;
struct VertexOut { @builtin(position) position: vec4<f32>, @location(0) uv: vec2<f32> }
@@ -20,19 +20,19 @@ fn linear_to_srgb(x: vec3<f32>) -> vec3<f32> {
let low = x * 12.92; let high = 1.055 * pow(x, vec3(1.0 / 2.4)) - vec3(0.055);
return select(high, low, x <= vec3(0.0031308));
}
@fragment fn fs_tone_map(in: VertexOut) -> @location(0) vec4<f32> { let c=sample_source(in.uv); return vec4(linear_to_srgb(aces(c.rgb * parameters.a.x)), c.a); }
@fragment fn fs_tone_map(in: VertexOut) -> @location(0) vec4<f32> { let c=sample_source(in.uv); return vec4(linear_to_srgb(aces(c.rgb * parameters.values[0].x)), c.a); }
@fragment fn fs_bloom_extract(in: VertexOut) -> @location(0) vec4<f32> {
let c=sample_source(in.uv); let brightness=max(c.r,max(c.g,c.b)); let knee=max(parameters.a.y,0.00001); let soft=clamp((brightness-parameters.a.x+knee)/(2.0*knee),0.0,1.0); let contribution=max(brightness-parameters.a.x,0.0)+soft*soft*knee; return vec4(c.rgb*contribution/max(brightness,0.00001),1.0);
let c=sample_source(in.uv); let brightness=max(c.r,max(c.g,c.b)); let knee=max(parameters.values[0].y,0.00001); let soft=clamp((brightness-parameters.values[0].x+knee)/(2.0*knee),0.0,1.0); let contribution=max(brightness-parameters.values[0].x,0.0)+soft*soft*knee; return vec4(c.rgb*contribution/max(brightness,0.00001),1.0);
}
@fragment fn fs_bloom_blur(in: VertexOut) -> @location(0) vec4<f32> {
let size=vec2<f32>(textureDimensions(source_texture)); let step=parameters.a.xy*parameters.a.z/size;
let size=vec2<f32>(textureDimensions(source_texture)); let step=parameters.values[0].xy*parameters.values[0].z/size;
var c=sample_source(in.uv)*0.227027; c+=sample_source(in.uv+step*1.384615)*0.316216; c+=sample_source(in.uv-step*1.384615)*0.316216; c+=sample_source(in.uv+step*3.230769)*0.070270; c+=sample_source(in.uv-step*3.230769)*0.070270; return c;
}
@fragment fn fs_bloom_composite(in: VertexOut) -> @location(0) vec4<f32> { let c=sample_source(in.uv); return vec4(c.rgb+textureSampleLevel(second_texture,linear_clamp,in.uv,0.0).rgb*parameters.a.x,c.a); }
@fragment fn fs_bloom_composite(in: VertexOut) -> @location(0) vec4<f32> { let c=sample_source(in.uv); return vec4(c.rgb+textureSampleLevel(second_texture,linear_clamp,in.uv,0.0).rgb*parameters.values[0].x,c.a); }
fn luminance(c: vec3<f32>) -> f32 { return dot(c,vec3(0.2126,0.7152,0.0722)); }
@fragment fn fs_luminance_edge(in: VertexOut) -> @location(0) vec4<f32> {
let d=1.0/vec2<f32>(textureDimensions(source_texture)); var gx=0.0; var gy=0.0;
gx += -luminance(sample_source(in.uv+d*vec2(-1.0,-1.0)).rgb)+luminance(sample_source(in.uv+d*vec2(1.0,-1.0)).rgb); gx += -2.0*luminance(sample_source(in.uv+d*vec2(-1.0,0.0)).rgb)+2.0*luminance(sample_source(in.uv+d*vec2(1.0,0.0)).rgb); gx += -luminance(sample_source(in.uv+d*vec2(-1.0,1.0)).rgb)+luminance(sample_source(in.uv+d*vec2(1.0,1.0)).rgb);
gy += -luminance(sample_source(in.uv+d*vec2(-1.0,-1.0)).rgb)-2.0*luminance(sample_source(in.uv+d*vec2(0.0,-1.0)).rgb)-luminance(sample_source(in.uv+d*vec2(1.0,-1.0)).rgb); gy += luminance(sample_source(in.uv+d*vec2(-1.0,1.0)).rgb)+2.0*luminance(sample_source(in.uv+d*vec2(0.0,1.0)).rgb)+luminance(sample_source(in.uv+d*vec2(1.0,1.0)).rgb);
let edge=clamp(length(vec2(gx,gy))*parameters.a.x,0.0,1.0); return vec4(vec3(edge),1.0);
let edge=clamp(length(vec2(gx,gy))*parameters.values[0].x,0.0,1.0); return vec4(vec3(edge),1.0);
}
+128 -49
View File
@@ -27,6 +27,104 @@ pub use pipeline_library::PipelineLibrary;
const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
#[repr(C, align(16))]
#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
struct FullscreenUniforms {
values: [[f32; 4]; 8],
}
fn pack_fullscreen_uniforms(
key: &str,
parameters: &crate::render_graph::NormalizedParameters,
) -> Option<FullscreenUniforms> {
use crate::render_graph::NormalizedParameters;
let first = match (key, parameters) {
(
"fullscreen_copy" | "frame_out",
NormalizedParameters::FullscreenCopy | NormalizedParameters::FrameOut,
) => [0.; 4],
("tone_map", NormalizedParameters::ToneMap { exposure }) => [*exposure, 0., 0., 0.],
("bloom_extract", NormalizedParameters::BloomExtract { threshold, knee }) => {
[*threshold, *knee, 0., 0.]
}
("bloom_blur", NormalizedParameters::BloomBlur { direction, radius }) => {
[direction[0], direction[1], *radius, 0.]
}
("bloom_composite", NormalizedParameters::BloomComposite { intensity }) => {
[*intensity, 0., 0., 0.]
}
("luminance_edge", NormalizedParameters::LuminanceEdge { strength }) => {
[*strength, 0., 0., 0.]
}
_ => return None,
};
let mut values = [[0.; 4]; 8];
values[0] = first;
Some(FullscreenUniforms { values })
}
fn resolve_fullscreen_entry(key: &str) -> Option<&'static str> {
match key {
"fullscreen_copy" | "frame_out" => Some("fs_copy"),
"tone_map" => Some("fs_tone_map"),
"bloom_extract" => Some("fs_bloom_extract"),
"bloom_blur" => Some("fs_bloom_blur"),
"bloom_composite" => Some("fs_bloom_composite"),
"luminance_edge" => Some("fs_luminance_edge"),
_ => None,
}
}
#[cfg(test)]
mod fullscreen_tests {
use super::*;
use crate::render_graph::NormalizedParameters;
#[test]
fn fullscreen_uniform_abi_and_packer_are_fixed() {
assert_eq!(std::mem::size_of::<FullscreenUniforms>(), 128);
assert_eq!(std::mem::align_of::<FullscreenUniforms>(), 16);
let packed = pack_fullscreen_uniforms(
"bloom_blur",
&NormalizedParameters::BloomBlur {
direction: [0.0, 1.0],
radius: 3.0,
},
)
.unwrap();
assert_eq!(packed.values[0], [0.0, 1.0, 3.0, 0.0]);
assert!(packed.values[1..].iter().all(|value| *value == [0.0; 4]));
assert_eq!(bytemuck::bytes_of(&packed).len(), 128);
assert!(
pack_fullscreen_uniforms("tone_map", &NormalizedParameters::FullscreenCopy).is_none()
);
}
#[test]
fn fullscreen_entries_are_explicit() {
assert_eq!(resolve_fullscreen_entry("fullscreen_copy"), Some("fs_copy"));
assert_eq!(resolve_fullscreen_entry("frame_out"), Some("fs_copy"));
assert_eq!(resolve_fullscreen_entry("tone_map"), Some("fs_tone_map"));
assert_eq!(
resolve_fullscreen_entry("bloom_extract"),
Some("fs_bloom_extract")
);
assert_eq!(
resolve_fullscreen_entry("bloom_blur"),
Some("fs_bloom_blur")
);
assert_eq!(
resolve_fullscreen_entry("bloom_composite"),
Some("fs_bloom_composite")
);
assert_eq!(
resolve_fullscreen_entry("luminance_edge"),
Some("fs_luminance_edge")
);
assert_eq!(resolve_fullscreen_entry("unknown"), None);
}
}
struct GpuTextureSlot {
_texture: wgpu::Texture,
view: wgpu::TextureView,
@@ -840,7 +938,7 @@ impl<T: Scene + 'static> Renderer<T> {
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
min_binding_size: wgpu::BufferSize::new(128),
},
count: None,
},
@@ -872,11 +970,14 @@ impl<T: Scene + 'static> Renderer<T> {
});
let mut executions = Vec::new();
for (index, execution) in graph.executions.iter().enumerate() {
let contract = crate::render_graph::contract(&execution.executor.key)
.ok_or_else(|| fail("executor contract missing"))?;
match execution.executor.key.as_str() {
"frustum_cull" => executions.push(PreparedExecution::FrustumCull),
"mesh_query" => executions.push(PreparedExecution::MeshQuery),
"frame_out" | "fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur"
| "bloom_composite" | "luminance_edge" => {
_ if execution.executor.key == "frame_out"
|| contract.fullscreen_policy.is_some() =>
{
let frame_out = execution.executor.key == "frame_out";
let (source, second) = if frame_out {
let ExecutionKind::FrameOut { color } = execution.kind else {
@@ -884,51 +985,37 @@ impl<T: Scene + 'static> Renderer<T> {
};
(color, color)
} else {
match execution.inputs.as_slice() {
[source, _color_target] => (source.resource, source.resource),
[source, bloom, _color_target]
if execution.executor.key == "bloom_composite" =>
{
(source.resource, bloom.resource)
}
let sampled: Vec<_> = contract
.inputs
.iter()
.enumerate()
.filter(|(_, input)| {
input.role == crate::render_graph::InputRole::SampledTexture
})
.map(|(index, _)| {
execution.inputs.get(index).map(|input| input.resource)
})
.collect::<Option<_>>()
.ok_or_else(|| fail("fullscreen inputs mismatch"))?;
match (contract.fullscreen_policy, sampled.as_slice()) {
(
Some(crate::render_graph::FullscreenPolicy::BloomComposite),
[source, second],
) => (*source, *second),
(Some(_), [source]) => (*source, *source),
_ => return Err(fail("fullscreen inputs mismatch")),
}
};
let values: [f32; 8] =
match (execution.executor.key.as_str(), &execution.parameters) {
(
"fullscreen_copy" | "frame_out",
NormalizedParameters::FullscreenCopy
| NormalizedParameters::FrameOut,
) => [0.; 8],
("tone_map", NormalizedParameters::ToneMap { exposure }) => {
[*exposure, 0., 0., 0., 0., 0., 0., 0.]
}
(
"bloom_extract",
NormalizedParameters::BloomExtract { threshold, knee },
) => [*threshold, *knee, 0., 0., 0., 0., 0., 0.],
(
"bloom_blur",
NormalizedParameters::BloomBlur { direction, radius },
) => [direction[0], direction[1], *radius, 0., 0., 0., 0., 0.],
(
"bloom_composite",
NormalizedParameters::BloomComposite { intensity },
) => [*intensity, 0., 0., 0., 0., 0., 0., 0.],
(
"luminance_edge",
NormalizedParameters::LuminanceEdge { strength },
) => [*strength, 0., 0., 0., 0., 0., 0., 0.],
_ => return Err(fail("executor parameters mismatch")),
};
let values =
pack_fullscreen_uniforms(&execution.executor.key, &execution.parameters)
.ok_or_else(|| fail("executor parameters mismatch"))?;
use wgpu::util::DeviceExt;
let uniform =
self.context
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(" post parameters"),
contents: bytemuck::cast_slice(&values),
contents: bytemuck::bytes_of(&values),
usage: wgpu::BufferUsages::UNIFORM,
});
let target_format = if frame_out {
@@ -960,16 +1047,8 @@ impl<T: Scene + 'static> Renderer<T> {
.descriptor
.format
};
let entry = match execution.executor.key.as_str() {
"fullscreen_copy" => "fs_copy",
"tone_map" => "fs_tone_map",
"bloom_extract" => "fs_bloom_extract",
"bloom_blur" => "fs_bloom_blur",
"bloom_composite" => "fs_bloom_composite",
"luminance_edge" => "fs_luminance_edge",
"frame_out" => "fs_copy",
_ => return Err(fail("fullscreen executor mismatch")),
};
let entry = resolve_fullscreen_entry(&execution.executor.key)
.ok_or_else(|| fail("fullscreen executor mismatch"))?;
let pipeline = self.context.device.create_render_pipeline(
&wgpu::RenderPipelineDescriptor {
label: Some(" post pipeline"),