#![allow(dead_code)] use std::{ collections::BTreeMap, fmt::{Debug, Display}, sync::Arc, }; use crate::{ buffers::{Buffer, BufferDesc}, commands, def_monotonic_id, device::{self, DeviceOwned}, images::{self, Image, ImageDesc}, util::{self, Rgba, WithLifetime}, }; use ash::vk; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct GraphResourceId(pub(crate) u32); impl Display for GraphResourceId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "#{}", self.0) } } #[derive(Debug, Clone)] pub enum GraphResourceDesc { Image(ImageDesc), Buffer(BufferDesc), } impl From for GraphResource { fn from(value: GraphResourceDesc) -> Self { match value { GraphResourceDesc::Image(image_desc) => Self::ImageDesc(image_desc), GraphResourceDesc::Buffer(buffer_desc) => Self::BufferDesc(buffer_desc), } } } #[derive(Default, Debug, PartialEq, Eq)] pub enum GraphResource { Framebuffer(Arc), ImportedImage(Arc), ImportedBuffer(Arc), Image(Arc), Buffer(Buffer), ImageDesc(ImageDesc), BufferDesc(BufferDesc), #[default] Default, } impl GraphResource { fn simple_hash(&self) -> u64 { use std::hash::{Hash, Hasher}; let mut state = std::hash::DefaultHasher::new(); let discr = core::mem::discriminant(self); discr.hash(&mut state); match self { GraphResource::Framebuffer(swapchain_frame) => { (swapchain_frame.handle()).hash(&mut state) } GraphResource::ImportedImage(image) => image.handle().hash(&mut state), GraphResource::ImportedBuffer(buffer) => buffer.handle().hash(&mut state), GraphResource::Image(image) => image.handle().hash(&mut state), GraphResource::Buffer(buffer) => buffer.handle().hash(&mut state), GraphResource::ImageDesc(image_desc) => image_desc.hash(&mut state), GraphResource::BufferDesc(buffer_desc) => buffer_desc.hash(&mut state), GraphResource::Default => {} } state.finish() } } #[derive(Debug, Clone, Copy)] pub enum LoadOp { Clear(Rgba), Load, DontCare, } #[derive(Debug, Clone, Copy)] pub enum StoreOp { DontCare, Store, } pub struct RenderContext<'a> { pub device: device::Device, pub cmd: commands::SingleUseCommand, pub resources: &'a [GraphResource], pub framebuffer: Option, } impl RenderContext<'_> { pub fn get_image(&self, id: GraphResourceId) -> Option<&Arc> { self.resources.get(id.0 as usize).and_then(|res| match res { GraphResource::ImportedImage(arc) => Some(arc), GraphResource::Image(image) => Some(image), GraphResource::Framebuffer(fb) => Some(fb), _ => None, }) } pub fn get_buffer(&self, id: GraphResourceId) -> Option<&Buffer> { self.resources.get(id.0 as usize).and_then(|res| match res { GraphResource::ImportedBuffer(arc) => Some(arc.as_ref()), GraphResource::Buffer(buffer) => Some(buffer), _ => None, }) } pub fn get_framebuffer(&self) -> Option<&Image> { self.framebuffer .and_then(|rid| self.resources.get(rid.0 as usize)) .and_then(|res| match res { GraphResource::Framebuffer(arc) => Some(arc.as_ref()), _ => None, }) } } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct Access { pub stage: vk::PipelineStageFlags2, pub mask: vk::AccessFlags2, pub layout: vk::ImageLayout, } impl core::ops::BitOr for Access { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { //assert_eq!(self.layout, rhs.layout); Self { stage: self.stage | rhs.stage, mask: self.mask | rhs.mask, layout: self.layout.max(rhs.layout), } } } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct AccessMask { pub stage: vk::PipelineStageFlags2, pub mask: vk::AccessFlags2, } impl AccessMask { pub fn empty() -> Self { Self { stage: vk::PipelineStageFlags2::NONE, mask: vk::AccessFlags2::empty(), } } pub fn is_empty(&self) -> bool { self.stage.is_empty() && self.mask.is_empty() } } impl core::ops::BitOr for AccessMask { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { Self { stage: self.stage | rhs.stage, mask: self.mask | rhs.mask, } } } impl core::ops::Not for AccessMask { type Output = Self; fn not(self) -> Self::Output { Self { stage: !self.stage, mask: !self.mask, } } } impl core::ops::BitAnd for AccessMask { type Output = Self; fn bitand(self, rhs: Self) -> Self::Output { Self { stage: self.stage & rhs.stage, mask: self.mask & rhs.mask, } } } impl core::ops::BitXor for AccessMask { type Output = Self; fn bitxor(self, rhs: Self) -> Self::Output { Self { stage: self.stage ^ rhs.stage, mask: self.mask ^ rhs.mask, } } } impl Access { pub fn into_access_mask(&self) -> AccessMask { AccessMask { stage: self.stage, mask: self.mask, } } pub fn empty() -> Self { Self { stage: vk::PipelineStageFlags2::NONE, mask: vk::AccessFlags2::empty(), layout: vk::ImageLayout::UNDEFINED, } } pub fn undefined() -> Self { Self { stage: vk::PipelineStageFlags2::NONE, mask: vk::AccessFlags2::empty(), layout: vk::ImageLayout::UNDEFINED, } } /// Only use this for `Ord`! pub fn min() -> Self { Self { stage: vk::PipelineStageFlags2::from_raw(u64::MAX), mask: vk::AccessFlags2::from_raw(u64::MAX), layout: vk::ImageLayout::UNDEFINED, } } /// Only use this for `Ord`! pub fn max() -> Self { Self { stage: vk::PipelineStageFlags2::from_raw(u64::MAX), mask: vk::AccessFlags2::from_raw(u64::MAX), layout: vk::ImageLayout::from_raw(i32::MAX), } } pub fn general() -> Self { Self { stage: vk::PipelineStageFlags2::NONE, mask: vk::AccessFlags2::empty(), layout: vk::ImageLayout::GENERAL, } } pub fn transfer_read() -> Self { Self { stage: vk::PipelineStageFlags2::TRANSFER, mask: vk::AccessFlags2::TRANSFER_READ, layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL, } } pub fn transfer_write() -> Self { Self { stage: vk::PipelineStageFlags2::TRANSFER, mask: vk::AccessFlags2::TRANSFER_WRITE, layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, } } pub fn vertex_read() -> Self { Self { stage: vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT, mask: vk::AccessFlags2::VERTEX_ATTRIBUTE_READ, layout: vk::ImageLayout::UNDEFINED, } } pub fn index_read() -> Self { Self { stage: vk::PipelineStageFlags2::INDEX_INPUT, mask: vk::AccessFlags2::INDEX_READ, layout: vk::ImageLayout::UNDEFINED, } } pub fn indirect_read() -> Self { Self { stage: vk::PipelineStageFlags2::DRAW_INDIRECT, mask: vk::AccessFlags2::INDIRECT_COMMAND_READ, layout: vk::ImageLayout::UNDEFINED, } } pub fn color_attachment_read_only() -> Self { Self { stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, mask: vk::AccessFlags2::COLOR_ATTACHMENT_READ, layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, } } pub fn color_attachment_write_only() -> Self { Self { stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE, layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, } } pub fn color_attachment_read_write() -> Self { Self { stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE | vk::AccessFlags2::COLOR_ATTACHMENT_READ, layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, } } pub fn present() -> Self { Self { stage: vk::PipelineStageFlags2::NONE, mask: vk::AccessFlags2::empty(), layout: vk::ImageLayout::PRESENT_SRC_KHR, } } } pub type RecordFn = dyn FnOnce(&RenderContext) -> crate::Result<()> + Send; pub struct PassDesc { // this pass performs `Access` read on `GraphResourceId`. // some `GraphResourceId` may occur multiple times. pub reads: Vec<(GraphResourceId, Access)>, // this pass performs `Access` write on `GraphResourceId`. // some `GraphResourceId` may occur multiple times. pub writes: Vec<(GraphResourceId, Access)>, pub record: Option>, } impl Default for PassDesc { fn default() -> Self { Self { reads: Default::default(), writes: Default::default(), record: None, } } } impl Debug for PassDesc { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PassDesc") .field("reads", &self.reads) .field("write", &self.writes) .finish_non_exhaustive() } } def_monotonic_id!(pub RenderGraphPassId); // Non-imported resources remain `RenderGraphResourceDesc`s because they may be // able to be aliased. // This should be dual to liveness/register allocation in a compiler. // Dummy-impl is just allocating every resource_desc itself. 5head-impl is trying // to find resource_descs which are eq, but whose liveness doesn't overlap. #[derive(Debug)] pub struct RenderGraph { resources: Vec, pass_descs: Vec, /// the rendergraph produces these resources. Any passes on which these /// outputs do not depend are pruned. outputs: BTreeMap, pub(crate) framebuffer: Option, } impl RenderGraph { pub fn new() -> Self { let mut pass_descs = Vec::new(); pass_descs.push(PassDesc::default()); Self { resources: Vec::new(), pass_descs, outputs: BTreeMap::new(), framebuffer: None, } } pub fn get_framebuffer(&self) -> Option { self.framebuffer } fn get_next_resource_id(&mut self) -> GraphResourceId { GraphResourceId(self.resources.len() as u32) } fn input_pass_mut(&mut self) -> &mut PassDesc { &mut self.pass_descs[0] } pub fn add_resource(&mut self, desc: GraphResourceDesc) -> GraphResourceId { let id = self.get_next_resource_id(); self.resources.push(desc.into()); id } pub fn mark_as_output(&mut self, id: GraphResourceId, access: Access) { _ = self.outputs.try_insert(id, access); } pub fn import_resource(&mut self, res: GraphResource, access: Access) -> GraphResourceId { if let Some(i) = self .resources .iter() .position(|other| res.simple_hash() == other.simple_hash()) { GraphResourceId(i as u32) } else { let id = self.get_next_resource_id(); self.resources.push(res); self.input_pass_mut().writes.push((id, access)); id } } pub fn import_image(&mut self, image: Arc, access: Access) -> GraphResourceId { let res = GraphResource::ImportedImage(image); self.import_resource(res, access) } pub fn import_buffer(&mut self, buffer: Arc, access: Access) -> GraphResourceId { let res = GraphResource::ImportedBuffer(buffer); self.import_resource(res, access) } pub fn import_framebuffer(&mut self, frame: Arc) -> GraphResourceId { let rid = self.import_resource( GraphResource::Framebuffer(frame.clone()), Access::undefined(), ); self.mark_as_output(rid, Access::present()); self.framebuffer = Some(rid); rid } pub fn add_pass(&mut self, pass: PassDesc) { self.pass_descs.push(pass); } // https://blog.traverseresearch.nl/render-graph-101-f42646255636 // https://github.com/EmbarkStudios/kajiya/blob/main/crates/lib/kajiya-rg/src/graph.rs // https://themaister.net/blog/2017/08/15/render-graphs-and-vulkan-a-deep-dive/ pub fn resolve( &mut self, device: device::Device, ) -> crate::Result>> { let output_reads = self .outputs .iter() .map(|(rid, access)| (*rid, *access)) .collect::>(); self.add_pass(PassDesc { reads: output_reads, writes: vec![], ..Default::default() }); let topo = util::timed("Resolving Render Graph", || { let mut refmap = graph_resolver::NodeRefsMap::new(self.resources.len(), self.pass_descs.len()); refmap.allocate_ref_ranges(&self.pass_descs); refmap.ref_passes(&self.pass_descs); let dag = refmap.build_dag(); let topo = refmap.toposort_dag(dag); topo }); // create internal resources: util::timed("Create internal RenderGraph resources:", || { for (i, res) in self.resources.iter_mut().enumerate() { match res { GraphResource::ImageDesc(image_desc) => { tracing::trace!("creating resource #{i:?} with {image_desc:?}"); *res = GraphResource::Image(Arc::new(Image::new( device.clone(), image_desc.clone(), )?)); } GraphResource::BufferDesc(buffer_desc) => { tracing::trace!("creating resource #{i:?} with {buffer_desc:?}"); *res = GraphResource::Buffer(Buffer::new( device.clone(), buffer_desc.clone(), )?); } _ => {} } } ash::prelude::VkResult::Ok(()) })?; let pool = commands::SingleUseCommandPool::new(device.clone(), device.graphics_queue().clone())?; let resources = &self.resources; let cmds = topo .iter() .rev() .map(|(passes, accesses)| { let passes = passes .into_iter() .map(|i| core::mem::take(&mut self.pass_descs[i.index()])) .collect::>(); (passes, accesses) }) .map({ |(passes, accesses)| { let cmd = pool.alloc()?; // transitions for (&id, &(from, to)) in accesses.iter() { Self::transition_resource( &resources[id.0 as usize], device.dev(), unsafe { &cmd.buffer() }, from, to, ); } let ctx = RenderContext { device: device.clone(), cmd, resources, framebuffer: self.framebuffer, }; for pass in passes { if let Some(record) = pass.record { (record)(&ctx)?; } } ctx.cmd.end()?; crate::Result::Ok(ctx.cmd) } }) .collect::>>()?; let cmd_list = commands::CommandList(cmds); Ok(WithLifetime::new(cmd_list)) } pub fn get_outputs(&mut self) -> BTreeMap { let outputs = self .outputs .iter() .map(|(id, _)| (*id, core::mem::take(&mut self.resources[id.0 as usize]))) .collect::>(); outputs } pub fn transition_resource( res: &GraphResource, dev: &ash::Device, cmd: &vk::CommandBuffer, from: Access, to: Access, ) { let barrier: Barrier = match res { GraphResource::Framebuffer(arc) => { image_barrier(arc.handle(), arc.format(), from, to, None).into() } GraphResource::ImportedImage(arc) => { image_barrier(arc.handle(), arc.format(), from, to, None).into() } GraphResource::ImportedBuffer(arc) => { buffer_barrier(arc.handle(), 0, arc.len(), from, to, None).into() } GraphResource::Image(image) => { image_barrier(image.handle(), image.format(), from, to, None).into() } GraphResource::Buffer(buffer) => { buffer_barrier(buffer.handle(), 0, buffer.len(), from, to, None).into() } _ => { unreachable!() } }; unsafe { dev.cmd_pipeline_barrier2(*cmd, &((&barrier).into())); } } fn transition_resource_to( accesses: &mut BTreeMap, resources: &BTreeMap, dev: &ash::Device, cmd: &vk::CommandBuffer, id: GraphResourceId, to: Access, ) { let old_access = accesses.get(&id); let res = resources.get(&id); if let (Some(&old_access), Some(res)) = (old_access, res) { Self::transition_resource(res, dev, cmd, old_access, to); accesses.insert(id, to); } } } mod graph_resolver { use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Display; use ash::vk; use petgraph::visit::EdgeRef; use crate::render_graph::*; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct PassNode(u16); impl PassNode { pub fn index(&self) -> usize { self.0 as usize } pub fn as_u32(&self) -> u32 { self.0 as u32 } pub fn pass(i: usize) -> Self { Self(i as u16) } } #[derive(Debug, Clone, Copy)] pub enum Barrier { Logical, Execution { src: vk::PipelineStageFlags2, dst: vk::PipelineStageFlags2, }, LayoutTransition { src: (vk::PipelineStageFlags2, vk::ImageLayout), dst: (vk::PipelineStageFlags2, vk::ImageLayout), }, MakeAvailable { src: (vk::PipelineStageFlags2, vk::AccessFlags2), dst: vk::PipelineStageFlags2, }, MakeVisible { src: vk::PipelineStageFlags2, dst: (vk::PipelineStageFlags2, vk::AccessFlags2), }, MemoryBarrier { src: (vk::PipelineStageFlags2, vk::AccessFlags2), dst: (vk::PipelineStageFlags2, vk::AccessFlags2), }, } impl Display for Barrier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Barrier::Logical => write!(f, "Logical"), Barrier::Execution { .. } => write!(f, "Execution"), Barrier::LayoutTransition { .. } => write!(f, "Layout"), Barrier::MakeAvailable { .. } => write!(f, "MakeAvailable"), Barrier::MakeVisible { .. } => write!(f, "MakeVisible"), Barrier::MemoryBarrier { .. } => write!(f, "MemoryBarrier"), } } } pub struct NodeRefsMap { num_resources: usize, num_passes: usize, // bitmap of passes referencing rid references: Vec, // range into ref_accesses*: start, end, index ref_ranges: Vec<(u32, u32, u32)>, ref_accesses: Vec<(Option, Option)>, ref_access_passid: Vec, } impl NodeRefsMap { pub fn new(num_resources: usize, num_passes: usize) -> Self { Self { num_resources, num_passes, references: vec![0; ((num_passes) * num_resources).div_ceil(64) as usize], ref_ranges: Vec::new(), ref_accesses: Vec::new(), ref_access_passid: Vec::new(), } } pub fn allocate_ref_ranges(&mut self, passes: &[PassDesc]) { let mut rid_passcount = vec![0; self.num_resources]; for pass in passes.iter() { for rid in pass .reads .iter() .chain(pass.writes.iter()) .map(|id| id.0) .collect::>() { rid_passcount[rid.0 as usize] += 1; } } let mut total = 0; for num_passes in rid_passcount { self.ref_ranges.push((total, total + num_passes, 0)); self.ref_accesses .extend((0..num_passes).map(|_| (None, None))); self.ref_access_passid .extend((0..num_passes).map(|_| PassNode(0))); total += num_passes; } } fn get_accesses_for_rid_pass_mut( &mut self, rid: GraphResourceId, pass: PassNode, ) -> &mut (Option, Option) { let (start, _, i) = self.ref_ranges[rid.0 as usize]; let idx = self.ref_access_passid[start as usize..(start + i) as usize] .binary_search(&pass) .unwrap_or_else(|_| { // increase counter self.ref_ranges[rid.0 as usize].2 += 1; i as usize }) + start as usize; self.ref_access_passid[idx] = pass; &mut self.ref_accesses[idx] } fn get_reads_for_rid_pass_mut( &mut self, rid: GraphResourceId, pass: PassNode, ) -> &mut Option { &mut self.get_accesses_for_rid_pass_mut(rid, pass).0 } fn get_writes_for_rid_pass_mut( &mut self, rid: GraphResourceId, pass: PassNode, ) -> &mut Option { &mut self.get_accesses_for_rid_pass_mut(rid, pass).1 } fn get_accesses_for_rid_pass( &self, rid: GraphResourceId, pass: PassNode, ) -> (Option, Option) { let (start, _, i) = self.ref_ranges[rid.0 as usize]; let Some(idx) = self.ref_access_passid[start as usize..(start + i) as usize] .binary_search(&pass) .ok() else { return (None, None); }; let idx = idx + start as usize; self.ref_accesses[idx] } fn get_reads_for_rid_pass(&self, rid: GraphResourceId, pass: PassNode) -> Option { self.get_accesses_for_rid_pass(rid, pass).0 } fn get_writes_for_rid_pass(&self, rid: GraphResourceId, pass: PassNode) -> Option { self.get_accesses_for_rid_pass(rid, pass).1 } fn reference_rid_pass(&mut self, rid: GraphResourceId, pass: PassNode) { let bit_idx = rid.0 as usize * (self.num_passes) + pass.index(); let word_idx = bit_idx / 64; let word_offset = bit_idx % 64; tracing::trace!( bit_idx, word_idx, word_offset, "pass: {pass:?} references rid: {rid:?} " ); self.references[word_idx] |= 1 << word_offset; } pub fn ref_passes(&mut self, passes: &[PassDesc]) { for (i, pass) in passes.iter().enumerate() { let pass_id = PassNode::pass(i); for &(rid, access) in &pass.reads { let read = self .get_reads_for_rid_pass_mut(rid, pass_id) .get_or_insert(Access::empty()); *read = *read | access; // TODO: check for first pass as well self.reference_rid_pass(rid, PassNode::pass(i)); } for &(rid, access) in &pass.writes { let write = self .get_writes_for_rid_pass_mut(rid, pass_id) .get_or_insert(Access::empty()); *write = *write | access; // TODO: check for first pass as well self.reference_rid_pass(rid, PassNode::pass(i)); } } } pub fn build_dag( &self, ) -> petgraph::stable_graph::StableDiGraph { struct Edge { from: PassNode, to: PassNode, rid: GraphResourceId, barrier: Barrier, } #[derive(Debug, Clone, Copy)] enum Ref { Write(PassNode, Access), Read(PassNode, Access), } impl Ref { fn node(&self) -> PassNode { match self { Ref::Write(node, _) | Ref::Read(node, _) => *node, } } fn access(&self) -> Access { match self { Ref::Write(_, access) | Ref::Read(_, access) => *access, } } } let mut edges = Vec::::new(); let bits = crate::util::BitIter::new(&self.references, self.num_resources * (self.num_passes)) .chunks(self.num_passes); tracing::trace!("building edges."); for (i, bits) in bits.enumerate() { let rid = GraphResourceId(i as u32); let mut to_make_available = AccessMask::empty(); let mut made_available = AccessMask::empty(); let mut last_ref = Option::::None; let mut last_write = Option::::None; for pass in bits { let pass = PassNode::pass(pass); if let Some(last_ref) = last_ref.as_ref() { edges.push(Edge { from: last_ref.node(), to: pass, rid, barrier: Barrier::Logical, }); } let read = self.get_reads_for_rid_pass(rid, pass); if let Some(read) = read { tracing::trace!("read: {:?}", read); let make_visible = read.into_access_mask() & !made_available; if let Some(last_write) = last_write.as_ref() { let from = last_write.node(); let to = pass; let from_write = last_write.access(); // if last_write is some, make visible the writes if !make_visible.is_empty() && !from_write.stage.is_empty() { made_available = made_available | make_visible; edges.push(Edge { from, to, rid, barrier: Barrier::MakeVisible { src: from_write.stage, dst: (make_visible.stage, make_visible.mask), }, }); } // make available any changes if !to_make_available.is_empty() { edges.push(Edge { from, to, rid, barrier: Barrier::MakeAvailable { src: (to_make_available.stage, to_make_available.mask), dst: read.stage, }, }); to_make_available = AccessMask::empty(); } if make_visible.is_empty() && !to_make_available.is_empty() { // still require a-after-b edges.push(Edge { from, to, rid, barrier: Barrier::Execution { src: from_write.stage, dst: read.stage, }, }); } } // layout transition from previous pass, either read or write if let Some(last_ref) = last_ref.as_ref() { if last_ref.access().layout != read.layout { let from = last_ref.node(); let to = pass; edges.push(Edge { from, to, rid, barrier: Barrier::LayoutTransition { src: (last_ref.access().stage, last_ref.access().layout), dst: (read.stage, read.layout), }, }); } } } let write = self.get_writes_for_rid_pass(rid, pass); if let Some(write) = write { tracing::trace!("write: {:?}", write); if let Some(last) = last_ref.as_ref() && last.access().layout != write.layout { let before = last.access(); edges.push(Edge { from: last.node(), to: pass, rid, barrier: Barrier::LayoutTransition { src: (before.stage, before.layout), dst: (write.stage, write.layout), }, }); } match last_ref.as_ref() { Some(Ref::Read(node, before)) => { // execution barrier to ward against write-after-read edges.push(Edge { from: *node, to: pass, rid, barrier: Barrier::Execution { src: before.stage, dst: write.stage, }, }); } _ => {} } } if let Some(read) = read { last_ref = Some(Ref::Read(pass, read)); } if let Some(write) = write { last_write = Some(Ref::Write(pass, write)); last_ref = last_write; } } } let mut dag = petgraph::stable_graph::StableDiGraph::::new( ); for i in 0..self.num_passes { dag.add_node(PassNode::pass(i)); } // insert edges for edge in edges { let Edge { from, to, rid, barrier, } = edge; dag.add_edge(from.as_u32().into(), to.as_u32().into(), (rid, barrier)); } // prune dead ends let mut sinks = dag .externals(petgraph::Direction::Outgoing) .filter(|idx| dag.node_weight(*idx) != Some(&PassNode::pass(self.num_passes - 1))) .collect::>(); while let Some(sink) = sinks.pop() { let mut neighbors = dag .neighbors_directed(sink, petgraph::Direction::Incoming) .detach(); while let Some((edge, node)) = neighbors.next(&dag) { dag.remove_edge(edge); if dag .neighbors_directed(node, petgraph::Direction::Outgoing) .count() == 0 { sinks.push(node); } } dag.remove_node(sink); } #[cfg(any(debug_assertions, test))] std::fs::write( "render_graph2.dot", &format!( "{:?}", petgraph::dot::Dot::with_attr_getters( &dag, &[], &|_graph, edgeref| { format!( "label = \"{},{:#?}\"", edgeref.weight().0, edgeref.weight().1, ) }, &|_graph, noderef| { format!( "label = \"Pass({:?})\"", petgraph::visit::NodeRef::weight(&noderef) ) } ) ), ) .expect("writing render_graph repr"); dag } pub fn toposort_dag( &self, mut dag: petgraph::stable_graph::StableDiGraph, ) -> Vec<(Vec, BTreeMap)> { let mut topomap = Vec::new(); let mut sinks = dag .externals(petgraph::Direction::Outgoing) .collect::>(); let mut next_sinks = BTreeSet::new(); loop { tracing::trace!("sinks: {sinks:?}, next_sinks: {next_sinks:?}"); if sinks.is_empty() { break; } let mut passes = Vec::with_capacity(self.num_passes); let mut barriers = BTreeMap::new(); for &sink in sinks.iter() { for &(rid, barrier) in dag .edges_directed(sink, petgraph::Direction::Incoming) .map(|edge| edge.weight()) { let before_and_after = match barrier { Barrier::Logical => None, Barrier::Execution { src, dst } => Some(( Access { stage: src, ..Access::empty() }, Access { stage: dst, ..Access::empty() }, )), Barrier::LayoutTransition { src: (src, from), dst: (dst, to), } => Some(( Access { stage: src, layout: from, ..Access::empty() }, Access { stage: dst, layout: to, ..Access::empty() }, )), Barrier::MakeAvailable { src: (stage, mask), dst, } => Some(( Access { stage, mask, ..Access::empty() }, Access { stage: dst, ..Access::empty() }, )), Barrier::MakeVisible { src, dst: (stage, mask), } => Some(( Access { stage: src, ..Access::empty() }, Access { stage, mask, ..Access::empty() }, )), Barrier::MemoryBarrier { src: (src_stage, src_mask), dst: (dst_stage, dst_mask), } => Some(( Access { stage: src_stage, mask: src_mask, ..Access::empty() }, Access { stage: dst_stage, mask: dst_mask, ..Access::empty() }, )), }; if let Some((before, after)) = before_and_after { // initial access is transitioned at the beginning // this affects imported resources only. barriers .entry(rid) .and_modify(|(from, to)| { *from = *from | before; *to = *to | after; }) .or_insert((before, after)); } } dag.edges_directed(sink, petgraph::Direction::Incoming) .for_each(|edge| { let node = edge.source(); if dag .neighbors_directed(node, petgraph::Direction::Outgoing) .all(|node| node == sink) { next_sinks.insert(node); } }); passes.push(*dag.node_weight(sink).unwrap()); dag.remove_node(sink); } topomap.push((passes, barriers)); sinks.clear(); core::mem::swap(&mut sinks, &mut next_sinks); } topomap } } } pub enum Barrier { Image(vk::ImageMemoryBarrier2<'static>), Buffer(vk::BufferMemoryBarrier2<'static>), } impl<'a> From<&'a Barrier> for vk::DependencyInfo<'a> { fn from(value: &'a Barrier) -> Self { let info = vk::DependencyInfo::default(); let info = match value { Barrier::Image(barrier) => info.image_memory_barriers(core::slice::from_ref(barrier)), Barrier::Buffer(barrier) => info.buffer_memory_barriers(core::slice::from_ref(barrier)), }; info } } impl From> for Barrier { fn from(value: vk::ImageMemoryBarrier2<'static>) -> Self { Self::Image(value) } } impl From> for Barrier { fn from(value: vk::BufferMemoryBarrier2<'static>) -> Self { Self::Buffer(value) } } pub fn buffer_barrier( buffer: vk::Buffer, offset: u64, size: u64, before: Access, after: Access, queue_families: Option<(u32, u32)>, ) -> vk::BufferMemoryBarrier2<'static> { vk::BufferMemoryBarrier2::default() .buffer(buffer) .offset(offset) .size(size) .src_access_mask(before.mask) .src_stage_mask(before.stage) .dst_access_mask(after.mask) .dst_stage_mask(after.stage) .src_queue_family_index( queue_families .map(|(src, _)| src) .unwrap_or(vk::QUEUE_FAMILY_IGNORED), ) .dst_queue_family_index( queue_families .map(|(_, dst)| dst) .unwrap_or(vk::QUEUE_FAMILY_IGNORED), ) } pub fn image_barrier( image: vk::Image, format: vk::Format, before_access: Access, after_access: Access, queue_families: Option<(u32, u32)>, ) -> vk::ImageMemoryBarrier2<'static> { vk::ImageMemoryBarrier2::default() .src_access_mask(before_access.mask) .src_stage_mask(before_access.stage) .dst_access_mask(after_access.mask) .dst_stage_mask(after_access.stage) .image(image) .old_layout(before_access.layout) .new_layout(after_access.layout) .subresource_range(vk::ImageSubresourceRange { aspect_mask: util::image_aspect_from_format(format), ..images::SUBRESOURCERANGE_ALL }) .src_queue_family_index( queue_families .map(|(src, _)| src) .unwrap_or(vk::QUEUE_FAMILY_IGNORED), ) .dst_queue_family_index( queue_families .map(|(_, dst)| dst) .unwrap_or(vk::QUEUE_FAMILY_IGNORED), ) } pub fn clear_pass(rg: &mut RenderGraph, color: Rgba, target: GraphResourceId) { let reads = [(target, Access::transfer_write())].to_vec(); let writes = [(target, Access::transfer_write())].to_vec(); let record: Box = Box::new({ move |ctx| { let target = ctx.get_image(target).unwrap(); let cmd = &ctx.cmd; cmd.clear_color_image( target.handle(), target.format(), vk::ImageLayout::TRANSFER_DST_OPTIMAL, color, &[images::SUBRESOURCERANGE_COLOR_ALL], ); Ok(()) } }); rg.add_pass(PassDesc { reads, writes, record: Some(record), }); } #[deprecated = "mark target as output with Access::present()."] pub fn present_pass(rg: &mut RenderGraph, target: GraphResourceId) { let reads = vec![(target, Access::present())]; let writes = vec![(target, Access::present())]; rg.add_pass(PassDesc { reads, writes, record: None, }); }