Luna: add command impls, simple wireframe demo

This commit is contained in:
janis 2026-10-23 23:01:22 +02:00
parent cb02051cf3
commit ab0812ff72
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
9 changed files with 1074 additions and 148 deletions

View file

@ -31,6 +31,8 @@ struct WinitState {
renderer: Renderer2, renderer: Renderer2,
last_redraw: std::time::Instant, last_redraw: std::time::Instant,
egui_state: renderer::EguiState, egui_state: renderer::EguiState,
wireframe: Option<renderer::rendering::Wireframe>,
typed_graph: bool,
} }
impl WinitState { impl WinitState {
@ -38,6 +40,29 @@ impl WinitState {
const BASE_HEIGHT: u32 = 600; const BASE_HEIGHT: u32 = 600;
fn new(window_title: String, display: DisplayHandle) -> WinitState { fn new(window_title: String, display: DisplayHandle) -> WinitState {
let renderer = Renderer2::new(display.as_raw()).expect("renderer"); 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 { Self {
windows: BTreeMap::new(), windows: BTreeMap::new(),
@ -49,6 +74,8 @@ impl WinitState {
// TODO: pass down this error and add some kind of error handling UI or dump // TODO: pass down this error and add some kind of error handling UI or dump
egui_state: renderer::EguiState::new(renderer.device().clone()).unwrap(), egui_state: renderer::EguiState::new(renderer.device().clone()).unwrap(),
renderer, renderer,
wireframe,
typed_graph,
last_redraw: std::time::Instant::now(), last_redraw: std::time::Instant::now(),
} }
} }
@ -99,39 +126,55 @@ impl WinitState {
.filter_map(|id| self.egui_state.lookup_texture(*id)) .filter_map(|id| self.egui_state.lookup_texture(*id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// rendering // rendering
let render = self.renderer.draw_graph(&window.surface, |renderer, rg| { let render_result = if self.typed_graph {
use renderer::rendering::{egui_pass, egui_pre_pass}; let wireframe = self
let framebuffer = rg.get_framebuffer().unwrap(); .wireframe
let dev = renderer.device().clone(); .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; use renderer::ash::vk::Handle;
let [r, g, b]: [f32; 3] = let [r, g, b]: [f32; 3] =
rand::prelude::StdRng::seed_from_u64(window.surface.raw().as_raw()) rand::prelude::StdRng::seed_from_u64(window.surface.raw().as_raw())
.random::<[f32; 3]>(); .random::<[f32; 3]>();
render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer); render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer);
egui_pre_pass( egui_pre_pass(
&dev, &dev,
rg, rg,
&mut renderer.texture_manager, &mut renderer.texture_manager,
&mut self.egui_state, &mut self.egui_state,
&output, &output,
)?; )?;
let _ = egui_pass( let _ = egui_pass(
&dev, &dev,
rg, rg,
&mut renderer.texture_manager, &mut renderer.texture_manager,
&mut renderer.samplers, &mut renderer.samplers,
&mut self.egui_state, &mut self.egui_state,
&window.egui_platform.context(), &window.egui_platform.context(),
output, output,
framebuffer, framebuffer,
)?; )?;
renderer::Result::Ok(()) renderer::Result::Ok(())
}); });
smol::block_on(render).flatten()
};
match smol::block_on(render).flatten() { match render_result {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
tracing::error!("encountered error while rendering: {e}"); tracing::error!("encountered error while rendering: {e}");

View file

@ -335,7 +335,13 @@ pub mod traits {
tracing::trace!("submitted queue {:?} and fence {:?}", queue, fence); 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( 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 { mod command_pools {
use std::{borrow::Cow, cell::Cell, mem::ManuallyDrop, sync::Arc}; use std::{borrow::Cow, cell::Cell, mem::ManuallyDrop, sync::Arc};
@ -689,10 +697,12 @@ mod command_pools {
pub fn free_command_buffers(&self) { pub fn free_command_buffers(&self) {
self.free_list_mut(|levels, buffers| { self.free_list_mut(|levels, buffers| {
unsafe { if !buffers.is_empty() {
self.device() unsafe {
.dev() self.device()
.free_command_buffers(self.handle(), &buffers); .dev()
.free_command_buffers(self.handle(), &buffers);
}
} }
levels.clear(); levels.clear();
buffers.clear(); buffers.clear();
@ -829,6 +839,13 @@ mod command_pools {
} }
impl CommandPools { impl CommandPools {
pub fn new(device: Device) -> Self {
Self {
device,
shards: ThreadLocal::new(),
}
}
pub fn get_for_family(&self, family: u32) -> Arc<CommandPool> { pub fn get_for_family(&self, family: u32) -> Arc<CommandPool> {
let shard = self let shard = self
.shards .shards

View file

@ -184,6 +184,10 @@ pub struct Image {
} }
impl Image { impl Image {
pub(crate) fn desc(&self) -> &ImageDesc {
&self.desc
}
pub fn new(device: Device, desc: ImageDesc) -> crate::Result<Self> { pub fn new(device: Device, desc: ImageDesc) -> crate::Result<Self> {
let (image, requirements) = Self::new_raw(device.clone(), &desc)?; let (image, requirements) = Self::new_raw(device.clone(), &desc)?;
let alloc = device.alloc2.lock().allocate(&AllocationCreateDesc { let alloc = device.alloc2.lock().allocate(&AllocationCreateDesc {

View file

@ -36,6 +36,7 @@ pub mod sync;
mod text; mod text;
pub mod util; pub mod util;
use commands::traits::CommandBufferExt;
use device::Device; use device::Device;
mod texture { mod texture {
@ -91,6 +92,8 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
Result(#[from] ash::vk::Result), Result(#[from] ash::vk::Result),
#[error(transparent)] #[error(transparent)]
CommandPool(#[from] commands::CommandPoolError),
#[error(transparent)]
CStrError(#[from] core::ffi::c_str::FromBytesUntilNulError), CStrError(#[from] core::ffi::c_str::FromBytesUntilNulError),
#[error(transparent)] #[error(transparent)]
Utf8Error(#[from] core::str::Utf8Error), Utf8Error(#[from] core::str::Utf8Error),
@ -925,6 +928,7 @@ impl EguiState {
pub struct Renderer2 { pub struct Renderer2 {
pub device: Device, pub device: Device,
command_pools: commands::CommandPools,
pub samplers: SamplerCache, pub samplers: SamplerCache,
pub texture_manager: texture::TextureManager, pub texture_manager: texture::TextureManager,
@ -946,6 +950,7 @@ impl Renderer2 {
PhysicalDeviceFeatures { PhysicalDeviceFeatures {
core: vk::PhysicalDeviceFeatures { core: vk::PhysicalDeviceFeatures {
multi_draw_indirect: vk::TRUE, multi_draw_indirect: vk::TRUE,
fill_mode_non_solid: vk::TRUE,
..Default::default() ..Default::default()
}, },
core11: vk::PhysicalDeviceVulkan11Features { core11: vk::PhysicalDeviceVulkan11Features {
@ -970,6 +975,7 @@ impl Renderer2 {
)?; )?;
Ok(Self { Ok(Self {
command_pools: commands::CommandPools::new(device.clone()),
samplers: SamplerCache::new(device.clone()), samplers: SamplerCache::new(device.clone()),
texture_manager: texture::TextureManager::new(device.clone()), texture_manager: texture::TextureManager::new(device.clone()),
display, display,
@ -1047,6 +1053,79 @@ impl Renderer2 {
Ok(out) Ok(out)
} }
pub async fn draw_graph_typed<T, F>(&mut self, surface: &swapchain::Surface, cb: F) -> Result<T>
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; pub use vk::Extent2D;

View file

@ -16,6 +16,7 @@ use crate::{
device::DeviceOwned, device::DeviceOwned,
render_graph::recorder::{CommandRecorder, SideEffectMap}, render_graph::recorder::{CommandRecorder, SideEffectMap},
}; };
use ash_formats::{FormatExt as _, FormatHelpers as _};
use super::resources::*; use super::resources::*;
@ -36,7 +37,10 @@ impl<T: Resource> Command for ImportResource<T> {
)); ));
} }
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 // No actual GPU commands needed for import, as it's just a logical operation
} }
} }
@ -72,7 +76,7 @@ impl<T: Resource, U: Resource> Copy<T, U> {
vk::AccessFlags2::TRANSFER_READ, vk::AccessFlags2::TRANSFER_READ,
Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL), 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::PipelineStageFlags2::TRANSFER,
vk::AccessFlags2::TRANSFER_WRITE, vk::AccessFlags2::TRANSFER_WRITE,
Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), 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 { 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 struct BeginRendering {
pub color_attachments: Vec<TextureRegion>, pub color_attachments: Vec<RenderingAttachment>,
pub depth_attachment: Option<TextureRegion>, pub depth_attachment: Option<RenderingAttachment>,
pub stencil_attachment: Option<TextureRegion>, pub stencil_attachment: Option<RenderingAttachment>,
pub area: (u32, u32), pub area: (u32, u32),
pub layers: 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 { impl Command for BeginRendering {
fn side_effects(&self, mut map: SideEffectMap) { fn side_effects(&self, mut map: SideEffectMap) {
for attachment in &self.color_attachments { for attachment in &self.color_attachments {
@ -313,30 +358,107 @@ impl Command for BeginRendering {
// pass instance referencing framebuffer-local stages must contain // pass instance referencing framebuffer-local stages must contain
// only framebuffer-local stages. // only framebuffer-local stages.
// TODO: consider loadop and storeop? attachment
attachment.side_effect(map.reborrow().into_side_effect_map2( .region
vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, .side_effect(map.reborrow().into_side_effect_map2(
vk::AccessFlags2::COLOR_ATTACHMENT_READ, vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
Some(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL), 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 { if let Some(depth) = &self.depth_attachment {
depth.side_effect(map.reborrow().into_side_effect_map2( depth
vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS, .region
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ, .side_effect(map.reborrow().into_side_effect_map2(
Some(vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL), 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 { if let Some(stencil) = &self.stencil_attachment {
stencil.side_effect(map.reborrow().into_side_effect_map2( stencil
vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS, .region
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ, .side_effect(map.reborrow().into_side_effect_map2(
Some(vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL), 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::<Vec<_>>();
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; pub struct EndRendering;
@ -346,6 +468,31 @@ impl Command for EndRendering {
// No resource access, but ends the render pass // 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) {} fn apply(self, _recorder: &mut CommandRecorder) {}
} }
@ -475,7 +622,20 @@ impl Command for SetViewport {
// No resource access, but affects the viewport state // 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 { pub struct SetScissor {
@ -490,7 +650,22 @@ impl Command for SetScissor {
// No resource access, but affects the scissor state // 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 { pub struct PushConstants {
@ -580,16 +755,34 @@ impl<T: IsDrawData> Command for Draw<T> {
} }
} }
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<vk::ImageMemoryBarrier2<'static>>,
pub buffer_barriers: Vec<vk::BufferMemoryBarrier2<'static>>,
pub(crate) image_layouts: Vec<(ResourceId, vk::ImageLayout)>,
}
impl Command for PipelineBarrier { impl Command for PipelineBarrier {
fn side_effects(&self, _map: SideEffectMap) {} fn side_effects(&self, _map: SideEffectMap) {}
fn apply(self, _recorder: &mut CommandRecorder) { fn apply(self, recorder: &mut CommandRecorder) {
todo!() 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 { mod sealed {
use super::*;
pub trait IsDrawData { pub trait IsDrawData {
fn side_effects(&self, _map: super::SideEffectMap) {} fn side_effects(&self, _map: super::SideEffectMap) {}
fn apply(
self,
recorder: &mut CommandRecorder,
vertex_buffers: Vec<BufferSlice>,
index_buffer: Option<(BufferSlice, IndexFormat)>,
);
} }
pub trait InsideRenderPass {} pub trait InsideRenderPass {}
pub trait OutsideRenderPass {} pub trait OutsideRenderPass {}
@ -608,8 +809,47 @@ mod sealed {
use ash::vk; use ash::vk;
pub(super) use sealed::*; pub(super) use sealed::*;
impl IsDrawData for DrawData {} impl IsDrawData for DrawData {
impl IsDrawData for DrawIndexedData {} fn apply(
self,
recorder: &mut CommandRecorder,
vertex_buffers: Vec<BufferSlice>,
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<BufferSlice>,
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 { impl IsDrawData for DrawIndirectData {
fn side_effects(&self, mut map: SideEffectMap) { fn side_effects(&self, mut map: SideEffectMap) {
self.indirect_buffer self.indirect_buffer
@ -619,6 +859,24 @@ impl IsDrawData for DrawIndirectData {
None, None,
)); ));
} }
fn apply(
self,
recorder: &mut CommandRecorder,
vertex_buffers: Vec<BufferSlice>,
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 { impl IsDrawData for DrawIndexedIndirectData {
fn side_effects(&self, mut map: SideEffectMap) { fn side_effects(&self, mut map: SideEffectMap) {
@ -629,9 +887,65 @@ impl IsDrawData for DrawIndexedIndirectData {
None, None,
)); ));
} }
fn apply(
self,
recorder: &mut CommandRecorder,
vertex_buffers: Vec<BufferSlice>,
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::<Vec<_>>();
let offsets = vertex_buffers
.iter()
.map(|buffer| buffer.range.offset)
.collect::<Vec<_>>();
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 BeginRendering {}
impl<T: Resource> OutsideRenderPass for ImportResource<T> {}
impl OutsideRenderPass for PreparePresent {}
impl InsideRenderPass for EndRendering {} impl InsideRenderPass for EndRendering {}
impl<T: Resource, U: Resource> OutsideRenderPass for Copy<T, U> {} impl<T: Resource, U: Resource> OutsideRenderPass for Copy<T, U> {}
impl OutsideRenderPass for ClearTexture {} impl OutsideRenderPass for ClearTexture {}

View file

@ -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 ash::vk;
use derive_more::derive::{Deref, DerefMut}; use gpu_allocator::MemoryLocation;
use parking_lot::RwLock;
use crate::{ use crate::{
buffers, images, Device, buffers,
device::{AllocationStrategy, QueueFlags},
images,
render_graph::{ render_graph::{
recorder::CommandList, recorder::CommandList,
resources::{self, ResourceId}, resources::{self, ResourceId},
}, },
}; };
pub struct GraphResourceRegistry { #[derive(Default)]
images: BTreeMap<ResourceId, UnsafeCell<images::Image>>, pub(crate) struct GraphResourceRegistry {
views: BTreeMap<ResourceId, UnsafeCell<images::ImageView>>, pub(crate) images: RwLock<BTreeMap<ResourceId, Arc<images::Image>>>,
buffers: BTreeMap<ResourceId, UnsafeCell<buffers::Buffer>>, pub(crate) views: RwLock<BTreeMap<ResourceId, images::ImageView>>,
pub(crate) buffers: RwLock<BTreeMap<ResourceId, Arc<std::cell::UnsafeCell<buffers::Buffer>>>>,
pub(crate) shared_buffers: RwLock<BTreeMap<ResourceId, Arc<buffers::Buffer>>>,
pub(crate) owning_queues: RwLock<BTreeMap<ResourceId, Option<u32>>>,
} }
#[derive(Deref, DerefMut)] pub struct StagingBuffer<'a> {
struct StagingBuffer<'a> { pub id: ResourceId,
id: ResourceId, buffer: Arc<std::cell::UnsafeCell<buffers::Buffer>>,
#[deref] _pd: PhantomData<&'a mut buffers::Buffer>,
#[deref_mut]
buffer: &'a mut buffers::Buffer,
} }
#[derive(Deref, DerefMut)] pub struct Buffer<'a> {
struct Buffer<'a> { pub id: ResourceId,
id: ResourceId, buffer: Arc<std::cell::UnsafeCell<buffers::Buffer>>,
#[deref] _pd: PhantomData<&'a mut buffers::Buffer>,
#[deref_mut] }
buffer: &'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> { pub struct GraphBuilder<'r> {
_pd: PhantomData<fn(&'r ())>, device: Device,
registry: Arc<GraphResourceRegistry>,
next_id: Cell<u64>,
_pd: PhantomData<fn(&'r mut ())>,
} }
enum GraphResource {
Buffer(buffers::Buffer, Option<u32>),
SharedBuffer(Arc<buffers::Buffer>, Option<u32>),
Image(Arc<images::Image>),
}
impl<'r> GraphBuilder<'r> { impl<'r> GraphBuilder<'r> {
fn make_command_list<'cmd>(&self) -> CommandList<'cmd> { pub fn new(device: Device) -> Self {
todo!() Self {
} device,
fn make_staging_buffer(&self, _size: u64) -> StagingBuffer<'r> { registry: Arc::new(GraphResourceRegistry::default()),
todo!() next_id: Cell::new(0),
_pd: PhantomData,
}
} }
fn make_buffer( pub(crate) fn registry(&self) -> Arc<GraphResourceRegistry> {
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, &self,
_size: u64, size: u64,
_usage: vk::BufferUsageFlags, usage: vk::BufferUsageFlags,
_location: gpu_allocator::MemoryLocation, location: MemoryLocation,
) -> Buffer<'r> { ) -> 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 // we need to know which queue currently owns this buffer to be able to
// transfer it to the correct queue family when using it // transfer it to the correct queue family when using it
fn import_buffer(&self, _buffer: buffers::Buffer, _owning_queue: Option<u32>) -> Buffer<'r> { pub fn import_buffer(&self, buffer: buffers::Buffer, owning_queue: Option<u32>) -> Buffer<'r> {
todo!() let id = self.register_resource(GraphResource::Buffer(buffer, owning_queue));
self.buffer_wrapper(id)
}
pub fn import_shared_buffer(
&self,
buffer: Arc<buffers::Buffer>,
owning_queue: Option<u32>,
) -> 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<images::Image>) -> resources::Texture {
resources::Texture(self.register_resource(GraphResource::Image(image)))
} }
} }
@ -231,14 +401,21 @@ mod api_demo {
}); });
let mut pass_cmd = cmd.begin_rendering( let mut pass_cmd = cmd.begin_rendering(
vec![resources::TextureRegion { vec![commands::RenderingAttachment {
texture: resources::Texture(render_target), region: resources::TextureRegion {
range: resources::TextureRange { texture: resources::Texture(render_target),
aspect: vk::ImageAspectFlags::COLOR, range: resources::TextureRange {
mip_levels: (0..).into(), aspect: vk::ImageAspectFlags::COLOR,
array_layers: (0..).into(), mip_levels: (0..).into(),
origin: (0, 0, 0), array_layers: (0..).into(),
extent: (extent.0, extent.1, 1), 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, None,
@ -262,9 +439,6 @@ mod api_demo {
max_depth: 1.0, max_depth: 1.0,
}); });
fn factory<T>() -> T {
unimplemented!()
}
pass_cmd.push(commands::BindPipeline( pass_cmd.push(commands::BindPipeline(
pipeline.raw(), pipeline.raw(),
pipeline.bind_point(), pipeline.bind_point(),

View file

@ -7,3 +7,15 @@ mod commands;
mod graph_builder; mod graph_builder;
mod recorder; mod recorder;
mod resources; 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,
};

View file

@ -3,44 +3,131 @@ use std::{
marker::PhantomData, marker::PhantomData,
mem::{MaybeUninit, size_of}, mem::{MaybeUninit, size_of},
ptr::NonNull, ptr::NonNull,
sync::Arc,
}; };
use ash::vk; use ash::vk;
use ash_formats::FormatExt;
use crate::{ use crate::{
commands::CommandBuffer, commands::CommandBuffer,
images::ImageDesc, images::{ImageDesc, ImageViewDesc},
render_graph::{ render_graph::{
commands::{Command, InsideRenderPass, OutsideRenderPass}, 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<GraphResourceRegistry>,
layouts: BTreeMap<ResourceId, vk::ImageLayout>,
}
impl CommandRecorder { impl CommandRecorder {
pub(crate) fn new(cmd_buffer: CommandBuffer, registry: Arc<GraphResourceRegistry>) -> Self {
Self {
cmd_buffer,
registry,
layouts: BTreeMap::new(),
}
}
pub fn cmd_buffer(&self) -> &CommandBuffer { pub fn cmd_buffer(&self) -> &CommandBuffer {
unimplemented!() &self.cmd_buffer
} }
pub fn get_buffer_handle(&self, _id: ResourceId) -> vk::Buffer { pub fn into_command_buffer(self) -> CommandBuffer {
unimplemented!() self.cmd_buffer
} }
pub fn get_image_desc(&self, _id: ResourceId) -> &ImageDesc { pub fn get_buffer_handle(&self, id: ResourceId) -> vk::Buffer {
unimplemented!() 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 { pub fn get_image_desc(&self, id: ResourceId) -> ImageDesc {
unimplemented!() self.registry
.images
.read()
.get(&id)
.expect("unknown graph image")
.desc()
.clone()
} }
pub fn get_image_layout(&self, _id: ResourceId) -> vk::ImageLayout { pub fn get_image_handle(&self, id: ResourceId) -> vk::Image {
unimplemented!() self.registry
.images
.read()
.get(&id)
.expect("unknown graph image")
.raw()
} }
pub fn get_image_view_handle(&self, _id: ResourceId) -> vk::ImageView { pub fn get_image_layout(&self, id: ResourceId) -> vk::ImageLayout {
unimplemented!() 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( pub fn begin_rendering(
&mut self, &mut self,
color_attachments: Vec<TextureRegion>, color_attachments: Vec<super::commands::RenderingAttachment>,
depth_attachment: Option<TextureRegion>, depth_attachment: Option<super::commands::RenderingAttachment>,
stencil_attachment: Option<TextureRegion>, stencil_attachment: Option<super::commands::RenderingAttachment>,
area: (u32, u32), area: (u32, u32),
layers: u32, layers: u32,
) -> RenderPass<'cmd, '_> { ) -> RenderPass<'cmd, '_> {
@ -219,7 +306,7 @@ impl<'cmd> CommandList<'cmd> {
let packed = Packed { let packed = Packed {
meta: CommandMeta { meta: CommandMeta {
apply_command: |recorder, command_ptr, cursor| { apply_command: |recorder, command_ptr, cursor| {
*cursor += size_of::<C>(); *cursor += size_of::<CommandMeta>() + size_of::<C>();
let command = unsafe { command_ptr.cast::<C>().read_unaligned() }; let command = unsafe { command_ptr.cast::<C>().read_unaligned() };
C::apply(command, recorder); C::apply(command, recorder);
}, },
@ -232,10 +319,14 @@ impl<'cmd> CommandList<'cmd> {
unsafe { unsafe {
let bytes = self.command_bytes.as_mut_ptr().add(len); let bytes = self.command_bytes.as_mut_ptr().add(len);
bytes.cast::<Packed<C>>().write_unaligned(packed); bytes.cast::<Packed<C>>().write_unaligned(packed);
self.command_bytes.set_len(len + size_of::<Packed<C>>());
} }
} }
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; // 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 // 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, resource_id: ResourceId,
command_index: u32, command_index: u32,
from: ResourceAccess, from: ResourceAccess,
to: usize, to: ResourceAccess,
} }
// map to keep track of the current state of each subresource // 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. // a barrier before the current command index.
let mut folded = false; let mut folded = false;
let len = entry.len();
for (_i, (sub, _state)) in &mut entry.iter_mut().enumerate().rev() { for (_i, (sub, _state)) in &mut entry.iter_mut().enumerate().rev() {
if !folded && sub.try_fold(&access) { if !folded && sub.try_fold(&access) {
access = *sub; access = *sub;
@ -306,13 +396,11 @@ impl<'cmd> CommandList<'cmd> {
// conflicting access must be of opposite polarity (or a // conflicting access must be of opposite polarity (or a
// layout transition) and no more accesses can be folded // layout transition) and no more accesses can be folded
// into it. // into it.
// we store the index of `to`, as more accesses may be
// folded into it, expanding mips/array layers.
barriers.push(Barrier { barriers.push(Barrier {
resource_id: *id, resource_id: *id,
command_index: *command_index, command_index: *command_index,
from: *sub, from: *sub,
to: len, to: access,
}); });
// we can stop iterating over previous accesses, as we // we can stop iterating over previous accesses, as we
@ -335,23 +423,83 @@ impl<'cmd> CommandList<'cmd> {
} }
} }
// construct barriers let mut result = Vec::with_capacity(barriers.len());
// TODO: need to know each images aspects at this point for barrier in barriers {
for _barrier in barriers { let pipeline_barrier = match (&barrier.from.range, &barrier.to.range) {
// stuff (
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));
} }
result.sort_by_key(|(command_index, _)| *command_index);
todo!() result
} }
fn record_commands(&self, recorder: &mut CommandRecorder) { pub(crate) fn record_commands(&self, recorder: &mut CommandRecorder) {
let mut cursor = 0; let mut cursor = 0;
let mut barriers = self.fold_side_effects(); let mut barriers = self.fold_side_effects(recorder);
let mut command_index = 0; let mut command_index = 0;
while cursor < self.command_bytes.len() { while cursor < self.command_bytes.len() {
barriers barriers
.drain(barriers.partition_point(|(idx, _)| *idx <= command_index)..) .drain(..barriers.partition_point(|(idx, _)| *idx <= command_index))
.for_each(|(_, barrier)| { .for_each(|(_, barrier)| {
barrier.apply(recorder); barrier.apply(recorder);
}); });

View file

@ -17,7 +17,10 @@ use crate::{
images::ImageViewDesc, images::ImageViewDesc,
pipeline, pipeline,
render_graph::{ 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, sync,
util::Rgba8, util::Rgba8,
@ -164,14 +167,14 @@ impl Wireframe {
); );
} }
let pipeline = Self::create_pipeline(dev.clone())?;
let future = cmd.submit_async( let future = cmd.submit_async(
None, None,
None, None,
Arc::new(sync::Fence::from_pool(&dev.pools.fences, None)?), Arc::new(sync::Fence::from_pool(&dev.pools.fences, None)?),
)?; )?;
let pipeline = Self::create_pipeline(dev.clone())?;
future.await; future.await;
Ok(Self { Ok(Self {
@ -212,13 +215,13 @@ impl Wireframe {
flags: vk::PipelineShaderStageCreateFlags::empty(), flags: vk::PipelineShaderStageCreateFlags::empty(),
module: &shader, module: &shader,
stage: vk::ShaderStageFlags::FRAGMENT, stage: vk::ShaderStageFlags::FRAGMENT,
entry: c"main".into(), entry: c"fragment".into(),
}, },
pipeline::ShaderStageDesc { pipeline::ShaderStageDesc {
flags: vk::PipelineShaderStageCreateFlags::empty(), flags: vk::PipelineShaderStageCreateFlags::empty(),
module: &shader, module: &shader,
stage: vk::ShaderStageFlags::VERTEX, stage: vk::ShaderStageFlags::VERTEX,
entry: c"main".into(), entry: c"vertex".into(),
}, },
], ],
render_pass: None, render_pass: None,
@ -380,4 +383,136 @@ impl Wireframe {
}); });
Ok(()) 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();
}
} }