From 393cfbbb639ad1acc0f264978f5f86a3fb310d5b Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 5 Jan 2025 01:16:30 +0100 Subject: [PATCH] even more rendergraph rewriting, attempt 2 --- crates/renderer/src/render_graph.rs | 1172 +++++++++++---------------- 1 file changed, 479 insertions(+), 693 deletions(-) diff --git a/crates/renderer/src/render_graph.rs b/crates/renderer/src/render_graph.rs index 4d7efe0..fbad2a5 100644 --- a/crates/renderer/src/render_graph.rs +++ b/crates/renderer/src/render_graph.rs @@ -1,35 +1,41 @@ #![allow(dead_code)] -use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, + sync::Arc, +}; use crate::{ buffers::{Buffer, BufferDesc}, commands, def_monotonic_id, device::{self, DeviceOwned}, - images::{self, Image, ImageDesc, SUBRESOURCERANGE_COLOR_ALL}, - texture, + images::{self, Image, ImageDesc}, + sync, util::{self, Rgba}, - EguiState, SwapchainFrame, + SwapchainFrame, }; -use ash::{prelude::VkResult, vk}; -use egui::Color32; +use ash::vk; use itertools::Itertools; -use petgraph::visit::NodeRef; +use petgraph::{ + graph::NodeIndex, + visit::{EdgeRef, IntoNodeReferences, NodeRef}, +}; -def_monotonic_id!(pub RenderGraphResourceId); +def_monotonic_id!(pub GraphResourceId); #[derive(Debug, Clone)] -pub enum RenderGraphResourceDesc { +pub enum GraphResourceDesc { Image(ImageDesc), Buffer(BufferDesc), } -#[derive(Debug)] -pub enum RenderGraphResource { +#[derive(Debug, PartialEq, Eq)] +pub enum GraphResource { Framebuffer(Arc), ImportedImage(Arc), ImportedBuffer(Arc), - Image(Image), + Image(Arc), Buffer(Buffer), } @@ -46,490 +52,144 @@ pub enum StoreOp { Store, } -struct AttachmentInfo { - size: glam::UVec2, - format: vk::Format, - load: LoadOp, - store: StoreOp, +pub struct RenderContext<'a> { + pub device: device::Device, + pub cmd: commands::SingleUseCommand, + pub resources: &'a BTreeMap, } -pub struct RenderContext<'a> { - device: device::Device, - cmd: commands::SingleUseCommand, - resources: &'a BTreeMap, +impl RenderContext<'_> { + pub fn get_image(&self, id: GraphResourceId) -> Option<&Arc> { + self.resources.get(&id).and_then(|res| match res { + GraphResource::ImportedImage(arc) => Some(arc), + GraphResource::Image(image) => Some(image), + _ => None, + }) + } + pub fn get_buffer(&self, id: GraphResourceId) -> Option<&Buffer> { + self.resources.get(&id).and_then(|res| match res { + GraphResource::ImportedBuffer(arc) => Some(arc.as_ref()), + GraphResource::Buffer(buffer) => Some(buffer), + _ => None, + }) + } } #[derive(Debug, Clone, Copy, Default)] -pub struct ResourceAccess { - stage: vk::PipelineStageFlags2, - mask: vk::AccessFlags2, - layout: Option, +pub struct Access { + pub stage: vk::PipelineStageFlags2, + pub mask: vk::AccessFlags2, + pub layout: Option, } -impl ResourceAccess { - fn undefined() -> Self { +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, + } + } +} + +impl Access { + pub fn undefined() -> Self { Self { stage: vk::PipelineStageFlags2::NONE, mask: vk::AccessFlags2::empty(), layout: Some(vk::ImageLayout::UNDEFINED), } } + pub fn general() -> Self { + Self { + stage: vk::PipelineStageFlags2::NONE, + mask: vk::AccessFlags2::empty(), + layout: Some(vk::ImageLayout::GENERAL), + } + } + pub fn transfer_read() -> Self { + Self { + stage: vk::PipelineStageFlags2::TRANSFER, + mask: vk::AccessFlags2::TRANSFER_READ, + layout: Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL), + } + } + pub fn transfer_write() -> Self { + Self { + stage: vk::PipelineStageFlags2::TRANSFER, + mask: vk::AccessFlags2::TRANSFER_WRITE, + layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), + } + } + pub fn vertex_read() -> Self { + Self { + stage: vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT, + mask: vk::AccessFlags2::VERTEX_ATTRIBUTE_READ, + layout: None, + } + } + pub fn index_read() -> Self { + Self { + stage: vk::PipelineStageFlags2::INDEX_INPUT, + mask: vk::AccessFlags2::INDEX_READ, + layout: None, + } + } + pub fn indirect_read() -> Self { + Self { + stage: vk::PipelineStageFlags2::DRAW_INDIRECT, + mask: vk::AccessFlags2::INDIRECT_COMMAND_READ, + layout: None, + } + } + pub fn color_attachment_read_only() -> Self { + Self { + stage: vk::PipelineStageFlags2::FRAGMENT_SHADER, + mask: vk::AccessFlags2::COLOR_ATTACHMENT_READ, + layout: Some(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL), + } + } + pub fn color_attachment_write_only() -> Self { + Self { + stage: vk::PipelineStageFlags2::FRAGMENT_SHADER, + mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE, + layout: Some(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL), + } + } + pub fn color_attachment_read_write() -> Self { + Self { + stage: vk::PipelineStageFlags2::FRAGMENT_SHADER, + mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE + | vk::AccessFlags2::COLOR_ATTACHMENT_READ, + layout: Some(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL), + } + } } -pub struct EguiPrePass { - out_resources: - BTreeMap, - staging_image: Arc, - staging_buffer: Arc, - aliased_images: BTreeMap)>, - tessellated: Vec, - texture_data: BTreeMap, +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: Box, } -impl Debug for EguiPrePass { +impl Debug for PassDesc { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EguiPrePass") - .field("out_resources", &self.out_resources) + f.debug_struct("PassDesc") + .field("reads", &self.reads) + .field("write", &self.writes) .finish_non_exhaustive() } } -impl EguiPrePass { - pub fn new( - dev: &device::Device, - rg: &mut RenderGraph, - textures: &mut crate::texture::TextureManager, - egui_state: &mut EguiState, - egui: &egui::Context, - output: egui::FullOutput, - ) -> VkResult { - // calculate size for staging buffer. - // calculate size for staging image. - // allocate resource ids for textures in tessellated list (imported from texture manager) - // define accesses for resource ids - for (egui_id, delta) in output - .textures_delta - .set - .iter() - .filter(|(_, image)| image.is_whole()) - { - let image = Image::new( - dev.clone(), - ImageDesc { - name: Some(format!("egui-texture-{egui_id:?}").into()), - format: vk::Format::R8G8B8A8_UNORM, - extent: vk::Extent3D { - width: delta.image.width() as u32, - height: delta.image.height() as u32, - depth: 1, - }, - usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST, - mem_usage: vk_mem::MemoryUsage::AutoPreferDevice, - ..Default::default() - }, - )?; - - let tid = textures.insert_image(Arc::new(image)); - egui_state.textures.insert( - *egui_id, - crate::EguiTextureInfo { - id: tid, - options: delta.options, - }, - ); - } - - let (staging_size, image_size) = output.textures_delta.set.iter().fold( - (0usize, 0usize), - |(mut buffer, mut image), (_id, delta)| { - let bytes = - delta.image.height() * delta.image.width() * delta.image.bytes_per_pixel(); - if !delta.is_whole() { - image = image.max(bytes); - } - buffer = buffer + bytes; - - (buffer, image) - }, - ); - - let tessellated = egui.tessellate(output.shapes, output.pixels_per_point); - - let mut staging_buffer = Buffer::new( - dev.clone(), - BufferDesc { - name: Some("egui-prepass-staging-buffer".into()), - size: staging_size as u64, - usage: vk::BufferUsageFlags::TRANSFER_SRC, - queue_families: device::QueueFlags::empty(), - mem_usage: vk_mem::MemoryUsage::AutoPreferHost, - alloc_flags: vk_mem::AllocationCreateFlags::MAPPED - | vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE - | vk_mem::AllocationCreateFlags::STRATEGY_FIRST_FIT, - ..Default::default() - }, - )?; - let staging_image = Arc::new(Image::new( - dev.clone(), - ImageDesc { - name: Some("egui-prepass-staging-buffer".into()), - format: vk::Format::R8G8B8A8_UNORM, - extent: vk::Extent3D { - width: (image_size / 2) as u32, - height: (image_size - (image_size / 2)) as u32, - depth: 1, - }, - usage: vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::TRANSFER_DST, - queue_families: device::QueueFlags::empty(), - mem_usage: vk_mem::MemoryUsage::AutoPreferDevice, - ..Default::default() - }, - )?); - - let aliased_images = { - let mut staging_map = staging_buffer.map()?; - let mut offset = 0; - - let aliased_images = output - .textures_delta - .set - .iter() - .filter_map(|(id, delta)| { - let bytes = - delta.image.height() * delta.image.width() * delta.image.bytes_per_pixel(); - - let mem = &mut staging_map[offset..offset + bytes]; - match &delta.image { - egui::ImageData::Color(arc) => { - let slice = unsafe { - core::slice::from_raw_parts( - arc.pixels.as_ptr().cast::(), - arc.pixels.len() * size_of::(), - ) - }; - mem[..slice.len()].copy_from_slice(slice); - } - egui::ImageData::Font(font_image) => { - for (i, c) in font_image.srgba_pixels(None).enumerate() { - let bytes = c.to_array(); - mem[i * 4..(i + 1) * 4].copy_from_slice(&bytes); - } - } - } - - let old_offset = offset; - offset += bytes; - - if !delta.is_whole() { - unsafe { - let alias = staging_image - .clone() - .get_alias(ImageDesc { - name: Some( - format!("egui-prepass-staging-aliased-{id:?}").into(), - ), - format: vk::Format::R8G8B8A8_UNORM, - extent: vk::Extent3D { - width: delta.image.width() as u32, - height: delta.image.height() as u32, - depth: 1, - }, - usage: vk::ImageUsageFlags::TRANSFER_SRC - | vk::ImageUsageFlags::TRANSFER_DST, - queue_families: device::QueueFlags::empty(), - ..Default::default() - }) - .unwrap(); - Some((*id, (old_offset, bytes, delta.pos.unwrap(), alias))) - } - } else { - None - } - }) - .collect::>(); - - aliased_images - }; - - let out_resources = tessellated - .iter() - .filter_map(|prim| { - if let egui::epaint::Primitive::Mesh(ref mesh) = prim.primitive { - Some(mesh.texture_id) - } else { - None - } - }) - .dedup() - .filter_map(|egui_id| { - egui_state - .lookup_texture(egui_id) - .and_then(|tid| textures.get_texture(tid).map(|image| (egui_id, tid, image))) - }) - .map(|(egui_id, tid, image)| { - let rid = rg.import_image( - image, - ResourceAccess { - stage: vk::PipelineStageFlags2::NONE, - mask: vk::AccessFlags2::empty(), - layout: Some(vk::ImageLayout::GENERAL), - }, - ); - - ( - egui_id, - ( - rid, - tid, - ResourceAccess { - stage: vk::PipelineStageFlags2::TRANSFER, - mask: vk::AccessFlags2::TRANSFER_WRITE, - layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), - }, - ), - ) - }) - .collect::>(); - - Ok(Self { - aliased_images, - staging_buffer: Arc::new(staging_buffer), - staging_image, - out_resources, - tessellated, - texture_data: output - .textures_delta - .set - .into_iter() - .collect::>(), - }) - } -} - -impl Pass for EguiPrePass { - fn get_read_resource_access(&self, _id: RenderGraphResourceId) -> ResourceAccess { - unimplemented!() - } - - fn get_write_resource_access(&self, id: RenderGraphResourceId) -> ResourceAccess { - self.out_resources - .iter() - .find(|(_, (id2, _, _))| *id2 == id) - .map(|(_, (_, _, access))| *access) - .unwrap() - } - - fn get_queue_capability_requirements(&self) -> device::QueueFlags { - device::QueueFlags::TRANSFER - } - - fn get_read_dependencies<'a>(&'a self) -> Box + 'a> { - Box::new([].into_iter()) - } - - fn get_write_dependencies<'a>( - &'a self, - ) -> Box + 'a> { - Box::new(self.out_resources.values().map(|(id, _, _)| *id)) - } - - fn record(&self, ctx: &RenderContext) -> crate::Result<()> { - let buffer: Barrier = buffer_barrier( - self.staging_buffer.handle(), - 0, - self.staging_buffer.len(), - ResourceAccess { - stage: vk::PipelineStageFlags2::NONE, - mask: vk::AccessFlags2::empty(), - layout: None, - }, - ResourceAccess { - stage: vk::PipelineStageFlags2::TRANSFER, - mask: vk::AccessFlags2::TRANSFER_READ, - layout: None, - }, - None, - ) - .into(); - - unsafe { - ctx.device - .dev() - .cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&buffer).into())); - } - - for (id, (offset, _, pos, alias)) in &self.aliased_images { - let image: Barrier = image_barrier( - alias.handle(), - alias.format(), - ResourceAccess { - stage: vk::PipelineStageFlags2::NONE, - mask: vk::AccessFlags2::empty(), - layout: Some(vk::ImageLayout::UNDEFINED), - }, - ResourceAccess { - stage: vk::PipelineStageFlags2::TRANSFER, - mask: vk::AccessFlags2::TRANSFER_WRITE, - layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), - }, - None, - ) - .into(); - - unsafe { - ctx.device - .dev() - .cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&image).into())); - } - - ctx.cmd.copy_buffer_to_image( - self.staging_buffer.handle(), - alias.handle(), - vk::ImageLayout::TRANSFER_DST_OPTIMAL, - &[vk::BufferImageCopy { - buffer_offset: *offset as u64, - buffer_row_length: alias.width(), - buffer_image_height: alias.height(), - image_subresource: vk::ImageSubresourceLayers::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_array_layer(0) - .mip_level(0) - .layer_count(1), - image_offset: vk::Offset3D { x: 0, y: 0, z: 0 }, - image_extent: alias.size(), - }], - ); - - //self. - let Some(RenderGraphResource::ImportedImage(texture)) = self - .out_resources - .get(id) - .and_then(|(id, _, _)| ctx.resources.get(id)) - else { - continue; - }; - - let from_barrier = image_barrier( - alias.handle(), - alias.format(), - ResourceAccess { - stage: vk::PipelineStageFlags2::TRANSFER, - mask: vk::AccessFlags2::TRANSFER_WRITE, - layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), - }, - ResourceAccess { - stage: vk::PipelineStageFlags2::TRANSFER, - mask: vk::AccessFlags2::TRANSFER_READ, - layout: Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL), - }, - None, - ); - let to_barrier = image_barrier( - texture.handle(), - texture.format(), - ResourceAccess { - stage: vk::PipelineStageFlags2::NONE, - mask: vk::AccessFlags2::empty(), - layout: Some(vk::ImageLayout::GENERAL), - }, - ResourceAccess { - stage: vk::PipelineStageFlags2::TRANSFER, - mask: vk::AccessFlags2::TRANSFER_WRITE, - layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), - }, - None, - ); - - unsafe { - ctx.device.dev().cmd_pipeline_barrier2( - ctx.cmd.buffer(), - &vk::DependencyInfo::default() - .image_memory_barriers(&[from_barrier, to_barrier]), - ); - } - ctx.cmd.copy_images( - alias.handle(), - vk::ImageLayout::TRANSFER_SRC_OPTIMAL, - texture.handle(), - vk::ImageLayout::TRANSFER_DST_OPTIMAL, - &[vk::ImageCopy { - src_subresource: vk::ImageSubresourceLayers::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_array_layer(0) - .mip_level(0) - .layer_count(1), - src_offset: vk::Offset3D { x: 0, y: 0, z: 0 }, - dst_subresource: vk::ImageSubresourceLayers::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_array_layer(0) - .mip_level(0) - .layer_count(1), - dst_offset: vk::Offset3D { - x: pos[0] as i32, - y: pos[1] as i32, - z: 0, - }, - extent: alias.size(), - }], - ); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct EguiRenderPass { - descriptor_set: vk::DescriptorSet, - vertices: Buffer, - indices: Buffer, - draw_calls: Buffer, - texture_ids: Buffer, - textures_to_free: Vec, - num_draw_calls: usize, -} - -impl Pass for EguiRenderPass { - fn get_read_resource_access(&self, id: RenderGraphResourceId) -> ResourceAccess { - todo!() - } - - fn get_write_resource_access(&self, id: RenderGraphResourceId) -> ResourceAccess { - todo!() - } - - fn get_queue_capability_requirements(&self) -> device::QueueFlags { - todo!() - } - - fn get_read_dependencies<'a>(&'a self) -> Box + 'a> { - todo!() - } - - fn get_write_dependencies<'a>( - &'a self, - ) -> Box + 'a> { - todo!() - } - - fn record(&self, ctx: &RenderContext) -> crate::Result<()> { - todo!() - } -} - -pub trait Pass: Debug + Send { - fn get_read_resource_access(&self, id: RenderGraphResourceId) -> ResourceAccess; - fn get_write_resource_access(&self, id: RenderGraphResourceId) -> ResourceAccess; - /// mask of the queue capability requirements of this pass. - fn get_queue_capability_requirements(&self) -> device::QueueFlags; - /// returns an iterator over all (in) dependencies. - fn get_read_dependencies<'a>(&'a self) -> Box + 'a>; - fn get_write_dependencies<'a>(&'a self) - -> Box + 'a>; - - fn record(&self, ctx: &RenderContext) -> crate::Result<()>; -} - def_monotonic_id!(pub RenderGraphPassId); // Non-imported resources remain `RenderGraphResourceDesc`s because they may be @@ -539,13 +199,13 @@ def_monotonic_id!(pub RenderGraphPassId); // to find resource_descs which are eq, but whose liveness doesn't overlap. #[derive(Debug)] pub struct RenderGraph { - resource_descs: BTreeMap, - resources: BTreeMap, - accesses: BTreeMap, - passes: Vec>, + resource_descs: BTreeMap, + resources: BTreeMap, + accesses: BTreeMap, + pass_descs: Vec, /// the rendergraph produces these resources. Any passes on which these /// outputs do not depend are pruned. - outputs: Vec, + outputs: Vec, } impl RenderGraph { @@ -553,105 +213,155 @@ impl RenderGraph { Self { resource_descs: BTreeMap::new(), resources: BTreeMap::new(), - passes: Vec::new(), + pass_descs: Vec::new(), accesses: BTreeMap::new(), outputs: Vec::new(), } } - pub fn add_resource(&mut self, desc: RenderGraphResourceDesc) -> RenderGraphResourceId { - let id = RenderGraphResourceId::new(); + pub fn add_resource(&mut self, desc: GraphResourceDesc) -> GraphResourceId { + let id = GraphResourceId::new(); self.resource_descs.insert(id, desc); - self.accesses.insert(id, ResourceAccess::undefined()); + self.accesses.insert(id, Access::undefined()); id } - pub fn mark_as_output(&mut self, id: RenderGraphResourceId) { + pub fn mark_as_output(&mut self, id: GraphResourceId) { // TODO: dedup self.outputs.push(id); } - pub fn import_image( - &mut self, - image: Arc, - access: ResourceAccess, - ) -> RenderGraphResourceId { - let id = RenderGraphResourceId::new(); - self.resources - .insert(id, RenderGraphResource::ImportedImage(image)); - self.accesses.insert(id, access); + pub fn import_resource(&mut self, res: GraphResource, access: Access) -> GraphResourceId { + if let Some((&id, _)) = self + .resources + .iter() + .find(|(_, resident)| &&res == resident) + { + id + } else { + let id = GraphResourceId::new(); + self.resources.insert(id, res); + self.accesses.insert(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 id = GraphResourceId::new(); + self.resources.insert(id, GraphResource::Framebuffer(frame)); id } - pub fn import_buffer( - &mut self, - buffer: Arc, - access: ResourceAccess, - ) -> RenderGraphResourceId { - let id = RenderGraphResourceId::new(); - self.resources - .insert(id, RenderGraphResource::ImportedBuffer(buffer)); - self.accesses.insert(id, access); - id - } - pub fn import_framebuffer(&mut self, frame: Arc) -> RenderGraphResourceId { - let id = RenderGraphResourceId::new(); - self.resources - .insert(id, RenderGraphResource::Framebuffer(frame)); - id - } - pub fn add_pass(&mut self, pass: P) { - self.passes.push(Box::new(pass)); + 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<()> { + pub fn resolve( + &mut self, + device: device::Device, + ) -> crate::Result> { // create internal resources: for (&id, desc) in self.resource_descs.iter() { match desc.clone() { - RenderGraphResourceDesc::Image(image_desc) => { + GraphResourceDesc::Image(image_desc) => { self.resources.insert( id, - RenderGraphResource::Image(Image::new(device.clone(), image_desc)?), + GraphResource::Image(Arc::new(Image::new(device.clone(), image_desc)?)), ); } - RenderGraphResourceDesc::Buffer(buffer_desc) => { + GraphResourceDesc::Buffer(buffer_desc) => { self.resources.insert( id, - RenderGraphResource::Buffer(Buffer::new(device.clone(), buffer_desc)?), + GraphResource::Buffer(Buffer::new(device.clone(), buffer_desc)?), ); } } } eprintln!("{:#?}", &self); - let mut last_write = BTreeMap::new(); - let mut dag = petgraph::stable_graph::StableDiGraph::new(); + #[derive(Debug, Clone, Copy)] + enum PassNode { + First, + Pass(usize), + Last, + } + + let root = dag.add_node(PassNode::First); + let mut last_write = self + .resources + .keys() + .filter_map(|id| self.accesses.get(id).map(|access| (*id, (root, *access)))) + .collect::>(); + // insert edges between write->read edges of 2 passes - for (i, pass) in self.passes.iter().enumerate() { - let node = dag.add_node(i); - for dep in pass.get_read_dependencies() { - if let Some(&other) = last_write.get(&dep) { - dag.add_edge(other, node, dep); + for (i, pass) in self.pass_descs.iter().enumerate() { + let node = dag.add_node(PassNode::Pass(i)); + + let mut read_accesses = BTreeMap::new(); + for (rid, access) in &pass.reads { + read_accesses + .entry(*rid) + .and_modify(|a| { + // a single pass must not read one image twice with different layouts + *a = *a | *access; + }) + .or_insert(*access); + } + + for (rid, after) in read_accesses { + if let Some(&(other, before)) = last_write.get(&rid) { + dag.add_edge(other, node, (rid, (before, after))); } } - for dep in pass.get_write_dependencies() { - // keep track of which pass last wrote to a resource - // this is the node to build an edge from next time this resource is read - last_write.insert(dep, node); + let mut write_accesses = BTreeMap::new(); + for (rid, access) in &pass.writes { + write_accesses + .entry(*rid) + .and_modify(|a| { + // a single pass must not write one image twice with different layouts + *a = *a | *access; + }) + .or_insert(*access); + } + + for (rid, after) in write_accesses { + last_write.insert(rid, (node, after)); } } - // pseudo pass for trackingoutputs - let output = dag.add_node(!0); - for (id, node) in self + // pseudo pass for tracking outputs + let output = dag.add_node(PassNode::Last); + for (id, (node, access)) in self .outputs .iter() .filter_map(|&id| last_write.get(&id).cloned().map(|node| (id, node))) { - dag.add_edge(node, output, id); + dag.add_edge( + node, + output, + ( + id, + ( + access, + // make output writes available + Access { + stage: vk::PipelineStageFlags2::NONE, + mask: vk::AccessFlags2::empty(), + ..access + }, + ), + ), + ); } // prune dead nodes @@ -668,6 +378,63 @@ impl RenderGraph { } } + // handle layout additional transitions + let edges = dag + .node_references() + .map(|(source, _)| { + let mut per_resourcelayout_multimap: BTreeMap< + (GraphResourceId, Option), + Vec<(Access, NodeIndex)>, + > = BTreeMap::new(); + let mut resources = BTreeSet::new(); + + dag.edges_directed(source, petgraph::Direction::Outgoing) + .for_each(|edge| { + let (rid, (_, after)) = edge.weight(); + let target = edge.target(); + let key = (*rid, after.layout); + let item = (*after, target); + resources.insert(*rid); + + per_resourcelayout_multimap + .entry(key) + .and_modify(|list| list.push(item)) + .or_insert(vec![item]); + }); + + let mut edges = vec![]; + for resource in resources { + for (a, b) in per_resourcelayout_multimap + .range( + (resource, None) + ..=(resource, Some(vk::ImageLayout::from_raw(i32::MAX))), + ) + .tuple_windows() + { + let a = a.1; + let b = b.1; + + // create new edge between all members of (a) and (b). + // topological mapping will fold all transitions into one. + for i in 0..a.len().max(b.len()) { + let from = a.get(i).unwrap_or(a.last().unwrap()); + let to = b.get(i).unwrap_or(b.last().unwrap()); + + let edge = ((from.1, to.1), (resource, (from.0, to.0))); + edges.push(edge); + } + } + } + + edges + }) + .flatten() + .collect::>(); + + for ((from, to), edge) in edges { + dag.add_edge(from, to, edge); + } + #[cfg(any(debug_assertions, test))] std::fs::write( "render_graph.dot", @@ -683,52 +450,46 @@ impl RenderGraph { ) .expect("writing render_graph repr"); - struct RenderGraphStage { - passes: Vec>, - accesses: Vec<(RenderGraphResourceId, ResourceAccess)>, - } - let mut topological_map = Vec::new(); let mut top_dag = dag.clone(); + + // create topological map of DAG from sink to source loop { - let (sources, indices): (Vec<_>, Vec<_>) = top_dag - .externals(petgraph::Direction::Incoming) + let (sinks, passes): (Vec<_>, Vec<_>) = top_dag + .externals(petgraph::Direction::Outgoing) .filter(|&id| id != output) .filter_map(|id| top_dag.node_weight(id).cloned().map(|idx| (id, idx))) .unzip(); - if sources.is_empty() { + if sinks.is_empty() { break; } - for &source in &sources { - top_dag.remove_node(source); + let mut barriers = BTreeMap::new(); + + for &sink in &sinks { + top_dag + .edges_directed(sink, petgraph::Direction::Incoming) + .for_each(|edge| { + let (rid, (before, after)) = edge.weight(); + + barriers + .entry(*rid) + .and_modify(|(from, to)| { + *from = *from | *before; + *to = *to | *after; + }) + .or_insert((*before, *after)); + }); + top_dag.remove_node(sink); } - let mut accesses = BTreeMap::::new(); - - // TODO: distinguish between make-available and make-visible - for &idx in &indices { - let pass = &self.passes[idx]; - for id in pass.get_read_dependencies() { - let access = pass.get_read_resource_access(id); - accesses - .entry(id) - .and_modify(|entry| { - entry.stage = entry.stage.max(access.stage); - entry.mask |= access.mask; - // TODO: layout - }) - .or_insert(access); - } - } - - topological_map.push((indices, accesses)); + topological_map.push((passes, barriers)); } // I don't think this can currently happen with the way passes are added. top_dag.remove_node(output); - if dag.node_count() > 0 { + if top_dag.node_count() > 0 { panic!("dag is cyclic!"); } @@ -740,25 +501,32 @@ impl RenderGraph { let resources = &self.resources; let cmds = topological_map .iter() + .rev() .map(|(set, accesses)| { let pool = pool.clone(); let device = device.clone(); let passes = set .into_iter() - .map(|i| self.passes.remove(*i)) + .filter_map(|pass| { + if let &PassNode::Pass(i) = pass { + Some(i) + } else { + None + } + }) + .map(|i| self.pass_descs.remove(i)) .collect::>(); let cmd = pool.alloc()?; // transitions - for (&id, &access) in accesses.iter() { - Self::transition_resource_to( - &mut self.accesses, - resources, + for (&id, &(from, to)) in accesses.iter() { + Self::transition_resource( + resources.get(&id).unwrap(), device.dev(), unsafe { &cmd.buffer() }, - id, - access, + from, + to, ); } @@ -768,58 +536,76 @@ impl RenderGraph { resources, }; - for pass in &passes { - pass.record(&ctx)?; + for pass in passes { + (pass.record)(&ctx)?; } crate::Result::Ok(ctx.cmd) }) .collect::>>()?; - Ok(()) + let cmd_list = commands::CommandList(cmds); + let future = cmd_list.submit(None, None, Arc::new(sync::Fence::create(device.clone())?))?; + + future.block()?; + + let outputs = self + .outputs + .iter() + .filter_map(|id| self.resources.remove(id).map(|res| (*id, res))) + .collect::>(); + Ok(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.image, 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() + } + }; + + unsafe { + dev.cmd_pipeline_barrier2(*cmd, &((&barrier).into())); + } } fn transition_resource_to( - accesses: &mut BTreeMap, - resources: &BTreeMap, + accesses: &mut BTreeMap, + resources: &BTreeMap, dev: &ash::Device, cmd: &vk::CommandBuffer, - id: RenderGraphResourceId, - access: ResourceAccess, + 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) { - let barrier: Barrier = match res { - RenderGraphResource::Framebuffer(arc) => { - image_barrier(arc.image, arc.format, old_access, access, None).into() - } - RenderGraphResource::ImportedImage(arc) => { - image_barrier(arc.handle(), arc.format(), old_access, access, None).into() - } - RenderGraphResource::ImportedBuffer(arc) => { - buffer_barrier(arc.handle(), 0, arc.len(), old_access, access, None).into() - } - RenderGraphResource::Image(image) => { - image_barrier(image.handle(), image.format(), old_access, access, None).into() - } - RenderGraphResource::Buffer(buffer) => { - buffer_barrier(buffer.handle(), 0, buffer.len(), old_access, access, None) - .into() - } - }; - - accesses.insert(id, access); - - unsafe { - dev.cmd_pipeline_barrier2(*cmd, &((&barrier).into())); - } + Self::transition_resource(res, dev, cmd, old_access, to); + accesses.insert(id, to); } } } -enum Barrier { +pub enum Barrier { Image(vk::ImageMemoryBarrier2<'static>), Buffer(vk::BufferMemoryBarrier2<'static>), } @@ -851,8 +637,8 @@ pub fn buffer_barrier( buffer: vk::Buffer, offset: u64, size: u64, - before: ResourceAccess, - after: ResourceAccess, + before: Access, + after: Access, queue_families: Option<(u32, u32)>, ) -> vk::BufferMemoryBarrier2<'static> { vk::BufferMemoryBarrier2::default() @@ -878,8 +664,8 @@ pub fn buffer_barrier( pub fn image_barrier( image: vk::Image, format: vk::Format, - before_access: ResourceAccess, - after_access: ResourceAccess, + before_access: Access, + after_access: Access, queue_families: Option<(u32, u32)>, ) -> vk::ImageMemoryBarrier2<'static> { vk::ImageMemoryBarrier2::default() @@ -906,100 +692,100 @@ pub fn image_barrier( ) } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - macro_rules! def_dummy_pass { - ($name:ident: {$queue:path, $layout_in:path, $layout_out:path}) => { - #[derive(Debug, Clone)] - struct $name(Vec, Vec); - impl Pass for $name { - fn get_read_resource_access(&self, _id: RenderGraphResourceId) -> ResourceAccess { - ResourceAccess::default() - } - fn get_write_resource_access(&self, _id: RenderGraphResourceId) -> ResourceAccess { - ResourceAccess::default() - } +// macro_rules! def_dummy_pass { +// ($name:ident: {$queue:path, $layout_in:path, $layout_out:path}) => { +// #[derive(Debug, Clone)] +// struct $name(Vec, Vec); +// impl Pass for $name { +// fn get_read_resource_access(&self, _id: RenderGraphResourceId) -> ResourceAccess { +// ResourceAccess::default() +// } +// fn get_write_resource_access(&self, _id: RenderGraphResourceId) -> ResourceAccess { +// ResourceAccess::default() +// } - fn get_queue_capability_requirements(&self) -> device::QueueFlags { - $queue - } +// fn get_queue_capability_requirements(&self) -> device::QueueFlags { +// $queue +// } - fn get_read_dependencies<'a>( - &'a self, - ) -> Box + 'a> { - Box::new(self.0.iter().cloned()) - } +// fn get_read_dependencies<'a>( +// &'a self, +// ) -> Box + 'a> { +// Box::new(self.0.iter().cloned()) +// } - fn get_write_dependencies<'a>( - &'a self, - ) -> Box + 'a> { - Box::new(self.1.iter().cloned()) - } +// fn get_write_dependencies<'a>( +// &'a self, +// ) -> Box + 'a> { +// Box::new(self.1.iter().cloned()) +// } - fn record(&self, _ctx: &RenderContext) -> crate::Result<()> { - Ok(()) - } - } - }; - } +// fn record(&self, _ctx: &RenderContext) -> crate::Result<()> { +// Ok(()) +// } +// } +// }; +// } - def_dummy_pass!(DepthPass: { - device::QueueFlags::GRAPHICS, - vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL, - vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL}); - def_dummy_pass!(RenderPass: { - device::QueueFlags::GRAPHICS, - vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, - vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL}); - def_dummy_pass!(AsyncPass: { - device::QueueFlags::ASYNC_COMPUTE, - vk::ImageLayout::UNDEFINED, - vk::ImageLayout::GENERAL}); - def_dummy_pass!(PostProcessPass: { - device::QueueFlags::ASYNC_COMPUTE, - vk::ImageLayout::GENERAL, - vk::ImageLayout::GENERAL}); - def_dummy_pass!(PresentPass: { - device::QueueFlags::PRESENT, - vk::ImageLayout::PRESENT_SRC_KHR, - vk::ImageLayout::UNDEFINED}); - def_dummy_pass!(DepthVisualisationPass: { - device::QueueFlags::ASYNC_COMPUTE, - vk::ImageLayout::GENERAL, - vk::ImageLayout::UNDEFINED}); +// def_dummy_pass!(DepthPass: { +// device::QueueFlags::GRAPHICS, +// vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL, +// vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL}); +// def_dummy_pass!(RenderPass: { +// device::QueueFlags::GRAPHICS, +// vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, +// vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL}); +// def_dummy_pass!(AsyncPass: { +// device::QueueFlags::ASYNC_COMPUTE, +// vk::ImageLayout::UNDEFINED, +// vk::ImageLayout::GENERAL}); +// def_dummy_pass!(PostProcessPass: { +// device::QueueFlags::ASYNC_COMPUTE, +// vk::ImageLayout::GENERAL, +// vk::ImageLayout::GENERAL}); +// def_dummy_pass!(PresentPass: { +// device::QueueFlags::PRESENT, +// vk::ImageLayout::PRESENT_SRC_KHR, +// vk::ImageLayout::UNDEFINED}); +// def_dummy_pass!(DepthVisualisationPass: { +// device::QueueFlags::ASYNC_COMPUTE, +// vk::ImageLayout::GENERAL, +// vk::ImageLayout::UNDEFINED}); - #[test] - fn resolve_graph() { - let mut graph = RenderGraph::new(); - let gbuffer = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc { - ..Default::default() - })); - let depth_image = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc { - ..Default::default() - })); - let depth_visualisation = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc { - ..Default::default() - })); - let compute_buffer = graph.add_resource(RenderGraphResourceDesc::Buffer(BufferDesc { - ..Default::default() - })); +// #[test] +// fn resolve_graph() { +// let mut graph = RenderGraph::new(); +// let gbuffer = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc { +// ..Default::default() +// })); +// let depth_image = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc { +// ..Default::default() +// })); +// let depth_visualisation = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc { +// ..Default::default() +// })); +// let compute_buffer = graph.add_resource(RenderGraphResourceDesc::Buffer(BufferDesc { +// ..Default::default() +// })); - graph.add_pass(DepthPass(vec![depth_image], vec![depth_image])); - graph.add_pass(DepthVisualisationPass( - vec![depth_image, depth_visualisation], - vec![depth_visualisation], - )); - graph.add_pass(AsyncPass(vec![compute_buffer], vec![compute_buffer])); - graph.add_pass(RenderPass( - vec![depth_image, compute_buffer, gbuffer], - vec![gbuffer], - )); - graph.add_pass(PostProcessPass(vec![gbuffer], vec![gbuffer])); - graph.mark_as_output(gbuffer); - graph.mark_as_output(depth_image); +// graph.add_pass(DepthPass(vec![depth_image], vec![depth_image])); +// graph.add_pass(DepthVisualisationPass( +// vec![depth_image, depth_visualisation], +// vec![depth_visualisation], +// )); +// graph.add_pass(AsyncPass(vec![compute_buffer], vec![compute_buffer])); +// graph.add_pass(RenderPass( +// vec![depth_image, compute_buffer, gbuffer], +// vec![gbuffer], +// )); +// graph.add_pass(PostProcessPass(vec![gbuffer], vec![gbuffer])); +// graph.mark_as_output(gbuffer); +// graph.mark_as_output(depth_image); - // graph.resolve(); - } -} +// // graph.resolve(); +// } +// }