Initial attempt to implement Render Graph
This commit is contained in:
parent
173cfe2acf
commit
3732fc5e3d
33 changed files with 8851 additions and 938 deletions
|
@ -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" }
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
|
1025
modules/khors-graphics/src/node_editor/mod.rs
Normal file
1025
modules/khors-graphics/src/node_editor/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
504
modules/khors-graphics/src/renderer.rs
Normal file
504
modules/khors-graphics/src/renderer.rs
Normal 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",
|
||||
}
|
||||
}
|
||||
|
33
modules/khors-graphics/src/rendergraph/error.rs
Normal file
33
modules/khors-graphics/src/rendergraph/error.rs
Normal 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),
|
||||
}
|
7
modules/khors-graphics/src/rendergraph/mod.rs
Normal file
7
modules/khors-graphics/src/rendergraph/mod.rs
Normal 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;
|
172
modules/khors-graphics/src/rendergraph/node.rs
Normal file
172
modules/khors-graphics/src/rendergraph/node.rs
Normal 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>>()
|
||||
}
|
||||
}
|
395
modules/khors-graphics/src/rendergraph/pass.rs
Normal file
395
modules/khors-graphics/src/rendergraph/pass.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
622
modules/khors-graphics/src/rendergraph/rendergraph.rs
Normal file
622
modules/khors-graphics/src/rendergraph/rendergraph.rs
Normal 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(¤t_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(¤t_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(),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
1
modules/khors-graphics/src/temp/mod.rs
Normal file
1
modules/khors-graphics/src/temp/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod model;
|
|
@ -1,4 +1,4 @@
|
|||
use super::vulkan::vertex::{Position, Normal};
|
||||
use crate::vulkan::vertex::{Position, Normal};
|
||||
|
||||
pub const POSITIONS: [Position; 531] = [
|
||||
Position {
|
14
modules/khors-graphics/src/vulkan/framebuffer.rs
Normal file
14
modules/khors-graphics/src/vulkan/framebuffer.rs
Normal 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()
|
||||
})
|
||||
}
|
|
@ -1,2 +1,4 @@
|
|||
// pub mod pipeline;
|
||||
pub mod pipeline;
|
||||
pub mod framebuffer;
|
||||
pub mod vertex;
|
||||
pub mod renderpass;
|
||||
|
|
|
@ -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(),
|
||||
|
|
116
modules/khors-graphics/src/vulkan/renderpass.rs
Normal file
116
modules/khors-graphics/src/vulkan/renderpass.rs
Normal 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()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue