From d66071f7bb438714ecff4bb1363fe589ddb4b980 Mon Sep 17 00:00:00 2001 From: Janis Date: Sat, 4 Jan 2025 02:16:43 +0100 Subject: [PATCH] rendergraph work that is probably all for nothing --- crates/renderer/src/render_graph.rs | 541 +++++++++++++++++++++++++--- 1 file changed, 495 insertions(+), 46 deletions(-) diff --git a/crates/renderer/src/render_graph.rs b/crates/renderer/src/render_graph.rs index 46874a0..7b5bf12 100644 --- a/crates/renderer/src/render_graph.rs +++ b/crates/renderer/src/render_graph.rs @@ -5,17 +5,20 @@ use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; use crate::{ buffers::{Buffer, BufferDesc}, commands, def_monotonic_id, - device::{self, Device, DeviceOwned}, - images::{self, Image, ImageDesc}, + device::{self, DeviceOwned}, + images::{self, Image, ImageDesc, SUBRESOURCERANGE_COLOR_ALL}, + texture, util::{self, Rgba}, - SwapchainFrame, + EguiState, SwapchainFrame, }; -use ash::vk; +use ash::{prelude::VkResult, vk}; +use egui::Color32; +use itertools::Itertools; use petgraph::visit::NodeRef; def_monotonic_id!(pub RenderGraphResourceId); -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum RenderGraphResourceDesc { Image(ImageDesc), Buffer(BufferDesc), @@ -50,9 +53,10 @@ struct AttachmentInfo { store: StoreOp, } -pub struct RenderContext { +pub struct RenderContext<'a> { device: device::Device, cmd: commands::SingleUseCommand, + resources: &'a BTreeMap, } #[derive(Debug, Clone, Copy, Default)] @@ -72,15 +76,450 @@ impl ResourceAccess { } } +pub struct EguiPrePass { + out_resources: + BTreeMap, + staging_image: Arc, + staging_buffer: Arc, + aliased_images: BTreeMap, + tessellated: Vec, + texture_data: BTreeMap, +} + +impl Debug for EguiPrePass { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EguiPrePass") + .field("out_resources", &self.out_resources) + .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() + .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; - /// returns the layout the pass requires an image dependency to be in prior - /// to the pass. - fn get_layout_of_in_image_dependency(&self, id: RenderGraphResourceId) -> vk::ImageLayout; - /// returns the layout the pass will leave an image dependency in after the - /// pass. - fn get_layout_of_out_image_dependency(&self, id: RenderGraphResourceId) -> vk::ImageLayout; /// mask of the queue capability requirements of this pass. fn get_queue_capability_requirements(&self) -> device::QueueFlags; /// returns an iterator over all (in) dependencies. @@ -88,7 +527,7 @@ pub trait Pass: Debug + Send { fn get_write_dependencies<'a>(&'a self) -> Box + 'a>; - fn record(self: Box, ctx: &RenderContext) -> crate::Result<()>; + fn record(&self, ctx: &RenderContext) -> crate::Result<()>; } def_monotonic_id!(pub RenderGraphPassId); @@ -166,6 +605,24 @@ impl RenderGraph { // 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<()> { + // create internal resources: + for (&id, desc) in self.resource_descs.iter() { + match desc.clone() { + RenderGraphResourceDesc::Image(image_desc) => { + self.resources.insert( + id, + RenderGraphResource::Image(Image::new(device.clone(), image_desc)?), + ); + } + RenderGraphResourceDesc::Buffer(buffer_desc) => { + self.resources.insert( + id, + RenderGraphResource::Buffer(Buffer::new(device.clone(), buffer_desc)?), + ); + } + } + } + eprintln!("{:#?}", &self); let mut last_write = BTreeMap::new(); @@ -280,7 +737,8 @@ impl RenderGraph { let pool = commands::SingleUseCommandPool::new(device.clone(), device.graphics_queue().clone())?; - let tasks = topological_map + let resources = &self.resources; + let cmds = topological_map .iter() .map(|(set, accesses)| { let pool = pool.clone(); @@ -294,39 +752,43 @@ impl RenderGraph { // transitions for (&id, &access) in accesses.iter() { - self.transition_resource_to(device.dev(), unsafe { &cmd.buffer() }, id, access); + Self::transition_resource_to( + &mut self.accesses, + resources, + device.dev(), + unsafe { &cmd.buffer() }, + id, + access, + ); } - let task = smol::spawn(async move { - let ctx = RenderContext { device, cmd }; + let ctx = RenderContext { + device, + cmd, + resources, + }; - for pass in passes { - pass.record(&ctx)?; - } + for pass in &passes { + pass.record(&ctx)?; + } - crate::Result::Ok(ctx.cmd) - }); - - crate::Result::Ok(task) + crate::Result::Ok(ctx.cmd) }) .collect::>>()?; - let commands = smol::block_on(futures::future::join_all(tasks)) - .into_iter() - .collect::>>()?; - Ok(()) } fn transition_resource_to( - &mut self, + accesses: &mut BTreeMap, + resources: &BTreeMap, dev: &ash::Device, cmd: &vk::CommandBuffer, id: RenderGraphResourceId, access: ResourceAccess, ) { - let old_access = self.accesses.get(&id); - let res = self.resources.get(&id); + 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 { @@ -348,7 +810,7 @@ impl RenderGraph { } }; - self.accesses.insert(id, access); + accesses.insert(id, access); unsafe { dev.cmd_pipeline_barrier2(*cmd, &((&barrier).into())); @@ -459,19 +921,6 @@ mod tests { fn get_write_resource_access(&self, _id: RenderGraphResourceId) -> ResourceAccess { ResourceAccess::default() } - fn get_layout_of_in_image_dependency( - &self, - _id: RenderGraphResourceId, - ) -> vk::ImageLayout { - $layout_in - } - - fn get_layout_of_out_image_dependency( - &self, - _id: RenderGraphResourceId, - ) -> vk::ImageLayout { - $layout_out - } fn get_queue_capability_requirements(&self) -> device::QueueFlags { $queue @@ -489,7 +938,7 @@ mod tests { Box::new(self.1.iter().cloned()) } - fn record(self: Box, _ctx: &RenderContext) -> crate::Result<()> { + fn record(&self, _ctx: &RenderContext) -> crate::Result<()> { Ok(()) } }