feat: compile pipeline-specific render pass cohorts

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-29 14:49:22 +00:00
co-authored by heaust
parent 27e3dd64ae
commit 780f194be4
16 changed files with 1519 additions and 376 deletions
+564 -59
View File
@@ -21,8 +21,8 @@ struct CullParameters {
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct PipelineParameters {
pipeline: String,
struct RasterParameters {
draw_order: i32,
depth_compare: CompareFunction,
depth_write_enabled: bool,
clear_depth: f32,
@@ -137,6 +137,11 @@ fn color(value: [f32; 4], min: f32, max: f32, base: &str) -> Result<[f32; 3], Gr
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct OutputKey(usize, u16);
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum AttachmentRoot {
Authored(OutputKey),
CompilerDefault { node: usize, ordinal: u16 },
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum TransitionTargetKey {
Authored(OutputKey),
CompilerDefaultInput {
@@ -244,6 +249,7 @@ fn reaches(
from: usize,
to: usize,
outgoing_edges: &[Vec<usize>],
ordering_outgoing: &[Vec<usize>],
edges: &[DependencyEdge],
live: &HashSet<usize>,
memo: &mut HashMap<(usize, usize), bool>,
@@ -268,6 +274,11 @@ fn reaches(
stack.push(next);
}
}
for &next in &ordering_outgoing[node] {
if live.contains(&next) {
stack.push(next);
}
}
}
memo.insert((from, to), answer);
answer
@@ -741,21 +752,9 @@ fn decode(node: &Node, i: usize) -> Result<NormalizedParameters, GraphError> {
descriptor: normalize_texture(p.texture, &base)?,
}
}
"pipeline" => {
let p: PipelineParameters =
key if contract(key).is_some_and(|contract| contract.is_raster_draw()) => {
let p: RasterParameters =
serde_json::from_value(node.parameters.clone()).map_err(invalid)?;
let valid_name = !p.pipeline.is_empty()
&& p.pipeline.len() <= 64
&& p.pipeline.bytes().enumerate().all(|(i, c)| {
c == b'_' || c.is_ascii_alphanumeric() && (i > 0 || c.is_ascii_alphabetic())
});
if !valid_name {
return Err(error(
"GRAPH_PARAMETERS_INVALID",
"pipeline must be a 1-64 byte identifier",
format!("{base}.pipeline"),
));
}
if !p.clear_depth.is_finite() || !(0.0..=1.0).contains(&p.clear_depth) {
return Err(error(
"GRAPH_PARAMETERS_INVALID",
@@ -770,8 +769,8 @@ fn decode(node: &Node, i: usize) -> Result<NormalizedParameters, GraphError> {
format!("{base}.clearColor"),
));
}
NormalizedParameters::Pipeline {
pipeline: p.pipeline,
NormalizedParameters::Raster {
draw_order: p.draw_order,
depth_compare: p.depth_compare,
depth_write_enabled: p.depth_write_enabled,
clear_depth: p.clear_depth,
@@ -1102,6 +1101,271 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
e.producer_output_ordinal,
)
});
let is_normalizable_target = |edge: &DependencyEdge| {
contracts[edge.from_node].is_raster_draw()
&& contracts[edge.to_node].is_raster_draw()
&& matches!(
contracts[edge.to_node].inputs[edge.consumer_input_ordinal as usize].role,
InputRole::ColorTarget { .. } | InputRole::DepthTarget
)
};
let is_exact_reader = |edge: &DependencyEdge| {
let input = &contracts[edge.to_node].inputs[edge.consumer_input_ordinal as usize];
input.role == InputRole::SampledTexture
|| (input.role == InputRole::SemanticRead
&& contracts[edge.from_node].outputs[edge.producer_output_ordinal as usize]
.semantic_type
== SemanticType::Texture)
};
// Compute demand from authored resource dependencies only. In particular,
// a later WAR ordering edge must never resurrect its reader.
let mut provisional_live = HashSet::new();
let mut provisional_stack: Vec<_> = contracts
.iter()
.enumerate()
.filter(|(i, contract)| {
contract.inherently_observable && graph.nodes[*i].state == NodeState::Enabled
})
.map(|(i, _)| i)
.collect();
let mut authored_deps = vec![Vec::new(); graph.nodes.len()];
for edge in &edges {
authored_deps[edge.to_node].push(edge.from_node);
}
while let Some(node) = provisional_stack.pop() {
if provisional_live.insert(node) {
provisional_stack.extend(authored_deps[node].iter().copied());
}
}
// Preserve authored, non-normalized dataflow before attachment normalization. It
// is used both to diagnose impossible draw-order normalization and to find
// readers which must finish before an attachment version is overwritten.
let ordinary_edges: Vec<_> = edges
.iter()
.filter(|edge| !is_normalizable_target(edge))
.cloned()
.collect();
let mut target_consumers = HashMap::<OutputKey, Vec<usize>>::new();
let mut ordinary_consumers = HashMap::<OutputKey, Vec<usize>>::new();
for edge in &edges {
let key = OutputKey(edge.from_node, edge.producer_output_ordinal);
if is_normalizable_target(edge) {
target_consumers.entry(key).or_default().push(edge.to_node);
} else if is_exact_reader(edge) && provisional_live.contains(&edge.to_node) {
ordinary_consumers
.entry(key)
.or_default()
.push(edge.to_node);
}
}
let observation_cuts: HashSet<_> = target_consumers
.keys()
.filter(|key| ordinary_consumers.contains_key(key))
.copied()
.collect();
let mut ordering_edges = Vec::new();
// Normalize raster attachment versions before liveness. Authored graphs may
// express a render-pass cohort either as a chain or as direct siblings of
// one texture. Turn both forms into an explicit, deterministic SSA chain.
fn attachment_root(
node: usize,
ordinal: u16,
bound: &[BTreeMap<&str, BoundInput>],
contracts: &[&Contract],
colors: &mut HashMap<(usize, u16), u8>,
roots: &mut HashMap<(usize, u16), AttachmentRoot>,
) -> Result<AttachmentRoot, GraphError> {
if let Some(&root) = roots.get(&(node, ordinal)) {
return Ok(root);
}
if colors.get(&(node, ordinal)) == Some(&1) {
return Err(error(
"GRAPH_ATTACHMENT_LINEAGE_INVALID",
"attachment lineage is cyclic",
format!("nodes[{node}].inputs"),
));
}
colors.insert((node, ordinal), 1);
let socket = if ordinal == 0 {
"colorTarget"
} else {
"depthTarget"
};
let root = match bound[node].get(socket).map(|binding| binding.producer) {
None => AttachmentRoot::CompilerDefault { node, ordinal },
Some(output)
if !contracts[output.0].is_raster_draw()
&& contracts[output.0].outputs[output.1 as usize].semantic_type
== SemanticType::Texture =>
{
AttachmentRoot::Authored(output)
}
Some(output) if contracts[output.0].is_raster_draw() && output.1 == ordinal => {
attachment_root(output.0, ordinal, bound, contracts, colors, roots)?
}
Some(_) => {
return Err(error(
"GRAPH_ATTACHMENT_LINEAGE_INVALID",
"attachment lineage has no texture or default root",
format!("nodes[{node}].inputs.{socket}"),
))
}
};
colors.insert((node, ordinal), 2);
roots.insert((node, ordinal), root);
Ok(root)
}
let raster_nodes: Vec<_> = contracts
.iter()
.enumerate()
.filter_map(|(i, contract)| contract.is_raster_draw().then_some(i))
.collect();
let mut aliases = HashMap::<OutputKey, OutputKey>::new();
for ordinal in 0..=1u16 {
let mut colors = HashMap::new();
let mut roots = HashMap::new();
let mut cohorts = BTreeMap::<AttachmentRoot, Vec<usize>>::new();
for &node in &raster_nodes {
let root = attachment_root(node, ordinal, &bound, &contracts, &mut colors, &mut roots)?;
cohorts.entry(root).or_default().push(node);
}
for (root, mut members) in cohorts {
members.sort_by_key(|&node| {
let NormalizedParameters::Raster { draw_order, .. } = params[node] else {
unreachable!()
};
(draw_order, node)
});
// An observed version ends its aliasing segment. Writers after the
// cut continue the same physical lineage, but may not replace the
// version seen by the reader.
let mut segment_start = 0;
for position in 0..members.len() {
let at_cut = observation_cuts.contains(&OutputKey(members[position], ordinal));
if at_cut || position + 1 == members.len() {
let terminal = OutputKey(members[position], ordinal);
for &member in &members[segment_start..=position] {
aliases.insert(OutputKey(member, ordinal), terminal);
}
if let Some(&next_writer) = members.get(position + 1) {
for &member in &members[segment_start..=position] {
let output = OutputKey(member, ordinal);
for &reader in ordinary_consumers.get(&output).into_iter().flatten() {
if reader != next_writer {
ordering_edges.push((reader, next_writer));
}
}
}
}
segment_start = position + 1;
}
}
let socket = if ordinal == 0 {
"colorTarget"
} else {
"depthTarget"
};
for (position, &member) in members.iter().enumerate() {
let target = if position == 0 {
match root {
AttachmentRoot::Authored(output) => Some(output),
AttachmentRoot::CompilerDefault { .. } => None,
}
} else {
Some(OutputKey(members[position - 1], ordinal))
};
bound[member].remove(socket);
if let Some(producer) = target {
bound[member].insert(socket, BoundInput { producer });
}
}
}
}
ordering_edges.sort_unstable();
ordering_edges.dedup();
// Replace authored attachment dependencies with the canonical WAW chain,
// and redirect observations of any cohort member to its terminal version.
edges.retain(|edge| {
!(contracts[edge.to_node].is_raster_draw()
&& (edge.to_socket == "colorTarget" || edge.to_socket == "depthTarget"))
});
for &node in &raster_nodes {
for (socket, ordinal) in [("colorTarget", 0u16), ("depthTarget", 1u16)] {
let Some(binding) = bound[node].get(socket) else {
continue;
};
let input_ordinal = contracts[node]
.inputs
.iter()
.position(|input| input.name == socket)
.expect("raster target contract") as u16;
edges.push(DependencyEdge {
from_node: binding.producer.0,
from_socket: contracts[binding.producer.0].outputs[binding.producer.1 as usize]
.name
.into(),
producer_output_ordinal: binding.producer.1,
to_node: node,
to_socket: socket.into(),
consumer_input_ordinal: input_ordinal,
resource: NodeOutputRef {
node: graph.nodes[binding.producer.0].id.clone(),
socket: contracts[binding.producer.0].outputs[binding.producer.1 as usize]
.name
.into(),
},
});
let _ = ordinal;
}
}
for node in 0..graph.nodes.len() {
for input in contracts[node].inputs.iter().filter(|input| {
matches!(
input.role,
InputRole::SampledTexture | InputRole::SemanticRead
)
}) {
let Some(binding) = bound[node].get_mut(input.name) else {
continue;
};
if input.role == InputRole::SemanticRead
&& contracts[binding.producer.0].outputs[binding.producer.1 as usize].semantic_type
!= SemanticType::Texture
{
continue;
}
if let Some(&terminal) = aliases.get(&binding.producer) {
binding.producer = terminal;
}
}
}
for edge in &mut edges {
if is_exact_reader(edge) {
let key = OutputKey(edge.from_node, edge.producer_output_ordinal);
if let Some(&terminal) = aliases.get(&key) {
edge.from_node = terminal.0;
edge.producer_output_ordinal = terminal.1;
edge.from_socket = contracts[terminal.0].outputs[terminal.1 as usize]
.name
.into();
edge.resource = NodeOutputRef {
node: graph.nodes[terminal.0].id.clone(),
socket: edge.from_socket.clone(),
};
}
}
}
edges.sort_by_key(|e| {
(
e.to_node,
e.consumer_input_ordinal,
e.from_node,
e.producer_output_ordinal,
)
});
let mut deps = vec![Vec::new(); graph.nodes.len()];
for edge in &edges {
deps[edge.to_node].push(edge.from_node);
@@ -1122,6 +1386,42 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
stack.extend(deps[i].iter().copied());
}
}
// A canonical attachment writer edge may not reverse authored ordinary
// dataflow. Keep this distinct from generic cycle reporting so authors get
// the draw-order parameter which made normalization impossible.
let mut ordinary_outgoing = vec![Vec::new(); graph.nodes.len()];
for edge in &ordinary_edges {
ordinary_outgoing[edge.from_node].push(edge.to_node);
}
let ordinary_reaches = |from: usize, to: usize| {
let mut seen = vec![false; graph.nodes.len()];
let mut pending = vec![from];
while let Some(node) = pending.pop() {
if node == to {
return true;
}
if !seen[node] {
seen[node] = true;
pending.extend(ordinary_outgoing[node].iter().copied());
}
}
false
};
let mut draw_order_conflicts: Vec<_> = edges
.iter()
.filter(|edge| {
contracts[edge.from_node].is_raster_draw()
&& contracts[edge.to_node].is_raster_draw()
&& matches!(
contracts[edge.to_node].inputs[edge.consumer_input_ordinal as usize].role,
InputRole::ColorTarget { .. } | InputRole::DepthTarget
)
&& ordinary_reaches(edge.to_node, edge.from_node)
})
.map(|edge| edge.to_node)
.collect();
draw_order_conflicts.sort_unstable();
draw_order_conflicts.dedup();
// IDs are independent of scheduling: original node order, then contract output order.
// Source nodes expose only outputs that survived active-edge/liveness analysis;
// executable nodes retain their complete output shape for runtime lowering.
@@ -1215,10 +1515,10 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
let mut default_roots: Vec<DefaultDraft> = Vec::new();
let mut default_targets = HashMap::new();
for i in 0..graph.nodes.len() {
if !live.contains(&i) || contracts[i].key != "pipeline" {
if !live.contains(&i) || !contracts[i].is_raster_draw() {
continue;
}
for (input_ordinal, (socket, role, format, opposite)) in [
for (socket, role, format, opposite) in [
(
"colorTarget",
CompilerTextureRole::ColorTarget,
@@ -1233,12 +1533,16 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
),
]
.into_iter()
.enumerate()
{
if bound[i].contains_key(socket) {
continue;
}
let input_ordinal = input_ordinal as u16 + 2;
let input_ordinal = contracts[i]
.inputs
.iter()
.position(|input| input.name == socket)
.expect("compiler target has a contract input")
as u16;
let key = TransitionTargetKey::CompilerDefaultInput {
owner_node: i,
input_ordinal,
@@ -1267,8 +1571,8 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
if !live.contains(&i) {
continue;
}
let transition_sockets: &[(&str, u16)] = match contracts[i].key {
"pipeline" => &[("colorTarget", 0), ("depthTarget", 1)],
let transition_sockets: &[(&str, u16)] = match contracts[i] {
ref contract if contract.is_raster_draw() => &[("colorTarget", 0), ("depthTarget", 1)],
_ if contracts[i].fullscreen_policy.is_some() => &[("colorTarget", 0)],
_ => continue,
};
@@ -1388,6 +1692,12 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
outgoing_edges[edge.from_node].push(index);
}
}
let mut ordering_outgoing = vec![Vec::new(); graph.nodes.len()];
for &(from, to) in &ordering_edges {
if live.contains(&from) && live.contains(&to) {
ordering_outgoing[from].push(to);
}
}
for outgoing in &mut outgoing_edges {
outgoing.sort_by_key(|&index| {
let edge = &edges[index];
@@ -1404,7 +1714,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
if !live.contains(&i) {
continue;
}
if contracts[i].key == "pipeline" {
if contracts[i].is_raster_draw() {
if bound[i].get("colorTarget").map(|b| b.producer)
== bound[i].get("depthTarget").map(|b| b.producer)
&& bound[i].contains_key("colorTarget")
@@ -1532,7 +1842,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
format!("nodes[{}].inputs.{}", edge.to_node, edge.to_socket),
));
}
if contracts[edge.from_node].key != "pipeline" || edge.producer_output_ordinal != 0 {
if !contracts[edge.from_node].is_raster_draw() || edge.producer_output_ordinal != 0 {
return Err(error(
"GRAPH_ILLEGAL_ACCESS",
"multisampled color texture is not a produced pipeline color",
@@ -1571,6 +1881,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
i,
next.writer_node,
&outgoing_edges,
&ordering_outgoing,
&edges,
&live,
&mut reachability,
@@ -1587,7 +1898,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
// Validate every independently resolved attachment before graph cycle reporting.
for i in 0..graph.nodes.len() {
if !live.contains(&i) || contracts[i].key != "pipeline" {
if !live.contains(&i) || !contracts[i].is_raster_draw() {
continue;
}
let cd = version_of
@@ -1780,12 +2091,23 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
}
// Stable Kahn scheduling is deliberately after resource and access validation.
if let Some(&node) = draw_order_conflicts.iter().find(|node| live.contains(node)) {
return Err(error(
"GRAPH_DRAW_ORDER_CONFLICT",
"draw order conflicts with authored dependencies",
format!("nodes[{node}].parameters.drawOrder"),
));
}
let mut indegree = vec![0; graph.nodes.len()];
for &node in &live {
indegree[node] = edges
.iter()
.filter(|edge| edge.to_node == node && live.contains(&edge.from_node))
.count();
.count()
+ ordering_edges
.iter()
.filter(|(from, to)| *to == node && live.contains(from))
.count();
}
let mut queue = BinaryHeap::new();
for &node in &live {
@@ -1803,6 +2125,12 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
queue.push(Reverse(consumer));
}
}
for &consumer in &ordering_outgoing[node] {
indegree[consumer] -= 1;
if indegree[consumer] == 0 {
queue.push(Reverse(consumer));
}
}
}
if order.len() != live.len() {
let residual: Vec<_> = (0..graph.nodes.len())
@@ -1811,6 +2139,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
fn cycle_dfs(
node: usize,
outgoing_edges: &[Vec<usize>],
ordering_outgoing: &[Vec<usize>],
edges: &[DependencyEdge],
residual: &[bool],
colors: &mut [u8],
@@ -1829,6 +2158,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
if let Some(cycle) = cycle_dfs(
to,
outgoing_edges,
ordering_outgoing,
edges,
residual,
colors,
@@ -1848,6 +2178,31 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
return Some(cycle);
}
}
for &to in &ordering_outgoing[node] {
if !residual[to] {
continue;
}
if colors[to] == 0 {
if cycle_dfs(
to,
outgoing_edges,
ordering_outgoing,
edges,
residual,
colors,
node_stack,
edge_stack,
)
.is_some()
{
// Ordering edges are synthetic and have no authored
// socket payload, but still constitute a real cycle.
return Some(Vec::new());
}
} else if colors[to] == 1 {
return Some(Vec::new());
}
}
node_stack.pop();
colors[node] = 2;
None
@@ -1859,6 +2214,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
cycle = cycle_dfs(
node,
&outgoing_edges,
&ordering_outgoing,
&edges,
&residual,
&mut colors,
@@ -2189,15 +2545,15 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
.collect();
let mut accesses = Vec::new();
let kind = match contracts[i].key {
"pipeline" => {
_ if contracts[i].is_raster_draw() => {
let color = output_ids[&OutputKey(i, 0)];
let depth = output_ids[&OutputKey(i, 1)];
let clear = match params[i] {
NormalizedParameters::Pipeline { clear_color, .. } => clear_color,
NormalizedParameters::Raster { clear_color, .. } => clear_color,
_ => unreachable!(),
};
let clear_depth = match &params[i] {
NormalizedParameters::Pipeline { clear_depth, .. } => *clear_depth,
NormalizedParameters::Raster { clear_depth, .. } => *clear_depth,
_ => unreachable!(),
};
let first_color = version_of[&OutputKey(i, 0)].1 == 0;
@@ -2280,20 +2636,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
},
});
}
ExecutionKind::Render {
color_attachments: vec![ColorAttachmentPlan {
resource: color,
resolve_target,
location: 0,
load: cl,
store: source_store,
}],
depth_stencil: Some(DepthStencilAttachmentPlan {
resource: depth,
load: dl,
store: StoreOp::Store,
}),
}
ExecutionKind::RasterDraw
}
_ if contracts[i].fullscreen_policy.is_some() => {
let color = output_ids[&OutputKey(i, 0)];
@@ -2321,16 +2664,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
full_overwrite: true,
},
});
ExecutionKind::Render {
color_attachments: vec![ColorAttachmentPlan {
resource: color,
resolve_target: None,
location: 0,
load,
store: StoreOp::Store,
}],
depth_stencil: None,
}
ExecutionKind::Fullscreen
}
"frame_out" => {
let r = input_resource("color");
@@ -2584,7 +2918,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
let mut predicates = Vec::new();
for (execution, compiled) in executions.iter().enumerate() {
let node = compiled.original_node_index as usize;
if contracts[node].key != "pipeline" {
if !contracts[node].is_raster_draw() {
continue;
}
let mesh = output_ids[&bound[node]["mesh"].producer];
@@ -2599,7 +2933,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
let predicate = if let Some(binding) = bound[node].get("predicate") {
expression_ids[&binding.producer]
} else {
let NormalizedParameters::Pipeline {
let NormalizedParameters::Raster {
predicate_default, ..
} = params[node]
else {
@@ -2651,9 +2985,15 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
}
}
}
// Dense lifetimes touch bindings, outputs, and accesses.
let render_passes = build_render_passes(&executions, &resources, &families);
let execution_pass: Vec<u32> = render_passes
.iter()
.enumerate()
.flat_map(|(pass, value)| value.executions.iter().map(move |_| pass as u32))
.collect();
// Dense lifetimes use physical pass ordinals; producer metadata remains logical.
for (ordinal, e) in executions.iter().enumerate() {
let ordinal = ordinal as u32;
let ordinal = execution_pass[ordinal];
let mut touched = BTreeSet::new();
for x in &e.inputs {
touched.insert(x.resource);
@@ -2710,6 +3050,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
node_count: graph.nodes.len() as u32,
resources,
executions,
render_passes,
texture_families: families,
allocation_classes: classes,
culled_node_count: (graph.nodes.len() - live.len()) as u32,
@@ -2719,6 +3060,170 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
})
}
pub(crate) fn execution_attachments(
execution: &CompiledExecution,
) -> (Vec<ColorAttachmentPlan>, Option<DepthStencilAttachmentPlan>) {
let mut colors = Vec::new();
let mut depth = None;
for access in &execution.accesses {
match access.mode {
AccessMode::ColorAttachment {
location,
load,
store,
..
} => colors.push(ColorAttachmentPlan {
resource: access.resource,
resolve_target: execution.accesses.iter().find_map(|candidate| {
match candidate.mode {
AccessMode::ColorResolve {
source,
location: l,
} if source == access.resource && l == location => Some(candidate.resource),
_ => None,
}
}),
location,
load,
store,
}),
AccessMode::DepthAttachment { load, store, .. } => {
depth = Some(DepthStencilAttachmentPlan {
resource: access.resource,
load,
store,
})
}
_ => {}
}
}
colors.sort_by_key(|color| color.location);
(colors, depth)
}
pub(crate) fn build_render_passes(
executions: &[CompiledExecution],
resources: &[CompiledResource],
families: &[TextureFamily],
) -> Vec<PhysicalRenderPass> {
let family = |resource: u32| {
resources
.get(resource as usize)
.and_then(|resource| match resource.plan {
ResourcePlan::Texture { family, .. }
| ResourcePlan::TextureSource { family, .. } => Some(family),
_ => None,
})
};
let mut passes: Vec<PhysicalRenderPass> = Vec::new();
for (index, execution) in executions.iter().enumerate() {
if matches!(execution.kind, ExecutionKind::FrameOut { .. }) {
passes.push(PhysicalRenderPass {
executions: vec![index as u32],
kind: PhysicalRenderPassKind::Surface,
});
continue;
}
let (colors, depth) = execution_attachments(execution);
let mut merged = false;
if matches!(execution.kind, ExecutionKind::RasterDraw)
&& colors.iter().all(|v| v.load == NormalizedColorLoad::Load)
&& depth
.as_ref()
.is_none_or(|v| v.load == NormalizedDepthLoad::Load)
{
if let Some(PhysicalRenderPass {
executions: members,
kind:
PhysicalRenderPassKind::Texture {
color_attachments: previous_colors,
depth_stencil: previous_depth,
},
}) = passes.last_mut()
{
let previous = &executions[*members.last().unwrap() as usize];
let target_input = |socket: &str| {
execution
.inputs
.iter()
.find(|input| input.socket == socket)
.map(|input| input.resource)
};
let exact = previous.outputs.iter().any(|out| {
out.socket == "color" && Some(out.resource) == target_input("colorTarget")
}) && depth.as_ref().is_none_or(|_| {
previous.outputs.iter().any(|out| {
out.socket == "depth" && Some(out.resource) == target_input("depthTarget")
})
});
let compatible = previous_colors.len() == colors.len()
&& previous_colors.iter().zip(&colors).all(|(a, b)| {
a.location == b.location
&& family(a.resource) == family(b.resource)
&& family(a.resource).is_some_and(|f| {
family(b.resource).is_some_and(|next_family| {
families.get(f as usize).is_some_and(|first| {
families.get(next_family as usize).is_some_and(|next| {
family_descriptor(first) == family_descriptor(next)
})
})
})
})
})
&& match (previous_depth.as_ref(), depth.as_ref()) {
(None, None) => true,
(Some(a), Some(b)) => {
family(a.resource) == family(b.resource)
&& family(a.resource).is_some_and(|f| {
family(b.resource).is_some_and(|next_family| {
families.get(f as usize).is_some_and(|first| {
families.get(next_family as usize).is_some_and(|next| {
family_descriptor(first) == family_descriptor(next)
})
})
})
})
}
_ => false,
};
let no_resolve = previous_colors.iter().all(|v| v.resolve_target.is_none());
let no_external = previous.outputs.iter().all(|out| {
executions
.iter()
.enumerate()
.filter(|(_, e)| e.inputs.iter().any(|v| v.resource == out.resource))
.all(|(consumer, _)| consumer == index)
});
if exact && compatible && no_resolve && no_external {
for (physical, final_value) in previous_colors.iter_mut().zip(&colors) {
physical.resource = final_value.resource;
physical.resolve_target = final_value.resolve_target;
physical.store = final_value.store;
}
if let (Some(physical), Some(final_value)) =
(previous_depth.as_mut(), depth.as_ref())
{
physical.resource = final_value.resource;
physical.store = final_value.store;
}
members.push(index as u32);
merged = true;
}
}
}
if !merged {
passes.push(PhysicalRenderPass {
executions: vec![index as u32],
kind: PhysicalRenderPassKind::Texture {
color_attachments: colors,
depth_stencil: depth,
},
});
}
}
passes
}
fn extent_layers(e: &NormalizedTextureExtent) -> u32 {
match e {
NormalizedTextureExtent::Absolute {