diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index f2467e4..158a4da 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -670,10 +670,10 @@ impl SamplerCache { pub fn get_sampler(&mut self, desc: pipeline::SamplerDesc) -> VkResult { use std::collections::hash_map::Entry; let entry = match self.samplers.entry(desc) { - Entry::Occupied(entry) => entry.get().handle(), + Entry::Occupied(entry) => entry.get().raw(), Entry::Vacant(entry) => { let sampler = pipeline::Sampler::new(self.device.clone(), entry.key())?; - entry.insert(sampler).handle() + entry.insert(sampler).raw() } }; diff --git a/crates/renderer/src/pipeline.rs b/crates/renderer/src/pipeline.rs index 479ee46..3302376 100644 --- a/crates/renderer/src/pipeline.rs +++ b/crates/renderer/src/pipeline.rs @@ -350,8 +350,6 @@ impl DescriptorSetLayout { } } -use crate::device::DeviceOwned; - impl> ExternallyManagedObject for vk::PipelineLayout { unsafe fn destroy(self, device: &T) { unsafe { @@ -491,6 +489,10 @@ impl Sampler { sampler: DeviceObject::new(device, handle), }) } + + pub fn raw(&self) -> vk::Sampler { + *self.sampler + } } impl> ExternallyManagedObject for vk::ShaderModule { diff --git a/crates/renderer/src/render_graph/commands.rs b/crates/renderer/src/render_graph/commands.rs new file mode 100644 index 0000000..706a259 --- /dev/null +++ b/crates/renderer/src/render_graph/commands.rs @@ -0,0 +1,152 @@ +#![allow(dead_code)] + +use super::resources::*; + +pub struct CopyBuffers { + pub src: Read, + pub dst: Write, +} + +pub struct CopyTextures { + pub src: Read, + pub dst: Write, +} + +pub struct ClearTexture { + pub dst: Write, + pub clear_value: [f32; 4], +} + +pub struct CopyBufferToTexture { + pub src: Read, + pub dst: Write, +} + +pub struct CopyTextureToBuffer { + pub src: Read, + pub dst: Write, +} + +pub struct Copy { + pub src: Read, + pub dst: Write, +} + +pub struct UpdateBuffer { + pub dst: Write, + pub data: Vec, +} + +pub struct UpdateTexture { + pub dst: Write, + pub data: Vec, +} + +pub struct Update { + pub dst: Write, + pub data: Vec, +} + +pub struct RenderPass { + pub color_attachments: Vec>, + pub depth_attachment: Option>, + pub stencil_attachment: Option>, + pub area: (u32, u32), + pub layers: u32, +} + +pub struct BindPipeline(pub Pipeline); +pub struct BindVertexBuffers { + pub buffers: Vec>, +} +pub struct BindIndexBuffer(pub Read, pub IndexFormat); +pub struct BindDescriptorSets { + pub sets: Vec, +} + +pub struct SetViewport { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, + pub min_depth: f32, + pub max_depth: f32, +} + +pub struct SetScissor { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +pub struct PushConstants { + pub data: Box<[u8]>, +} + +pub struct DrawData { + pub vertex_count: u32, + pub instance_count: u32, + pub first_vertex: u32, + pub first_instance: u32, +} + +pub struct DrawIndexedData { + pub index_count: u32, + pub instance_count: u32, + pub first_index: u32, + pub vertex_offset: i32, + pub first_instance: u32, +} + +pub struct DrawIndirectData { + pub indirect_buffer: Read, +} + +pub struct DrawIndexedIndirectData { + pub indirect_buffer: Read, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum IndexFormat { + Uint8, + Uint16, + Uint32, +} + +pub struct Draw { + pub data: T, + pub vertex_buffers: Vec>, + pub index_buffer: Option<(Read, IndexFormat)>, + pub count_buffer: Option>, +} + +pub struct Dispatch; + +mod sealed { + pub trait IsDrawData {} + pub trait InsideRenderPass {} + pub trait OutsideRenderPass {} +} +use sealed::*; + +impl IsDrawData for DrawData {} +impl IsDrawData for DrawIndexedData {} +impl IsDrawData for DrawIndirectData {} +impl IsDrawData for DrawIndexedIndirectData {} + +impl InsideRenderPass for BindPipeline {} +impl OutsideRenderPass for BindPipeline {} +impl InsideRenderPass for BindVertexBuffers {} +impl OutsideRenderPass for BindVertexBuffers {} +impl InsideRenderPass for BindIndexBuffer {} +impl OutsideRenderPass for BindIndexBuffer {} +impl InsideRenderPass for BindDescriptorSets {} +impl OutsideRenderPass for BindDescriptorSets {} +impl OutsideRenderPass for PushConstants {} + +impl InsideRenderPass for SetViewport {} +impl InsideRenderPass for SetScissor {} +impl InsideRenderPass for PushConstants {} +impl InsideRenderPass for Draw {} +impl InsideRenderPass for T {} diff --git a/crates/renderer/src/render_graph/legacy.rs b/crates/renderer/src/render_graph/legacy.rs new file mode 100644 index 0000000..db6f580 --- /dev/null +++ b/crates/renderer/src/render_graph/legacy.rs @@ -0,0 +1,1320 @@ +#![allow(dead_code)] + +use std::{ + collections::BTreeMap, + fmt::{Debug, Display}, + sync::Arc, +}; + +use crate::{ + buffers::{Buffer, BufferDesc}, + commands::{self, traits::CommandBufferExt}, + def_monotonic_id, + device::{self}, + 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.raw()).hash(&mut state), + GraphResource::ImportedImage(image) => image.raw().hash(&mut state), + GraphResource::ImportedBuffer(buffer) => buffer.raw().hash(&mut state), + GraphResource::Image(image) => image.raw().hash(&mut state), + GraphResource::Buffer(buffer) => buffer.raw().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(); + + refmap.toposort_dag(dag) + }); + + // 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(), + )?); + } + _ => {} + } + } + crate::Result::Ok(()) + })?; + + let pool = + commands::SingleUseCommandPool::new(device.clone(), device.main_queue().clone())?; + + let resources = &self.resources; + let cmds = topo + .into_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() { + let buffer = unsafe { cmd.buffer() }; + Self::transition_resource( + &resources[id.0 as usize], + device.dev(), + &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 { + self.outputs + .keys() + .map(|id| (*id, core::mem::take(&mut self.resources[id.0 as usize]))) + .collect::>() + } + + 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.raw(), arc.format(), from, to, None).into() + } + GraphResource::ImportedImage(arc) => { + image_barrier(arc.raw(), arc.format(), from, to, None).into() + } + GraphResource::ImportedBuffer(arc) => { + buffer_barrier(arc.raw(), 0, arc.len(), from, to, None).into() + } + GraphResource::Image(image) => { + image_barrier(image.raw(), image.format(), from, to, None).into() + } + GraphResource::Buffer(buffer) => { + buffer_barrier(buffer.raw(), 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)], + 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.raw(), + 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, + }); +} diff --git a/crates/renderer/src/render_graph/mod.rs b/crates/renderer/src/render_graph/mod.rs index db6f580..373f988 100644 --- a/crates/renderer/src/render_graph/mod.rs +++ b/crates/renderer/src/render_graph/mod.rs @@ -1,1320 +1,6 @@ -#![allow(dead_code)] +mod legacy; +pub use legacy::*; -use std::{ - collections::BTreeMap, - fmt::{Debug, Display}, - sync::Arc, -}; - -use crate::{ - buffers::{Buffer, BufferDesc}, - commands::{self, traits::CommandBufferExt}, - def_monotonic_id, - device::{self}, - 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.raw()).hash(&mut state), - GraphResource::ImportedImage(image) => image.raw().hash(&mut state), - GraphResource::ImportedBuffer(buffer) => buffer.raw().hash(&mut state), - GraphResource::Image(image) => image.raw().hash(&mut state), - GraphResource::Buffer(buffer) => buffer.raw().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(); - - refmap.toposort_dag(dag) - }); - - // 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(), - )?); - } - _ => {} - } - } - crate::Result::Ok(()) - })?; - - let pool = - commands::SingleUseCommandPool::new(device.clone(), device.main_queue().clone())?; - - let resources = &self.resources; - let cmds = topo - .into_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() { - let buffer = unsafe { cmd.buffer() }; - Self::transition_resource( - &resources[id.0 as usize], - device.dev(), - &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 { - self.outputs - .keys() - .map(|id| (*id, core::mem::take(&mut self.resources[id.0 as usize]))) - .collect::>() - } - - 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.raw(), arc.format(), from, to, None).into() - } - GraphResource::ImportedImage(arc) => { - image_barrier(arc.raw(), arc.format(), from, to, None).into() - } - GraphResource::ImportedBuffer(arc) => { - buffer_barrier(arc.raw(), 0, arc.len(), from, to, None).into() - } - GraphResource::Image(image) => { - image_barrier(image.raw(), image.format(), from, to, None).into() - } - GraphResource::Buffer(buffer) => { - buffer_barrier(buffer.raw(), 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)], - 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.raw(), - 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, - }); -} +mod commands; +mod recorder; +mod resources; diff --git a/crates/renderer/src/render_graph/recorder.rs b/crates/renderer/src/render_graph/recorder.rs new file mode 100644 index 0000000..7e45b94 --- /dev/null +++ b/crates/renderer/src/render_graph/recorder.rs @@ -0,0 +1,2 @@ +struct CommandRecorder; +struct RenderPassRecorder; diff --git a/crates/renderer/src/render_graph/resources.rs b/crates/renderer/src/render_graph/resources.rs new file mode 100644 index 0000000..dd1c4fb --- /dev/null +++ b/crates/renderer/src/render_graph/resources.rs @@ -0,0 +1,34 @@ +use crate::images::MipRange; + +pub trait Resource: Sized {} + +mod impls { + use super::*; + + impl Resource for Buffer {} + impl Resource for Texture {} + impl Resource for BufferSlice {} + impl Resource for TextureRegion {} +} + +pub struct Pipeline; +pub struct DescriptorSet; + +pub struct Buffer; +pub struct Texture; +pub struct BufferSlice { + pub buffer: Buffer, + pub offset: u64, + pub size: u64, +} +pub struct TextureRegion { + pub texture: Texture, + pub mip_levels: MipRange, + pub array_layers: MipRange, + pub origin: (u32, u32, u32), + pub extent: (u32, u32, u32), +} + +pub struct Read(pub T); +pub struct Write(pub T); +pub struct ReadWrite(pub T);