From ab0812ff72ca1045c12cb22f8a93fea74fd4eb13 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 23 Oct 2026 23:01:22 +0200 Subject: [PATCH] Luna: add command impls, simple wireframe demo --- crates/game/src/main.rs | 101 +++-- crates/renderer/src/commands.rs | 29 +- crates/renderer/src/images.rs | 4 + crates/renderer/src/lib.rs | 79 ++++ crates/renderer/src/render_graph/commands.rs | 378 ++++++++++++++++-- .../src/render_graph/graph_builder.rs | 260 ++++++++++-- crates/renderer/src/render_graph/mod.rs | 12 + crates/renderer/src/render_graph/recorder.rs | 214 ++++++++-- crates/renderer/src/rendering/mod.rs | 145 ++++++- 9 files changed, 1074 insertions(+), 148 deletions(-) diff --git a/crates/game/src/main.rs b/crates/game/src/main.rs index bbb8138..f13963e 100644 --- a/crates/game/src/main.rs +++ b/crates/game/src/main.rs @@ -31,6 +31,8 @@ struct WinitState { renderer: Renderer2, last_redraw: std::time::Instant, egui_state: renderer::EguiState, + wireframe: Option, + typed_graph: bool, } impl WinitState { @@ -38,6 +40,29 @@ impl WinitState { const BASE_HEIGHT: u32 = 600; fn new(window_title: String, display: DisplayHandle) -> WinitState { let renderer = Renderer2::new(display.as_raw()).expect("renderer"); + let typed_graph = std::env::var_os("VIDYA_TYPED_GRAPH").is_some(); + let wireframe = typed_graph.then(|| { + smol::block_on(renderer::rendering::Wireframe::from_triangles_indexed( + renderer.device().clone(), + vec![ + glam::vec3(-1.5, -1.0, 0.0), + glam::vec3(1.5, -1.0, 0.0), + glam::vec3(0.0, 1.5, 0.0), + ], + vec![0, 1, 2], + vec![ + renderer::util::Rgba8([255, 80, 80, 255]), + renderer::util::Rgba8([80, 255, 120, 255]), + renderer::util::Rgba8([80, 160, 255, 255]), + ], + )) + .expect("wireframe scene") + }); + if typed_graph { + tracing::warn!( + "VIDYA_TYPED_GRAPH enabled; egui is not yet supported on the typed graph" + ); + } Self { windows: BTreeMap::new(), @@ -49,6 +74,8 @@ impl WinitState { // TODO: pass down this error and add some kind of error handling UI or dump egui_state: renderer::EguiState::new(renderer.device().clone()).unwrap(), renderer, + wireframe, + typed_graph, last_redraw: std::time::Instant::now(), } } @@ -99,39 +126,55 @@ impl WinitState { .filter_map(|id| self.egui_state.lookup_texture(*id)) .collect::>(); // rendering - let render = self.renderer.draw_graph(&window.surface, |renderer, rg| { - use renderer::rendering::{egui_pass, egui_pre_pass}; - let framebuffer = rg.get_framebuffer().unwrap(); - let dev = renderer.device().clone(); + let render_result = if self.typed_graph { + let wireframe = self + .wireframe + .as_ref() + .expect("typed graph wireframe initialized"); + smol::block_on(self.renderer.draw_graph_typed( + &window.surface, + move |_renderer, builder, target, extent, list| { + wireframe.typed_pass(builder, target, extent, list); + renderer::Result::Ok(()) + }, + )) + .and_then(|result| result) + } else { + let render = self.renderer.draw_graph(&window.surface, |renderer, rg| { + use renderer::rendering::{egui_pass, egui_pre_pass}; + let framebuffer = rg.get_framebuffer().unwrap(); + let dev = renderer.device().clone(); - use renderer::ash::vk::Handle; - let [r, g, b]: [f32; 3] = - rand::prelude::StdRng::seed_from_u64(window.surface.raw().as_raw()) - .random::<[f32; 3]>(); - render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer); - egui_pre_pass( - &dev, - rg, - &mut renderer.texture_manager, - &mut self.egui_state, - &output, - )?; + use renderer::ash::vk::Handle; + let [r, g, b]: [f32; 3] = + rand::prelude::StdRng::seed_from_u64(window.surface.raw().as_raw()) + .random::<[f32; 3]>(); + render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer); + egui_pre_pass( + &dev, + rg, + &mut renderer.texture_manager, + &mut self.egui_state, + &output, + )?; - let _ = egui_pass( - &dev, - rg, - &mut renderer.texture_manager, - &mut renderer.samplers, - &mut self.egui_state, - &window.egui_platform.context(), - output, - framebuffer, - )?; + let _ = egui_pass( + &dev, + rg, + &mut renderer.texture_manager, + &mut renderer.samplers, + &mut self.egui_state, + &window.egui_platform.context(), + output, + framebuffer, + )?; - renderer::Result::Ok(()) - }); + renderer::Result::Ok(()) + }); + smol::block_on(render).flatten() + }; - match smol::block_on(render).flatten() { + match render_result { Ok(_) => {} Err(e) => { tracing::error!("encountered error while rendering: {e}"); diff --git a/crates/renderer/src/commands.rs b/crates/renderer/src/commands.rs index a476e8e..3eb438a 100644 --- a/crates/renderer/src/commands.rs +++ b/crates/renderer/src/commands.rs @@ -335,7 +335,13 @@ pub mod traits { tracing::trace!("submitted queue {:?} and fence {:?}", queue, fence); - Ok(crate::sync::FenceFuture::new(fence)) + let future = crate::sync::FenceFuture::new(fence); + Ok(async move { + future.await; + // Keep the command buffer allocated until the submitted work + // has completed; it is only safe to return it to the pool then. + drop(self); + }) } fn image_barrier( @@ -621,7 +627,9 @@ pub mod traits { } } -pub use command_pools::{CommandBuffer, CommandBufferFuture, CommandPool, CommandPools}; +pub use command_pools::{ + CommandBuffer, CommandBufferFuture, CommandPool, CommandPools, Error as CommandPoolError, +}; mod command_pools { use std::{borrow::Cow, cell::Cell, mem::ManuallyDrop, sync::Arc}; @@ -689,10 +697,12 @@ mod command_pools { pub fn free_command_buffers(&self) { self.free_list_mut(|levels, buffers| { - unsafe { - self.device() - .dev() - .free_command_buffers(self.handle(), &buffers); + if !buffers.is_empty() { + unsafe { + self.device() + .dev() + .free_command_buffers(self.handle(), &buffers); + } } levels.clear(); buffers.clear(); @@ -829,6 +839,13 @@ mod command_pools { } impl CommandPools { + pub fn new(device: Device) -> Self { + Self { + device, + shards: ThreadLocal::new(), + } + } + pub fn get_for_family(&self, family: u32) -> Arc { let shard = self .shards diff --git a/crates/renderer/src/images.rs b/crates/renderer/src/images.rs index 4c70f9e..f16a4b6 100644 --- a/crates/renderer/src/images.rs +++ b/crates/renderer/src/images.rs @@ -184,6 +184,10 @@ pub struct Image { } impl Image { + pub(crate) fn desc(&self) -> &ImageDesc { + &self.desc + } + pub fn new(device: Device, desc: ImageDesc) -> crate::Result { let (image, requirements) = Self::new_raw(device.clone(), &desc)?; let alloc = device.alloc2.lock().allocate(&AllocationCreateDesc { diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 6048d68..ffe364c 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -36,6 +36,7 @@ pub mod sync; mod text; pub mod util; +use commands::traits::CommandBufferExt; use device::Device; mod texture { @@ -91,6 +92,8 @@ pub enum Error { #[error(transparent)] Result(#[from] ash::vk::Result), #[error(transparent)] + CommandPool(#[from] commands::CommandPoolError), + #[error(transparent)] CStrError(#[from] core::ffi::c_str::FromBytesUntilNulError), #[error(transparent)] Utf8Error(#[from] core::str::Utf8Error), @@ -925,6 +928,7 @@ impl EguiState { pub struct Renderer2 { pub device: Device, + command_pools: commands::CommandPools, pub samplers: SamplerCache, pub texture_manager: texture::TextureManager, @@ -946,6 +950,7 @@ impl Renderer2 { PhysicalDeviceFeatures { core: vk::PhysicalDeviceFeatures { multi_draw_indirect: vk::TRUE, + fill_mode_non_solid: vk::TRUE, ..Default::default() }, core11: vk::PhysicalDeviceVulkan11Features { @@ -970,6 +975,7 @@ impl Renderer2 { )?; Ok(Self { + command_pools: commands::CommandPools::new(device.clone()), samplers: SamplerCache::new(device.clone()), texture_manager: texture::TextureManager::new(device.clone()), display, @@ -1047,6 +1053,79 @@ impl Renderer2 { Ok(out) } + + pub async fn draw_graph_typed(&mut self, surface: &swapchain::Surface, cb: F) -> Result + where + F: FnOnce( + &mut Renderer2, + &mut render_graph::GraphBuilder<'_>, + render_graph::Texture, + (u32, u32), + &mut render_graph::CommandList<'_>, + ) -> T, + { + let Some(frame) = surface.acquire_image() else { + return Err(Error::SuboptimalSwapchain); + }; + let (frame, _suboptimal) = frame.await?; + + let mut builder = render_graph::GraphBuilder::new(self.device.clone()); + let target = builder.import_image(frame.image().clone()); + let target_id = target.0; + let image_extent = frame.image().desc().extent; + let extent = (image_extent.width, image_extent.height); + + let mut list = builder.make_command_list(); + // Seed the folder with UNDEFINED so BeginRendering emits the initial + // transition; CLEAR intentionally discards the image's previous contents. + list.push(render_graph::ImportResource { + resource: render_graph::Texture(target_id), + access: vk::AccessFlags2::NONE, + stage: vk::PipelineStageFlags2::NONE, + layout: Some(vk::ImageLayout::UNDEFINED), + }); + let out = cb(self, &mut builder, target, extent, &mut list); + list.push(render_graph::PreparePresent { + texture: render_graph::TextureRegion { + texture: render_graph::Texture(target_id), + range: render_graph::TextureRange { + aspect: vk::ImageAspectFlags::COLOR, + mip_levels: (0..1).into(), + array_layers: (0..1).into(), + origin: (0, 0, 0), + extent: (extent.0, extent.1, 1), + }, + }, + }); + + let queue = self.device.queues().graphics(); + let command_buffer = self.command_pools.allocate_buffer( + queue.family_index(), + Some("typed-frame".into()), + vk::CommandBufferLevel::PRIMARY, + vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, + )?; + let registry = builder.registry(); + let mut recorder = render_graph::CommandRecorder::new(command_buffer, registry); + list.record_commands(&mut recorder); + drop(list); + let command_buffer = recorder.into_command_buffer(); + let future = command_buffer.submit_to_queue( + queue, + Some(( + frame.acquire, + vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, + )), + Some(frame.release), + Arc::new(sync::Fence::from_pool(&self.device.pools.fences, None)?), + )?; + + future.await; + let release = frame.release; + frame.present(Some(release))?; + + Ok(out) + } } pub use vk::Extent2D; diff --git a/crates/renderer/src/render_graph/commands.rs b/crates/renderer/src/render_graph/commands.rs index 3cacc5a..4d4b14b 100644 --- a/crates/renderer/src/render_graph/commands.rs +++ b/crates/renderer/src/render_graph/commands.rs @@ -16,6 +16,7 @@ use crate::{ device::DeviceOwned, render_graph::recorder::{CommandRecorder, SideEffectMap}, }; +use ash_formats::{FormatExt as _, FormatHelpers as _}; use super::resources::*; @@ -36,7 +37,10 @@ impl Command for ImportResource { )); } - fn apply(self, _recorder: &mut CommandRecorder) { + fn apply(self, recorder: &mut CommandRecorder) { + if let Some(layout) = self.layout { + recorder.set_image_layout(self.resource.id(), layout); + } // No actual GPU commands needed for import, as it's just a logical operation } } @@ -72,7 +76,7 @@ impl Copy { vk::AccessFlags2::TRANSFER_READ, Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL), )); - self.src.side_effect(map.reborrow().into_side_effect_map2( + self.dst.side_effect(map.reborrow().into_side_effect_map2( vk::PipelineStageFlags2::TRANSFER, vk::AccessFlags2::TRANSFER_WRITE, Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), @@ -277,7 +281,28 @@ impl Command for ClearTexture { )); } - fn apply(self, _recorder: &mut CommandRecorder) {} + fn apply(self, recorder: &mut CommandRecorder) { + let cmd = recorder.cmd_buffer(); + let format = recorder.get_image_desc(self.dst.id()).format; + debug_assert!(format.aspect_flags().contains(vk::ImageAspectFlags::COLOR)); + let clear_value = format.clear_value(self.clear_value); + let range = self.dst.range; + let subresource = vk::ImageSubresourceRange::default() + .aspect_mask(range.aspect & format.aspect_flags()) + .base_mip_level(range.mip_levels.start()) + .level_count(range.mip_levels.len()) + .base_array_layer(range.array_layers.start()) + .layer_count(range.array_layers.len()); + unsafe { + cmd.device().raw.cmd_clear_color_image( + cmd.raw(), + recorder.get_image_handle(self.dst.id()), + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &clear_value, + &[subresource], + ); + } + } } pub struct UpdateBuffer { @@ -294,17 +319,37 @@ impl Command for UpdateBuffer { )); } - fn apply(self, _recorder: &mut CommandRecorder) {} + fn apply(self, recorder: &mut CommandRecorder) { + let cmd = recorder.cmd_buffer(); + let mut data = self.data; + let padded_len = data.len().div_ceil(4) * 4; + data.resize(padded_len, 0); + unsafe { + cmd.device().raw.cmd_update_buffer( + cmd.raw(), + recorder.get_buffer_handle(self.dst.id()), + self.dst.range.offset, + &data, + ); + } + } } pub struct BeginRendering { - pub color_attachments: Vec, - pub depth_attachment: Option, - pub stencil_attachment: Option, + pub color_attachments: Vec, + pub depth_attachment: Option, + pub stencil_attachment: Option, pub area: (u32, u32), pub layers: u32, } +pub struct RenderingAttachment { + pub region: TextureRegion, + pub load_op: vk::AttachmentLoadOp, + pub store_op: vk::AttachmentStoreOp, + pub clear_value: vk::ClearValue, +} + impl Command for BeginRendering { fn side_effects(&self, mut map: SideEffectMap) { for attachment in &self.color_attachments { @@ -313,30 +358,107 @@ impl Command for BeginRendering { // pass instance referencing framebuffer-local stages must contain // only framebuffer-local stages. - // TODO: consider loadop and storeop? - attachment.side_effect(map.reborrow().into_side_effect_map2( - vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, - vk::AccessFlags2::COLOR_ATTACHMENT_READ, - Some(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL), - )); + attachment + .region + .side_effect(map.reborrow().into_side_effect_map2( + vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + vk::AccessFlags2::COLOR_ATTACHMENT_WRITE + | if attachment.load_op == vk::AttachmentLoadOp::LOAD { + vk::AccessFlags2::COLOR_ATTACHMENT_READ + } else { + vk::AccessFlags2::empty() + }, + Some(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL), + )); } if let Some(depth) = &self.depth_attachment { - depth.side_effect(map.reborrow().into_side_effect_map2( - vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS, - vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ, - Some(vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL), - )); + depth + .region + .side_effect(map.reborrow().into_side_effect_map2( + vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS + | vk::PipelineStageFlags2::LATE_FRAGMENT_TESTS, + vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE + | if depth.load_op == vk::AttachmentLoadOp::LOAD { + vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ + } else { + vk::AccessFlags2::empty() + }, + Some(vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL), + )); } if let Some(stencil) = &self.stencil_attachment { - stencil.side_effect(map.reborrow().into_side_effect_map2( - vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS, - vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ, - Some(vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL), - )); + stencil + .region + .side_effect(map.reborrow().into_side_effect_map2( + vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS + | vk::PipelineStageFlags2::LATE_FRAGMENT_TESTS, + vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE + | if stencil.load_op == vk::AttachmentLoadOp::LOAD { + vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ + } else { + vk::AccessFlags2::empty() + }, + Some(vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL), + )); } } - fn apply(self, _recorder: &mut CommandRecorder) {} + fn apply(self, recorder: &mut CommandRecorder) { + let cmd = recorder.cmd_buffer(); + let color_attachments = self + .color_attachments + .iter() + .map(|attachment| { + vk::RenderingAttachmentInfo::default() + .image_view(recorder.get_image_view_handle(attachment.region.id())) + .image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .load_op(attachment.load_op) + .store_op(attachment.store_op) + .clear_value(attachment.clear_value) + }) + .collect::>(); + let depth_attachment = self.depth_attachment.as_ref().map(|attachment| { + vk::RenderingAttachmentInfo::default() + .image_view(recorder.get_image_view_handle(attachment.region.id())) + .image_layout(vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL) + .load_op(attachment.load_op) + .store_op(attachment.store_op) + .clear_value(attachment.clear_value) + }); + let stencil_attachment = self.stencil_attachment.as_ref().map(|attachment| { + vk::RenderingAttachmentInfo::default() + .image_view(recorder.get_image_view_handle(attachment.region.id())) + .image_layout(vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL) + .load_op(attachment.load_op) + .store_op(attachment.store_op) + .clear_value(attachment.clear_value) + }); + let rendering_info = vk::RenderingInfo::default() + .render_area(vk::Rect2D { + offset: vk::Offset2D { x: 0, y: 0 }, + extent: vk::Extent2D { + width: self.area.0, + height: self.area.1, + }, + }) + .layer_count(self.layers) + .color_attachments(&color_attachments); + let rendering_info = if let Some(depth) = depth_attachment.as_ref() { + rendering_info.depth_attachment(depth) + } else { + rendering_info + }; + let rendering_info = if let Some(stencil) = stencil_attachment.as_ref() { + rendering_info.stencil_attachment(stencil) + } else { + rendering_info + }; + unsafe { + cmd.device() + .raw + .cmd_begin_rendering(cmd.raw(), &rendering_info); + } + } } pub struct EndRendering; @@ -346,6 +468,31 @@ impl Command for EndRendering { // No resource access, but ends the render pass } + fn apply(self, recorder: &mut CommandRecorder) { + let cmd = recorder.cmd_buffer(); + unsafe { + cmd.device().raw.cmd_end_rendering(cmd.raw()); + } + } +} + +pub struct PreparePresent { + pub texture: TextureRegion, +} + +impl Command for PreparePresent { + fn side_effects(&self, mut map: SideEffectMap) { + // The present layout differs from the preceding color-attachment + // layout, so try_fold rejects this access and conflicts emits the + // release barrier even though this access has no memory scope. + self.texture + .side_effect(map.reborrow().into_side_effect_map2( + vk::PipelineStageFlags2::NONE, + vk::AccessFlags2::NONE, + Some(vk::ImageLayout::PRESENT_SRC_KHR), + )); + } + fn apply(self, _recorder: &mut CommandRecorder) {} } @@ -475,7 +622,20 @@ impl Command for SetViewport { // No resource access, but affects the viewport state } - fn apply(self, _recorder: &mut CommandRecorder) {} + fn apply(self, recorder: &mut CommandRecorder) { + let cmd = recorder.cmd_buffer(); + let viewport = vk::Viewport { + x: self.x, + y: self.y, + width: self.width, + height: self.height, + min_depth: self.min_depth, + max_depth: self.max_depth, + }; + unsafe { + cmd.device().raw.cmd_set_viewport(cmd.raw(), 0, &[viewport]); + } + } } pub struct SetScissor { @@ -490,7 +650,22 @@ impl Command for SetScissor { // No resource access, but affects the scissor state } - fn apply(self, _recorder: &mut CommandRecorder) {} + fn apply(self, recorder: &mut CommandRecorder) { + let cmd = recorder.cmd_buffer(); + let scissor = vk::Rect2D { + offset: vk::Offset2D { + x: self.x as i32, + y: self.y as i32, + }, + extent: vk::Extent2D { + width: self.width, + height: self.height, + }, + }; + unsafe { + cmd.device().raw.cmd_set_scissor(cmd.raw(), 0, &[scissor]); + } + } } pub struct PushConstants { @@ -580,16 +755,34 @@ impl Command for Draw { } } - fn apply(self, _recorder: &mut CommandRecorder) {} + fn apply(self, recorder: &mut CommandRecorder) { + self.data + .apply(recorder, self.vertex_buffers, self.index_buffer); + } } -pub struct PipelineBarrier; +pub struct PipelineBarrier { + pub image_barriers: Vec>, + pub buffer_barriers: Vec>, + pub(crate) image_layouts: Vec<(ResourceId, vk::ImageLayout)>, +} impl Command for PipelineBarrier { fn side_effects(&self, _map: SideEffectMap) {} - fn apply(self, _recorder: &mut CommandRecorder) { - todo!() + fn apply(self, recorder: &mut CommandRecorder) { + for (resource, layout) in self.image_layouts { + recorder.set_image_layout(resource, layout); + } + let cmd = recorder.cmd_buffer(); + let dependency_info = vk::DependencyInfo::default() + .image_memory_barriers(&self.image_barriers) + .buffer_memory_barriers(&self.buffer_barriers); + unsafe { + cmd.device() + .raw + .cmd_pipeline_barrier2(cmd.raw(), &dependency_info); + } } } @@ -599,8 +792,16 @@ pub trait Command: Sized { } mod sealed { + use super::*; + pub trait IsDrawData { fn side_effects(&self, _map: super::SideEffectMap) {} + fn apply( + self, + recorder: &mut CommandRecorder, + vertex_buffers: Vec, + index_buffer: Option<(BufferSlice, IndexFormat)>, + ); } pub trait InsideRenderPass {} pub trait OutsideRenderPass {} @@ -608,8 +809,47 @@ mod sealed { use ash::vk; pub(super) use sealed::*; -impl IsDrawData for DrawData {} -impl IsDrawData for DrawIndexedData {} +impl IsDrawData for DrawData { + fn apply( + self, + recorder: &mut CommandRecorder, + vertex_buffers: Vec, + index_buffer: Option<(BufferSlice, IndexFormat)>, + ) { + bind_draw_buffers(recorder, &vertex_buffers, index_buffer.as_ref()); + let cmd = recorder.cmd_buffer(); + unsafe { + cmd.device().raw.cmd_draw( + cmd.raw(), + self.vertex_count, + self.instance_count, + self.first_vertex, + self.first_instance, + ); + } + } +} +impl IsDrawData for DrawIndexedData { + fn apply( + self, + recorder: &mut CommandRecorder, + vertex_buffers: Vec, + index_buffer: Option<(BufferSlice, IndexFormat)>, + ) { + bind_draw_buffers(recorder, &vertex_buffers, index_buffer.as_ref()); + let cmd = recorder.cmd_buffer(); + unsafe { + cmd.device().raw.cmd_draw_indexed( + cmd.raw(), + self.index_count, + self.instance_count, + self.first_index, + self.vertex_offset, + self.first_instance, + ); + } + } +} impl IsDrawData for DrawIndirectData { fn side_effects(&self, mut map: SideEffectMap) { self.indirect_buffer @@ -619,6 +859,24 @@ impl IsDrawData for DrawIndirectData { None, )); } + fn apply( + self, + recorder: &mut CommandRecorder, + vertex_buffers: Vec, + index_buffer: Option<(BufferSlice, IndexFormat)>, + ) { + bind_draw_buffers(recorder, &vertex_buffers, index_buffer.as_ref()); + let cmd = recorder.cmd_buffer(); + unsafe { + cmd.device().raw.cmd_draw_indirect( + cmd.raw(), + recorder.get_buffer_handle(self.indirect_buffer.id()), + self.indirect_buffer.range.offset, + self.count, + self.stride, + ); + } + } } impl IsDrawData for DrawIndexedIndirectData { fn side_effects(&self, mut map: SideEffectMap) { @@ -629,9 +887,65 @@ impl IsDrawData for DrawIndexedIndirectData { None, )); } + fn apply( + self, + recorder: &mut CommandRecorder, + vertex_buffers: Vec, + index_buffer: Option<(BufferSlice, IndexFormat)>, + ) { + bind_draw_buffers(recorder, &vertex_buffers, index_buffer.as_ref()); + let cmd = recorder.cmd_buffer(); + unsafe { + cmd.device().raw.cmd_draw_indexed_indirect( + cmd.raw(), + recorder.get_buffer_handle(self.indirect_buffer.id()), + self.indirect_buffer.range.offset, + self.count, + self.stride, + ); + } + } +} + +fn bind_draw_buffers( + recorder: &mut CommandRecorder, + vertex_buffers: &[BufferSlice], + index_buffer: Option<&(BufferSlice, IndexFormat)>, +) { + let cmd = recorder.cmd_buffer(); + let handles = vertex_buffers + .iter() + .map(|buffer| recorder.get_buffer_handle(buffer.id())) + .collect::>(); + let offsets = vertex_buffers + .iter() + .map(|buffer| buffer.range.offset) + .collect::>(); + unsafe { + if !handles.is_empty() { + cmd.device() + .raw + .cmd_bind_vertex_buffers(cmd.raw(), 0, &handles, &offsets); + } + if let Some((buffer, format)) = index_buffer { + let index_type = match format { + IndexFormat::Uint8 => vk::IndexType::UINT8_KHR, + IndexFormat::Uint16 => vk::IndexType::UINT16, + IndexFormat::Uint32 => vk::IndexType::UINT32, + }; + cmd.device().raw.cmd_bind_index_buffer( + cmd.raw(), + recorder.get_buffer_handle(buffer.id()), + buffer.range.offset, + index_type, + ); + } + } } impl OutsideRenderPass for BeginRendering {} +impl OutsideRenderPass for ImportResource {} +impl OutsideRenderPass for PreparePresent {} impl InsideRenderPass for EndRendering {} impl OutsideRenderPass for Copy {} impl OutsideRenderPass for ClearTexture {} diff --git a/crates/renderer/src/render_graph/graph_builder.rs b/crates/renderer/src/render_graph/graph_builder.rs index 246512b..fe89056 100644 --- a/crates/renderer/src/render_graph/graph_builder.rs +++ b/crates/renderer/src/render_graph/graph_builder.rs @@ -1,62 +1,232 @@ -use std::{cell::UnsafeCell, collections::BTreeMap, marker::PhantomData}; +use std::{ + cell::Cell, + collections::BTreeMap, + marker::PhantomData, + ops::{Deref, DerefMut}, + sync::Arc, +}; use ash::vk; -use derive_more::derive::{Deref, DerefMut}; +use gpu_allocator::MemoryLocation; +use parking_lot::RwLock; use crate::{ - buffers, images, + Device, buffers, + device::{AllocationStrategy, QueueFlags}, + images, render_graph::{ recorder::CommandList, resources::{self, ResourceId}, }, }; -pub struct GraphResourceRegistry { - images: BTreeMap>, - views: BTreeMap>, - buffers: BTreeMap>, +#[derive(Default)] +pub(crate) struct GraphResourceRegistry { + pub(crate) images: RwLock>>, + pub(crate) views: RwLock>, + pub(crate) buffers: RwLock>>>, + pub(crate) shared_buffers: RwLock>>, + pub(crate) owning_queues: RwLock>>, } -#[derive(Deref, DerefMut)] -struct StagingBuffer<'a> { - id: ResourceId, - #[deref] - #[deref_mut] - buffer: &'a mut buffers::Buffer, +pub struct StagingBuffer<'a> { + pub id: ResourceId, + buffer: Arc>, + _pd: PhantomData<&'a mut buffers::Buffer>, } -#[derive(Deref, DerefMut)] -struct Buffer<'a> { - id: ResourceId, - #[deref] - #[deref_mut] - buffer: &'a mut buffers::Buffer, +pub struct Buffer<'a> { + pub id: ResourceId, + buffer: Arc>, + _pd: PhantomData<&'a mut buffers::Buffer>, +} + +impl Deref for StagingBuffer<'_> { + type Target = buffers::Buffer; + + fn deref(&self) -> &Self::Target { + // The wrapper is unique and not Clone; the frame driver must not start + // recording from the registry until all wrappers have been dropped. + unsafe { &*self.buffer.get() } + } +} + +impl DerefMut for StagingBuffer<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + // The wrapper is unique and not Clone; the frame driver must not start + // recording from the registry until all wrappers have been dropped. + unsafe { &mut *self.buffer.get() } + } +} + +impl Deref for Buffer<'_> { + type Target = buffers::Buffer; + + fn deref(&self) -> &Self::Target { + // See the StagingBuffer deref safety invariant above. + unsafe { &*self.buffer.get() } + } +} + +impl DerefMut for Buffer<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + // See the StagingBuffer deref safety invariant above. + unsafe { &mut *self.buffer.get() } + } } pub struct GraphBuilder<'r> { - _pd: PhantomData, + device: Device, + registry: Arc, + next_id: Cell, + _pd: PhantomData, } + +enum GraphResource { + Buffer(buffers::Buffer, Option), + SharedBuffer(Arc, Option), + Image(Arc), +} + impl<'r> GraphBuilder<'r> { - fn make_command_list<'cmd>(&self) -> CommandList<'cmd> { - todo!() - } - fn make_staging_buffer(&self, _size: u64) -> StagingBuffer<'r> { - todo!() + pub fn new(device: Device) -> Self { + Self { + device, + registry: Arc::new(GraphResourceRegistry::default()), + next_id: Cell::new(0), + _pd: PhantomData, + } } - fn make_buffer( + pub(crate) fn registry(&self) -> Arc { + Arc::clone(&self.registry) + } + + fn register_resource(&self, resource: GraphResource) -> ResourceId { + let id = ResourceId(self.next_id.get()); + self.next_id.set( + self.next_id + .get() + .checked_add(1) + .expect("graph resource ID counter overflowed"), + ); + + match resource { + GraphResource::Buffer(buffer, owning_queue) => { + self.registry + .buffers + .write() + .insert(id, Arc::new(std::cell::UnsafeCell::new(buffer))); + self.registry.owning_queues.write().insert(id, owning_queue); + } + GraphResource::SharedBuffer(buffer, owning_queue) => { + self.registry.shared_buffers.write().insert(id, buffer); + self.registry.owning_queues.write().insert(id, owning_queue); + } + GraphResource::Image(image) => { + self.registry.images.write().insert(id, image); + } + } + id + } + + fn buffer_wrapper(&self, id: ResourceId) -> Buffer<'r> { + let buffer = self + .registry + .buffers + .read() + .get(&id) + .expect("new graph buffer was not registered") + .clone(); + Buffer { + id, + buffer, + _pd: PhantomData, + } + } + + fn staging_buffer_wrapper(&self, id: ResourceId) -> StagingBuffer<'r> { + let buffer = self + .registry + .buffers + .read() + .get(&id) + .expect("new graph staging buffer was not registered") + .clone(); + StagingBuffer { + id, + buffer, + _pd: PhantomData, + } + } + + pub fn make_command_list<'cmd>(&self) -> CommandList<'cmd> { + CommandList::new() + } + + pub fn make_staging_buffer(&self, size: u64) -> StagingBuffer<'r> { + let buffer = buffers::Buffer::new( + self.device.clone(), + buffers::BufferDesc { + size, + usage: vk::BufferUsageFlags::TRANSFER_SRC, + mem_location: MemoryLocation::CpuToGpu, + queue_families: QueueFlags::empty(), + alloc_scheme: AllocationStrategy::AllocatorManaged, + ..Default::default() + }, + ) + .expect("failed to create graph staging buffer"); + let id = self.register_resource(GraphResource::Buffer(buffer, None)); + self.staging_buffer_wrapper(id) + } + + pub fn make_buffer( &self, - _size: u64, - _usage: vk::BufferUsageFlags, - _location: gpu_allocator::MemoryLocation, + size: u64, + usage: vk::BufferUsageFlags, + location: MemoryLocation, ) -> Buffer<'r> { - todo!() + let buffer = buffers::Buffer::new( + self.device.clone(), + buffers::BufferDesc { + size, + usage, + mem_location: location, + queue_families: QueueFlags::empty(), + alloc_scheme: AllocationStrategy::AllocatorManaged, + ..Default::default() + }, + ) + .expect("failed to create graph buffer"); + let id = self.register_resource(GraphResource::Buffer(buffer, None)); + self.buffer_wrapper(id) } // we need to know which queue currently owns this buffer to be able to // transfer it to the correct queue family when using it - fn import_buffer(&self, _buffer: buffers::Buffer, _owning_queue: Option) -> Buffer<'r> { - todo!() + pub fn import_buffer(&self, buffer: buffers::Buffer, owning_queue: Option) -> Buffer<'r> { + let id = self.register_resource(GraphResource::Buffer(buffer, owning_queue)); + self.buffer_wrapper(id) + } + + pub fn import_shared_buffer( + &self, + buffer: Arc, + owning_queue: Option, + ) -> ResourceId { + self.register_resource(GraphResource::SharedBuffer(buffer, owning_queue)) + } + + pub fn make_image(&self, desc: images::ImageDesc) -> resources::Texture { + let image = Arc::new( + images::Image::new(self.device.clone(), desc).expect("failed to create graph image"), + ); + resources::Texture(self.register_resource(GraphResource::Image(image))) + } + + pub fn import_image(&self, image: Arc) -> resources::Texture { + resources::Texture(self.register_resource(GraphResource::Image(image))) } } @@ -231,14 +401,21 @@ mod api_demo { }); let mut pass_cmd = cmd.begin_rendering( - vec![resources::TextureRegion { - texture: resources::Texture(render_target), - range: resources::TextureRange { - aspect: vk::ImageAspectFlags::COLOR, - mip_levels: (0..).into(), - array_layers: (0..).into(), - origin: (0, 0, 0), - extent: (extent.0, extent.1, 1), + vec![commands::RenderingAttachment { + region: resources::TextureRegion { + texture: resources::Texture(render_target), + range: resources::TextureRange { + aspect: vk::ImageAspectFlags::COLOR, + mip_levels: (0..).into(), + array_layers: (0..).into(), + origin: (0, 0, 0), + extent: (extent.0, extent.1, 1), + }, + }, + load_op: vk::AttachmentLoadOp::CLEAR, + store_op: vk::AttachmentStoreOp::STORE, + clear_value: vk::ClearValue { + color: vk::ClearColorValue { float32: [0.0; 4] }, }, }], None, @@ -262,9 +439,6 @@ mod api_demo { max_depth: 1.0, }); - fn factory() -> T { - unimplemented!() - } pass_cmd.push(commands::BindPipeline( pipeline.raw(), pipeline.bind_point(), diff --git a/crates/renderer/src/render_graph/mod.rs b/crates/renderer/src/render_graph/mod.rs index 14a6caa..739882d 100644 --- a/crates/renderer/src/render_graph/mod.rs +++ b/crates/renderer/src/render_graph/mod.rs @@ -7,3 +7,15 @@ mod commands; mod graph_builder; mod recorder; mod resources; + +pub use commands::{ + BindPipeline, Draw, DrawIndexedData, ImportResource, IndexFormat, PreparePresent, + PushConstants, RenderingAttachment, SetScissor, SetViewport, +}; +pub use graph_builder::GraphBuilder; +pub use recorder::CommandList; +pub(crate) use recorder::CommandRecorder; +pub use resources::{ + Buffer as GraphBuffer, BufferRange, BufferSlice, ResourceId, Texture, TextureRange, + TextureRegion, +}; diff --git a/crates/renderer/src/render_graph/recorder.rs b/crates/renderer/src/render_graph/recorder.rs index 67e8ee4..c07d195 100644 --- a/crates/renderer/src/render_graph/recorder.rs +++ b/crates/renderer/src/render_graph/recorder.rs @@ -3,44 +3,131 @@ use std::{ marker::PhantomData, mem::{MaybeUninit, size_of}, ptr::NonNull, + sync::Arc, }; use ash::vk; +use ash_formats::FormatExt; use crate::{ commands::CommandBuffer, - images::ImageDesc, + images::{ImageDesc, ImageViewDesc}, render_graph::{ commands::{Command, InsideRenderPass, OutsideRenderPass}, - resources::{ResourceAccess, ResourceId, ResourceRange, TextureRegion}, + resources::{ResourceAccess, ResourceId, ResourceRange}, }, }; -pub struct CommandRecorder; +use super::graph_builder::GraphResourceRegistry; + +pub struct CommandRecorder { + cmd_buffer: CommandBuffer, + registry: Arc, + layouts: BTreeMap, +} impl CommandRecorder { + pub(crate) fn new(cmd_buffer: CommandBuffer, registry: Arc) -> Self { + Self { + cmd_buffer, + registry, + layouts: BTreeMap::new(), + } + } + pub fn cmd_buffer(&self) -> &CommandBuffer { - unimplemented!() + &self.cmd_buffer } - pub fn get_buffer_handle(&self, _id: ResourceId) -> vk::Buffer { - unimplemented!() + pub fn into_command_buffer(self) -> CommandBuffer { + self.cmd_buffer } - pub fn get_image_desc(&self, _id: ResourceId) -> &ImageDesc { - unimplemented!() + pub fn get_buffer_handle(&self, id: ResourceId) -> vk::Buffer { + if let Some(buffer) = self.registry.buffers.read().get(&id) { + unsafe { (&*buffer.get()).raw() } + } else { + self.registry + .shared_buffers + .read() + .get(&id) + .expect("unknown graph buffer") + .raw() + } } - pub fn get_image_handle(&self, _id: ResourceId) -> vk::Image { - unimplemented!() + pub fn get_image_desc(&self, id: ResourceId) -> ImageDesc { + self.registry + .images + .read() + .get(&id) + .expect("unknown graph image") + .desc() + .clone() } - pub fn get_image_layout(&self, _id: ResourceId) -> vk::ImageLayout { - unimplemented!() + pub fn get_image_handle(&self, id: ResourceId) -> vk::Image { + self.registry + .images + .read() + .get(&id) + .expect("unknown graph image") + .raw() } - pub fn get_image_view_handle(&self, _id: ResourceId) -> vk::ImageView { - unimplemented!() + pub fn get_image_layout(&self, id: ResourceId) -> vk::ImageLayout { + self.layouts + .get(&id) + .copied() + .unwrap_or(vk::ImageLayout::UNDEFINED) + } + + pub fn get_image_view_handle(&self, id: ResourceId) -> vk::ImageView { + if let Some(view) = self.registry.views.read().get(&id) { + return view.raw(); + } + + let mut views = self.registry.views.write(); + if let Some(view) = views.get(&id) { + return view.raw(); + } + + let image = self + .registry + .images + .read() + .get(&id) + .expect("unknown graph image") + .clone(); + let desc = image.desc(); + let view_desc = ImageViewDesc { + name: None, + flags: vk::ImageViewCreateFlags::empty(), + kind: match desc.kind { + vk::ImageType::TYPE_1D if desc.array_layers > 1 => vk::ImageViewType::TYPE_1D_ARRAY, + vk::ImageType::TYPE_1D => vk::ImageViewType::TYPE_1D, + vk::ImageType::TYPE_2D if desc.array_layers > 1 => vk::ImageViewType::TYPE_2D_ARRAY, + vk::ImageType::TYPE_2D => vk::ImageViewType::TYPE_2D, + vk::ImageType::TYPE_3D => vk::ImageViewType::TYPE_3D, + _ => unreachable!("unsupported image type"), + }, + format: desc.format, + aspect: desc.format.aspect_flags(), + mip_range: desc.mip_level_range(), + layer_range: desc.layer_range(), + components: vk::ComponentMapping::default(), + }; + let view = image + .create_view(view_desc) + .expect("failed to create a full-range graph image view"); + let view = std::mem::ManuallyDrop::into_inner(view); + let handle = view.raw(); + views.insert(id, view); + handle + } + + pub(crate) fn set_image_layout(&mut self, id: ResourceId, layout: vk::ImageLayout) { + self.layouts.insert(id, layout); } } @@ -186,9 +273,9 @@ impl<'cmd> CommandList<'cmd> { pub fn begin_rendering( &mut self, - color_attachments: Vec, - depth_attachment: Option, - stencil_attachment: Option, + color_attachments: Vec, + depth_attachment: Option, + stencil_attachment: Option, area: (u32, u32), layers: u32, ) -> RenderPass<'cmd, '_> { @@ -219,7 +306,7 @@ impl<'cmd> CommandList<'cmd> { let packed = Packed { meta: CommandMeta { apply_command: |recorder, command_ptr, cursor| { - *cursor += size_of::(); + *cursor += size_of::() + size_of::(); let command = unsafe { command_ptr.cast::().read_unaligned() }; C::apply(command, recorder); }, @@ -232,10 +319,14 @@ impl<'cmd> CommandList<'cmd> { unsafe { let bytes = self.command_bytes.as_mut_ptr().add(len); bytes.cast::>().write_unaligned(packed); + self.command_bytes.set_len(len + size_of::>()); } } - fn fold_side_effects(&self) -> Vec<(u32, super::commands::PipelineBarrier)> { + fn fold_side_effects( + &self, + recorder: &mut CommandRecorder, + ) -> Vec<(u32, super::commands::PipelineBarrier)> { // Buffer accesses can be granularly tracked by their offset and size; // Image accesses are tracked by their subresource range, which means all regions of the same mip/array layer are considered the same subresource for the purposes of ImageMemoryBarriers @@ -259,7 +350,7 @@ impl<'cmd> CommandList<'cmd> { resource_id: ResourceId, command_index: u32, from: ResourceAccess, - to: usize, + to: ResourceAccess, } // map to keep track of the current state of each subresource @@ -291,7 +382,6 @@ impl<'cmd> CommandList<'cmd> { // a barrier before the current command index. let mut folded = false; - let len = entry.len(); for (_i, (sub, _state)) in &mut entry.iter_mut().enumerate().rev() { if !folded && sub.try_fold(&access) { access = *sub; @@ -306,13 +396,11 @@ impl<'cmd> CommandList<'cmd> { // conflicting access must be of opposite polarity (or a // layout transition) and no more accesses can be folded // into it. - // we store the index of `to`, as more accesses may be - // folded into it, expanding mips/array layers. barriers.push(Barrier { resource_id: *id, command_index: *command_index, from: *sub, - to: len, + to: access, }); // we can stop iterating over previous accesses, as we @@ -335,23 +423,83 @@ impl<'cmd> CommandList<'cmd> { } } - // construct barriers - // TODO: need to know each images aspects at this point - for _barrier in barriers { - // stuff + let mut result = Vec::with_capacity(barriers.len()); + for barrier in barriers { + let pipeline_barrier = match (&barrier.from.range, &barrier.to.range) { + ( + ResourceRange::Texture { + layout: old_layout, .. + }, + ResourceRange::Texture { + aspect, + mip_level, + array_layers, + layout: new_layout, + .. + }, + ) => { + let format_aspects = recorder + .get_image_desc(barrier.resource_id) + .format + .aspect_flags(); + let aspects = *aspect & format_aspects; + if aspects.is_empty() { + tracing::debug!(resource = ?barrier.resource_id, "skipping image barrier with no compatible aspects"); + continue; + } + let image_barrier = vk::ImageMemoryBarrier2::default() + .src_stage_mask(barrier.from.stages) + .src_access_mask(barrier.from.access) + .dst_stage_mask(barrier.to.stages) + .dst_access_mask(barrier.to.access) + .old_layout(*old_layout) + .new_layout(*new_layout) + .image(recorder.get_image_handle(barrier.resource_id)) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(aspects) + .base_mip_level(mip_level.start()) + .level_count(mip_level.len()) + .base_array_layer(array_layers.start()) + .layer_count(array_layers.len()), + ); + super::commands::PipelineBarrier { + image_barriers: vec![image_barrier], + buffer_barriers: Vec::new(), + image_layouts: vec![(barrier.resource_id, *new_layout)], + } + } + (ResourceRange::Buffer { offset, size }, ResourceRange::Buffer { .. }) => { + let buffer_barrier = vk::BufferMemoryBarrier2::default() + .src_stage_mask(barrier.from.stages) + .src_access_mask(barrier.from.access) + .dst_stage_mask(barrier.to.stages) + .dst_access_mask(barrier.to.access) + .buffer(recorder.get_buffer_handle(barrier.resource_id)) + .offset(*offset) + .size(*size); + super::commands::PipelineBarrier { + image_barriers: Vec::new(), + buffer_barriers: vec![buffer_barrier], + image_layouts: Vec::new(), + } + } + _ => continue, + }; + result.push((barrier.command_index, pipeline_barrier)); } - - todo!() + result.sort_by_key(|(command_index, _)| *command_index); + result } - fn record_commands(&self, recorder: &mut CommandRecorder) { + pub(crate) fn record_commands(&self, recorder: &mut CommandRecorder) { let mut cursor = 0; - let mut barriers = self.fold_side_effects(); + let mut barriers = self.fold_side_effects(recorder); let mut command_index = 0; while cursor < self.command_bytes.len() { barriers - .drain(barriers.partition_point(|(idx, _)| *idx <= command_index)..) + .drain(..barriers.partition_point(|(idx, _)| *idx <= command_index)) .for_each(|(_, barrier)| { barrier.apply(recorder); }); diff --git a/crates/renderer/src/rendering/mod.rs b/crates/renderer/src/rendering/mod.rs index 768a239..2d87507 100644 --- a/crates/renderer/src/rendering/mod.rs +++ b/crates/renderer/src/rendering/mod.rs @@ -17,7 +17,10 @@ use crate::{ images::ImageViewDesc, pipeline, render_graph::{ - Access, GraphResourceId, PassDesc, RecordFn, RenderContext, RenderGraph, buffer_barrier, + Access, BindPipeline, BufferRange, BufferSlice, Draw, DrawIndexedData, GraphBuffer, + GraphBuilder, GraphResourceId, ImportResource, IndexFormat, PassDesc, PushConstants, + RecordFn, RenderContext, RenderGraph, RenderingAttachment, SetScissor, SetViewport, + Texture, TextureRange, TextureRegion, buffer_barrier, }, sync, util::Rgba8, @@ -164,14 +167,14 @@ impl Wireframe { ); } + let pipeline = Self::create_pipeline(dev.clone())?; + let future = cmd.submit_async( None, None, Arc::new(sync::Fence::from_pool(&dev.pools.fences, None)?), )?; - let pipeline = Self::create_pipeline(dev.clone())?; - future.await; Ok(Self { @@ -212,13 +215,13 @@ impl Wireframe { flags: vk::PipelineShaderStageCreateFlags::empty(), module: &shader, stage: vk::ShaderStageFlags::FRAGMENT, - entry: c"main".into(), + entry: c"fragment".into(), }, pipeline::ShaderStageDesc { flags: vk::PipelineShaderStageCreateFlags::empty(), module: &shader, stage: vk::ShaderStageFlags::VERTEX, - entry: c"main".into(), + entry: c"vertex".into(), }, ], render_pass: None, @@ -380,4 +383,136 @@ impl Wireframe { }); Ok(()) } + + pub fn typed_pass( + &self, + builder: &mut GraphBuilder<'_>, + target: Texture, + extent: (u32, u32), + list: &mut crate::render_graph::CommandList<'_>, + ) { + // Buffers are shared with this long-lived scene object, so the typed + // graph retains Arc references rather than taking ownership of them. + let positions_size = self.positions.len(); + let indices_size = self.indices.len(); + let colors_size = self.colors.len(); + let positions = builder.import_shared_buffer(self.positions.clone(), None); + let indices = builder.import_shared_buffer(self.indices.clone(), None); + let colors = builder.import_shared_buffer(self.colors.clone(), None); + + list.push(ImportResource { + resource: GraphBuffer(positions), + access: vk::AccessFlags2::VERTEX_ATTRIBUTE_READ, + stage: vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT, + layout: None, + }); + list.push(ImportResource { + resource: GraphBuffer(indices), + access: vk::AccessFlags2::INDEX_READ, + stage: vk::PipelineStageFlags2::INDEX_INPUT, + layout: None, + }); + list.push(ImportResource { + resource: GraphBuffer(colors), + access: vk::AccessFlags2::VERTEX_ATTRIBUTE_READ, + stage: vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT, + layout: None, + }); + + let proj = proj::vulkan::perspective( + core::f32::consts::FRAC_PI_4, + extent.0 as f32 / extent.1 as f32, + 0.1, + 100.0, + ); + let pos = vec3(0.0, 0.0, -5.0); + let view = view::look_at_mat4(pos, pos + vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0)); + let to_clip = proj * view; + + let mut pass = list.begin_rendering( + vec![RenderingAttachment { + region: TextureRegion { + texture: target, + range: TextureRange { + aspect: vk::ImageAspectFlags::COLOR, + mip_levels: (0..1).into(), + array_layers: (0..1).into(), + origin: (0, 0, 0), + extent: (extent.0, extent.1, 1), + }, + }, + load_op: vk::AttachmentLoadOp::CLEAR, + store_op: vk::AttachmentStoreOp::STORE, + clear_value: vk::ClearValue { + color: vk::ClearColorValue { + float32: [0.1, 0.1, 0.12, 1.0], + }, + }, + }], + None, + None, + extent, + 1, + ); + pass.push(SetScissor { + x: 0, + y: 0, + width: extent.0, + height: extent.1, + }); + pass.push(SetViewport { + x: 0.0, + y: 0.0, + width: extent.0 as f32, + height: extent.1 as f32, + min_depth: 0.0, + max_depth: 1.0, + }); + pass.push(BindPipeline( + self.pipeline.raw(), + vk::PipelineBindPoint::GRAPHICS, + )); + pass.push(PushConstants { + pipeline_layout: self.pipeline.layout().raw(), + stage_flags: vk::ShaderStageFlags::VERTEX, + offset: 0, + data: bytemuck::cast_slice(&[to_clip]).to_vec(), + }); + pass.push(Draw { + data: DrawIndexedData { + index_count: self.num_indices, + instance_count: 1, + first_index: 0, + vertex_offset: 0, + first_instance: 0, + }, + vertex_buffers: vec![ + BufferSlice { + buffer: GraphBuffer(positions), + range: BufferRange { + offset: 0, + size: positions_size, + }, + }, + BufferSlice { + buffer: GraphBuffer(colors), + range: BufferRange { + offset: 0, + size: colors_size, + }, + }, + ], + index_buffer: Some(( + BufferSlice { + buffer: GraphBuffer(indices), + range: BufferRange { + offset: 0, + size: indices_size, + }, + }, + IndexFormat::Uint32, + )), + }); + pass.finalise(); + } }