rendergraph work that is probably all for nothing

This commit is contained in:
Janis 2025-01-04 02:16:43 +01:00
parent e8d4e1af98
commit d66071f7bb

View file

@ -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<RenderGraphResourceId, RenderGraphResource>,
}
#[derive(Debug, Clone, Copy, Default)]
@ -72,15 +76,450 @@ impl ResourceAccess {
}
}
pub struct EguiPrePass {
out_resources:
BTreeMap<egui::TextureId, (RenderGraphResourceId, texture::TextureId, ResourceAccess)>,
staging_image: Arc<Image>,
staging_buffer: Arc<Buffer>,
aliased_images: BTreeMap<egui::TextureId, (usize, usize, [usize; 2], Image)>,
tessellated: Vec<egui::ClippedPrimitive>,
texture_data: BTreeMap<egui::TextureId, egui::epaint::ImageDelta>,
}
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<Self> {
// 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::<u8>(),
arc.pixels.len() * size_of::<Color32>(),
)
};
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::<BTreeMap<_, _>>();
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::<BTreeMap<_, _>>();
Ok(Self {
aliased_images,
staging_buffer: Arc::new(staging_buffer),
staging_image,
out_resources,
tessellated,
texture_data: output
.textures_delta
.set
.into_iter()
.collect::<BTreeMap<_, _>>(),
})
}
}
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<dyn Iterator<Item = RenderGraphResourceId> + 'a> {
Box::new([].into_iter())
}
fn get_write_dependencies<'a>(
&'a self,
) -> Box<dyn Iterator<Item = RenderGraphResourceId> + '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<crate::texture::TextureId>,
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<dyn Iterator<Item = RenderGraphResourceId> + 'a> {
todo!()
}
fn get_write_dependencies<'a>(
&'a self,
) -> Box<dyn Iterator<Item = RenderGraphResourceId> + '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<dyn Iterator<Item = RenderGraphResourceId> + 'a>;
fn record(self: Box<Self>, 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::<crate::Result<Vec<_>>>()?;
let commands = smol::block_on(futures::future::join_all(tasks))
.into_iter()
.collect::<crate::Result<Vec<_>>>()?;
Ok(())
}
fn transition_resource_to(
&mut self,
accesses: &mut BTreeMap<RenderGraphResourceId, ResourceAccess>,
resources: &BTreeMap<RenderGraphResourceId, RenderGraphResource>,
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<Self>, _ctx: &RenderContext) -> crate::Result<()> {
fn record(&self, _ctx: &RenderContext) -> crate::Result<()> {
Ok(())
}
}