Initial attempt to implement Render Graph

This commit is contained in:
Tony Klink 2024-04-06 22:31:24 -06:00
parent 173cfe2acf
commit 3732fc5e3d
Signed by: klink
GPG key ID: 85175567C4D19231
33 changed files with 8851 additions and 938 deletions

View file

@ -8,9 +8,12 @@ edition = "2021"
[dependencies]
khors-core = { path = "../../khors-core", version = "0.1.0" }
egui-vulkano = { path = "../../vendor/egui-vulkano", version = "0.1.0" }
egui-snarl = { path = "../../vendor/egui-snarl", features = ["serde"] }
anyhow = "1.0.80"
thiserror = "1.0.58"
egui = "0.27.1"
syn = "2.0.58"
glam = "0.27.0"
winit = { version = "0.29.15",features = ["rwh_05"] }
vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" }

View file

@ -1,555 +1,10 @@
pub mod debug_gui;
pub mod render_module;
use flax::{entity_ids, BoxedSystem, Query, QueryBorrow, Schedule, System, World};
use glam::{
f32::{Mat3, Vec3},
Mat4,
};
use std::sync::Arc;
use vulkano::{
buffer::{
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
Buffer, BufferCreateInfo, BufferUsage,
},
command_buffer::{
allocator::{CommandBufferAllocator, StandardCommandBufferAllocator},
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
RenderPassBeginInfo,
},
descriptor_set::{
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet,
},
device::DeviceOwned,
format::Format,
image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
pipeline::{
graphics::{
color_blend::{ColorBlendAttachmentState, ColorBlendState},
depth_stencil::{DepthState, DepthStencilState},
input_assembly::InputAssemblyState,
multisample::MultisampleState,
rasterization::RasterizationState,
vertex_input::{Vertex, VertexDefinition},
viewport::{Viewport, ViewportState},
GraphicsPipelineCreateInfo,
},
layout::PipelineDescriptorSetLayoutCreateInfo,
GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout,
PipelineShaderStageCreateInfo,
},
render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass},
shader::EntryPoint,
sync::GpuFuture,
};
use vulkano_util::{
context::VulkanoContext, renderer::VulkanoWindowRenderer, window::VulkanoWindows,
};
use egui_vulkano::Gui;
use crate::debug_gui::DebugGuiStack;
use crate::render_module::RenderModule as ThreadLocalModule;
use self::{
model::{INDICES, NORMALS, POSITIONS},
vulkan::vertex::{Normal, Position},
};
pub mod events;
mod model;
pub mod renderer;
pub mod rendergraph;
pub mod node_editor;
mod temp;
mod test_pipeline;
mod vulkan;
pub struct RenderModule {
schedule: Schedule,
memory_allocator: Arc<StandardMemoryAllocator>,
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
command_buffer_allocator: Arc<dyn CommandBufferAllocator>,
viewport: Viewport,
rotation_start: std::time::Instant,
}
impl RenderModule {
pub fn new(
vk_context: &mut VulkanoContext,
_vk_windows: &mut VulkanoWindows,
_schedule: &mut Schedule,
_world: &mut World,
_events: &mut khors_core::events::Events,
) -> Self {
let schedule = Schedule::builder()
.with_system(add_distance_system())
.build();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(
vk_context.device().clone(),
));
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
vk_context.device().clone(),
Default::default(),
));
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
vk_context.device().clone(),
Default::default(),
));
let viewport = Viewport {
offset: [0.0, 0.0],
extent: [0.0, 0.0],
depth_range: 0.0..=1.0,
};
let rotation_start = std::time::Instant::now();
Self {
schedule,
memory_allocator,
descriptor_set_allocator,
command_buffer_allocator,
viewport,
rotation_start,
}
}
}
impl ThreadLocalModule for RenderModule {
fn on_update(
&mut self,
gui_stack: &mut DebugGuiStack,
vk_context: &mut VulkanoContext,
vk_windows: &mut vulkano_util::window::VulkanoWindows,
world: &mut World,
_events: &mut khors_core::events::Events,
_frame_time: std::time::Duration,
) -> anyhow::Result<()> {
self.schedule.execute_seq(world).unwrap();
let viewport = &mut self.viewport;
for (window_id, renderer) in vk_windows.iter_mut() {
let gui = gui_stack.get_mut(*window_id).unwrap();
draw(
vk_context.device().clone(),
self.memory_allocator.clone(),
self.descriptor_set_allocator.clone(),
self.command_buffer_allocator.clone(),
viewport,
vk_context,
renderer,
gui,
self.rotation_start,
);
}
Ok(())
}
}
pub fn add_distance_system() -> BoxedSystem {
let query = Query::new(entity_ids());
System::builder()
.with_query(query)
.build(|mut query: QueryBorrow<'_, flax::EntityIds, _>| {
for _id in &mut query {
// println!("----------: {}", _id.index());
}
})
.boxed()
}
fn draw(
device: Arc<vulkano::device::Device>,
memory_allocator: Arc<StandardMemoryAllocator>,
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
command_buffer_allocator: Arc<dyn CommandBufferAllocator>,
_viewport: &mut Viewport,
context: &mut VulkanoContext,
renderer: &mut VulkanoWindowRenderer,
gui: &mut Gui,
rotation_start: std::time::Instant,
) {
let vertex_buffer = Buffer::from_iter(
memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
POSITIONS,
)
.unwrap();
let normals_buffer = Buffer::from_iter(
memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
NORMALS,
)
.unwrap();
let index_buffer = Buffer::from_iter(
memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::INDEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
INDICES,
)
.unwrap();
let uniform_buffer = SubbufferAllocator::new(
memory_allocator.clone(),
SubbufferAllocatorCreateInfo {
buffer_usage: BufferUsage::UNIFORM_BUFFER,
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
);
let render_pass = vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
format: renderer.swapchain_format(),
samples: 1,
load_op: Clear,
store_op: Store,
},
depth_stencil: {
format: Format::D16_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
},
pass: {
color: [color],
depth_stencil: {depth_stencil},
},
)
.unwrap();
let vs = vs::load(device.clone())
.unwrap()
.entry_point("main")
.unwrap();
let fs = fs::load(device.clone())
.unwrap()
.entry_point("main")
.unwrap();
let (mut pipeline, mut framebuffers) = window_size_dependent_setup(
memory_allocator.clone(),
vs.clone(),
fs.clone(),
renderer.swapchain_image_views(),
render_pass.clone(),
);
// Do not draw the frame when the screen size is zero. On Windows, this can
// occur when minimizing the application.
let image_extent: [u32; 2] = renderer.window().inner_size().into();
if image_extent.contains(&0) {
return;
}
// Begin rendering by acquiring the gpu future from the window renderer.
let previous_frame_end = renderer
.acquire(None, |swapchain_images| {
// Whenever the window resizes we need to recreate everything dependent
// on the window size. In this example that
// includes the swapchain, the framebuffers
// and the dynamic state viewport.
let (new_pipeline, new_framebuffers) = window_size_dependent_setup(
memory_allocator.clone(),
vs.clone(),
fs.clone(),
swapchain_images,
render_pass.clone(),
);
pipeline = new_pipeline;
framebuffers = new_framebuffers;
})
.unwrap();
let uniform_buffer_subbuffer = {
let elapsed = rotation_start.elapsed();
let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0;
let rotation = Mat3::from_rotation_y(rotation as f32);
// NOTE: This teapot was meant for OpenGL where the origin is at the lower left
// instead the origin is at the upper left in Vulkan, so we reverse the Y axis.
let aspect_ratio = renderer.aspect_ratio();
let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0);
let view = Mat4::look_at_rh(
Vec3::new(0.4, 0.3, 1.0),
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, -1.0, 0.0),
);
let scale = Mat4::from_scale(Vec3::splat(0.01));
let uniform_data = vs::Data {
world: Mat4::from_mat3(rotation).to_cols_array_2d(),
view: (view * scale).to_cols_array_2d(),
proj: proj.to_cols_array_2d(),
};
let subbuffer = uniform_buffer.allocate_sized().unwrap();
*subbuffer.write().unwrap() = uniform_data;
subbuffer
};
let mut builder = RecordingCommandBuffer::new(
command_buffer_allocator.clone(),
context.graphics_queue().queue_family_index(),
CommandBufferLevel::Primary,
CommandBufferBeginInfo {
usage: CommandBufferUsage::OneTimeSubmit,
..Default::default()
},
)
.unwrap();
let layout = &pipeline.layout().set_layouts()[0];
let set = DescriptorSet::new(
descriptor_set_allocator.clone(),
layout.clone(),
[WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)],
[],
)
.unwrap();
builder
.begin_render_pass(
RenderPassBeginInfo {
clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into()), Some(1f32.into())],
..RenderPassBeginInfo::framebuffer(
framebuffers[renderer.image_index() as usize].clone(),
)
},
Default::default(),
)
.unwrap()
.bind_pipeline_graphics(pipeline.clone())
.unwrap()
.bind_descriptor_sets(
PipelineBindPoint::Graphics,
pipeline.layout().clone(),
0,
set,
)
.unwrap()
.bind_vertex_buffers(0, (vertex_buffer.clone(), normals_buffer.clone()))
.unwrap()
.bind_index_buffer(index_buffer.clone())
.unwrap();
unsafe {
builder
.draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0)
.unwrap();
}
builder.end_render_pass(Default::default()).unwrap();
// Finish recording the command buffer by calling `end`.
let command_buffer = builder.end().unwrap();
draw_gui(gui);
let before_future = previous_frame_end
.then_execute(context.graphics_queue().clone(), command_buffer)
.unwrap()
.boxed();
let after_future = gui
.draw_on_image(before_future, renderer.swapchain_image_view())
.boxed();
// The color output is now expected to contain our triangle. But in order to
// show it on the screen, we have to *present* the image by calling
// `present` on the window renderer.
//
// This function does not actually present the image immediately. Instead it
// submits a present command at the end of the queue. This means that it will
// only be presented once the GPU has finished executing the command buffer
// that draws the triangle.
renderer.present(after_future, true);
}
fn draw_gui(gui: &mut Gui) {
let mut code = CODE.to_owned();
gui.immediate_ui(|gui| {
let ctx = gui.context();
egui::Window::new("Colors").vscroll(true).show(&ctx, |ui| {
ui.vertical_centered(|ui| {
ui.add(egui::widgets::Label::new("Hi there!"));
sized_text(ui, "Rich Text", 32.0);
});
ui.separator();
ui.columns(2, |columns| {
egui::ScrollArea::vertical()
.id_source("source")
.show(&mut columns[0], |ui| {
ui.add(
egui::TextEdit::multiline(&mut code).font(egui::TextStyle::Monospace),
);
});
egui::ScrollArea::vertical()
.id_source("rendered")
.show(&mut columns[1], |ui| {
ui.add(egui::widgets::Label::new("Good day!"));
});
});
});
});
}
fn sized_text(ui: &mut egui::Ui, text: impl Into<String>, size: f32) {
ui.label(
egui::RichText::new(text)
.size(size)
.family(egui::FontFamily::Monospace),
);
}
const CODE: &str = r"
# Some markup
```
let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1);
```
";
fn window_size_dependent_setup(
memory_allocator: Arc<StandardMemoryAllocator>,
vs: EntryPoint,
fs: EntryPoint,
image_views: &[Arc<ImageView>],
render_pass: Arc<RenderPass>,
) -> (Arc<GraphicsPipeline>, Vec<Arc<Framebuffer>>) {
let device = memory_allocator.device().clone();
let extent = image_views[0].image().extent();
let depth_buffer = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::D16_UNORM,
extent,
usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
let framebuffers = image_views
.iter()
.map(|image_view| {
Framebuffer::new(
render_pass.clone(),
FramebufferCreateInfo {
attachments: vec![image_view.clone(), depth_buffer.clone()],
..Default::default()
},
)
.unwrap()
})
.collect::<Vec<_>>();
// In the triangle example we use a dynamic viewport, as its a simple example. However in the
// teapot example, we recreate the pipelines with a hardcoded viewport instead. This allows the
// driver to optimize things, at the cost of slower window resizes.
// https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport
let pipeline = {
let vertex_input_state = [Position::per_vertex(), Normal::per_vertex()]
.definition(&vs)
.unwrap();
let stages = [
PipelineShaderStageCreateInfo::new(vs),
PipelineShaderStageCreateInfo::new(fs),
];
let layout = PipelineLayout::new(
device.clone(),
PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages)
.into_pipeline_layout_create_info(device.clone())
.unwrap(),
)
.unwrap();
let subpass = Subpass::from(render_pass, 0).unwrap();
GraphicsPipeline::new(
device,
None,
GraphicsPipelineCreateInfo {
stages: stages.into_iter().collect(),
vertex_input_state: Some(vertex_input_state),
input_assembly_state: Some(InputAssemblyState::default()),
viewport_state: Some(ViewportState {
viewports: [Viewport {
offset: [0.0, 0.0],
extent: [extent[0] as f32, extent[1] as f32],
depth_range: 0.0..=1.0,
}]
.into_iter()
.collect(),
..Default::default()
}),
rasterization_state: Some(RasterizationState::default()),
depth_stencil_state: Some(DepthStencilState {
depth: Some(DepthState::simple()),
..Default::default()
}),
multisample_state: Some(MultisampleState::default()),
color_blend_state: Some(ColorBlendState::with_attachment_states(
subpass.num_color_attachments(),
ColorBlendAttachmentState::default(),
)),
subpass: Some(subpass.into()),
..GraphicsPipelineCreateInfo::layout(layout)
},
)
.unwrap()
};
(pipeline, framebuffers)
}
mod vs {
vulkano_shaders::shader! {
ty: "vertex",
path: "src/vert.glsl",
}
}
mod fs {
vulkano_shaders::shader! {
ty: "fragment",
path: "src/frag.glsl",
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,504 @@
use flax::{entity_ids, BoxedSystem, Query, QueryBorrow, Schedule, System, World};
use glam::{
f32::{Mat3, Vec3},
Mat4,
};
use std::sync::Arc;
use vulkano::{
buffer::{
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
Buffer, BufferCreateInfo, BufferUsage,
},
command_buffer::{
allocator::{CommandBufferAllocator, StandardCommandBufferAllocator},
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
RenderPassBeginInfo,
},
descriptor_set::{
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet
},
device::DeviceOwned,
format::Format,
image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
pipeline::{graphics::viewport::Viewport, GraphicsPipeline, Pipeline, PipelineBindPoint},
render_pass::{Framebuffer, RenderPass},
shader::EntryPoint,
sync::GpuFuture,
};
use vulkano_util::{
context::VulkanoContext, renderer::VulkanoWindowRenderer, window::VulkanoWindows,
};
use crate::{node_editor, render_module::RenderModule as ThreadLocalModule, vulkan::renderpass::make_render_pass};
use crate::{
debug_gui::DebugGuiStack,
vulkan::{
framebuffer::make_framebuffer,
pipeline::{make_pipeline, PassInfo, PipelineInfo},
},
};
use egui_vulkano::Gui;
use super::node_editor::DemoApp;
use crate::temp::model::{INDICES, NORMALS, POSITIONS};
pub struct RenderModule {
schedule: Schedule,
memory_allocator: Arc<StandardMemoryAllocator>,
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
command_buffer_allocator: Arc<dyn CommandBufferAllocator>,
viewport: Viewport,
rotation_start: std::time::Instant,
node_editor: DemoApp,
}
impl RenderModule {
pub fn new(
vk_context: &mut VulkanoContext,
_vk_windows: &mut VulkanoWindows,
_schedule: &mut Schedule,
_world: &mut World,
_events: &mut khors_core::events::Events,
) -> Self {
let schedule = Schedule::builder()
.with_system(add_distance_system())
.build();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(
vk_context.device().clone(),
));
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
vk_context.device().clone(),
Default::default(),
));
let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
vk_context.device().clone(),
Default::default(),
));
let viewport = Viewport {
offset: [0.0, 0.0],
extent: [0.0, 0.0],
depth_range: 0.0..=1.0,
};
let node_editor = DemoApp::new();
let rotation_start = std::time::Instant::now();
Self {
schedule,
memory_allocator,
descriptor_set_allocator,
command_buffer_allocator,
viewport,
rotation_start,
node_editor,
}
}
}
impl ThreadLocalModule for RenderModule {
fn on_update(
&mut self,
gui_stack: &mut DebugGuiStack,
vk_context: &mut VulkanoContext,
vk_windows: &mut vulkano_util::window::VulkanoWindows,
world: &mut World,
_events: &mut khors_core::events::Events,
_frame_time: std::time::Duration,
) -> anyhow::Result<()> {
self.schedule.execute_seq(world).unwrap();
let viewport = &mut self.viewport;
for (window_id, renderer) in vk_windows.iter_mut() {
let gui = gui_stack.get_mut(*window_id).unwrap();
let node_editor = &mut self.node_editor;
draw(
vk_context.device().clone(),
self.memory_allocator.clone(),
self.descriptor_set_allocator.clone(),
self.command_buffer_allocator.clone(),
viewport,
vk_context,
renderer,
gui,
node_editor,
self.rotation_start,
);
}
Ok(())
}
}
pub fn add_distance_system() -> BoxedSystem {
let query = Query::new(entity_ids());
System::builder()
.with_query(query)
.build(|mut query: QueryBorrow<'_, flax::EntityIds, _>| {
for _id in &mut query {
// println!("----------: {}", _id.index());
}
})
.boxed()
}
#[allow(clippy::too_many_arguments)]
fn draw(
device: Arc<vulkano::device::Device>,
memory_allocator: Arc<StandardMemoryAllocator>,
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
command_buffer_allocator: Arc<dyn CommandBufferAllocator>,
_viewport: &mut Viewport,
context: &mut VulkanoContext,
renderer: &mut VulkanoWindowRenderer,
gui: &mut Gui,
node_editor: &mut DemoApp,
rotation_start: std::time::Instant,
) {
let vertex_buffer = Buffer::from_iter(
memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
POSITIONS,
)
.unwrap();
let normals_buffer = Buffer::from_iter(
memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
NORMALS,
)
.unwrap();
let index_buffer = Buffer::from_iter(
memory_allocator.clone(),
BufferCreateInfo {
usage: BufferUsage::INDEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
INDICES,
)
.unwrap();
let uniform_buffer = SubbufferAllocator::new(
memory_allocator.clone(),
SubbufferAllocatorCreateInfo {
buffer_usage: BufferUsage::UNIFORM_BUFFER,
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
);
// let render_pass = vulkano::single_pass_renderpass!(
// device.clone(),
// attachments: {
// color: {
// format: renderer.swapchain_format(),
// samples: 1,
// load_op: Clear,
// store_op: Store,
// },
// depth_stencil: {
// format: Format::D16_UNORM,
// samples: 1,
// load_op: Clear,
// store_op: DontCare,
// },
// },
// pass: {
// color: [color],
// depth_stencil: {depth_stencil},
// },
// )
// .unwrap();
let render_pass = make_render_pass(device.clone(), renderer);
let vs = vs::load(device.clone())
.unwrap()
.entry_point("main")
.unwrap();
let fs = fs::load(device.clone())
.unwrap()
.entry_point("main")
.unwrap();
let (mut pipeline, mut framebuffers) = window_size_dependent_setup(
memory_allocator.clone(),
vs.clone(),
fs.clone(),
renderer.swapchain_image_views(),
render_pass.clone(),
);
// Do not draw the frame when the screen size is zero. On Windows, this can
// occur when minimizing the application.
let image_extent: [u32; 2] = renderer.window().inner_size().into();
if image_extent.contains(&0) {
return;
}
// Begin rendering by acquiring the gpu future from the window renderer.
let previous_frame_end = renderer
.acquire(None, |swapchain_images| {
// Whenever the window resizes we need to recreate everything dependent
// on the window size. In this example that
// includes the swapchain, the framebuffers
// and the dynamic state viewport.
let (new_pipeline, new_framebuffers) = window_size_dependent_setup(
memory_allocator.clone(),
vs.clone(),
fs.clone(),
swapchain_images,
render_pass.clone(),
);
pipeline = new_pipeline;
framebuffers = new_framebuffers;
})
.unwrap();
let uniform_buffer_subbuffer = {
let elapsed = rotation_start.elapsed();
let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0;
let rotation = Mat3::from_rotation_y(rotation as f32);
// NOTE: This teapot was meant for OpenGL where the origin is at the lower left
// instead the origin is at the upper left in Vulkan, so we reverse the Y axis.
let aspect_ratio = renderer.aspect_ratio();
let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0);
let view = Mat4::look_at_rh(
Vec3::new(0.4, 0.3, 1.0),
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, -1.0, 0.0),
);
let scale = Mat4::from_scale(Vec3::splat(0.01));
let uniform_data = vs::Data {
world: Mat4::from_mat3(rotation).to_cols_array_2d(),
view: (view * scale).to_cols_array_2d(),
proj: proj.to_cols_array_2d(),
};
let subbuffer = uniform_buffer.allocate_sized().unwrap();
*subbuffer.write().unwrap() = uniform_data;
subbuffer
};
let mut builder = RecordingCommandBuffer::new(
command_buffer_allocator.clone(),
context.graphics_queue().queue_family_index(),
CommandBufferLevel::Primary,
CommandBufferBeginInfo {
usage: CommandBufferUsage::OneTimeSubmit,
..Default::default()
},
)
.unwrap();
let layout = &pipeline.layout().set_layouts()[0];
let set = DescriptorSet::new(
descriptor_set_allocator.clone(),
layout.clone(),
[WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)],
[],
)
.unwrap();
builder
.begin_render_pass(
RenderPassBeginInfo {
clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into()), Some(1f32.into())],
..RenderPassBeginInfo::framebuffer(
framebuffers[renderer.image_index() as usize].clone(),
)
},
Default::default(),
)
.unwrap()
.bind_pipeline_graphics(pipeline.clone())
.unwrap()
.bind_descriptor_sets(
PipelineBindPoint::Graphics,
pipeline.layout().clone(),
0,
set,
)
.unwrap()
.bind_vertex_buffers(0, (vertex_buffer.clone(), normals_buffer.clone()))
.unwrap()
.bind_index_buffer(index_buffer.clone())
.unwrap();
unsafe {
builder
.draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0)
.unwrap();
}
builder.end_render_pass(Default::default()).unwrap();
// Finish recording the command buffer by calling `end`.
let command_buffer = builder.end().unwrap();
draw_gui(gui, node_editor);
let before_future = previous_frame_end
.then_execute(context.graphics_queue().clone(), command_buffer)
.unwrap()
.boxed();
let after_future = gui
.draw_on_image(before_future, renderer.swapchain_image_view())
.boxed();
// The color output is now expected to contain our triangle. But in order to
// show it on the screen, we have to *present* the image by calling
// `present` on the window renderer.
//
// This function does not actually present the image immediately. Instead it
// submits a present command at the end of the queue. This means that it will
// only be presented once the GPU has finished executing the command buffer
// that draws the triangle.
renderer.present(after_future, true);
}
fn draw_gui(gui: &mut Gui, node_editor: &mut DemoApp) {
let mut code = CODE.to_owned();
gui.immediate_ui(|gui| {
let ctx = gui.context();
node_editor.update(&ctx);
egui::Window::new("Colors").vscroll(true).show(&ctx, |ui| {
ui.vertical_centered(|ui| {
ui.add(egui::widgets::Label::new("Hi there!"));
sized_text(ui, "Rich Text", 32.0);
});
ui.separator();
ui.columns(2, |columns| {
egui::ScrollArea::vertical()
.id_source("source")
.show(&mut columns[0], |ui| {
ui.add(
egui::TextEdit::multiline(&mut code).font(egui::TextStyle::Monospace),
);
});
egui::ScrollArea::vertical()
.id_source("rendered")
.show(&mut columns[1], |ui| {
ui.add(egui::widgets::Label::new("Good day!"));
});
});
});
});
}
fn sized_text(ui: &mut egui::Ui, text: impl Into<String>, size: f32) {
ui.label(
egui::RichText::new(text)
.size(size)
.family(egui::FontFamily::Monospace),
);
}
const CODE: &str = r"
# Some markup
```
let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1);
```
";
fn window_size_dependent_setup(
memory_allocator: Arc<StandardMemoryAllocator>,
vs: EntryPoint,
fs: EntryPoint,
image_views: &[Arc<ImageView>],
render_pass: Arc<RenderPass>,
) -> (Arc<GraphicsPipeline>, Vec<Arc<Framebuffer>>) {
let device = memory_allocator.device().clone();
let extent = image_views[0].image().extent();
let depth_buffer = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::D16_UNORM,
extent,
usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
let framebuffers = image_views
.iter()
.map(|image_view| {
make_framebuffer(
render_pass.clone(),
vec![image_view.clone(), depth_buffer.clone()],
)
.unwrap()
})
.collect::<Vec<_>>();
// In the triangle example we use a dynamic viewport, as its a simple example. However in the
// teapot example, we recreate the pipelines with a hardcoded viewport instead. This allows the
// driver to optimize things, at the cost of slower window resizes.
// https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport
let pipeline_info = PipelineInfo { vs, fs };
let pass_info = PassInfo::new(render_pass.clone(), 0, extent);
let pipeline = make_pipeline(device.clone(), &pipeline_info, &pass_info);
(pipeline, framebuffers)
}
mod vs {
vulkano_shaders::shader! {
ty: "vertex",
path: "src/temp/vert.glsl",
}
}
mod fs {
vulkano_shaders::shader! {
ty: "fragment",
path: "src/temp/frag.glsl",
}
}

View file

@ -0,0 +1,33 @@
use thiserror::Error;
use super::{node::NodeKind, rendergraph::{NodeIndex, ResourceKind}};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Failed executing rendergraph node")]
NodeExecution(#[from] anyhow::Error),
#[error("Rendergraph vulkan error")]
Vulkan(#[from] ivy_vulkan::Error),
#[error("Rendergraph graphics error")]
Graphics(#[from] ivy_graphics::Error),
#[error("Dependency cycle in rendergraph")]
DependencyCycle,
#[error("Node read attachment is missing corresponding write attachment for {2:?} required by node {0:?}: {1:?}")]
MissingWrite(NodeIndex, &'static str, ResourceKind),
#[error("Resource acquisition error")]
Resource(#[from] ivy_resources::Error),
#[error("Invalid node index {0:?}")]
InvalidNodeIndex(NodeIndex),
#[error("Specified node {0:?} is not the correct kind. Expected {1:?}, found {2:?}")]
InvalidNodeKind(NodeIndex, NodeKind, NodeKind),
}

View file

@ -0,0 +1,7 @@
// use flax::component;
// use vulkano::image::{view::ImageView, Image};
// pub mod node;
// pub mod pass;
// pub mod rendergraph;
// mod error;

View file

@ -0,0 +1,172 @@
use flax::World;
use vulkano::command_buffer::RecordingCommandBuffer;
use crate::vulkan::pipeline::PassInfo;
/// Represents a node in the renderpass.
pub trait Node: 'static + Send {
/// Returns the color attachments for this node. Should not be execution heavy function
fn color_attachments(&self) -> &[AttachmentInfo] {
&[]
}
fn output_attachments(&self) -> &[Handle<Texture>] {
&[]
}
/// Returns the read attachments for this node. Should not be execution heavy function
fn read_attachments(&self) -> &[Handle<Texture>] {
&[]
}
/// Partially sampled input attachments. Read from the same pixel coord we write to
fn input_attachments(&self) -> &[Handle<Texture>] {
&[]
}
/// Returns the optional depth attachment for this node. Should not be execution heavy function
fn depth_attachment(&self) -> Option<&AttachmentInfo> {
None
}
fn buffer_reads(&self) -> &[Buffer] {
&[]
}
fn buffer_writes(&self) -> &[Buffer] {
&[]
}
/// Returns the clear values to initiate this renderpass
fn clear_values(&self) -> &[ClearValue] {
&[]
}
fn node_kind(&self) -> NodeKind;
// Optional name, can be empty string
fn debug_name(&self) -> &'static str;
/// Execute this node inside a compatible renderpass
fn execute(
&mut self,
world: &mut World,
resources: &Resources,
cmd: &CommandBuffer,
pass_info: &PassInfo,
current_frame: usize,
) -> anyhow::Result<()>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeKind {
// A graphics rendering node. Renderpass and framebuffer will automatically be created.
Graphics,
// execution
// A node that will be executed on the transfer queue. Appropriate pipeline barriers will
// be inserted
Transfer,
// Compute,
}
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct AttachmentInfo {
// TODO, derive from edges
pub store_op: StoreOp,
pub load_op: LoadOp,
pub initial_layout: ImageLayout,
pub final_layout: ImageLayout,
pub resource: Handle<Texture>,
pub clear_value: ClearValue,
}
impl Default for AttachmentInfo {
fn default() -> Self {
Self {
store_op: StoreOp::STORE,
load_op: LoadOp::DONT_CARE,
initial_layout: ImageLayout::UNDEFINED,
final_layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
resource: Handle::null(),
clear_value: ClearValue::default(),
}
}
}
impl AttachmentInfo {
pub fn color(resource: Handle<Texture>) -> Self {
Self {
store_op: StoreOp::STORE,
load_op: LoadOp::CLEAR,
initial_layout: ImageLayout::UNDEFINED,
final_layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
clear_value: ClearValue::color(0.0, 0.0, 0.0, 1.0),
resource,
}
}
pub fn depth_discard(resource: Handle<Texture>) -> Self {
Self {
store_op: StoreOp::DONT_CARE,
load_op: LoadOp::CLEAR,
initial_layout: ImageLayout::UNDEFINED,
final_layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
clear_value: ClearValue::depth_stencil(1.0, 0),
resource,
}
}
pub fn depth_store(resource: Handle<Texture>) -> Self {
Self {
store_op: StoreOp::STORE,
load_op: LoadOp::CLEAR,
initial_layout: ImageLayout::UNDEFINED,
final_layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
clear_value: ClearValue::depth_stencil(1.0, 0),
resource,
}
}
}
/// Simple node for rendering a pass in the rendergraph
pub struct RenderNode<Pass, T> {
renderer: Handle<T>,
marker: PhantomData<Pass>,
}
impl<Pass, T> RenderNode<Pass, T> {
pub fn new(renderer: Handle<T>) -> Self {
Self {
renderer,
marker: PhantomData,
}
}
}
impl<Pass, T, E> Node for RenderNode<Pass, T>
where
Pass: ShaderPass,
T: 'static + Renderer<Error = E> + Send + Sync,
E: Into<anyhow::Error>,
{
fn node_kind(&self) -> NodeKind {
NodeKind::Graphics
}
fn execute(
&mut self,
world: &mut World,
resources: &Resources,
cmd: &CommandBuffer,
pass_info: &PassInfo,
current_frame: usize,
) -> anyhow::Result<()> {
resources
.get_mut(self.renderer)
.with_context(|| format!("Failed to borrow {:?} mutably", type_name::<T>()))?
.draw::<Pass>(world, resources, cmd, &[], pass_info, &[], current_frame)
.map_err(|e| e.into())
.with_context(|| format!("Failed to draw using {:?}", type_name::<T>()))
}
fn debug_name(&self) -> &'static str {
std::any::type_name::<RenderNode<Pass, T>>()
}
}

View file

@ -0,0 +1,395 @@
use std::collections::HashMap;
use vulkano::{render_pass::Framebuffer, sync::ImageMemoryBarrier};
use super::{
node::{Node, NodeKind},
rendergraph::{Edge, NodeIndex, ResourceKind},
};
pub struct Pass {
kind: PassKind,
nodes: Vec<NodeIndex>,
}
impl Pass {
// Creates a new pass from a group of compatible nodes.
// Two nodes are compatible if:
// * They have no full read dependencies to another.
// * Belong to the same kind and queue.
// * Have the same dependency level.
pub fn new<T>(
context: &SharedVulkanContext,
nodes: &Vec<Box<dyn Node>>,
textures: &T,
dependencies: &HashMap<NodeIndex, Vec<Edge>>,
pass_nodes: Vec<NodeIndex>,
kind: NodeKind,
extent: Extent,
) -> Result<Self>
where
T: Deref<Target = ResourceCache<Texture>>,
{
let kind = match kind {
NodeKind::Graphics => {
PassKind::graphics(context, nodes, textures, dependencies, &pass_nodes, extent)?
}
NodeKind::Transfer => {
PassKind::transfer(context, nodes, textures, dependencies, &pass_nodes, extent)?
}
};
Ok(Self {
kind,
nodes: pass_nodes,
})
}
pub fn execute(
&self,
world: &mut World,
cmd: &CommandBuffer,
nodes: &mut Vec<Box<dyn Node>>,
current_frame: usize,
extent: Extent,
) -> Result<()> {
match &self.kind {
PassKind::Graphics {
renderpass,
framebuffer,
clear_values,
} => {
cmd.begin_renderpass(&renderpass, &framebuffer, extent, clear_values);
self.nodes
.iter()
.enumerate()
.try_for_each(|(subpass, index)| -> Result<_> {
if subpass > 0 {
cmd.next_subpass(vk::SubpassContents::INLINE);
}
let node = &mut nodes[*index];
node.execute(
world,
resources,
cmd,
&PassInfo {
renderpass: renderpass.renderpass(),
subpass: subpass as u32,
extent,
color_attachment_count: node.color_attachments().len() as u32,
depth_attachment: node.depth_attachment().is_some(),
},
current_frame,
)?;
Ok(())
})?;
cmd.end_renderpass();
}
PassKind::Transfer {
src_stage,
image_barriers,
} => {
if !image_barriers.is_empty() {
cmd.pipeline_barrier(
*src_stage,
vk::PipelineStageFlags::TRANSFER,
&[],
image_barriers,
);
}
self.nodes.iter().try_for_each(|index| -> Result<_> {
nodes[*index]
.execute(world, resources, cmd, &PassInfo::default(), current_frame)
.map_err(|e| e.into())
})?;
}
}
Ok(())
}
/// Get a reference to the pass's kind.
pub fn kind(&self) -> &PassKind {
&self.kind
}
/// Get a reference to the pass's nodes.
pub fn nodes(&self) -> &[NodeIndex] {
self.nodes.as_slice()
}
}
pub enum PassKind {
Graphics {
renderpass: RenderPass,
framebuffer: Framebuffer,
clear_values: Vec<vk::ClearValue>,
// Index into first node of pass_nodes
},
Transfer {
src_stage: vk::PipelineStageFlags,
image_barriers: Vec<ImageMemoryBarrier>,
},
}
unsafe impl Send for PassKind {}
unsafe impl Sync for PassKind {}
impl PassKind {
fn graphics<T>(
context: &SharedVulkanContext,
nodes: &Vec<Box<dyn Node>>,
textures: &T,
dependencies: &HashMap<NodeIndex, Vec<Edge>>,
pass_nodes: &[NodeIndex],
extent: Extent,
) -> Result<Self>
where
T: Deref<Target = ResourceCache<Texture>>,
{
println!(
"Building pass with nodes: {:?}",
pass_nodes
.iter()
.map(|v| nodes[*v].debug_name())
.collect_vec()
);
// Collect clear values
let clear_values = pass_nodes
.iter()
.flat_map(|node| {
let node = &nodes[*node];
node.clear_values()
.iter()
.cloned()
.chain(repeat(ClearValue::default()).take(
node.color_attachments().len() + node.depth_attachment().iter().count()
- node.clear_values().len(),
))
})
.collect::<Vec<_>>();
// Generate subpass dependencies
let dependencies = pass_nodes
.iter()
.enumerate()
.flat_map(|(subpass_index, node_index)| {
// Get the dependencies of node.
dependencies
.get(node_index)
.into_iter()
.flat_map(|val| val.iter())
.flat_map(move |edge| match edge.kind {
EdgeKind::Sampled => Some(SubpassDependency {
src_subpass: vk::SUBPASS_EXTERNAL,
dst_subpass: subpass_index as u32,
src_stage_mask: edge.write_stage,
dst_stage_mask: edge.read_stage,
src_access_mask: edge.write_access,
dst_access_mask: edge.read_access,
dependency_flags: Default::default(),
}),
EdgeKind::Input => Some(SubpassDependency {
src_subpass: pass_nodes
.iter()
.enumerate()
.find(|(_, node)| **node == edge.src)
.unwrap()
.0 as u32,
dst_subpass: subpass_index as u32,
src_stage_mask: edge.write_stage,
dst_stage_mask: edge.read_stage,
src_access_mask: edge.write_access,
dst_access_mask: edge.read_access,
dependency_flags: vk::DependencyFlags::BY_REGION,
}),
EdgeKind::Attachment => Some(SubpassDependency {
src_subpass: pass_nodes
.iter()
.enumerate()
.find(|(_, node)| **node == edge.src)
.map(|v| v.0 as u32)
.unwrap_or(vk::SUBPASS_EXTERNAL),
dst_subpass: subpass_index as u32,
src_stage_mask: edge.write_stage,
dst_stage_mask: edge.read_stage,
src_access_mask: edge.write_access,
dst_access_mask: edge.read_access,
dependency_flags: vk::DependencyFlags::BY_REGION,
}),
EdgeKind::Buffer => None,
})
})
.collect::<Vec<_>>();
let mut attachment_descriptions = Vec::new();
let mut attachments = Vec::new();
let attachment_refs = pass_nodes
.iter()
.enumerate()
.map(|(_, node_index)| -> Result<_> {
let node = &nodes[*node_index];
let offset = attachments.len();
let color_attachments = node
.color_attachments()
.iter()
.enumerate()
.map(|(i, _)| AttachmentReference {
attachment: (i + offset) as u32,
layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
})
.collect::<Vec<_>>();
let input_attachments = node
.input_attachments()
.iter()
.map(|tex| -> Result<_> {
let view = textures.get(*tex)?.image_view();
Ok(AttachmentReference {
attachment: attachments
.iter()
.enumerate()
.find(|(_, val)| view == **val)
.unwrap()
.0 as u32,
layout: ImageLayout::SHADER_READ_ONLY_OPTIMAL,
})
})
.collect::<Result<Vec<_>>>()?;
let depth_attachment =
node.depth_attachment()
.as_ref()
.map(|_| AttachmentReference {
attachment: (color_attachments.len() + input_attachments.len() + offset)
as u32,
layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
});
for attachment in node
.color_attachments()
.iter()
.chain(node.depth_attachment().into_iter())
{
let texture = textures.get(attachment.resource)?;
attachments.push(texture.image_view());
attachment_descriptions.push(AttachmentDescription {
flags: vk::AttachmentDescriptionFlags::default(),
format: texture.format(),
samples: texture.samples(),
load_op: attachment.load_op,
store_op: attachment.store_op,
stencil_load_op: LoadOp::DONT_CARE,
stencil_store_op: StoreOp::DONT_CARE,
initial_layout: attachment.initial_layout,
final_layout: attachment.final_layout,
})
}
Ok((color_attachments, input_attachments, depth_attachment))
})
.collect::<Result<Vec<_>>>()?;
let subpasses = attachment_refs
.iter()
.map(
|(color_attachments, input_attachments, depth_attachment)| SubpassInfo {
color_attachments,
resolve_attachments: &[],
input_attachments: &input_attachments,
depth_attachment: *depth_attachment,
},
)
.collect::<Vec<_>>();
let renderpass_info = RenderPassInfo {
attachments: &attachment_descriptions,
subpasses: &subpasses,
dependencies: &dependencies,
};
let renderpass = RenderPass::new(context.device().clone(), &renderpass_info)?;
let framebuffer =
Framebuffer::new(context.device().clone(), &renderpass, &attachments, extent)?;
Ok(PassKind::Graphics {
renderpass,
framebuffer,
clear_values,
})
}
fn transfer<T>(
_context: &SharedVulkanContext,
_nodes: &Vec<Box<dyn Node>>,
textures: &T,
dependencies: &HashMap<NodeIndex, Vec<Edge>>,
pass_nodes: &[NodeIndex],
_extent: Extent,
) -> Result<Self>
where
T: Deref<Target = ResourceCache<Texture>>,
{
// Get the dependencies of node.
let mut src_stage = vk::PipelineStageFlags::default();
let image_barriers = dependencies
.get(&pass_nodes[0])
.into_iter()
.flat_map(|val| val.iter())
.filter_map(|val| {
if let ResourceKind::Texture(tex) = val.resource {
Some((val, tex))
} else {
None
}
})
.map(|(edge, texture)| -> Result<_> {
let src = textures.get(texture)?;
let aspect_mask =
if edge.read_access == vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE {
vk::ImageAspectFlags::DEPTH
} else {
vk::ImageAspectFlags::COLOR
};
src_stage = edge.write_stage.max(src_stage);
Ok(ImageMemoryBarrier {
src_access_mask: edge.write_access,
dst_access_mask: vk::AccessFlags::TRANSFER_READ,
old_layout: edge.layout,
new_layout: ImageLayout::TRANSFER_SRC_OPTIMAL,
src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
image: src.image(),
subresource_range: vk::ImageSubresourceRange {
aspect_mask,
base_mip_level: 0,
level_count: src.mip_levels(),
base_array_layer: 0,
layer_count: 1,
},
..Default::default()
})
})
.collect::<Result<Vec<_>>>()?;
Ok(Self::Transfer {
src_stage,
image_barriers,
})
}
}

View file

@ -0,0 +1,622 @@
use super::error::Error;
use std::collections::HashMap;
use vulkano::{
command_buffer::pool::{CommandPool, CommandPoolCreateFlags, CommandPoolCreateInfo},
sync::{fence::Fence, semaphore::Semaphore},
};
use super::{node::Node, pass::Pass};
pub type PassIndex = usize;
pub type NodeIndex = usize;
/// Direct acyclic graph abstraction for renderpasses, barriers and subpass dependencies.
pub struct RenderGraph {
context: SharedVulkanContext,
/// The unordered nodes in the arena.
nodes: Vec<Box<dyn Node>>,
edges: HashMap<NodeIndex, Vec<Edge>>,
passes: Vec<Pass>,
// Maps a node to a pass index
node_pass_map: HashMap<NodeIndex, (PassIndex, u32)>,
// Data for each frame in flight
frames: Vec<FrameData>,
extent: Extent,
frames_in_flight: usize,
current_frame: usize,
}
impl RenderGraph {
/// Creates a new empty rendergraph.
pub fn new(
context: SharedVulkanContext,
frames_in_flight: usize,
) -> super::error::Result<Self> {
let frames = (0..frames_in_flight)
.map(|_| FrameData::new(context.clone()).map_err(|e| e.into()))
.collect::<super::error::Result<Vec<_>>>()?;
Ok(Self {
context,
nodes: Default::default(),
edges: Default::default(),
passes: Vec::new(),
node_pass_map: Default::default(),
frames,
extent: Extent::new(0, 0),
frames_in_flight,
current_frame: 0,
})
}
/// Adds a new node into the rendergraph.
/// **Note**: The new node won't take effect until [`RenderGraph::build`] is called.
pub fn add_node<T: 'static + Node>(&mut self, node: T) -> NodeIndex {
let i = self.nodes.len();
self.nodes.push(Box::new(node));
i
}
/// Add several nodes into the rendergraph.
/// Due to the concrete type of iterators, the nodes need to already be boxed.
/// Returns the node indices in order.
pub fn add_nodes<I: IntoIterator<Item = Box<dyn Node>>>(&mut self, nodes: I) -> Vec<NodeIndex> {
nodes
.into_iter()
.map(|node| {
let i = self.nodes.len();
self.nodes.push(node);
i
})
.collect_vec()
}
pub fn build_edges(
&mut self,
) -> crate::Result<(HashMap<usize, Vec<Edge>>, HashMap<usize, Vec<Edge>>)> {
// Iterate all node's read attachments, and find any node which has the same write
// attachment before the current node.
// Finally, automatically construct edges.
let nodes = &self.nodes;
let mut edges = HashMap::new();
let mut dependencies = HashMap::new();
nodes
.iter()
.enumerate()
.flat_map(|node| {
EdgeConstructor {
nodes,
dst: node.0,
reads: node.1.input_attachments().into_iter().cloned(),
kind: EdgeKind::Input,
}
.chain(BufferEdgeConstructor {
nodes,
dst: node.0,
reads: node.1.buffer_reads().into_iter().cloned(),
kind: EdgeKind::Buffer,
})
.chain(EdgeConstructor {
nodes,
dst: node.0,
reads: node.1.read_attachments().into_iter().cloned(),
kind: EdgeKind::Sampled,
})
.chain(
EdgeConstructor {
nodes,
dst: node.0,
reads: node.1.color_attachments().iter().map(|v| v.resource),
kind: EdgeKind::Attachment,
}
.into_iter()
.filter(|val| !matches!(*val, Err(Error::MissingWrite(_, _, _)))),
)
.chain(
EdgeConstructor {
nodes,
dst: node.0,
reads: node.1.output_attachments().into_iter().cloned(),
kind: EdgeKind::Sampled,
}
.into_iter()
.filter(|val| !matches!(*val, Err(Error::MissingWrite(_, _, _)))),
)
})
.try_for_each(
|edge: super::error::Result<Edge>| -> super::error::Result<_> {
let edge = edge?;
edges.entry(edge.src).or_insert_with(Vec::new).push(edge);
dependencies
.entry(edge.dst)
.or_insert_with(Vec::new)
.push(edge);
Ok(())
},
)?;
Ok((edges, dependencies))
}
pub fn node(&self, node: NodeIndex) -> super::error::Result<&dyn Node> {
self.nodes
.get(node)
.ok_or_else(|| Error::InvalidNodeIndex(node))
.map(|val| val.as_ref())
}
pub fn node_renderpass<'a>(
&'a self,
node: NodeIndex,
) -> super::error::Result<(&'a RenderPass, u32)> {
let (pass, index) = self
.node_pass_map
.get(&node)
.and_then(|(pass, subpass_index)| Some((self.passes.get(*pass)?, *subpass_index)))
.ok_or(Error::InvalidNodeIndex(node))?;
match pass.kind() {
PassKind::Graphics {
renderpass,
framebuffer: _,
clear_values: _,
} => Ok((renderpass, index)),
PassKind::Transfer { .. } => Err(Error::InvalidNodeKind(
node,
NodeKind::Graphics,
self.nodes[node].node_kind(),
)),
}
}
/// Builds or rebuilds the rendergraph and creates appropriate renderpasses and framebuffers.
pub fn build<T>(&mut self, textures: T, extent: Extent) -> super::error::Result<()>
where
T: Deref<Target = ResourceCache<Texture>>,
{
let (_edges, dependencies) = self.build_edges()?;
let (ordered, depths) = topological_sort(&self.nodes, &self.edges)?;
let context = &self.context;
let nodes = &self.nodes;
self.node_pass_map.clear();
let node_pass_map = &mut self.node_pass_map;
node_pass_map.clear();
let passes = &mut self.passes;
passes.clear();
// Build all graphics nodes
let groups = ordered.iter().cloned().group_by(|node| {
return (depths[node], nodes[*node].node_kind());
});
for (key, group) in &groups {
println!("New pass: {:?}", key);
let pass_nodes = group.collect_vec();
let pass = Pass::new(
context,
nodes,
&textures,
&dependencies,
pass_nodes,
key.1,
extent,
)?;
// Insert pass into slotmap
let pass_index = passes.len();
passes.push(pass);
let pass_nodes = passes[pass_index].nodes();
// Map the node into the pass
for (i, node) in pass_nodes.iter().enumerate() {
node_pass_map.insert(*node, (pass_index, i as u32));
}
}
self.extent = extent;
Ok(())
}
// Begins the current frame and ensures resources are ready by waiting on fences.
// Begins recording of the commandbuffers.
// Returns the current frame in flight
pub fn begin(&self) -> super::error::Result<usize> {
let frame = &self.frames[self.current_frame];
let device = self.context.device();
// Make sure frame is available before beginning execution
fence::wait(device, &[frame.fence], true)?;
fence::reset(device, &[frame.fence])?;
// Reset commandbuffers for this frame
frame.commandpool.reset(false)?;
// Get the commandbuffer for this frame
let commandbuffer = &frame.commandbuffer;
// Start recording
commandbuffer.begin(CommandBufferUsageFlags::ONE_TIME_SUBMIT)?;
Ok(self.current_frame)
}
// Executes the whole rendergraph by starting renderpass recording and filling it using the
// node execution functions. Submits the resulting commandbuffer.
pub fn execute(&mut self, world: &mut World) -> super::error::Result<()> {
// Reset all commandbuffers for this frame
let frame = &mut self.frames[self.current_frame];
let nodes = &mut self.nodes;
let passes = &self.passes;
let extent = self.extent;
let current_frame = self.current_frame;
let cmd = &frame.commandbuffer;
// Execute all nodes
passes
.iter()
.try_for_each(|pass| -> super::error::Result<()> {
pass.execute(world, &cmd, nodes, current_frame, extent)
})?;
Ok(())
}
/// Ends and submits recording of commandbuffer for the current frame, and increments the
/// current_frame.
pub fn end(&mut self) -> super::error::Result<()> {
let frame = &self.frames[self.current_frame];
let commandbuffer = &frame.commandbuffer;
commandbuffer.end()?;
// Submit the results
commandbuffer.submit(
self.context.graphics_queue(),
&[frame.wait_semaphore],
&[frame.signal_semaphore],
frame.fence,
&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT],
)?;
// Move to the next frame in flight and wrap around to n-buffer
self.current_frame = (self.current_frame + 1) % self.frames_in_flight;
Ok(())
}
/// Get a reference to the current signal semaphore for the specified frame
pub fn signal_semaphore(&self, current_frame: usize) -> Semaphore {
self.frames[current_frame].signal_semaphore
}
/// Get a reference to the current wait semaphore for the specified frame
pub fn wait_semaphore(&self, current_frame: usize) -> Semaphore {
self.frames[current_frame].wait_semaphore
}
/// Get a reference to the current fence for the specified frame
pub fn fence(&self, current_frame: usize) -> Fence {
self.frames[current_frame].fence
}
pub fn commandbuffer(&self, current_frame: usize) -> &CommandBuffer {
&self.frames[current_frame].commandbuffer
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum VisitedState {
Pending,
Visited,
}
type Depth = u32;
/// Toplogically sorts the graph provided by nodes and edges.
/// Returns a tuple containing a dense array of ordered node indices, and a map containing each node's maximum depth.
// TODO: Move graph functionality into separate crate.
fn topological_sort(
nodes: &Vec<Box<dyn Node>>,
edges: &HashMap<NodeIndex, Vec<Edge>>,
) -> super::error::Result<(Vec<NodeIndex>, HashMap<NodeIndex, Depth>)> {
fn internal(
stack: &mut Vec<NodeIndex>,
visited: &mut HashMap<NodeIndex, VisitedState>,
depths: &mut HashMap<NodeIndex, Depth>,
current_node: NodeIndex,
edges: &HashMap<NodeIndex, Vec<Edge>>,
depth: Depth,
) -> super::error::Result<()> {
// Update maximum recursion depth
depths
.entry(current_node)
.and_modify(|d| *d = (*d).max(depth))
.or_insert(depth);
// Node is already visited
match visited.get(&current_node) {
Some(VisitedState::Pending) => return Err(Error::DependencyCycle),
Some(VisitedState::Visited) => return Ok(()),
_ => {}
};
visited.insert(current_node, VisitedState::Pending);
// Add all children of `node`, before node to the stack.
edges
.get(&current_node)
.iter()
.flat_map(|node_edges| node_edges.iter())
.try_for_each(|edge| {
internal(
stack,
visited,
depths,
edge.dst,
edges,
// Break depth if sampling is required since they can't share subpasses
match edge.kind {
EdgeKind::Sampled | EdgeKind::Buffer => depth + 1,
EdgeKind::Attachment | EdgeKind::Input => depth,
},
)
})?;
stack.push(current_node);
visited.insert(current_node, VisitedState::Visited);
Ok(())
}
let nodes_iter = nodes.into_iter();
let cap = nodes_iter.size_hint().1.unwrap_or_default();
let mut stack = Vec::with_capacity(cap);
let mut visited = HashMap::with_capacity(cap);
let mut depths = HashMap::with_capacity(cap);
for (node, _) in nodes_iter.enumerate() {
internal(&mut stack, &mut visited, &mut depths, node, edges, 0)?;
}
// stack.reverse();
Ok((stack, depths))
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Edge {
pub src: NodeIndex,
pub dst: NodeIndex,
pub resource: ResourceKind,
pub write_stage: PipelineStageFlags,
pub read_stage: PipelineStageFlags,
pub write_access: vk::AccessFlags,
pub read_access: vk::AccessFlags,
pub layout: ImageLayout,
pub kind: EdgeKind,
}
impl std::ops::Deref for Edge {
type Target = ResourceKind;
fn deref(&self) -> &Self::Target {
&self.resource
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResourceKind {
Texture(Handle<Texture>),
Buffer(vk::Buffer),
}
impl From<vk::Buffer> for ResourceKind {
fn from(val: vk::Buffer) -> Self {
Self::Buffer(val)
}
}
impl From<Handle<Texture>> for ResourceKind {
fn from(val: Handle<Texture>) -> Self {
Self::Texture(val)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EdgeKind {
/// Dependency is sampled and requires the whole attachment to be ready.
Sampled,
/// Dependency is used as input attachment and can use dependency by region.
Input,
/// The attachment is loaded and written to. Depend on earlier nodes
Attachment,
Buffer,
}
impl Default for EdgeKind {
fn default() -> Self {
Self::Sampled
}
}
struct FrameData {
context: SharedVulkanContext,
fence: Fence,
commandpool: CommandPool,
commandbuffer: CommandBuffer,
wait_semaphore: Semaphore,
signal_semaphore: Semaphore,
}
impl FrameData {
pub fn new(context: SharedVulkanContext) -> super::error::Result<Self> {
let commandpool = CommandPool::new(
context.device().clone(),
CommandPoolCreateInfo {
flags: CommandPoolCreateFlags {
..Default::default()
},
queue_family_index: context.queue_families().graphics().unwrap(),
..Default::default()
},
)?;
let commandbuffer = commandpool.allocate_one()?;
let fence = fence::create(context.device(), true)?;
let wait_semaphore = semaphore::create(context.device())?;
let signal_semaphore = semaphore::create(context.device())?;
Ok(Self {
context,
fence,
commandpool,
commandbuffer,
wait_semaphore,
signal_semaphore,
})
}
}
struct EdgeConstructor<'a, I> {
nodes: &'a Vec<Box<dyn Node>>,
dst: NodeIndex,
reads: I,
kind: EdgeKind,
}
impl<'a, I: Iterator<Item = Handle<Texture>>> Iterator for EdgeConstructor<'a, I> {
type Item = super::error::Result<Edge>;
fn next(&mut self) -> Option<Self::Item> {
self.reads
.next()
// Find the corresponding write attachment
.map(move |read| {
self.nodes
.iter()
.enumerate()
.take_while(|(src, _)| *src != self.dst)
.filter_map(|(src, src_node)| {
// Found color attachment output
if let Some(write) = src_node
.color_attachments()
.iter()
.find(|w| w.resource == read)
{
Some(Edge {
src,
dst: self.dst,
resource: read.into(),
layout: write.final_layout,
write_stage: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER,
write_access: vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
read_access: vk::AccessFlags::SHADER_READ,
kind: self.kind,
})
// Found transfer attachment output
} else if let Some(_) =
src_node.output_attachments().iter().find(|d| **d == read)
{
Some(Edge {
src,
dst: self.dst,
resource: read.into(),
layout: ImageLayout::TRANSFER_DST_OPTIMAL,
write_stage: vk::PipelineStageFlags::TRANSFER,
read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER,
write_access: vk::AccessFlags::TRANSFER_WRITE,
read_access: vk::AccessFlags::SHADER_READ,
kind: self.kind,
})
} else if let Some(write) = src_node
.depth_attachment()
.as_ref()
.filter(|d| d.resource == read)
{
Some(Edge {
src,
dst: self.dst,
resource: read.into(),
layout: write.final_layout,
// Write stage is between
write_stage: vk::PipelineStageFlags::LATE_FRAGMENT_TESTS,
read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER,
write_access: vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
read_access: vk::AccessFlags::SHADER_READ,
kind: self.kind,
})
} else {
None
}
})
.last()
.ok_or(Error::MissingWrite(
self.dst,
self.nodes[self.dst].debug_name(),
read.into(),
))
})
}
}
struct BufferEdgeConstructor<'a, I> {
nodes: &'a Vec<Box<dyn Node>>,
dst: NodeIndex,
reads: I,
kind: EdgeKind,
}
impl<'a, I: Iterator<Item = vk::Buffer>> Iterator for BufferEdgeConstructor<'a, I> {
type Item = crate::Result<Edge>;
fn next(&mut self) -> Option<Self::Item> {
self.reads
.next()
// Find the corresponding write attachment
.map(move |read| {
self.nodes
.iter()
.enumerate()
.take_while(|(src, _)| *src != self.dst)
.filter_map(|(src, src_node)| {
// Found color attachment output
if let Some(_) = src_node.buffer_writes().iter().find(|w| **w == read) {
Some(Edge {
src,
dst: self.dst,
resource: read.into(),
layout: Default::default(),
write_stage: vk::PipelineStageFlags::TRANSFER,
read_stage: vk::PipelineStageFlags::VERTEX_SHADER,
write_access: vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
read_access: vk::AccessFlags::SHADER_READ,
kind: self.kind,
})
} else {
None
}
})
.last()
.ok_or(Error::MissingWrite(
self.dst,
self.nodes[self.dst].debug_name(),
read.into(),
))
})
}
}

View file

@ -0,0 +1 @@
pub mod model;

View file

@ -1,4 +1,4 @@
use super::vulkan::vertex::{Position, Normal};
use crate::vulkan::vertex::{Position, Normal};
pub const POSITIONS: [Position; 531] = [
Position {

View file

@ -0,0 +1,14 @@
use std::sync::Arc;
use vulkano::{image::view::ImageView, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass}};
pub fn make_framebuffer(render_pass: Arc<RenderPass>, attachments: Vec<Arc<ImageView>>) -> Result<Arc<Framebuffer>, vulkano::Validated<vulkano::VulkanError>> {
let extent = attachments[0].image().extent();
Framebuffer::new(render_pass.clone(), FramebufferCreateInfo {
attachments,
extent: [extent[0], extent[1]],
layers: 1,
..Default::default()
})
}

View file

@ -1,2 +1,4 @@
// pub mod pipeline;
pub mod pipeline;
pub mod framebuffer;
pub mod vertex;
pub mod renderpass;

View file

@ -17,14 +17,14 @@ use vulkano::{
GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo,
},
render_pass::{RenderPass, Subpass},
shader::ShaderModule,
shader::EntryPoint,
};
use super::vertex::{Normal, Position};
pub struct PipelineInfo {
pub vs: Arc<ShaderModule>,
pub fs: Arc<ShaderModule>,
pub vs: EntryPoint,
pub fs: EntryPoint,
}
pub fn make_pipeline(
@ -32,15 +32,12 @@ pub fn make_pipeline(
pipeline_info: &PipelineInfo,
pass_info: &PassInfo,
) -> Arc<GraphicsPipeline> {
let vs = pipeline_info.vs.entry_point("main").unwrap();
let fs = pipeline_info.fs.entry_point("main").unwrap();
let vertex_input_state = [Position::per_vertex(), Normal::per_vertex()]
.definition(&vs)
.definition(&pipeline_info.vs)
.unwrap();
let stages = [
PipelineShaderStageCreateInfo::new(vs),
PipelineShaderStageCreateInfo::new(fs),
PipelineShaderStageCreateInfo::new(pipeline_info.vs.clone()),
PipelineShaderStageCreateInfo::new(pipeline_info.fs.clone()),
];
let layout = PipelineLayout::new(
device.clone(),

View file

@ -0,0 +1,116 @@
use std::sync::Arc;
use vulkano::{device::Device, format::Format, render_pass::{AttachmentDescription, AttachmentReference, RenderPass as VkRenderPass, RenderPassCreateFlags, RenderPassCreateInfo, SubpassDependency, SubpassDescription, SubpassDescriptionFlags}};
use vulkano_util::renderer::VulkanoWindowRenderer;
#[derive(Default, Debug)]
pub struct SubpassInfo {
pub color_attachments: Vec<Option<AttachmentReference>>,
pub color_resolve_attachments: Vec<Option<AttachmentReference>>,
pub input_attachments: Vec<Option<AttachmentReference>>,
pub depth_stencil_attachment: Option<AttachmentReference>,
}
impl From<SubpassInfo> for SubpassDescription {
fn from(value: SubpassInfo) -> Self {
SubpassDescription {
flags: SubpassDescriptionFlags::default(),
color_attachments: value.color_attachments,
color_resolve_attachments: value.color_resolve_attachments,
input_attachments: value.input_attachments,
depth_stencil_attachment: value.depth_stencil_attachment,
..Default::default()
}
}
}
fn make_subpass_description(subpass_info: Arc<SubpassInfo>) -> SubpassDescription {
SubpassDescription {
flags: SubpassDescriptionFlags::default(),
color_attachments: subpass_info.color_attachments.clone(),
color_resolve_attachments: subpass_info.color_resolve_attachments.clone(),
input_attachments: subpass_info.input_attachments.clone(),
depth_stencil_attachment: subpass_info.depth_stencil_attachment.clone(),
..Default::default()
}
}
#[derive(Default)]
pub struct RenderPassInfo {
pub attachments: Vec<AttachmentDescription>,
pub subpasses: Vec<Arc<SubpassInfo>>,
pub dependencies: Vec<SubpassDependency>,
}
pub struct RenderPass {
device: Arc<Device>,
render_pass: Arc<VkRenderPass>
}
impl RenderPass {
pub fn new(device: Arc<vulkano::device::Device>, info: Arc<RenderPassInfo>) -> Self {
let vk_subpasses = info.subpasses.iter().map(|subpass| make_subpass_description(subpass.clone())).collect::<Vec<SubpassDescription>>();
let render_pass_create_info = RenderPassCreateInfo {
attachments: info.attachments.clone(),
dependencies: info.dependencies.clone(),
subpasses: vk_subpasses,
..Default::default()
};
let render_pass = VkRenderPass::new(device.clone(), render_pass_create_info).unwrap();
Self {
device: device.clone(),
render_pass
}
}
pub fn renderpass(&self) -> Arc<VkRenderPass> {
self.render_pass.clone()
}
}
pub fn make_render_pass(device: Arc<vulkano::device::Device>, renderer: &mut VulkanoWindowRenderer) -> Arc<VkRenderPass> {
let color_attachment = AttachmentDescription {
format: renderer.swapchain_format(),
samples: vulkano::image::SampleCount::Sample1,
load_op: vulkano::render_pass::AttachmentLoadOp::Clear,
store_op: vulkano::render_pass::AttachmentStoreOp::Store,
initial_layout: vulkano::image::ImageLayout::ColorAttachmentOptimal,
final_layout: vulkano::image::ImageLayout::ColorAttachmentOptimal,
..Default::default()
};
let depth_stencil_attachment = AttachmentDescription {
format: Format::D16_UNORM,
samples: vulkano::image::SampleCount::Sample1,
load_op: vulkano::render_pass::AttachmentLoadOp::Clear,
store_op: vulkano::render_pass::AttachmentStoreOp::DontCare,
initial_layout: vulkano::image::ImageLayout::DepthStencilAttachmentOptimal,
final_layout: vulkano::image::ImageLayout::DepthStencilAttachmentOptimal,
..Default::default()
};
let color_attachment_reference = AttachmentReference {
attachment: 0,
layout: color_attachment.initial_layout,
..Default::default()
};
let depth_stencil_attachment_reference = AttachmentReference {
attachment: 1,
layout: depth_stencil_attachment.initial_layout,
..Default::default()
};
let subpass_description = SubpassDescription {
color_attachments: vec![Some(color_attachment_reference)],
depth_stencil_attachment: Some(depth_stencil_attachment_reference),
..Default::default()
};
VkRenderPass::new(device.clone(), RenderPassCreateInfo {
attachments: vec![color_attachment, depth_stencil_attachment],
subpasses: vec![subpass_description],
..Default::default()
}).unwrap()
}