use super::*;
use super::{RenderGraphImageSpecification, RenderGraphOutputImageId};
use crate::graph::graph_image::{PhysicalImageId, RenderGraphImageUser, VirtualImageId};
use crate::graph::graph_node::RenderGraphNodeId;
use crate::graph::{RenderGraphBuilder, RenderGraphImageConstraint, RenderGraphImageUsageId};
use crate::nodes::RenderPhaseIndex;
use crate::{BufferResource, GraphicsPipelineRenderTargetMeta};
use crate::{ImageViewResource, ResourceArc};
use fnv::{FnvHashMap, FnvHashSet};
use rafx_api::{RafxFormat, RafxLoadOp, RafxResourceState, RafxSampleCount, RafxStoreOp};
fn visit_node(
graph: &RenderGraphBuilder,
node_id: RenderGraphNodeId,
visited: &mut Vec<bool>,
visiting: &mut Vec<bool>,
visiting_stack: &mut Vec<RenderGraphNodeId>,
ordered_list: &mut Vec<RenderGraphNodeId>,
) {
if visited[node_id.0] {
return;
}
if visiting[node_id.0] {
log::trace!("Found cycle in graph");
log::trace!("{:?}", graph.node(node_id));
for v in visiting_stack.iter().rev() {
log::trace!("{:?}", graph.node(*v));
}
panic!("Graph has a cycle");
}
visiting[node_id.0] = true;
visiting_stack.push(node_id);
let node = graph.node(node_id);
for read in &node.image_reads {
let upstream_node = graph.image_version_info(read.image).creator_node;
visit_node(
graph,
upstream_node,
visited,
visiting,
visiting_stack,
ordered_list,
);
}
for modify in &node.image_modifies {
let upstream_node = graph.image_version_info(modify.input).creator_node;
visit_node(
graph,
upstream_node,
visited,
visiting,
visiting_stack,
ordered_list,
);
}
for sampled_image in &node.sampled_images {
let upstream_node = graph.image_version_info(*sampled_image).creator_node;
visit_node(
graph,
upstream_node,
visited,
visiting,
visiting_stack,
ordered_list,
);
}
for read in &node.buffer_reads {
let upstream_node = graph.buffer_version_info(read.buffer).creator_node;
visit_node(
graph,
upstream_node,
visited,
visiting,
visiting_stack,
ordered_list,
);
}
for modify in &node.buffer_modifies {
let upstream_node = graph.buffer_version_info(modify.input).creator_node;
visit_node(
graph,
upstream_node,
visited,
visiting,
visiting_stack,
ordered_list,
);
}
ordered_list.push(node_id);
visited[node_id.0] = true;
visiting_stack.pop();
visiting[node_id.0] = false;
}
#[profiling::function]
fn determine_node_order(graph: &RenderGraphBuilder) -> Vec<RenderGraphNodeId> {
let mut visiting = vec![false; graph.nodes.len()];
let mut visiting_stack = Vec::default();
let mut visited = vec![false; graph.nodes.len()];
let mut ordered_list = Vec::default();
for output_image_id in &graph.output_images {
let output_node = graph.image_version_info(output_image_id.usage).creator_node;
log::trace!(
"Traversing dependencies of output image created by node {:?} {:?}",
output_node,
graph.node(output_node).name()
);
visit_node(
graph,
output_node,
&mut visited,
&mut visiting,
&mut visiting_stack,
&mut ordered_list,
);
}
for output_buffer_id in &graph.output_buffers {
let output_node = graph
.buffer_version_info(output_buffer_id.usage)
.creator_node;
log::trace!(
"Traversing dependencies of output buffer created by node {:?} {:?}",
output_node,
graph.node(output_node).name()
);
visit_node(
graph,
output_node,
&mut visited,
&mut visiting,
&mut visiting_stack,
&mut ordered_list,
);
}
ordered_list
}
pub struct DetermineConstraintsResult {
images: FnvHashMap<RenderGraphImageUsageId, RenderGraphImageSpecification>,
buffers: FnvHashMap<RenderGraphBufferUsageId, RenderGraphBufferSpecification>,
}
impl DetermineConstraintsResult {
pub fn image_specification(
&self,
image: RenderGraphImageUsageId,
) -> Option<&RenderGraphImageSpecification> {
self.images.get(&image)
}
pub fn buffer_specification(
&self,
buffer: RenderGraphBufferUsageId,
) -> Option<&RenderGraphBufferSpecification> {
self.buffers.get(&buffer)
}
}
#[profiling::function]
fn determine_constraints(
graph: &RenderGraphBuilder,
node_execution_order: &[RenderGraphNodeId],
) -> DetermineConstraintsResult {
let mut image_version_states: FnvHashMap<RenderGraphImageUsageId, RenderGraphImageConstraint> =
Default::default();
let mut buffer_version_states: FnvHashMap<
RenderGraphBufferUsageId,
RenderGraphBufferConstraint,
> = Default::default();
log::trace!("Propagating constraints");
log::trace!(" Set up input images");
log::trace!(" Set up input buffers");
log::trace!(" Propagate constraints FORWARD");
for node_id in node_execution_order.iter() {
let node = graph.node(*node_id);
log::trace!(" node {:?} {:?}", node_id, node.name());
for image_create in &node.image_creates {
log::trace!(
" Create image {:?} {:?}",
image_create.image,
graph.image_resource(image_create.image).name
);
let version_state = image_version_states
.entry(graph.image_version_create_usage(image_create.image))
.or_default();
if !version_state.try_merge(&image_create.constraint) {
panic!("Unexpected constraints on image being created");
}
log::trace!(
" Forward propagate constraints {:?} {:?}",
image_create.image,
version_state
);
}
for buffer_create in &node.buffer_creates {
log::trace!(
" Create buffer {:?} {:?}",
buffer_create.buffer,
graph.buffer_resource(buffer_create.buffer).name
);
let version_state = buffer_version_states
.entry(graph.buffer_version_create_usage(buffer_create.buffer))
.or_default();
if !version_state.try_merge(&buffer_create.constraint) {
panic!("Unexpected constraints on buffer being created");
}
log::trace!(
" Forward propagate constraints {:?} {:?}",
buffer_create.buffer,
version_state
);
}
for image_modify in &node.image_modifies {
log::trace!(
" Modify image {:?} {:?} -> {:?} {:?}",
image_modify.input,
graph.image_resource(image_modify.input).name,
image_modify.output,
graph.image_resource(image_modify.output).name
);
let input_state = image_version_states
.entry(graph.image_version_create_usage(image_modify.input))
.or_default();
let mut image_modify_constraint = image_modify.constraint.clone();
image_modify_constraint.partial_merge(&input_state);
let output_state = image_version_states
.entry(graph.image_version_create_usage(image_modify.output))
.or_default();
output_state.partial_merge(&image_modify_constraint);
log::trace!(" Forward propagate constraints {:?}", output_state);
}
for buffer_modify in &node.buffer_modifies {
log::trace!(
" Modify buffer {:?} {:?} -> {:?} {:?}",
buffer_modify.input,
graph.buffer_resource(buffer_modify.input).name,
buffer_modify.output,
graph.buffer_resource(buffer_modify.output).name
);
let input_state = buffer_version_states
.entry(graph.buffer_version_create_usage(buffer_modify.input))
.or_default();
let mut buffer_modify_constraint = buffer_modify.constraint.clone();
buffer_modify_constraint.partial_merge(&input_state);
let output_state = buffer_version_states
.entry(graph.buffer_version_create_usage(buffer_modify.output))
.or_default();
output_state.partial_merge(&buffer_modify_constraint);
log::trace!(" Forward propagate constraints {:?}", output_state);
}
}
log::trace!(" Set up output images");
for output_image in &graph.output_images {
log::trace!(
" Image {:?} {:?}",
output_image,
graph.image_resource(output_image.usage).name
);
let output_image_version_state = image_version_states
.entry(graph.image_version_create_usage(output_image.usage))
.or_default();
let output_constraint = output_image.specification.clone().into();
output_image_version_state.partial_merge(&output_constraint);
image_version_states.insert(
output_image.usage,
output_image.specification.clone().into(),
);
}
for output_buffer in &graph.output_buffers {
log::trace!(
" Buffer {:?} {:?}",
output_buffer,
graph.buffer_resource(output_buffer.usage).name
);
let output_buffer_version_state = buffer_version_states
.entry(graph.buffer_version_create_usage(output_buffer.usage))
.or_default();
let output_constraint = output_buffer.specification.clone().into();
output_buffer_version_state.partial_merge(&output_constraint);
buffer_version_states.insert(
output_buffer.usage,
output_buffer.specification.clone().into(),
);
}
log::trace!(" Propagate constraints BACKWARD");
for node_id in node_execution_order.iter().rev() {
let node = graph.node(*node_id);
log::trace!(" node {:?} {:?}", node_id, node.name());
for image_read in &node.image_reads {
log::trace!(
" Read image {:?} {:?}",
image_read.image,
graph.image_resource(image_read.image).name
);
let version_state = image_version_states
.entry(graph.image_version_create_usage(image_read.image))
.or_default();
version_state.partial_merge(&image_read.constraint);
let mut image_read_constraint = image_read.constraint.clone();
image_read_constraint.partial_merge(&version_state);
log::trace!(
" Read constraints will be {:?}",
image_read_constraint
);
if let Some(spec) = image_read_constraint.try_convert_to_specification() {
image_version_states.insert(image_read.image, spec.into());
} else {
panic!(
"Not enough information in the graph to determine the specification for image {:?} {:?} being read by node {:?} {:?}. Constraints are: {:?}",
image_read.image,
graph.image_resource(image_read.image).name,
node.id(),
node.name(),
image_version_states.get(&image_read.image)
);
}
}
for buffer_read in &node.buffer_reads {
log::trace!(
" Read buffer {:?} {:?}",
buffer_read.buffer,
graph.buffer_resource(buffer_read.buffer).name
);
let version_state = buffer_version_states
.entry(graph.buffer_version_create_usage(buffer_read.buffer))
.or_default();
version_state.partial_merge(&buffer_read.constraint);
let mut buffer_read_constraint = buffer_read.constraint.clone();
buffer_read_constraint.partial_merge(&version_state);
log::trace!(
" Read constraints will be {:?}",
buffer_read_constraint
);
if let Some(spec) = buffer_read_constraint.try_convert_to_specification() {
buffer_version_states.insert(buffer_read.buffer, spec.into());
} else {
panic!(
"Not enough information in the graph to determine the specification for buffer {:?} {:?} being read by node {:?} {:?}. Constraints are: {:?}",
buffer_read.buffer,
graph.buffer_resource(buffer_read.buffer).name,
node.id(),
node.name(),
buffer_version_states.get(&buffer_read.buffer)
);
}
}
for image_modify in &node.image_modifies {
log::trace!(
" Modify image {:?} {:?} <- {:?} {:?}",
image_modify.input,
graph.image_resource(image_modify.input).name,
image_modify.output,
graph.image_resource(image_modify.output).name
);
let output_image_constraint = image_version_states
.entry(graph.image_version_create_usage(image_modify.output))
.or_default()
.clone();
let input_state = image_version_states
.entry(graph.image_version_create_usage(image_modify.input))
.or_default();
input_state.partial_merge(&output_image_constraint);
image_version_states.insert(image_modify.input, output_image_constraint.clone());
}
for buffer_modify in &node.buffer_modifies {
log::trace!(
" Modify buffer {:?} {:?} <- {:?} {:?}",
buffer_modify.input,
graph.buffer_resource(buffer_modify.input).name,
buffer_modify.output,
graph.buffer_resource(buffer_modify.output).name
);
let output_buffer_constraint = buffer_version_states
.entry(graph.buffer_version_create_usage(buffer_modify.output))
.or_default()
.clone();
let input_state = buffer_version_states
.entry(graph.buffer_version_create_usage(buffer_modify.input))
.or_default();
input_state.partial_merge(&output_buffer_constraint);
buffer_version_states.insert(buffer_modify.input, output_buffer_constraint.clone());
}
}
let mut image_specs = FnvHashMap::default();
for (k, v) in image_version_states {
image_specs.insert(k, v.try_convert_to_specification().unwrap());
}
let mut buffer_specs = FnvHashMap::default();
for (k, v) in buffer_version_states {
buffer_specs.insert(k, v.try_convert_to_specification().unwrap());
}
DetermineConstraintsResult {
images: image_specs,
buffers: buffer_specs,
}
}
#[profiling::function]
fn insert_resolves(
graph: &mut RenderGraphBuilder,
node_execution_order: &[RenderGraphNodeId],
constraint_results: &mut DetermineConstraintsResult,
) {
log::trace!("Insert resolves in graph where necessary");
for node_id in node_execution_order {
let mut resolves_to_add = Vec::default();
let node = graph.node(*node_id);
log::trace!(" node {:?}", node_id);
for (color_attachment_index, color_attachment) in node.color_attachments.iter().enumerate()
{
if let Some(color_attachment) = color_attachment {
log::trace!(" color attachment {}", color_attachment_index);
if let Some(write_image) = color_attachment.write_image {
let write_spec = constraint_results.image_specification(write_image).unwrap();
if write_spec.samples == RafxSampleCount::SampleCount1 {
log::trace!(" already non-MSAA");
continue;
}
let mut resolve_spec = write_spec.clone();
resolve_spec.samples = RafxSampleCount::SampleCount1;
let mut usages_to_move = vec![];
for (usage_index, read_usage) in graph
.image_version_info(write_image)
.read_usages
.iter()
.enumerate()
{
log::trace!(
" usage {}, {:?}",
usage_index,
graph.image_usages[read_usage.0].usage_type
);
let read_spec =
constraint_results.image_specification(*read_usage).unwrap();
if *read_spec == *write_spec {
continue;
} else if *read_spec == resolve_spec {
usages_to_move.push(*read_usage);
break;
} else {
log::trace!(
" incompatibility cannot be fixed via renderpass resolve"
);
log::trace!(" resolve: {:?}", resolve_spec);
log::trace!(" read : {:?}", read_spec);
}
}
if !usages_to_move.is_empty() {
resolves_to_add.push((
color_attachment_index,
resolve_spec,
usages_to_move,
));
}
}
}
}
for (resolve_attachment_index, resolve_spec, usages_to_move) in resolves_to_add {
log::trace!(
" ADDING RESOLVE FOR NODE {:?} ATTACHMENT {}",
node_id,
resolve_attachment_index
);
let image = graph.create_resolve_attachment(
*node_id,
resolve_attachment_index,
resolve_spec.clone().into(),
Default::default(),
);
constraint_results.images.insert(image, resolve_spec);
for usage in usages_to_move {
let from = graph.image_usages[usage.0].version;
let to = graph.image_usages[image.0].version;
log::trace!(
" MOVE USAGE {:?} from {:?} to {:?}",
usage,
from,
to
);
graph.redirect_image_usage(usage, from, to)
}
}
}
}
#[derive(Debug)]
pub struct AssignVirtualResourcesResult {
image_usage_to_virtual: FnvHashMap<RenderGraphImageUsageId, VirtualImageId>,
buffer_usage_to_virtual: FnvHashMap<RenderGraphBufferUsageId, VirtualBufferId>,
}
#[profiling::function]
fn assign_virtual_resources(
graph: &RenderGraphBuilder,
node_execution_order: &[RenderGraphNodeId],
constraint_results: &mut DetermineConstraintsResult,
) -> AssignVirtualResourcesResult {
#[derive(Default)]
struct VirtualImageIdAllocator {
next_id: usize,
}
impl VirtualImageIdAllocator {
fn allocate(&mut self) -> VirtualImageId {
let id = VirtualImageId(self.next_id);
self.next_id += 1;
id
}
}
#[derive(Default)]
struct VirtualBufferIdAllocator {
next_id: usize,
}
impl VirtualBufferIdAllocator {
fn allocate(&mut self) -> VirtualBufferId {
let id = VirtualBufferId(self.next_id);
self.next_id += 1;
id
}
}
let mut image_usage_to_virtual: FnvHashMap<RenderGraphImageUsageId, VirtualImageId> =
FnvHashMap::default();
let mut buffer_usage_to_virtual: FnvHashMap<RenderGraphBufferUsageId, VirtualBufferId> =
FnvHashMap::default();
let mut virtual_image_id_allocator = VirtualImageIdAllocator::default();
let mut virtual_buffer_id_allocator = VirtualBufferIdAllocator::default();
log::trace!("Associate images written by nodes with virtual images");
for node in node_execution_order.iter() {
let node = graph.node(*node);
log::trace!(" node {:?} {:?}", node.id().0, node.name());
let mut written_images = vec![];
let mut written_buffers = vec![];
for image_create in &node.image_creates {
let virtual_image = virtual_image_id_allocator.allocate();
log::trace!(
" Create {:?} will use image {:?}",
image_create.image,
virtual_image
);
image_usage_to_virtual.insert(image_create.image, virtual_image);
written_images.push(image_create.image);
}
for buffer_create in &node.buffer_creates {
let virtual_buffer = virtual_buffer_id_allocator.allocate();
log::trace!(
" Create {:?} will use buffer {:?}",
buffer_create.buffer,
virtual_buffer
);
buffer_usage_to_virtual.insert(buffer_create.buffer, virtual_buffer);
written_buffers.push(buffer_create.buffer);
}
for image_modify in &node.image_modifies {
assert_eq!(
constraint_results.image_specification(image_modify.input),
constraint_results.image_specification(image_modify.output)
);
let virtual_image = *image_usage_to_virtual.get(&image_modify.input).unwrap();
log::trace!(
" Modify {:?} will pass through image {:?}",
image_modify.output,
virtual_image
);
image_usage_to_virtual.insert(image_modify.output, virtual_image);
written_images.push(image_modify.output);
}
for buffer_modify in &node.buffer_modifies {
assert_eq!(
constraint_results.buffer_specification(buffer_modify.input),
constraint_results.buffer_specification(buffer_modify.output)
);
let virtual_buffer = *buffer_usage_to_virtual.get(&buffer_modify.input).unwrap();
log::trace!(
" Modify {:?} will pass through buffer {:?}",
buffer_modify.output,
virtual_buffer
);
buffer_usage_to_virtual.insert(buffer_modify.output, virtual_buffer);
written_buffers.push(buffer_modify.output);
}
for written_image in written_images {
let written_image_version_info = graph.image_version_info(written_image);
let mut read_count = 0;
let mut write_count = 0;
for usage in &written_image_version_info.read_usages {
if graph.image_usages[usage.0].usage_type.is_read_only() {
read_count += 1;
} else {
write_count += 1;
}
}
let write_virtual_image = *image_usage_to_virtual.get(&written_image).unwrap();
let write_type = graph.image_usages[written_image.0].usage_type;
let written_spec = constraint_results
.image_specification(written_image)
.unwrap();
for usage_resource_id in &written_image_version_info.read_usages {
let usage_spec = match constraint_results.image_specification(*usage_resource_id) {
Some(usage_spec) => usage_spec,
None => continue,
};
let specifications_match = *written_spec == *usage_spec;
let is_read_or_exclusive_write = (read_count > 0
&& graph.image_usages[usage_resource_id.0]
.usage_type
.is_read_only())
|| write_count <= 1;
let read_type = graph.image_usages[usage_resource_id.0].usage_type;
if specifications_match && is_read_or_exclusive_write {
log::trace!(
" Usage {:?} will share an image with {:?} ({:?} -> {:?})",
written_image,
usage_resource_id,
write_type,
read_type
);
let overwritten_image =
image_usage_to_virtual.insert(*usage_resource_id, write_virtual_image);
assert!(overwritten_image.is_none());
} else {
let virtual_image = virtual_image_id_allocator.allocate();
log::trace!(
" Allocate image {:?} for {:?} ({:?} -> {:?}) (specifications_match match: {} is_read_or_exclusive_write: {})",
virtual_image,
usage_resource_id,
write_type,
read_type,
specifications_match,
is_read_or_exclusive_write
);
if !specifications_match {
log::trace!(" written: {:?}", written_spec);
log::trace!(" usage : {:?}", usage_spec);
}
let overwritten_image =
image_usage_to_virtual.insert(*usage_resource_id, virtual_image);
assert!(overwritten_image.is_none());
println!(" written: {:?}", written_spec);
println!(" usage : {:?}", usage_spec);
panic!("Render graph does not currently support blit from one image to another to fix image compatibility");
}
}
}
for written_buffer in written_buffers {
let written_buffer_version_info = graph.buffer_version_info(written_buffer);
let mut read_count = 0;
let mut write_count = 0;
for usage in &written_buffer_version_info.read_usages {
if graph.buffer_usages[usage.0].usage_type.is_read_only() {
read_count += 1;
} else {
write_count += 1;
}
}
let write_virtual_buffer = *buffer_usage_to_virtual.get(&written_buffer).unwrap();
let write_type = graph.buffer_usages[written_buffer.0].usage_type;
let written_spec = constraint_results
.buffer_specification(written_buffer)
.unwrap();
for usage_resource_id in &written_buffer_version_info.read_usages {
let usage_spec = match constraint_results.buffer_specification(*usage_resource_id) {
Some(usage_spec) => usage_spec,
None => continue,
};
let specifications_match = *written_spec == *usage_spec;
let is_read_or_exclusive_write = (read_count > 0
&& graph.buffer_usages[usage_resource_id.0]
.usage_type
.is_read_only())
|| write_count <= 1;
let read_type = graph.buffer_usages[usage_resource_id.0].usage_type;
if specifications_match && is_read_or_exclusive_write {
log::trace!(
" Usage {:?} will share a buffer with {:?} ({:?} -> {:?})",
written_buffer,
usage_resource_id,
write_type,
read_type
);
let overwritten_buffer =
buffer_usage_to_virtual.insert(*usage_resource_id, write_virtual_buffer);
assert!(overwritten_buffer.is_none());
} else {
let virtual_buffer = virtual_buffer_id_allocator.allocate();
log::trace!(
" Allocate buffer {:?} for {:?} ({:?} -> {:?}) (specifications_match match: {} is_read_or_exclusive_write: {})",
virtual_buffer,
usage_resource_id,
write_type,
read_type,
specifications_match,
is_read_or_exclusive_write
);
if !specifications_match {
log::trace!(" written: {:?}", written_spec);
log::trace!(" usage : {:?}", usage_spec);
}
let overwritten_buffer =
buffer_usage_to_virtual.insert(*usage_resource_id, virtual_buffer);
assert!(overwritten_buffer.is_none());
panic!("Render graph does not currently support blit from one buffer to another to fix buffer compatibility");
}
}
}
}
AssignVirtualResourcesResult {
image_usage_to_virtual,
buffer_usage_to_virtual,
}
}
#[profiling::function]
fn build_physical_passes(
graph: &RenderGraphBuilder,
node_execution_order: &[RenderGraphNodeId],
constraints: &DetermineConstraintsResult,
virtual_resources: &AssignVirtualResourcesResult,
) -> Vec<RenderGraphPass> {
#[derive(Debug)]
enum PassNode {
RenderNode(RenderGraphNodeId),
ComputeNode(RenderGraphNodeId),
}
let mut pass_nodes = Vec::default();
for node_id in node_execution_order {
let node = graph.node(*node_id);
let is_compute = node.color_attachments.is_empty() && node.depth_attachment.is_none();
debug_assert_eq!(is_compute && !node.resolve_attachments.is_empty(), false);
if is_compute {
pass_nodes.push(PassNode::ComputeNode(*node_id));
} else {
pass_nodes.push(PassNode::RenderNode(*node_id));
}
}
log::trace!("gather pass info");
let mut passes = Vec::default();
for pass_node in pass_nodes {
log::trace!(" nodes in pass: {:?}", pass_node);
fn find_or_insert_attachment(
attachments: &mut Vec<RenderGraphPassAttachment>,
usage: RenderGraphImageUsageId,
virtual_image: VirtualImageId,
) -> (usize, bool) {
if let Some(position) = attachments
.iter()
.position(|x| x.virtual_image == virtual_image)
{
(position, false)
} else {
attachments.push(RenderGraphPassAttachment {
usage,
virtual_image,
image: None,
image_view: None,
load_op: RafxLoadOp::DontCare,
stencil_load_op: RafxLoadOp::DontCare,
store_op: RafxStoreOp::DontCare,
stencil_store_op: RafxStoreOp::DontCare,
clear_color: Default::default(),
format: RafxFormat::UNDEFINED,
samples: RafxSampleCount::SampleCount1,
initial_state: RafxResourceState::UNDEFINED,
final_state: RafxResourceState::UNDEFINED,
});
(attachments.len() - 1, true)
}
}
match pass_node {
PassNode::ComputeNode(compute_node) => {
passes.push(RenderGraphPass::Compute(RenderGraphComputePass {
node: compute_node,
pre_pass_barrier: Default::default(),
}));
}
PassNode::RenderNode(renderpass_node) => {
let mut renderpass_attachments = Vec::default();
log::trace!(" subpass node: {:?}", renderpass_node);
let subpass_node = graph.node(renderpass_node);
if subpass_node.color_attachments.is_empty()
&& subpass_node.depth_attachment.is_none()
{
assert!(subpass_node.resolve_attachments.is_empty());
log::trace!(" Not generating a subpass - no attachments");
continue;
}
let mut pass_color_attachments: [Option<usize>; MAX_COLOR_ATTACHMENTS] =
Default::default();
let mut pass_resolve_attachments: [Option<usize>; MAX_COLOR_ATTACHMENTS] =
Default::default();
let mut pass_depth_attachment = Default::default();
for (color_attachment_index, color_attachment) in
subpass_node.color_attachments.iter().enumerate()
{
if let Some(color_attachment) = color_attachment {
let read_or_write_usage = color_attachment
.read_image
.or(color_attachment.write_image)
.unwrap();
let virtual_image = virtual_resources
.image_usage_to_virtual
.get(&read_or_write_usage)
.unwrap();
let specification = constraints.images.get(&read_or_write_usage).unwrap();
log::trace!(" virtual attachment (color): {:?}", virtual_image);
let (pass_attachment_index, is_first_usage) = find_or_insert_attachment(
&mut renderpass_attachments,
read_or_write_usage,
*virtual_image,
);
pass_color_attachments[color_attachment_index] =
Some(pass_attachment_index);
let mut attachment = &mut renderpass_attachments[pass_attachment_index];
if is_first_usage {
if color_attachment.clear_color_value.is_some() {
attachment.load_op = RafxLoadOp::Clear;
attachment.clear_color = Some(AttachmentClearValue::Color(
color_attachment.clear_color_value.unwrap(),
))
} else if color_attachment.read_image.is_some() {
attachment.load_op = RafxLoadOp::Load;
}
attachment.format = specification.format.into();
attachment.samples = specification.samples.into();
};
let store_op = if let Some(write_image) = color_attachment.write_image {
if !graph.image_version_info(write_image).read_usages.is_empty() {
RafxStoreOp::Store
} else {
RafxStoreOp::DontCare
}
} else {
RafxStoreOp::DontCare
};
attachment.store_op = store_op;
attachment.stencil_store_op = RafxStoreOp::DontCare;
}
}
for (resolve_attachment_index, resolve_attachment) in
subpass_node.resolve_attachments.iter().enumerate()
{
if let Some(resolve_attachment) = resolve_attachment {
let write_image = resolve_attachment.write_image;
let virtual_image = virtual_resources
.image_usage_to_virtual
.get(&write_image)
.unwrap();
let specification = constraints.images.get(&write_image).unwrap();
log::trace!(" virtual attachment (resolve): {:?}", virtual_image);
let (pass_attachment_index, is_first_usage) = find_or_insert_attachment(
&mut renderpass_attachments,
write_image,
*virtual_image,
);
pass_resolve_attachments[resolve_attachment_index] =
Some(pass_attachment_index);
assert!(is_first_usage);
let mut attachment = &mut renderpass_attachments[pass_attachment_index];
attachment.format = specification.format.into();
attachment.samples = specification.samples.into();
let store_op =
if !graph.image_version_info(write_image).read_usages.is_empty() {
RafxStoreOp::Store
} else {
RafxStoreOp::DontCare
};
attachment.store_op = store_op;
attachment.stencil_store_op = RafxStoreOp::DontCare;
}
}
if let Some(depth_attachment) = &subpass_node.depth_attachment {
let read_or_write_usage = depth_attachment
.read_image
.or(depth_attachment.write_image)
.unwrap();
let virtual_image = virtual_resources
.image_usage_to_virtual
.get(&read_or_write_usage)
.unwrap();
let specification = constraints.images.get(&read_or_write_usage).unwrap();
log::trace!(" virtual attachment (depth): {:?}", virtual_image);
let (pass_attachment_index, is_first_usage) = find_or_insert_attachment(
&mut renderpass_attachments,
read_or_write_usage,
*virtual_image,
);
pass_depth_attachment = Some(pass_attachment_index);
let mut attachment = &mut renderpass_attachments[pass_attachment_index];
if is_first_usage {
if depth_attachment.clear_depth_stencil_value.is_some() {
if depth_attachment.has_depth {
attachment.load_op = RafxLoadOp::Clear;
}
if depth_attachment.has_stencil {
attachment.stencil_load_op = RafxLoadOp::Clear;
}
attachment.clear_color = Some(AttachmentClearValue::DepthStencil(
depth_attachment.clear_depth_stencil_value.unwrap(),
));
} else if depth_attachment.read_image.is_some() {
if depth_attachment.has_depth {
attachment.load_op = RafxLoadOp::Load;
}
if depth_attachment.has_stencil {
attachment.stencil_load_op = RafxLoadOp::Load;
}
}
attachment.format = specification.format.into();
attachment.samples = specification.samples.into();
};
let store_op = if let Some(write_image) = depth_attachment.write_image {
if !graph.image_version_info(write_image).read_usages.is_empty() {
RafxStoreOp::Store
} else {
RafxStoreOp::DontCare
}
} else {
RafxStoreOp::DontCare
};
if depth_attachment.has_depth {
attachment.store_op = store_op;
}
if depth_attachment.has_stencil {
attachment.stencil_store_op = store_op;
}
}
passes.push(RenderGraphPass::Renderpass(RenderGraphRenderPass {
node_id: renderpass_node,
attachments: renderpass_attachments,
color_attachments: pass_color_attachments,
depth_attachment: pass_depth_attachment,
resolve_attachments: pass_resolve_attachments,
pre_pass_barrier: None,
post_pass_barrier: None,
}));
}
}
}
passes
}
#[derive(Debug)]
struct AssignPhysicalResourcesResult {
image_usage_to_physical: FnvHashMap<RenderGraphImageUsageId, PhysicalImageId>,
image_usage_to_image_view: FnvHashMap<RenderGraphImageUsageId, PhysicalImageViewId>,
image_views: Vec<RenderGraphImageView>,
image_virtual_to_physical: FnvHashMap<VirtualImageId, PhysicalImageId>,
image_specifications: Vec<RenderGraphImageSpecification>,
buffer_usage_to_physical: FnvHashMap<RenderGraphBufferUsageId, PhysicalBufferId>,
buffer_virtual_to_physical: FnvHashMap<VirtualBufferId, PhysicalBufferId>,
buffer_specifications: Vec<RenderGraphBufferSpecification>,
}
#[profiling::function]
fn assign_physical_resources(
graph: &RenderGraphBuilder,
constraints: &DetermineConstraintsResult,
virtual_resources: &AssignVirtualResourcesResult,
passes: &mut [RenderGraphPass],
) -> AssignPhysicalResourcesResult {
log::trace!("-- Assign physical resources --");
struct PhysicalImageReuseRequirements {
virtual_id: VirtualImageId,
specification: RenderGraphImageSpecification,
first_node_pass_index: usize,
last_node_pass_index: usize,
}
struct PhysicalBufferReuseRequirements {
virtual_id: VirtualBufferId,
specification: RenderGraphBufferSpecification,
first_node_pass_index: usize,
last_node_pass_index: usize,
}
fn add_or_modify_reuse_image_requirements(
virtual_resources: &AssignVirtualResourcesResult,
constraints: &DetermineConstraintsResult,
pass_index: usize,
usage: RenderGraphImageUsageId,
reuse_requirements: &mut Vec<PhysicalImageReuseRequirements>,
reuse_requirements_lookup: &mut FnvHashMap<VirtualImageId, usize>,
) {
let virtual_id = virtual_resources.image_usage_to_virtual[&usage];
let reused_image_requirements_index = *reuse_requirements_lookup
.entry(virtual_id)
.or_insert_with(|| {
let reused_image_requirements_index = reuse_requirements.len();
let specification = &constraints.images[&usage];
reuse_requirements.push(PhysicalImageReuseRequirements {
virtual_id,
first_node_pass_index: pass_index,
last_node_pass_index: pass_index,
specification: specification.clone(),
});
log::trace!(" Add requirement {:?} {:?}", virtual_id, specification);
reused_image_requirements_index
});
reuse_requirements[reused_image_requirements_index].last_node_pass_index = pass_index;
}
fn add_or_modify_reuse_buffer_requirements(
virtual_resources: &AssignVirtualResourcesResult,
constraints: &DetermineConstraintsResult,
pass_index: usize,
usage: RenderGraphBufferUsageId,
reuse_requirements: &mut Vec<PhysicalBufferReuseRequirements>,
reuse_requirements_lookup: &mut FnvHashMap<VirtualBufferId, usize>,
) {
let virtual_id = virtual_resources.buffer_usage_to_virtual[&usage];
let reused_buffer_requirements_index = *reuse_requirements_lookup
.entry(virtual_id)
.or_insert_with(|| {
let reused_buffer_requirements_index = reuse_requirements.len();
let specification = &constraints.buffers[&usage];
reuse_requirements.push(PhysicalBufferReuseRequirements {
virtual_id,
first_node_pass_index: pass_index,
last_node_pass_index: pass_index,
specification: specification.clone(),
});
log::trace!(" Add requirement {:?} {:?}", virtual_id, specification);
reused_buffer_requirements_index
});
reuse_requirements[reused_buffer_requirements_index].last_node_pass_index = pass_index;
}
let mut image_reuse_requirements = Vec::<PhysicalImageReuseRequirements>::default();
let mut image_reuse_requirements_lookup = FnvHashMap::<VirtualImageId, usize>::default();
let mut buffer_reuse_requirements = Vec::<PhysicalBufferReuseRequirements>::default();
let mut buffer_reuse_requirements_lookup = FnvHashMap::<VirtualBufferId, usize>::default();
for (pass_index, pass) in passes.iter().enumerate() {
let subpass_node_id = pass.node();
let node = graph.node(subpass_node_id);
for image_modify in &node.image_modifies {
add_or_modify_reuse_image_requirements(
virtual_resources,
constraints,
pass_index,
image_modify.input,
&mut image_reuse_requirements,
&mut image_reuse_requirements_lookup,
);
add_or_modify_reuse_image_requirements(
virtual_resources,
constraints,
pass_index,
image_modify.output,
&mut image_reuse_requirements,
&mut image_reuse_requirements_lookup,
);
}
for image_read in &node.image_reads {
add_or_modify_reuse_image_requirements(
virtual_resources,
constraints,
pass_index,
image_read.image,
&mut image_reuse_requirements,
&mut image_reuse_requirements_lookup,
);
}
for image_create in &node.image_creates {
add_or_modify_reuse_image_requirements(
virtual_resources,
constraints,
pass_index,
image_create.image,
&mut image_reuse_requirements,
&mut image_reuse_requirements_lookup,
);
}
for image_sample in &node.sampled_images {
add_or_modify_reuse_image_requirements(
virtual_resources,
constraints,
pass_index,
*image_sample,
&mut image_reuse_requirements,
&mut image_reuse_requirements_lookup,
);
}
for buffer_modify in &node.buffer_modifies {
add_or_modify_reuse_buffer_requirements(
virtual_resources,
constraints,
pass_index,
buffer_modify.input,
&mut buffer_reuse_requirements,
&mut buffer_reuse_requirements_lookup,
);
add_or_modify_reuse_buffer_requirements(
virtual_resources,
constraints,
pass_index,
buffer_modify.output,
&mut buffer_reuse_requirements,
&mut buffer_reuse_requirements_lookup,
);
}
for buffer_read in &node.buffer_reads {
add_or_modify_reuse_buffer_requirements(
virtual_resources,
constraints,
pass_index,
buffer_read.buffer,
&mut buffer_reuse_requirements,
&mut buffer_reuse_requirements_lookup,
);
}
for buffer_create in &node.buffer_creates {
add_or_modify_reuse_buffer_requirements(
virtual_resources,
constraints,
pass_index,
buffer_create.buffer,
&mut buffer_reuse_requirements,
&mut buffer_reuse_requirements_lookup,
);
}
}
struct PhysicalImage {
specification: RenderGraphImageSpecification,
last_node_pass_index: usize,
can_be_reused: bool,
}
struct PhysicalBuffer {
specification: RenderGraphBufferSpecification,
last_node_pass_index: usize,
can_be_reused: bool,
}
let mut physical_images = Vec::<PhysicalImage>::default();
let mut image_virtual_to_physical = FnvHashMap::<VirtualImageId, PhysicalImageId>::default();
let mut physical_buffers = Vec::<PhysicalBuffer>::default();
let mut buffer_virtual_to_physical = FnvHashMap::<VirtualBufferId, PhysicalBufferId>::default();
for output_image in &graph.output_images {
let physical_image_id = PhysicalImageId(physical_images.len());
physical_images.push(PhysicalImage {
specification: output_image.specification.clone(),
last_node_pass_index: passes.len() - 1,
can_be_reused: false,
});
let virtual_id = virtual_resources.image_usage_to_virtual[&output_image.usage];
let old = image_virtual_to_physical.insert(virtual_id, physical_image_id);
assert!(old.is_none());
log::trace!(
" Output Image {:?} -> {:?} Used in passes [{}:{}]",
virtual_id,
physical_image_id,
0,
passes.len() - 1
);
}
for output_buffer in &graph.output_buffers {
let physical_buffer_id = PhysicalBufferId(physical_buffers.len());
physical_buffers.push(PhysicalBuffer {
specification: output_buffer.specification.clone(),
last_node_pass_index: passes.len() - 1,
can_be_reused: false,
});
let virtual_id = virtual_resources.buffer_usage_to_virtual[&output_buffer.usage];
let old = buffer_virtual_to_physical.insert(virtual_id, physical_buffer_id);
assert!(old.is_none());
log::trace!(
" Output Buffer {:?} -> {:?} Used in passes [{}:{}]",
virtual_id,
physical_buffer_id,
0,
passes.len() - 1
);
}
for reuse_requirements in &image_reuse_requirements {
if image_virtual_to_physical.contains_key(&reuse_requirements.virtual_id) {
continue;
}
let mut physical_image_id = None;
for (physical_image_index, physical_image) in physical_images.iter_mut().enumerate() {
if physical_image.last_node_pass_index < reuse_requirements.first_node_pass_index
&& physical_image.can_be_reused
{
if physical_image
.specification
.try_merge(&reuse_requirements.specification)
{
physical_image.last_node_pass_index = reuse_requirements.last_node_pass_index;
physical_image_id = Some(PhysicalImageId(physical_image_index));
log::trace!(
" Intermediate Image (Reuse) {:?} -> {:?} Used in passes [{}:{}]",
reuse_requirements.virtual_id,
physical_image_id,
reuse_requirements.first_node_pass_index,
reuse_requirements.last_node_pass_index
);
break;
}
}
}
let physical_image_id = physical_image_id.unwrap_or_else(|| {
let physical_image_id = PhysicalImageId(physical_images.len());
physical_images.push(PhysicalImage {
specification: reuse_requirements.specification.clone(),
last_node_pass_index: reuse_requirements.last_node_pass_index,
can_be_reused: true,
});
log::trace!(
" Intermediate Image (Create new) {:?} -> {:?} Used in passes [{}:{}]",
reuse_requirements.virtual_id,
physical_image_id,
reuse_requirements.first_node_pass_index,
reuse_requirements.last_node_pass_index
);
physical_image_id
});
image_virtual_to_physical.insert(reuse_requirements.virtual_id, physical_image_id);
}
for reuse_requirements in &buffer_reuse_requirements {
if buffer_virtual_to_physical.contains_key(&reuse_requirements.virtual_id) {
continue;
}
let mut physical_buffer_id = None;
for (physical_buffer_index, physical_buffer) in physical_buffers.iter_mut().enumerate() {
if physical_buffer.last_node_pass_index < reuse_requirements.first_node_pass_index
&& physical_buffer.can_be_reused
{
if physical_buffer
.specification
.try_merge(&reuse_requirements.specification)
{
physical_buffer.last_node_pass_index = reuse_requirements.last_node_pass_index;
physical_buffer_id = Some(PhysicalBufferId(physical_buffer_index));
log::trace!(
" Intermediate Buffer (Reuse) {:?} -> {:?} Used in passes [{}:{}]",
reuse_requirements.virtual_id,
physical_buffer_id,
reuse_requirements.first_node_pass_index,
reuse_requirements.last_node_pass_index
);
break;
}
}
}
let physical_buffer_id = physical_buffer_id.unwrap_or_else(|| {
let physical_buffer_id = PhysicalBufferId(physical_buffers.len());
physical_buffers.push(PhysicalBuffer {
specification: reuse_requirements.specification.clone(),
last_node_pass_index: reuse_requirements.last_node_pass_index,
can_be_reused: true,
});
log::trace!(
" Intermediate Buffer (Create new) {:?} -> {:?} Used in passes [{}:{}]",
reuse_requirements.virtual_id,
physical_buffer_id,
reuse_requirements.first_node_pass_index,
reuse_requirements.last_node_pass_index
);
physical_buffer_id
});
buffer_virtual_to_physical.insert(reuse_requirements.virtual_id, physical_buffer_id);
}
let mut image_usage_to_physical = FnvHashMap::default();
for (&usage, virtual_image) in &virtual_resources.image_usage_to_virtual {
image_usage_to_physical.insert(usage, image_virtual_to_physical[virtual_image]);
}
let mut buffer_usage_to_physical = FnvHashMap::default();
for (&usage, virtual_buffer) in &virtual_resources.buffer_usage_to_virtual {
buffer_usage_to_physical.insert(usage, buffer_virtual_to_physical[virtual_buffer]);
}
let mut image_subresource_to_view = FnvHashMap::default();
let mut image_views = Vec::default();
let mut image_usage_to_image_view = FnvHashMap::default();
for (&usage, &physical_image) in &image_usage_to_physical {
let image_specification = constraints.image_specification(usage).unwrap();
let image_view = RenderGraphImageView {
physical_image,
format: image_specification.format,
view_options: graph.image_usages[usage.0].view_options.clone(),
};
let image_view_id = *image_subresource_to_view
.entry(image_view.clone())
.or_insert_with(|| {
let image_view_id = PhysicalImageViewId(image_views.len());
image_views.push(image_view);
image_view_id
});
let old = image_usage_to_image_view.insert(usage, image_view_id);
assert!(old.is_none());
}
for pass in passes {
if let RenderGraphPass::Renderpass(renderpass) = pass {
for attachment in &mut renderpass.attachments {
let physical_image = image_virtual_to_physical[&attachment.virtual_image];
let image_view_id = image_usage_to_image_view[&attachment.usage];
attachment.image = Some(physical_image);
attachment.image_view = Some(image_view_id);
}
}
}
let image_specifications: Vec<_> = physical_images
.into_iter()
.map(|x| x.specification)
.collect();
let buffer_specifications: Vec<_> = physical_buffers
.into_iter()
.map(|x| x.specification)
.collect();
AssignPhysicalResourcesResult {
image_usage_to_physical,
image_virtual_to_physical,
image_usage_to_image_view,
image_views,
image_specifications,
buffer_usage_to_physical,
buffer_virtual_to_physical,
buffer_specifications,
}
}
#[profiling::function]
fn build_node_barriers(
graph: &RenderGraphBuilder,
node_execution_order: &[RenderGraphNodeId],
_constraints: &DetermineConstraintsResult,
physical_resources: &AssignPhysicalResourcesResult,
) -> FnvHashMap<RenderGraphNodeId, RenderGraphNodeResourceBarriers> {
let mut resource_barriers =
FnvHashMap::<RenderGraphNodeId, RenderGraphNodeResourceBarriers>::default();
for node_id in node_execution_order {
let node = graph.node(*node_id);
let mut image_node_barriers: FnvHashMap<PhysicalImageId, RenderGraphPassImageBarriers> =
Default::default();
let mut buffer_node_barriers: FnvHashMap<PhysicalBufferId, RenderGraphPassBufferBarriers> =
Default::default();
for color_attachment in &node.color_attachments {
if let Some(color_attachment) = color_attachment {
let read_or_write_usage = color_attachment
.read_image
.or(color_attachment.write_image)
.unwrap();
let physical_image = physical_resources
.image_usage_to_physical
.get(&read_or_write_usage)
.unwrap();
image_node_barriers
.entry(*physical_image)
.or_insert_with(|| {
RenderGraphPassImageBarriers::new(RafxResourceState::RENDER_TARGET)
});
}
}
for resolve_attachment in &node.resolve_attachments {
if let Some(resolve_attachment) = resolve_attachment {
let physical_image = physical_resources
.image_usage_to_physical
.get(&resolve_attachment.write_image)
.unwrap();
image_node_barriers
.entry(*physical_image)
.or_insert_with(|| {
RenderGraphPassImageBarriers::new(RafxResourceState::RENDER_TARGET)
});
}
}
if let Some(depth_attachment) = &node.depth_attachment {
let read_or_write_usage = depth_attachment
.read_image
.or(depth_attachment.write_image)
.unwrap();
let physical_image = physical_resources
.image_usage_to_physical
.get(&read_or_write_usage)
.unwrap();
image_node_barriers
.entry(*physical_image)
.or_insert_with(|| {
RenderGraphPassImageBarriers::new(RafxResourceState::DEPTH_WRITE)
});
}
for sampled_image in &node.sampled_images {
let physical_image = physical_resources
.image_usage_to_physical
.get(sampled_image)
.unwrap();
image_node_barriers
.entry(*physical_image)
.or_insert_with(|| {
RenderGraphPassImageBarriers::new(RafxResourceState::PIXEL_SHADER_RESOURCE)
});
}
for buffer_create in &node.buffer_creates {
let physical_buffer = physical_resources
.buffer_usage_to_physical
.get(&buffer_create.buffer)
.unwrap();
buffer_node_barriers
.entry(*physical_buffer)
.or_insert_with(|| {
RenderGraphPassBufferBarriers::new(RafxResourceState::UNORDERED_ACCESS)
});
}
for buffer_read in &node.buffer_reads {
let physical_buffer = physical_resources
.buffer_usage_to_physical
.get(&buffer_read.buffer)
.unwrap();
buffer_node_barriers
.entry(*physical_buffer)
.or_insert_with(|| {
RenderGraphPassBufferBarriers::new(RafxResourceState::UNORDERED_ACCESS)
});
}
for buffer_modify in &node.buffer_modifies {
let physical_buffer = physical_resources
.buffer_usage_to_physical
.get(&buffer_modify.input)
.unwrap();
buffer_node_barriers
.entry(*physical_buffer)
.or_insert_with(|| {
RenderGraphPassBufferBarriers::new(RafxResourceState::UNORDERED_ACCESS)
});
}
resource_barriers.insert(
*node_id,
RenderGraphNodeResourceBarriers {
image_barriers: image_node_barriers,
buffer_barriers: buffer_node_barriers,
},
);
}
resource_barriers
}
#[profiling::function]
fn build_pass_barriers(
graph: &RenderGraphBuilder,
_node_execution_order: &[RenderGraphNodeId],
_constraints: &DetermineConstraintsResult,
physical_resources: &AssignPhysicalResourcesResult,
node_barriers: &FnvHashMap<RenderGraphNodeId, RenderGraphNodeResourceBarriers>,
passes: &mut [RenderGraphPass],
) {
log::trace!("-- build_pass_barriers --");
struct ImageState {
resource_state: RafxResourceState,
}
impl Default for ImageState {
fn default() -> Self {
ImageState {
resource_state: RafxResourceState::UNDEFINED,
}
}
}
struct BufferState {
resource_state: RafxResourceState,
}
impl Default for BufferState {
fn default() -> Self {
BufferState {
resource_state: RafxResourceState::UNDEFINED,
}
}
}
let mut image_states: Vec<ImageState> =
Vec::with_capacity(physical_resources.image_specifications.len());
image_states.resize_with(physical_resources.image_specifications.len(), || {
Default::default()
});
let mut buffer_states: Vec<BufferState> =
Vec::with_capacity(physical_resources.buffer_specifications.len());
buffer_states.resize_with(physical_resources.buffer_specifications.len(), || {
Default::default()
});
for (pass_index, pass) in passes.iter_mut().enumerate() {
log::trace!("pass {}", pass_index);
let mut attachment_initial_state: Vec<Option<RafxResourceState>> = Default::default();
if let RenderGraphPass::Renderpass(pass) = pass {
attachment_initial_state.resize_with(pass.attachments.len(), || None);
}
let subpass_node_id = pass.node();
let node_barriers = &node_barriers[&subpass_node_id];
struct ImageTransition {
physical_image_id: PhysicalImageId,
old_state: RafxResourceState,
new_state: RafxResourceState,
}
struct BufferTransition {
physical_buffer_id: PhysicalBufferId,
old_state: RafxResourceState,
new_state: RafxResourceState,
}
let mut image_transitions = Vec::default();
for (physical_image_id, image_barrier) in &node_barriers.image_barriers {
log::trace!(" image {:?}", physical_image_id);
let image_state = &mut image_states[physical_image_id.0];
let resource_state_change = image_state.resource_state != image_barrier.resource_state;
if resource_state_change {
log::trace!(
" state change! {:?} -> {:?}",
image_state.resource_state,
image_barrier.resource_state
);
if resource_state_change {
image_transitions.push(ImageTransition {
physical_image_id: *physical_image_id,
old_state: image_state.resource_state,
new_state: image_barrier.resource_state,
});
}
image_state.resource_state = image_barrier.resource_state;
}
if let RenderGraphPass::Renderpass(pass) = pass {
for (attachment_index, attachment) in &mut pass.attachments.iter_mut().enumerate() {
if attachment.image.unwrap() == *physical_image_id {
if attachment_initial_state[attachment_index].is_none() {
attachment_initial_state[attachment_index] =
Some(image_state.resource_state.into());
attachment.initial_state = image_barrier.resource_state.into();
}
attachment.final_state = image_barrier.resource_state.into();
break;
}
}
}
}
let mut buffer_transitions = Vec::default();
for (physical_buffer_id, buffer_barrier) in &node_barriers.buffer_barriers {
log::trace!(" buffer {:?}", physical_buffer_id);
let buffer_state = &mut buffer_states[physical_buffer_id.0];
let resource_state_change =
buffer_state.resource_state != buffer_barrier.resource_state;
if resource_state_change {
log::trace!(
" state change! {:?} -> {:?}",
buffer_state.resource_state,
buffer_barrier.resource_state
);
buffer_transitions.push(BufferTransition {
physical_buffer_id: *physical_buffer_id,
old_state: buffer_state.resource_state,
new_state: buffer_barrier.resource_state,
});
buffer_state.resource_state = buffer_barrier.resource_state;
}
}
let image_barriers: Vec<_> = image_transitions
.into_iter()
.map(|image_transition| {
assert_ne!(image_transition.new_state, RafxResourceState::UNDEFINED);
PrepassImageBarrier {
image: image_transition.physical_image_id,
old_state: image_transition.old_state,
new_state: image_transition.new_state,
}
})
.collect();
let buffer_barriers: Vec<_> = buffer_transitions
.into_iter()
.map(|buffer_transition| {
assert_ne!(buffer_transition.new_state, RafxResourceState::UNDEFINED);
PrepassBufferBarrier {
buffer: buffer_transition.physical_buffer_id,
old_state: buffer_transition.old_state,
new_state: buffer_transition.new_state,
}
})
.collect();
if !image_barriers.is_empty() || !buffer_barriers.is_empty() {
let barrier = PrepassBarrier {
image_barriers,
buffer_barriers,
};
pass.set_pre_pass_barrier(barrier);
}
log::trace!("Check for output images");
for (output_image_index, output_image) in graph.output_images.iter().enumerate() {
if graph.image_version_info(output_image.usage).creator_node == subpass_node_id {
let output_physical_image =
physical_resources.image_usage_to_physical[&output_image.usage];
log::trace!(
"Output image {} usage {:?} created by node {:?} physical image {:?}",
output_image_index,
output_image.usage,
subpass_node_id,
output_physical_image
);
if let RenderGraphPass::Renderpass(pass) = pass {
let mut image_barriers = vec![];
for (attachment_index, attachment) in
&mut pass.attachments.iter_mut().enumerate()
{
if attachment.image.unwrap() == output_physical_image {
log::trace!(" attachment {}", attachment_index);
if attachment.final_state != output_image.final_state {
image_barriers.push(PrepassImageBarrier {
image: attachment.image.unwrap(),
old_state: attachment.final_state.into(),
new_state: output_image.final_state.into(),
})
}
}
}
if !image_barriers.is_empty() {
pass.post_pass_barrier = Some(PostpassBarrier {
buffer_barriers: vec![],
image_barriers,
});
}
}
}
}
}
}
#[profiling::function]
fn create_output_passes(
graph: &RenderGraphBuilder,
passes: Vec<RenderGraphPass>,
) -> Vec<RenderGraphOutputPass> {
let mut renderpasses = Vec::with_capacity(passes.len());
for pass in passes {
match pass {
RenderGraphPass::Renderpass(pass) => {
let attachment_images = pass
.attachments
.iter()
.map(|attachment| attachment.image_view.unwrap())
.collect();
let debug_name = graph.node(pass.node_id).name;
let mut color_formats = vec![];
let mut sample_count = None;
for color_attachment in &pass.color_attachments {
if let Some(color_attachment) = color_attachment {
color_formats.push(pass.attachments[*color_attachment].format);
sample_count = Some(
sample_count.unwrap_or(pass.attachments[*color_attachment].samples),
);
}
}
let mut depth_format = None;
if let Some(depth_attachment) = pass.depth_attachment {
depth_format = Some(pass.attachments[depth_attachment].format);
sample_count =
Some(sample_count.unwrap_or(pass.attachments[depth_attachment].samples));
}
let render_target_meta = GraphicsPipelineRenderTargetMeta::new(
color_formats,
depth_format,
sample_count.unwrap(),
);
let mut color_render_targets = Vec::with_capacity(MAX_COLOR_ATTACHMENTS);
for (color_index, attachment_index) in pass.color_attachments.iter().enumerate() {
if let Some(attachment_index) = attachment_index {
let attachment = &pass.attachments[*attachment_index];
let attachment_usage = &graph.image_usages[attachment.usage.0];
let array_slice = attachment_usage.view_options.array_slice;
let mip_slice = attachment_usage.view_options.mip_slice;
let mut resolve_image = None;
let mut resolve_array_slice = None;
let mut resolve_mip_slice = None;
let mut resolve_store_op = RafxStoreOp::DontCare;
if let Some(resolve_attachment_index) =
pass.resolve_attachments[color_index]
{
let resolve_attachment = &pass.attachments[resolve_attachment_index];
let resolve_attachment_usage =
&graph.image_usages[resolve_attachment.usage.0];
resolve_image = Some(resolve_attachment.image.unwrap());
resolve_array_slice = resolve_attachment_usage.view_options.array_slice;
resolve_mip_slice = resolve_attachment_usage.view_options.mip_slice;
resolve_store_op = resolve_attachment.store_op;
}
color_render_targets.push(RenderGraphColorRenderTarget {
image: attachment.image.unwrap(),
load_op: attachment.load_op,
store_op: attachment.store_op,
clear_value: attachment
.clear_color
.clone()
.map(|x| x.to_color_clear_value())
.unwrap_or_default(),
array_slice,
mip_slice,
resolve_image,
resolve_store_op,
resolve_array_slice,
resolve_mip_slice,
});
}
}
let mut depth_stencil_render_target = None;
if let Some(attachment_index) = pass.depth_attachment {
let attachment = &pass.attachments[attachment_index];
let array_slice = graph.image_usages[attachment.usage.0]
.view_options
.array_slice;
let mip_slice = graph.image_usages[attachment.usage.0]
.view_options
.mip_slice;
depth_stencil_render_target = Some(RenderGraphDepthStencilRenderTarget {
image: attachment.image.unwrap(),
depth_load_op: attachment.load_op,
stencil_load_op: attachment.stencil_load_op,
depth_store_op: attachment.store_op,
stencil_store_op: attachment.stencil_store_op,
clear_value: attachment
.clear_color
.clone()
.map(|x| x.to_depth_stencil_clear_value())
.unwrap_or_default(),
array_slice,
mip_slice,
});
}
let output_pass = RenderGraphOutputRenderPass {
node_id: pass.node_id,
attachment_images,
pre_pass_barrier: pass.pre_pass_barrier,
post_pass_barrier: pass.post_pass_barrier,
debug_name,
color_render_targets,
depth_stencil_render_target,
render_target_meta,
};
renderpasses.push(RenderGraphOutputPass::Renderpass(output_pass));
}
RenderGraphPass::Compute(pass) => {
let output_pass = RenderGraphOutputComputePass {
node: pass.node,
pre_pass_barrier: pass.pre_pass_barrier,
post_pass_barrier: None,
debug_name: graph.node(pass.node).name,
};
renderpasses.push(RenderGraphOutputPass::Compute(output_pass));
}
}
}
renderpasses
}
#[allow(dead_code)]
fn print_constraints(
graph: &RenderGraphBuilder,
constraint_results: &mut DetermineConstraintsResult,
) {
log::trace!("Image constraints:");
for (image_index, image_resource) in graph.image_resources.iter().enumerate() {
log::trace!(" Image {:?} {:?}", image_index, image_resource.name);
for (version_index, version) in image_resource.versions.iter().enumerate() {
log::trace!(" Version {}", version_index);
log::trace!(
" Writen as: {:?}",
constraint_results.image_specification(version.create_usage)
);
for (usage_index, usage) in version.read_usages.iter().enumerate() {
log::trace!(
" Read Usage {}: {:?}",
usage_index,
constraint_results.image_specification(*usage)
);
}
}
}
log::trace!("Buffer constraints:");
for (buffer_index, buffer_resource) in graph.buffer_resources.iter().enumerate() {
log::trace!(" Buffer {:?} {:?}", buffer_index, buffer_resource.name);
for (version_index, version) in buffer_resource.versions.iter().enumerate() {
log::trace!(" Version {}", version_index);
log::trace!(
" Writen as: {:?}",
constraint_results.buffer_specification(version.create_usage)
);
for (usage_index, usage) in version.read_usages.iter().enumerate() {
log::trace!(
" Read Usage {}: {:?}",
usage_index,
constraint_results.buffer_specification(*usage)
);
}
}
}
}
#[allow(dead_code)]
fn print_image_compatibility(
graph: &RenderGraphBuilder,
constraint_results: &DetermineConstraintsResult,
) {
log::trace!("Image Compatibility Report:");
for (image_index, image_resource) in graph.image_resources.iter().enumerate() {
log::trace!(" Image {:?} {:?}", image_index, image_resource.name);
for (version_index, version) in image_resource.versions.iter().enumerate() {
let write_specification = constraint_results.image_specification(version.create_usage);
log::trace!(" Version {}: {:?}", version_index, version);
for (usage_index, usage) in version.read_usages.iter().enumerate() {
let read_specification = constraint_results.image_specification(*usage);
if write_specification == read_specification {
log::trace!(" read usage {} matches", usage_index);
} else {
log::trace!(" read usage {} does not match", usage_index);
log::trace!(" produced: {:?}", write_specification);
log::trace!(" required: {:?}", read_specification);
}
}
}
}
}
#[allow(dead_code)]
fn print_node_barriers(
node_barriers: &FnvHashMap<RenderGraphNodeId, RenderGraphNodeResourceBarriers>
) {
log::trace!("Barriers:");
for (node_id, barriers) in node_barriers.iter() {
log::trace!(" pass {:?}", node_id);
log::trace!(" resource states");
for (physical_id, barriers) in &barriers.image_barriers {
log::trace!(" {:?}: {:?}", physical_id, barriers.resource_state);
}
for (physical_id, barriers) in &barriers.buffer_barriers {
log::trace!(" {:?}: {:?}", physical_id, barriers.resource_state);
}
}
}
#[allow(dead_code)]
fn verify_unculled_image_usages_specifications_exist(
graph: &RenderGraphBuilder,
node_execution_order: &Vec<RenderGraphNodeId>,
constraint_results: &DetermineConstraintsResult,
) {
for (_image_index, image_resource) in graph.image_resources.iter().enumerate() {
for (_version_index, version) in image_resource.versions.iter().enumerate() {
if node_execution_order.contains(&version.creator_node)
&& constraint_results
.images
.get(&version.create_usage)
.is_none()
{
let usage_info = &graph.image_usages[version.create_usage.0];
panic!(
"Could not determine specification for image {:?} use by {:?} for {:?}",
version.create_usage, usage_info.user, usage_info.usage_type
);
}
for (_, usage) in version.read_usages.iter().enumerate() {
let usage_info = &graph.image_usages[usage.0];
let is_scheduled = match &usage_info.user {
RenderGraphImageUser::Node(node_id) => node_execution_order.contains(node_id),
RenderGraphImageUser::Output(_) => true,
};
if is_scheduled && constraint_results.images.get(usage).is_none() {
panic!(
"Could not determine specification for image {:?} used by {:?} for {:?}",
usage, usage_info.user, usage_info.usage_type
);
}
}
}
}
}
#[allow(dead_code)]
fn print_final_images(
output_images: &FnvHashMap<PhysicalImageViewId, RenderGraphPlanOutputImage>,
intermediate_images: &FnvHashMap<PhysicalImageId, RenderGraphImageSpecification>,
) {
log::trace!("-- IMAGES --");
for (physical_id, intermediate_image_spec) in intermediate_images {
log::trace!(
"Intermediate Image: {:?} {:?}",
physical_id,
intermediate_image_spec
);
}
for (physical_id, output_image) in output_images {
log::trace!("Output Image: {:?} {:?}", physical_id, output_image);
}
}
#[allow(dead_code)]
fn print_final_image_usage(
graph: &RenderGraphBuilder,
assign_physical_resources_result: &AssignPhysicalResourcesResult,
constraint_results: &DetermineConstraintsResult,
renderpasses: &Vec<RenderGraphOutputPass>,
) {
log::debug!("-- IMAGE USAGE --");
for (pass_index, pass) in renderpasses.iter().enumerate() {
log::debug!("pass {}", pass_index);
let node = graph.node(pass.node());
log::debug!(" subpass {:?} {:?}", pass.node(), node.name);
for (color_attachment_index, color_attachment) in node.color_attachments.iter().enumerate()
{
if let Some(color_attachment) = color_attachment {
let read_or_write = color_attachment
.read_image
.or_else(|| color_attachment.write_image)
.unwrap();
let physical_image =
assign_physical_resources_result.image_usage_to_physical[&read_or_write];
let write_name = color_attachment
.write_image
.map(|x| graph.image_resource(x).name)
.flatten();
log::debug!(
" Color Attachment {}: {:?} Name: {:?} Constraints: {:?}",
color_attachment_index,
physical_image,
write_name,
constraint_results.images[&read_or_write]
);
}
}
for (resolve_attachment_index, resolve_attachment) in
node.resolve_attachments.iter().enumerate()
{
if let Some(resolve_attachment) = resolve_attachment {
let physical_image = assign_physical_resources_result.image_usage_to_physical
[&resolve_attachment.write_image];
let write_name = graph.image_resource(resolve_attachment.write_image).name;
log::debug!(
" Resolve Attachment {}: {:?} Name: {:?} Constraints: {:?}",
resolve_attachment_index,
physical_image,
write_name,
constraint_results.images[&resolve_attachment.write_image]
);
}
}
if let Some(depth_attachment) = &node.depth_attachment {
let read_or_write = depth_attachment
.read_image
.or_else(|| depth_attachment.write_image)
.unwrap();
let physical_image =
assign_physical_resources_result.image_usage_to_physical[&read_or_write];
let write_name = depth_attachment
.write_image
.map(|x| graph.image_resource(x).name)
.flatten();
log::debug!(
" Depth Attachment: {:?} Name: {:?} Constraints: {:?}",
physical_image,
write_name,
constraint_results.images[&read_or_write]
);
}
for sampled_image in &node.sampled_images {
let physical_image =
assign_physical_resources_result.image_usage_to_physical[sampled_image];
let write_name = graph.image_resource(*sampled_image).name;
log::debug!(
" Sampled: {:?} Name: {:?} Constraints: {:?}",
physical_image,
write_name,
constraint_results.images[sampled_image]
);
}
}
for output_image in &graph.output_images {
let physical_image =
assign_physical_resources_result.image_usage_to_physical[&output_image.usage];
let write_name = graph.image_resource(output_image.usage).name;
log::debug!(
" Output Image {:?} Name: {:?} Constraints: {:?}",
physical_image,
write_name,
constraint_results.images[&output_image.usage]
);
}
}
#[derive(Debug)]
pub struct RenderGraphPlanOutputImage {
pub output_id: RenderGraphOutputImageId,
pub dst_image: ResourceArc<ImageViewResource>,
}
#[derive(Debug)]
pub struct RenderGraphPlanOutputBuffer {
pub output_id: RenderGraphOutputBufferId,
pub dst_buffer: ResourceArc<BufferResource>,
}
pub struct RenderGraphPlan {
pub(super) passes: Vec<RenderGraphOutputPass>,
pub(super) output_images: FnvHashMap<PhysicalImageViewId, RenderGraphPlanOutputImage>,
pub(super) output_buffers: FnvHashMap<PhysicalBufferId, RenderGraphPlanOutputBuffer>,
pub(super) intermediate_images: FnvHashMap<PhysicalImageId, RenderGraphImageSpecification>,
pub(super) intermediate_buffers: FnvHashMap<PhysicalBufferId, RenderGraphBufferSpecification>,
pub(super) image_views: Vec<RenderGraphImageView>,
pub(super) node_to_pass_index: FnvHashMap<RenderGraphNodeId, usize>,
pub(super) _image_usage_to_physical: FnvHashMap<RenderGraphImageUsageId, PhysicalImageId>,
pub(super) image_usage_to_view: FnvHashMap<RenderGraphImageUsageId, PhysicalImageViewId>,
pub(super) buffer_usage_to_physical: FnvHashMap<RenderGraphBufferUsageId, PhysicalBufferId>,
pub(super) visit_node_callbacks:
FnvHashMap<RenderGraphNodeId, RenderGraphNodeVisitNodeCallback>,
pub(super) _render_phase_dependencies:
FnvHashMap<RenderGraphNodeId, FnvHashSet<RenderPhaseIndex>>,
}
impl RenderGraphPlan {
#[profiling::function]
pub(super) fn new(mut graph: RenderGraphBuilder) -> RenderGraphPlan {
log::trace!("-- Create render graph plan --");
let node_execution_order = determine_node_order(&graph);
log::trace!("Execution order of unculled nodes:");
for node in &node_execution_order {
log::trace!(" Node {:?} {:?}", node, graph.node(*node).name());
}
let mut constraint_results = determine_constraints(&graph, &node_execution_order);
insert_resolves(&mut graph, &node_execution_order, &mut constraint_results);
let assign_virtual_images_result =
assign_virtual_resources(&graph, &node_execution_order, &mut constraint_results);
let mut passes = build_physical_passes(
&graph,
&node_execution_order,
&constraint_results,
&assign_virtual_images_result,
);
let assign_physical_resources_result = assign_physical_resources(
&graph,
&constraint_results,
&assign_virtual_images_result,
&mut passes,
);
let node_barriers = build_node_barriers(
&graph,
&node_execution_order,
&constraint_results,
&assign_physical_resources_result,
);
print_node_barriers(&node_barriers);
build_pass_barriers(
&graph,
&node_execution_order,
&constraint_results,
&assign_physical_resources_result,
&node_barriers,
&mut passes,
);
let output_passes = create_output_passes(&graph, passes);
let mut output_images: FnvHashMap<PhysicalImageViewId, RenderGraphPlanOutputImage> =
Default::default();
let mut output_image_physical_ids = FnvHashSet::default();
for output_image in &graph.output_images {
let output_image_view =
assign_physical_resources_result.image_usage_to_image_view[&output_image.usage];
output_images.insert(
output_image_view,
RenderGraphPlanOutputImage {
output_id: output_image.output_image_id,
dst_image: output_image.dst_image.clone(),
},
);
output_image_physical_ids.insert(
assign_physical_resources_result.image_views[output_image_view.0].physical_image,
);
}
let mut output_buffers: FnvHashMap<PhysicalBufferId, RenderGraphPlanOutputBuffer> =
Default::default();
let mut output_buffer_physical_ids = FnvHashSet::default();
for output_buffer in &graph.output_buffers {
let output_buffer_id =
assign_physical_resources_result.buffer_usage_to_physical[&output_buffer.usage];
output_buffers.insert(
output_buffer_id,
RenderGraphPlanOutputBuffer {
output_id: output_buffer.output_buffer_id,
dst_buffer: output_buffer.dst_buffer.clone(),
},
);
output_buffer_physical_ids.insert(output_buffer_id);
}
let mut intermediate_images: FnvHashMap<PhysicalImageId, RenderGraphImageSpecification> =
Default::default();
for (index, specification) in assign_physical_resources_result
.image_specifications
.iter()
.enumerate()
{
let physical_image = PhysicalImageId(index);
if output_image_physical_ids.contains(&physical_image) {
continue;
}
intermediate_images.insert(physical_image, specification.clone());
}
let mut intermediate_buffers: FnvHashMap<PhysicalBufferId, RenderGraphBufferSpecification> =
Default::default();
for (index, specification) in assign_physical_resources_result
.buffer_specifications
.iter()
.enumerate()
{
let physical_buffer = PhysicalBufferId(index);
if output_buffer_physical_ids.contains(&physical_buffer) {
continue;
}
intermediate_buffers.insert(physical_buffer, specification.clone());
}
print_final_images(&output_images, &intermediate_images);
print_final_image_usage(
&graph,
&assign_physical_resources_result,
&constraint_results,
&output_passes,
);
let mut node_to_pass_index = FnvHashMap::default();
for (pass_index, pass) in output_passes.iter().enumerate() {
node_to_pass_index.insert(pass.node(), pass_index);
}
RenderGraphPlan {
passes: output_passes,
output_images,
output_buffers,
intermediate_images,
intermediate_buffers,
image_views: assign_physical_resources_result.image_views,
node_to_pass_index,
_image_usage_to_physical: assign_physical_resources_result.image_usage_to_physical,
image_usage_to_view: assign_physical_resources_result.image_usage_to_image_view,
buffer_usage_to_physical: assign_physical_resources_result.buffer_usage_to_physical,
visit_node_callbacks: graph.visit_node_callbacks,
_render_phase_dependencies: graph.render_phase_dependencies,
}
}
}