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:
@@ -946,8 +946,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
|||||||
}
|
}
|
||||||
let transition_sockets: &[(&str, u16)] = match contracts[i].key {
|
let transition_sockets: &[(&str, u16)] = match contracts[i].key {
|
||||||
"pipeline" => &[("colorTarget", 0), ("depthTarget", 1)],
|
"pipeline" => &[("colorTarget", 0), ("depthTarget", 1)],
|
||||||
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur" | "bloom_composite"
|
_ if contracts[i].fullscreen_policy.is_some() => &[("colorTarget", 0)],
|
||||||
| "luminance_edge" => &[("colorTarget", 0)],
|
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
for &(input_socket, output_ordinal) in transition_sockets {
|
for &(input_socket, output_ordinal) in transition_sockets {
|
||||||
@@ -1118,12 +1117,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
|||||||
format!("nodes[{i}].inputs"),
|
format!("nodes[{i}].inputs"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if contracts[i].key != "frame_out"
|
} else if contracts[i].fullscreen_policy.is_some() {
|
||||||
&& contracts[i]
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.any(|input| matches!(input.role, InputRole::SampledTexture))
|
|
||||||
{
|
|
||||||
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));
|
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 {
|
if hazard {
|
||||||
return Err(error(
|
return Err(error(
|
||||||
@@ -1213,13 +1207,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..graph.nodes.len() {
|
for i in 0..graph.nodes.len() {
|
||||||
if !live.contains(&i)
|
if !live.contains(&i) || contracts[i].fullscreen_policy.is_none() {
|
||||||
|| contracts[i].key == "frame_out"
|
|
||||||
|| !contracts[i]
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.any(|input| matches!(input.role, InputRole::SampledTexture))
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let source_key = bound[i]["source"].producer;
|
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| {
|
let authored_target_ok = target_descriptor.is_some_and(|descriptor| {
|
||||||
descriptor.format == TextureFormat::Rgba16Float && is_single_view_d2(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 bloom_key = bound[i]["bloom"].producer;
|
||||||
let Some(&(bloom_family_id, _, _)) = version_of.get(&bloom_key) else {
|
let Some(&(bloom_family_id, _, _)) = version_of.get(&bloom_key) else {
|
||||||
return Err(error(
|
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 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
|
let target_matches_source = target_descriptor
|
||||||
.is_some_and(|descriptor| descriptor.extent == source_descriptor.extent);
|
.is_some_and(|descriptor| descriptor.extent == source_descriptor.extent);
|
||||||
let descriptor_ok = match contracts[i].key {
|
let descriptor_ok = match contracts[i].fullscreen_policy {
|
||||||
"fullscreen_copy" => {
|
Some(FullscreenPolicy::Copy) => {
|
||||||
target_descriptor.is_none() && source_is_full_surface
|
target_descriptor.is_none() && source_is_full_surface
|
||||||
|| target_descriptor.is_some_and(|descriptor| {
|
|| target_descriptor.is_some_and(|descriptor| {
|
||||||
descriptor.format != TextureFormat::Depth32Float
|
descriptor.format != TextureFormat::Depth32Float
|
||||||
@@ -1273,15 +1263,17 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
|||||||
&& descriptor.extent == source_descriptor.extent
|
&& 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::Depth32Float
|
||||||
&& descriptor.format != TextureFormat::R32Float
|
&& descriptor.format != TextureFormat::R32Float
|
||||||
&& is_single_view_d2(descriptor)
|
&& is_single_view_d2(descriptor)
|
||||||
&& descriptor.extent == source_descriptor.extent
|
&& descriptor.extent == source_descriptor.extent
|
||||||
}),
|
}),
|
||||||
"bloom_extract" => authored_target_ok,
|
Some(FullscreenPolicy::BloomExtract) => authored_target_ok,
|
||||||
"bloom_blur" | "luminance_edge" => authored_target_ok && target_matches_source,
|
Some(FullscreenPolicy::HdrSameExtent) => authored_target_ok && target_matches_source,
|
||||||
"bloom_composite" => authored_target_ok && target_matches_source && bloom_input_ok,
|
Some(FullscreenPolicy::BloomComposite) => {
|
||||||
|
authored_target_ok && target_matches_source && bloom_input_ok
|
||||||
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
if !source_ok || !descriptor_ok {
|
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"
|
_ if contracts[i].fullscreen_policy.is_some() => {
|
||||||
| "luminance_edge" => {
|
|
||||||
let color = output_ids[&OutputKey(i, 0)];
|
let color = output_ids[&OutputKey(i, 0)];
|
||||||
let load = NormalizedColorLoad::Clear {
|
let load = NormalizedColorLoad::Clear {
|
||||||
value: [0.0, 0.0, 0.0, 0.0],
|
value: [0.0, 0.0, 0.0, 0.0],
|
||||||
};
|
};
|
||||||
accesses.push(CompiledAccess {
|
for input in contracts[i]
|
||||||
socket: "source".into(),
|
.inputs
|
||||||
resource: input_resource("source"),
|
.iter()
|
||||||
mode: AccessMode::SampledTexture,
|
.filter(|input| input.role == InputRole::SampledTexture)
|
||||||
});
|
{
|
||||||
if contracts[i].key == "bloom_composite" {
|
|
||||||
accesses.push(CompiledAccess {
|
accesses.push(CompiledAccess {
|
||||||
socket: "bloom".into(),
|
socket: input.name.into(),
|
||||||
resource: input_resource("bloom"),
|
resource: input_resource(input.name),
|
||||||
mode: AccessMode::SampledTexture,
|
mode: AccessMode::SampledTexture,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ pub enum ExecutionClass {
|
|||||||
Frame,
|
Frame,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FullscreenPolicy {
|
||||||
|
Copy,
|
||||||
|
ToneMap,
|
||||||
|
HdrSameExtent,
|
||||||
|
BloomExtract,
|
||||||
|
BloomComposite,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum InputCardinality {
|
pub enum InputCardinality {
|
||||||
@@ -81,6 +90,8 @@ pub struct Contract {
|
|||||||
pub inputs: &'static [InputSocketContract],
|
pub inputs: &'static [InputSocketContract],
|
||||||
pub outputs: &'static [OutputSocketContract],
|
pub outputs: &'static [OutputSocketContract],
|
||||||
pub inherently_observable: bool,
|
pub inherently_observable: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub fullscreen_policy: Option<FullscreenPolicy>,
|
||||||
}
|
}
|
||||||
|
|
||||||
use SemanticType::*;
|
use SemanticType::*;
|
||||||
@@ -268,6 +279,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: NONE_IN,
|
inputs: NONE_IN,
|
||||||
outputs: MESH_OUT,
|
outputs: MESH_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: None,
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "texture",
|
key: "texture",
|
||||||
@@ -276,6 +288,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: NONE_IN,
|
inputs: NONE_IN,
|
||||||
outputs: TEXTURE_OUT,
|
outputs: TEXTURE_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: None,
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "frustum_cull",
|
key: "frustum_cull",
|
||||||
@@ -284,6 +297,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: CULL_IN,
|
inputs: CULL_IN,
|
||||||
outputs: CULLED_OUT,
|
outputs: CULLED_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: None,
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "mesh_query",
|
key: "mesh_query",
|
||||||
@@ -292,6 +306,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: QUERY_IN,
|
inputs: QUERY_IN,
|
||||||
outputs: DRAW_OUT,
|
outputs: DRAW_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: None,
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "pipeline_registry",
|
key: "pipeline_registry",
|
||||||
@@ -300,6 +315,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: REGISTRY_IN,
|
inputs: REGISTRY_IN,
|
||||||
outputs: ACTIVATION_OUT,
|
outputs: ACTIVATION_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: None,
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "pipeline",
|
key: "pipeline",
|
||||||
@@ -308,6 +324,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: PIPELINE_IN,
|
inputs: PIPELINE_IN,
|
||||||
outputs: PIPELINE_OUT,
|
outputs: PIPELINE_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: None,
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "fullscreen_copy",
|
key: "fullscreen_copy",
|
||||||
@@ -316,6 +333,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: FULLSCREEN_COPY_IN,
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: Some(FullscreenPolicy::Copy),
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "tone_map",
|
key: "tone_map",
|
||||||
@@ -324,6 +342,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: FULLSCREEN_COPY_IN,
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: Some(FullscreenPolicy::ToneMap),
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "bloom_extract",
|
key: "bloom_extract",
|
||||||
@@ -332,6 +351,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: FULLSCREEN_COPY_IN,
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: Some(FullscreenPolicy::BloomExtract),
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "bloom_blur",
|
key: "bloom_blur",
|
||||||
@@ -340,6 +360,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: FULLSCREEN_COPY_IN,
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: Some(FullscreenPolicy::HdrSameExtent),
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "bloom_composite",
|
key: "bloom_composite",
|
||||||
@@ -348,6 +369,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: BLOOM_COMPOSITE_IN,
|
inputs: BLOOM_COMPOSITE_IN,
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: Some(FullscreenPolicy::BloomComposite),
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "luminance_edge",
|
key: "luminance_edge",
|
||||||
@@ -356,6 +378,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: FULLSCREEN_COPY_IN,
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
inherently_observable: false,
|
inherently_observable: false,
|
||||||
|
fullscreen_policy: Some(FullscreenPolicy::HdrSameExtent),
|
||||||
},
|
},
|
||||||
Contract {
|
Contract {
|
||||||
key: "frame_out",
|
key: "frame_out",
|
||||||
@@ -364,6 +387,7 @@ pub static CONTRACTS: &[Contract] = &[
|
|||||||
inputs: FRAME_OUT_IN,
|
inputs: FRAME_OUT_IN,
|
||||||
outputs: NONE_OUT,
|
outputs: NONE_OUT,
|
||||||
inherently_observable: true,
|
inherently_observable: true,
|
||||||
|
fullscreen_policy: None,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -256,20 +256,13 @@ fn valid_pipeline_name(name: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn execution_supported(key: &str) -> bool {
|
fn execution_supported(key: &str) -> bool {
|
||||||
matches!(
|
contract(key).is_some_and(|contract| {
|
||||||
key,
|
contract.fullscreen_policy.is_some()
|
||||||
"frustum_cull"
|
|| matches!(
|
||||||
| "mesh_query"
|
key,
|
||||||
| "pipeline_registry"
|
"frustum_cull" | "mesh_query" | "pipeline_registry" | "pipeline" | "frame_out"
|
||||||
| "pipeline"
|
)
|
||||||
| "fullscreen_copy"
|
})
|
||||||
| "tone_map"
|
|
||||||
| "bloom_extract"
|
|
||||||
| "bloom_blur"
|
|
||||||
| "bloom_composite"
|
|
||||||
| "luminance_edge"
|
|
||||||
| "frame_out"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resource_is_mesh(graph: &CompiledGraph, id: u32) -> bool {
|
fn resource_is_mesh(graph: &CompiledGraph, id: u32) -> bool {
|
||||||
@@ -326,19 +319,12 @@ fn validate_fullscreen_execution(
|
|||||||
graph: &CompiledGraph,
|
graph: &CompiledGraph,
|
||||||
i: usize,
|
i: usize,
|
||||||
execution: &CompiledExecution,
|
execution: &CompiledExecution,
|
||||||
|
contract: &Contract,
|
||||||
) -> Result<(), GraphError> {
|
) -> Result<(), GraphError> {
|
||||||
let key = execution.executor.key.as_str();
|
let key = execution.executor.key.as_str();
|
||||||
if !matches!(
|
let Some(policy) = contract.fullscreen_policy else {
|
||||||
key,
|
|
||||||
"fullscreen_copy"
|
|
||||||
| "tone_map"
|
|
||||||
| "bloom_extract"
|
|
||||||
| "bloom_blur"
|
|
||||||
| "bloom_composite"
|
|
||||||
| "luminance_edge"
|
|
||||||
) {
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
};
|
||||||
let path = |field| format!("executions[{i}].{field}");
|
let path = |field| format!("executions[{i}].{field}");
|
||||||
let valid_parameters = match (key, &execution.parameters) {
|
let valid_parameters = match (key, &execution.parameters) {
|
||||||
("fullscreen_copy", NormalizedParameters::FullscreenCopy) => true,
|
("fullscreen_copy", NormalizedParameters::FullscreenCopy) => true,
|
||||||
@@ -372,16 +358,12 @@ fn validate_fullscreen_execution(
|
|||||||
path("parameters"),
|
path("parameters"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let expected_inputs = if key == "bloom_composite" {
|
let expected_inputs: Vec<_> = contract.inputs.iter().map(|input| input.name).collect();
|
||||||
&["source", "bloom", "colorTarget"][..]
|
|
||||||
} else {
|
|
||||||
&["source", "colorTarget"][..]
|
|
||||||
};
|
|
||||||
if execution.inputs.len() != expected_inputs.len()
|
if execution.inputs.len() != expected_inputs.len()
|
||||||
|| execution
|
|| execution
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.zip(expected_inputs)
|
.zip(&expected_inputs)
|
||||||
.any(|(v, s)| v.socket != *s)
|
.any(|(v, s)| v.socket != *s)
|
||||||
{
|
{
|
||||||
return Err(invalid("fullscreen inputs mismatch", path("inputs")));
|
return Err(invalid("fullscreen inputs mismatch", path("inputs")));
|
||||||
@@ -410,7 +392,18 @@ fn validate_fullscreen_execution(
|
|||||||
{
|
{
|
||||||
return Err(invalid("fullscreen attachment mismatch", path("kind")));
|
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
|
if execution.accesses.len() != sampled_count + 1
|
||||||
|| execution.inputs[..sampled_count]
|
|| execution.inputs[..sampled_count]
|
||||||
.iter()
|
.iter()
|
||||||
@@ -501,13 +494,13 @@ fn validate_fullscreen_execution(
|
|||||||
single_view_d2(d) && d.format == TextureFormat::Rgba16Float
|
single_view_d2(d) && d.format == TextureFormat::Rgba16Float
|
||||||
};
|
};
|
||||||
let descriptors_valid = hdr(source)
|
let descriptors_valid = hdr(source)
|
||||||
&& match key {
|
&& match policy {
|
||||||
"fullscreen_copy" => {
|
FullscreenPolicy::Copy => {
|
||||||
single_view_d2(target_d)
|
single_view_d2(target_d)
|
||||||
&& target_d.format != TextureFormat::Depth32Float
|
&& target_d.format != TextureFormat::Depth32Float
|
||||||
&& target_d.extent == source.extent
|
&& target_d.extent == source.extent
|
||||||
}
|
}
|
||||||
"tone_map" => {
|
FullscreenPolicy::ToneMap => {
|
||||||
single_view_d2(target_d)
|
single_view_d2(target_d)
|
||||||
&& !matches!(
|
&& !matches!(
|
||||||
target_d.format,
|
target_d.format,
|
||||||
@@ -515,14 +508,13 @@ fn validate_fullscreen_execution(
|
|||||||
)
|
)
|
||||||
&& target_d.extent == source.extent
|
&& target_d.extent == source.extent
|
||||||
}
|
}
|
||||||
"bloom_extract" => hdr(target_d),
|
FullscreenPolicy::BloomExtract => hdr(target_d),
|
||||||
"bloom_blur" | "luminance_edge" => hdr(target_d) && target_d.extent == source.extent,
|
FullscreenPolicy::HdrSameExtent => hdr(target_d) && target_d.extent == source.extent,
|
||||||
"bloom_composite" => {
|
FullscreenPolicy::BloomComposite => {
|
||||||
hdr(target_d)
|
hdr(target_d)
|
||||||
&& target_d.extent == source.extent
|
&& target_d.extent == source.extent
|
||||||
&& texture_descriptor(graph, execution.inputs[1].resource).is_some_and(hdr)
|
&& texture_descriptor(graph, execution.inputs[1].resource).is_some_and(hdr)
|
||||||
}
|
}
|
||||||
_ => false,
|
|
||||||
};
|
};
|
||||||
if !descriptors_valid {
|
if !descriptors_valid {
|
||||||
return Err(invalid(
|
return Err(invalid(
|
||||||
@@ -769,7 +761,7 @@ fn validate_canonical_plan(graph: &CompiledGraph) -> Result<(), GraphError> {
|
|||||||
referenced.insert(access.resource);
|
referenced.insert(access.resource);
|
||||||
}
|
}
|
||||||
validate_compute_execution(graph, i, execution)?;
|
validate_compute_execution(graph, i, execution)?;
|
||||||
validate_fullscreen_execution(graph, i, execution)?;
|
validate_fullscreen_execution(graph, i, execution, contract)?;
|
||||||
for resource in referenced {
|
for resource in referenced {
|
||||||
uses.get_mut(resource as usize)
|
uses.get_mut(resource as usize)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
@@ -950,8 +942,8 @@ pub fn prepare_runtime_plan(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"pipeline_registry" | "pipeline" => {}
|
"pipeline_registry" | "pipeline" => {}
|
||||||
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur" | "bloom_composite"
|
_ if contract(&execution.executor.key)
|
||||||
| "luminance_edge" => {}
|
.is_some_and(|contract| contract.fullscreen_policy.is_some()) => {}
|
||||||
"frustum_cull" => {}
|
"frustum_cull" => {}
|
||||||
"frame_out" => {
|
"frame_out" => {
|
||||||
if frame_out_index.replace(i).is_some() {
|
if frame_out_index.replace(i).is_some() {
|
||||||
|
|||||||
@@ -847,6 +847,31 @@ fn exact_phase_four_contract_catalog_and_mesh_metadata() {
|
|||||||
("frame_out", 1),
|
("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();
|
let mesh = contract("mesh").unwrap();
|
||||||
assert_eq!(mesh.execution, ExecutionClass::Source);
|
assert_eq!(mesh.execution, ExecutionClass::Source);
|
||||||
assert!(mesh.inputs.is_empty());
|
assert!(mesh.inputs.is_empty());
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@group(0) @binding(0) var source_texture: texture_2d<f32>;
|
@group(0) @binding(0) var source_texture: texture_2d<f32>;
|
||||||
@group(0) @binding(1) var second_texture: texture_2d<f32>;
|
@group(0) @binding(1) var second_texture: texture_2d<f32>;
|
||||||
@group(0) @binding(2) var linear_clamp: sampler;
|
@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;
|
@group(0) @binding(3) var<uniform> parameters: Parameters;
|
||||||
|
|
||||||
struct VertexOut { @builtin(position) position: vec4<f32>, @location(0) uv: vec2<f32> }
|
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);
|
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));
|
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> {
|
@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> {
|
@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;
|
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)); }
|
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> {
|
@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;
|
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);
|
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);
|
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
@@ -27,6 +27,104 @@ pub use pipeline_library::PipelineLibrary;
|
|||||||
|
|
||||||
const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
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 {
|
struct GpuTextureSlot {
|
||||||
_texture: wgpu::Texture,
|
_texture: wgpu::Texture,
|
||||||
view: wgpu::TextureView,
|
view: wgpu::TextureView,
|
||||||
@@ -840,7 +938,7 @@ impl<T: Scene + 'static> Renderer<T> {
|
|||||||
ty: wgpu::BindingType::Buffer {
|
ty: wgpu::BindingType::Buffer {
|
||||||
ty: wgpu::BufferBindingType::Uniform,
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
min_binding_size: None,
|
min_binding_size: wgpu::BufferSize::new(128),
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
@@ -872,11 +970,14 @@ impl<T: Scene + 'static> Renderer<T> {
|
|||||||
});
|
});
|
||||||
let mut executions = Vec::new();
|
let mut executions = Vec::new();
|
||||||
for (index, execution) in graph.executions.iter().enumerate() {
|
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() {
|
match execution.executor.key.as_str() {
|
||||||
"frustum_cull" => executions.push(PreparedExecution::FrustumCull),
|
"frustum_cull" => executions.push(PreparedExecution::FrustumCull),
|
||||||
"mesh_query" => executions.push(PreparedExecution::MeshQuery),
|
"mesh_query" => executions.push(PreparedExecution::MeshQuery),
|
||||||
"frame_out" | "fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur"
|
_ if execution.executor.key == "frame_out"
|
||||||
| "bloom_composite" | "luminance_edge" => {
|
|| contract.fullscreen_policy.is_some() =>
|
||||||
|
{
|
||||||
let frame_out = execution.executor.key == "frame_out";
|
let frame_out = execution.executor.key == "frame_out";
|
||||||
let (source, second) = if frame_out {
|
let (source, second) = if frame_out {
|
||||||
let ExecutionKind::FrameOut { color } = execution.kind else {
|
let ExecutionKind::FrameOut { color } = execution.kind else {
|
||||||
@@ -884,51 +985,37 @@ impl<T: Scene + 'static> Renderer<T> {
|
|||||||
};
|
};
|
||||||
(color, color)
|
(color, color)
|
||||||
} else {
|
} else {
|
||||||
match execution.inputs.as_slice() {
|
let sampled: Vec<_> = contract
|
||||||
[source, _color_target] => (source.resource, source.resource),
|
.inputs
|
||||||
[source, bloom, _color_target]
|
.iter()
|
||||||
if execution.executor.key == "bloom_composite" =>
|
.enumerate()
|
||||||
{
|
.filter(|(_, input)| {
|
||||||
(source.resource, bloom.resource)
|
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")),
|
_ => return Err(fail("fullscreen inputs mismatch")),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let values: [f32; 8] =
|
let values =
|
||||||
match (execution.executor.key.as_str(), &execution.parameters) {
|
pack_fullscreen_uniforms(&execution.executor.key, &execution.parameters)
|
||||||
(
|
.ok_or_else(|| fail("executor parameters mismatch"))?;
|
||||||
"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")),
|
|
||||||
};
|
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
let uniform =
|
let uniform =
|
||||||
self.context
|
self.context
|
||||||
.device
|
.device
|
||||||
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some(" post parameters"),
|
label: Some(" post parameters"),
|
||||||
contents: bytemuck::cast_slice(&values),
|
contents: bytemuck::bytes_of(&values),
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
usage: wgpu::BufferUsages::UNIFORM,
|
||||||
});
|
});
|
||||||
let target_format = if frame_out {
|
let target_format = if frame_out {
|
||||||
@@ -960,16 +1047,8 @@ impl<T: Scene + 'static> Renderer<T> {
|
|||||||
.descriptor
|
.descriptor
|
||||||
.format
|
.format
|
||||||
};
|
};
|
||||||
let entry = match execution.executor.key.as_str() {
|
let entry = resolve_fullscreen_entry(&execution.executor.key)
|
||||||
"fullscreen_copy" => "fs_copy",
|
.ok_or_else(|| fail("fullscreen executor mismatch"))?;
|
||||||
"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 pipeline = self.context.device.create_render_pipeline(
|
let pipeline = self.context.device.create_render_pipeline(
|
||||||
&wgpu::RenderPipelineDescriptor {
|
&wgpu::RenderPipelineDescriptor {
|
||||||
label: Some(" post pipeline"),
|
label: Some(" post pipeline"),
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
|||||||
if (
|
if (
|
||||||
!exactKeys(n, nodeKeys) ||
|
!exactKeys(n, nodeKeys) ||
|
||||||
n.known !== true ||
|
n.known !== true ||
|
||||||
n.typeVersion !== 1 ||
|
n.typeVersion !== descriptor.version ||
|
||||||
typeof n.muted !== "boolean" ||
|
typeof n.muted !== "boolean" ||
|
||||||
typeof n.collapsed !== "boolean" ||
|
typeof n.collapsed !== "boolean" ||
|
||||||
typeof n.label !== "string" ||
|
typeof n.label !== "string" ||
|
||||||
@@ -340,7 +340,7 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
|||||||
value: {
|
value: {
|
||||||
id: n.id,
|
id: n.id,
|
||||||
state: n.muted ? "muted" : "enabled",
|
state: n.muted ? "muted" : "enabled",
|
||||||
executor: { key: n.typeId, version: 1 },
|
executor: { key: n.typeId, version: descriptor.version },
|
||||||
parameters,
|
parameters,
|
||||||
inputs: {},
|
inputs: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const texture = {
|
|||||||
};
|
};
|
||||||
export const semanticCatalog = Object.freeze({
|
export const semanticCatalog = Object.freeze({
|
||||||
mesh: {
|
mesh: {
|
||||||
|
version: 1,
|
||||||
execution: "source",
|
execution: "source",
|
||||||
inputs: {},
|
inputs: {},
|
||||||
outputs: {
|
outputs: {
|
||||||
@@ -39,6 +40,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
texture: {
|
texture: {
|
||||||
|
version: 1,
|
||||||
execution: "source",
|
execution: "source",
|
||||||
inputs: {},
|
inputs: {},
|
||||||
outputs: { texture: o("texture") },
|
outputs: { texture: o("texture") },
|
||||||
@@ -60,6 +62,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
frustum_cull: {
|
frustum_cull: {
|
||||||
|
version: 1,
|
||||||
execution: "compute",
|
execution: "compute",
|
||||||
inputs: {
|
inputs: {
|
||||||
mesh: i("mesh_data"),
|
mesh: i("mesh_data"),
|
||||||
@@ -74,6 +77,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: { cameraSelection: "active" },
|
parameters: { cameraSelection: "active" },
|
||||||
},
|
},
|
||||||
mesh_query: {
|
mesh_query: {
|
||||||
|
version: 1,
|
||||||
execution: "compute",
|
execution: "compute",
|
||||||
inputs: {
|
inputs: {
|
||||||
mesh: i("mesh_data"),
|
mesh: i("mesh_data"),
|
||||||
@@ -87,12 +91,14 @@ export const semanticCatalog = Object.freeze({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pipeline_registry: {
|
pipeline_registry: {
|
||||||
|
version: 1,
|
||||||
execution: "cpu_preparation",
|
execution: "cpu_preparation",
|
||||||
inputs: { pipelineIndices: i("pipeline_index_stream") },
|
inputs: { pipelineIndices: i("pipeline_index_stream") },
|
||||||
outputs: { activation: o("pipeline_activation") },
|
outputs: { activation: o("pipeline_activation") },
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
pipeline: {
|
pipeline: {
|
||||||
|
version: 1,
|
||||||
execution: "render",
|
execution: "render",
|
||||||
inputs: {
|
inputs: {
|
||||||
mesh: i("mesh_data"),
|
mesh: i("mesh_data"),
|
||||||
@@ -105,6 +111,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: { pipeline: "gltf_standard", depthCompare: "less_equal", depthWriteEnabled: true, clearDepth: 1, clearColor: [0.015, 0.02, 0.03, 1] },
|
parameters: { pipeline: "gltf_standard", depthCompare: "less_equal", depthWriteEnabled: true, clearDepth: 1, clearColor: [0.015, 0.02, 0.03, 1] },
|
||||||
},
|
},
|
||||||
fullscreen_copy: {
|
fullscreen_copy: {
|
||||||
|
version: 1,
|
||||||
execution: "render",
|
execution: "render",
|
||||||
inputs: {
|
inputs: {
|
||||||
source: i("texture"),
|
source: i("texture"),
|
||||||
@@ -114,6 +121,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
tone_map: {
|
tone_map: {
|
||||||
|
version: 1,
|
||||||
execution: "render",
|
execution: "render",
|
||||||
inputs: {
|
inputs: {
|
||||||
source: i("texture"),
|
source: i("texture"),
|
||||||
@@ -123,6 +131,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: { exposure: 1 },
|
parameters: { exposure: 1 },
|
||||||
},
|
},
|
||||||
bloom_extract: {
|
bloom_extract: {
|
||||||
|
version: 1,
|
||||||
execution: "render",
|
execution: "render",
|
||||||
inputs: {
|
inputs: {
|
||||||
source: i("texture"),
|
source: i("texture"),
|
||||||
@@ -132,6 +141,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: { threshold: 1, knee: 0.5 },
|
parameters: { threshold: 1, knee: 0.5 },
|
||||||
},
|
},
|
||||||
bloom_blur: {
|
bloom_blur: {
|
||||||
|
version: 1,
|
||||||
execution: "render",
|
execution: "render",
|
||||||
inputs: {
|
inputs: {
|
||||||
source: i("texture"),
|
source: i("texture"),
|
||||||
@@ -141,6 +151,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: { direction: [1, 0], radius: 1 },
|
parameters: { direction: [1, 0], radius: 1 },
|
||||||
},
|
},
|
||||||
bloom_composite: {
|
bloom_composite: {
|
||||||
|
version: 1,
|
||||||
execution: "render",
|
execution: "render",
|
||||||
inputs: {
|
inputs: {
|
||||||
source: i("texture"),
|
source: i("texture"),
|
||||||
@@ -151,6 +162,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: { intensity: 1 },
|
parameters: { intensity: 1 },
|
||||||
},
|
},
|
||||||
luminance_edge: {
|
luminance_edge: {
|
||||||
|
version: 1,
|
||||||
execution: "render",
|
execution: "render",
|
||||||
inputs: {
|
inputs: {
|
||||||
source: i("texture"),
|
source: i("texture"),
|
||||||
@@ -160,6 +172,7 @@ export const semanticCatalog = Object.freeze({
|
|||||||
parameters: { strength: 2 },
|
parameters: { strength: 2 },
|
||||||
},
|
},
|
||||||
frame_out: {
|
frame_out: {
|
||||||
|
version: 1,
|
||||||
execution: "frame",
|
execution: "frame",
|
||||||
inputs: { color: i("texture") },
|
inputs: { color: i("texture") },
|
||||||
outputs: {},
|
outputs: {},
|
||||||
@@ -390,7 +403,7 @@ export const nodeDefinitions = Object.fromEntries(
|
|||||||
return [
|
return [
|
||||||
key,
|
key,
|
||||||
{
|
{
|
||||||
version: 1,
|
version: c.version,
|
||||||
title: key.replaceAll("_", " "),
|
title: key.replaceAll("_", " "),
|
||||||
behavior: "standard",
|
behavior: "standard",
|
||||||
style: c.execution,
|
style: c.execution,
|
||||||
@@ -427,7 +440,7 @@ export const descriptors = Object.fromEntries(
|
|||||||
Object.entries(semanticCatalog).map(([key, c]) => [
|
Object.entries(semanticCatalog).map(([key, c]) => [
|
||||||
key,
|
key,
|
||||||
{
|
{
|
||||||
version: 1,
|
version: c.version,
|
||||||
inputs: c.inputs,
|
inputs: c.inputs,
|
||||||
outputs: c.outputs,
|
outputs: c.outputs,
|
||||||
parameters: c.parameters,
|
parameters: c.parameters,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { descriptors } from "./catalog.js";
|
||||||
|
|
||||||
const input = (node, socket) => ({ node, socket });
|
const input = (node, socket) => ({ node, socket });
|
||||||
const node = (id, key, parameters = {}, inputs = {}) => ({
|
const node = (id, key, parameters = {}, inputs = {}) => ({
|
||||||
id, state: "enabled", executor: { key, version: 1 }, parameters, inputs,
|
id, state: "enabled", executor: { key, version: descriptors[key].version }, parameters, inputs,
|
||||||
});
|
});
|
||||||
const texture = (format, scale = 1) => ({
|
const texture = (format, scale = 1) => ({
|
||||||
texture: {
|
texture: {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
nodeDefinitions,
|
nodeDefinitions,
|
||||||
GRAPH_ID,
|
GRAPH_ID,
|
||||||
CATALOG_VERSION,
|
CATALOG_VERSION,
|
||||||
|
descriptors,
|
||||||
socketTypes,
|
socketTypes,
|
||||||
} from "../static/render-graph/catalog.js";
|
} from "../static/render-graph/catalog.js";
|
||||||
import { culling } from "../static/render-graph/presets.js";
|
import { culling } from "../static/render-graph/presets.js";
|
||||||
@@ -93,6 +94,12 @@ function fixture() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
test("catalog exhaustively mirrors all current contracts", () => {
|
test("catalog exhaustively mirrors all current contracts", () => {
|
||||||
|
for (const [key, semantic] of Object.entries(semanticCatalog)) {
|
||||||
|
assert.ok(Object.hasOwn(semantic, "version"));
|
||||||
|
assert.equal(semantic.version, 1);
|
||||||
|
assert.equal(nodeDefinitions[key].version, semantic.version);
|
||||||
|
assert.equal(descriptors[key].version, semantic.version);
|
||||||
|
}
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
Object.keys(semanticCatalog),
|
Object.keys(semanticCatalog),
|
||||||
[
|
[
|
||||||
@@ -306,6 +313,7 @@ test("adapter rejects hostile shape, IDs, duplicates, catalog, sockets and type
|
|||||||
reject((x) => (x.nodes[0].id = "bad id"), "AUTHORING_ID");
|
reject((x) => (x.nodes[0].id = "bad id"), "AUTHORING_ID");
|
||||||
reject((x) => (x.nodes[1].id = x.nodes[0].id), "AUTHORING_ID_DUPLICATE");
|
reject((x) => (x.nodes[1].id = x.nodes[0].id), "AUTHORING_ID_DUPLICATE");
|
||||||
reject((x) => (x.nodes[0].typeId = "wat"), "AUTHORING_NODE_TYPE");
|
reject((x) => (x.nodes[0].typeId = "wat"), "AUTHORING_NODE_TYPE");
|
||||||
|
reject((x) => (x.nodes[0].typeVersion = 2), "AUTHORING_NODE_INVALID");
|
||||||
reject((x) => (x.nodes[0].sockets = []), "AUTHORING_SOCKET_SET");
|
reject((x) => (x.nodes[0].sockets = []), "AUTHORING_SOCKET_SET");
|
||||||
reject((x) => (x.links[0].toSocketId = "missing:x"), "AUTHORING_LINK");
|
reject((x) => (x.links[0].toSocketId = "missing:x"), "AUTHORING_LINK");
|
||||||
reject((x) => {
|
reject((x) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import * as presets from "../static/render-graph/presets.js";
|
import * as presets from "../static/render-graph/presets.js";
|
||||||
|
import { descriptors } from "../static/render-graph/catalog.js";
|
||||||
|
|
||||||
const order = [
|
const order = [
|
||||||
"midnight",
|
"midnight",
|
||||||
@@ -168,6 +169,11 @@ test("presets have the exact canonical pipeline identities, schemas, and node se
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
assert.ok(!graph.nodes.some((node) => node.id === "copy"));
|
assert.ok(!graph.nodes.some((node) => node.id === "copy"));
|
||||||
|
assert.ok(
|
||||||
|
graph.nodes.every(
|
||||||
|
(node) => node.executor.version === descriptors[node.executor.key].version,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user