diff --git a/Cargo.lock b/Cargo.lock index 6745f1f..6f7a76d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1431,6 +1431,18 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "log", + "presser", + "thiserror 2.0.17", +] + [[package]] name = "gpu-descriptor" version = "0.3.2" @@ -2045,6 +2057,7 @@ dependencies = [ "bytemuck", "cargo-gpu", "env_logger", + "gpu-allocator", "mygraphics-shaders", "pollster", "raw-window-handle", @@ -2677,6 +2690,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "proc-macro-crate" version = "3.4.0" diff --git a/Cargo.toml b/Cargo.toml index b035c6d..9062a0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ - "graphics/mygraphics", - "graphics/mygraphics-shaders", - "xtask", + "graphics/mygraphics", + "graphics/mygraphics-shaders", + "xtask", ] resolver = "3" @@ -20,6 +20,7 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spir # API ash = "0.38" ash-window = "0.13" +gpu-allocator = { version = "0.28.0", default-features = false, features = ["std", "vulkan"] } wgpu = { version = "27.0.1", default-features = false, features = ["std", "parking_lot", "vulkan", "vulkan-portability", "spirv", "wgsl"] } pollster = "0.4.0" diff --git a/generated/graphics/ash/cargo-gpu/Cargo.toml b/generated/graphics/ash/cargo-gpu/Cargo.toml index c765d0b..f395198 100644 --- a/generated/graphics/ash/cargo-gpu/Cargo.toml +++ b/generated/graphics/ash/cargo-gpu/Cargo.toml @@ -19,6 +19,7 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spir # API ash = "0.38" ash-window = "0.13" +gpu-allocator = { version = "0.28.0", default-features = false, features = ["std", "vulkan"] } # rust-gpu cargo-gpu = { git = "https://github.com/Rust-GPU/cargo-gpu", rev = "bb74342b90066ce8b7d50ba8e058df356a54acf4" } diff --git a/generated/graphics/ash/cargo-gpu/mygraphics-shaders/src/lib.rs b/generated/graphics/ash/cargo-gpu/mygraphics-shaders/src/lib.rs index acf55ee..d2b343a 100644 --- a/generated/graphics/ash/cargo-gpu/mygraphics-shaders/src/lib.rs +++ b/generated/graphics/ash/cargo-gpu/mygraphics-shaders/src/lib.rs @@ -23,7 +23,7 @@ pub fn main_fs(vtx_color: Vec3, output: &mut Vec4) { #[spirv(vertex)] pub fn main_vs( #[spirv(vertex_index)] vert_id: i32, - #[spirv(push_constant)] constants: &ShaderConstants, + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] constants: &ShaderConstants, #[spirv(position)] vtx_pos: &mut Vec4, vtx_color: &mut Vec3, ) { diff --git a/generated/graphics/ash/cargo-gpu/mygraphics/Cargo.toml b/generated/graphics/ash/cargo-gpu/mygraphics/Cargo.toml index 38d9e61..7137e96 100644 --- a/generated/graphics/ash/cargo-gpu/mygraphics/Cargo.toml +++ b/generated/graphics/ash/cargo-gpu/mygraphics/Cargo.toml @@ -17,6 +17,7 @@ mygraphics-shaders = { path = "../mygraphics-shaders" } # API ash.workspace = true ash-window.workspace = true +gpu-allocator.workspace = true # other raw-window-handle.workspace = true diff --git a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/buffer.rs b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/buffer.rs new file mode 100644 index 0000000..3094c52 --- /dev/null +++ b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/buffer.rs @@ -0,0 +1,84 @@ +use crate::ash_renderer::device::MyDevice; +use ash::vk; +use bytemuck::NoUninit; +use gpu_allocator::MemoryLocation; +use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme}; +use std::borrow::Cow; +use std::sync::Arc; + +pub struct MyBuffer { + pub buffer: vk::Buffer, + pub allocation: Allocation, + pub name: String, + destroyed: bool, +} + +#[derive(Clone)] +pub struct BufferCreateInfo<'a> { + pub usage: vk::BufferUsageFlags, + pub location: MemoryLocation, + pub name: Option>, +} + +impl MyBuffer { + pub fn from_data( + device: &Arc, + info: BufferCreateInfo<'_>, + data: &T, + ) -> anyhow::Result { + Self::from_slice(device, info, bytemuck::bytes_of(data)) + } + + pub fn from_slice( + device: &Arc, + info: BufferCreateInfo<'_>, + data: &[T], + ) -> anyhow::Result { + unsafe { + let buffer = device.create_buffer( + &vk::BufferCreateInfo::default() + .size(size_of_val(data) as u64) + .usage(vk::BufferUsageFlags::STORAGE_BUFFER), + None, + )?; + let name = info.name.map(|a| a.into_owned()).unwrap_or_default(); + let mut allocation = device.borrow_allocator().allocate(&AllocationCreateDesc { + name: &name, + requirements: device.get_buffer_memory_requirements(buffer), + location: MemoryLocation::CpuToGpu, + linear: true, + allocation_scheme: AllocationScheme::GpuAllocatorManaged, + })?; + device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())?; + let mapped = &mut allocation.mapped_slice_mut().unwrap()[..size_of_val(data)]; + mapped.copy_from_slice(bytemuck::cast_slice(data)); + Ok(Self { + buffer, + allocation, + name, + destroyed: false, + }) + } + } + + /// Destroy this buffer + /// + /// # Safety + /// Buffer must not be in use + pub unsafe fn destroy(&mut self, device: &Arc) { + if !self.destroyed { + self.destroyed = true; + unsafe { + device.destroy_buffer(self.buffer, None); + } + } + } +} + +impl Drop for MyBuffer { + fn drop(&mut self) { + if !self.destroyed { + panic!("dropping Buffer {} without destroying it", &self.name); + } + } +} diff --git a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/device.rs b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/device.rs index 65066fa..3ccaa62 100644 --- a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/device.rs +++ b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/device.rs @@ -1,9 +1,10 @@ use anyhow::{Context, anyhow}; use ash::{ext, khr, vk}; +use gpu_allocator::vulkan::{Allocator, AllocatorCreateDesc}; use std::borrow::Cow; use std::ffi::{CStr, c_char}; use std::ops::Deref; -use std::sync::Arc; +use std::sync::{Arc, Mutex, MutexGuard}; /// Central struct containing the Vulkan instance and device, among others pub struct MyDevice { @@ -13,6 +14,7 @@ pub struct MyDevice { pub device: ash::Device, pub main_queue_family: u32, pub main_queue: vk::Queue, + allocator: Option>, pub debug_ext_instance: ext::debug_utils::Instance, pub debug_ext_device: ext::debug_utils::Device, pub surface_ext: khr::surface::Instance, @@ -134,6 +136,15 @@ impl MyDevice { .context("create_device")?; let main_queue = device.get_device_queue(main_queue_family, 0); + let allocator = Allocator::new(&AllocatorCreateDesc { + instance: instance.clone(), + device: device.clone(), + physical_device, + debug_settings: Default::default(), + buffer_device_address: false, + allocation_sizes: Default::default(), + })?; + Ok(Arc::new(Self { debug_ext_device: ext::debug_utils::Device::new(&instance, &device), surface_ext: khr::surface::Instance::new(&entry, &instance), @@ -144,16 +155,22 @@ impl MyDevice { device, main_queue_family, main_queue, + allocator: Some(Mutex::new(allocator)), debug_ext_instance: debug_instance, debug_callback, })) } } + + pub fn borrow_allocator(&self) -> MutexGuard<'_, Allocator> { + self.allocator.as_ref().unwrap().lock().unwrap() + } } impl Drop for MyDevice { fn drop(&mut self) { unsafe { + drop(self.allocator.take()); self.debug_ext_instance .destroy_debug_utils_messenger(self.debug_callback, None); self.device.destroy_device(None); @@ -178,6 +195,12 @@ unsafe extern "system" fn vulkan_debug_callback( .map_or(Cow::Borrowed(""), CStr::to_string_lossy); println!("{message_severity:?}: [{message_id_name}] : {message}"); - vk::FALSE + if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) + | message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) + { + vk::TRUE + } else { + vk::FALSE + } } } diff --git a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/global_descriptor_set.rs b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/global_descriptor_set.rs new file mode 100644 index 0000000..bf15fb0 --- /dev/null +++ b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/global_descriptor_set.rs @@ -0,0 +1,111 @@ +use crate::ash_renderer::device::MyDevice; +use ash::vk; +use std::sync::Arc; + +pub struct GlobalDescriptorSetLayout { + pub device: Arc, + pub layout: vk::DescriptorSetLayout, +} + +impl GlobalDescriptorSetLayout { + pub fn new(device: Arc) -> anyhow::Result> { + unsafe { + Ok(Arc::new(Self { + layout: device.create_descriptor_set_layout( + &vk::DescriptorSetLayoutCreateInfo::default().bindings(&[ + vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .stage_flags(vk::ShaderStageFlags::ALL_GRAPHICS) + .descriptor_count(1), + ]), + None, + )?, + device, + })) + } + } +} + +impl Drop for GlobalDescriptorSetLayout { + fn drop(&mut self) { + unsafe { + self.device.destroy_descriptor_set_layout(self.layout, None); + } + } +} + +/// This implementation of descriptor sets is kept simple on purpose, even at the cost of being quite inefficient. +/// Don't use this as a reference on how it should be done! +pub struct GlobalDescriptorSet { + pub layout: Arc, + pub pool: vk::DescriptorPool, + pub set: vk::DescriptorSet, + destroyed: bool, +} + +impl GlobalDescriptorSet { + /// # Safety + /// * `shader_constants` must not be dropped before `GlobalDescriptorSet` is dropped + /// * you must only drop this `GlobalDescriptorSet` when it is unused, e.g. by GPU execution + pub unsafe fn new( + layout: &Arc, + shader_constants: vk::Buffer, + ) -> anyhow::Result { + unsafe { + let device = &layout.device; + let pool = device.create_descriptor_pool( + &vk::DescriptorPoolCreateInfo::default() + .pool_sizes(&[vk::DescriptorPoolSize::default() + .ty(vk::DescriptorType::STORAGE_BUFFER) + .descriptor_count(1)]) + .max_sets(1), + None, + )?; + let set = device.allocate_descriptor_sets( + &vk::DescriptorSetAllocateInfo::default() + .descriptor_pool(pool) + .set_layouts(&[layout.layout]), + )?[0]; + device.update_descriptor_sets( + &[vk::WriteDescriptorSet::default() + .dst_set(set) + .dst_binding(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .descriptor_count(1) + .buffer_info(&[vk::DescriptorBufferInfo::default() + .buffer(shader_constants) + .offset(0) + .range(vk::WHOLE_SIZE)])], + &[], + ); + Ok(Self { + layout: layout.clone(), + pool, + set, + destroyed: false, + }) + } + } + + pub fn destroy(&mut self) { + if !self.destroyed { + self.destroyed = true; + unsafe { + let device = &self.layout.device; + device + .reset_descriptor_pool(self.pool, vk::DescriptorPoolResetFlags::empty()) + .ok(); + device.destroy_descriptor_pool(self.pool, None); + } + } + } +} + +impl Drop for GlobalDescriptorSet { + fn drop(&mut self) { + if !self.destroyed { + panic!("dropping GlobalDescriptorSet without destroying it"); + } + } +} diff --git a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/mod.rs b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/mod.rs index f72f697..289fc7d 100644 --- a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/mod.rs +++ b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/mod.rs @@ -1,5 +1,5 @@ use crate::ash_renderer::device::MyDevice; -use crate::ash_renderer::graphics::{MyRenderPipelineManager, MyRenderer}; +use crate::ash_renderer::renderer::MyRenderer; use crate::ash_renderer::swapchain::MySwapchainManager; use crate::util::enable_debug_layer; use ash::util::read_spv; @@ -11,8 +11,11 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +pub mod buffer; pub mod device; -pub mod graphics; +pub mod global_descriptor_set; +pub mod render_pipeline; +pub mod renderer; pub mod single_command_buffer; pub mod swapchain; @@ -34,24 +37,20 @@ pub fn main() -> anyhow::Result<()> { let extensions = ash_window::enumerate_required_extensions(window.display_handle()?.as_raw())?; let device = MyDevice::new(extensions, enable_debug_layer())?; let mut swapchain = MySwapchainManager::new(device.clone(), window)?; - let mut renderer = MyRenderer::new(MyRenderPipelineManager::new( - device.clone(), - swapchain.surface_format.format, - get_shaders()?, - )?)?; + let mut renderer = MyRenderer::new(device.clone(), swapchain.surface_format.format)?; let start = std::time::Instant::now(); let mut event_handler = move |event: Event<_>, event_loop_window_target: &ActiveEventLoop| match event { Event::AboutToWait => swapchain.render(|frame| { let extent = frame.extent; - let push_constants = ShaderConstants { + let shader_constants = ShaderConstants { width: extent.width, height: extent.height, time: start.elapsed().as_secs_f32(), }; - renderer.render_frame(frame, push_constants)?; + renderer.render_frame(frame, &shader_constants)?; Ok(()) }), Event::WindowEvent { event, .. } => { diff --git a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/graphics.rs b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/render_pipeline.rs similarity index 61% rename from generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/graphics.rs rename to generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/render_pipeline.rs index b860caf..dbb863e 100644 --- a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/graphics.rs +++ b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/render_pipeline.rs @@ -1,15 +1,14 @@ use crate::ash_renderer::device::MyDevice; -use crate::ash_renderer::single_command_buffer::SingleCommandBuffer; -use crate::ash_renderer::swapchain::DrawFrame; +use crate::ash_renderer::global_descriptor_set::{GlobalDescriptorSet, GlobalDescriptorSetLayout}; use anyhow::Context; use ash::vk; -use mygraphics_shaders::ShaderConstants; use std::sync::Arc; /// Manages the creation and recreation of [`MyRenderPipeline`], whenever new shader code ([`Self::set_shader_code`]) -/// is submitted or the spec constant is changed ([`Self::set_sky_fs_sun_intensity_factor`]) +/// is submitted pub struct MyRenderPipelineManager { pub device: Arc, + global_descriptor_set_layout: Arc, color_out_format: vk::Format, shader_code: Vec, pipeline: Option, @@ -24,11 +23,13 @@ pub struct MyRenderPipeline { impl MyRenderPipelineManager { pub fn new( device: Arc, + global_descriptor_set_layout: Arc, color_out_format: vk::Format, shader_code: Vec, ) -> anyhow::Result { Ok(Self { device, + global_descriptor_set_layout, color_out_format, shader_code, pipeline: None, @@ -65,12 +66,8 @@ impl MyRenderPipelineManager { )?; let pipeline_layout = self.device.create_pipeline_layout( - &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ - vk::PushConstantRange::default() - .offset(0) - .size(size_of::() as u32) - .stage_flags(vk::ShaderStageFlags::ALL), - ]), + &vk::PipelineLayoutCreateInfo::default() + .set_layouts(&[self.global_descriptor_set_layout.layout]), None, )?; @@ -179,7 +176,7 @@ impl MyRenderPipeline { cmd: vk::CommandBuffer, color_out: vk::ImageView, extent: vk::Extent2D, - push_constants: ShaderConstants, + global_descriptor_set: &GlobalDescriptorSet, ) -> anyhow::Result<()> { unsafe { let render_area = vk::Rect2D { @@ -218,12 +215,13 @@ impl MyRenderPipeline { }], ); device.cmd_set_scissor(cmd, 0, &[render_area]); - device.cmd_push_constants( + device.cmd_bind_descriptor_sets( cmd, + vk::PipelineBindPoint::GRAPHICS, self.pipeline_layout, - vk::ShaderStageFlags::ALL, 0, - bytemuck::bytes_of(&push_constants), + &[global_descriptor_set.set], + &[], ); device.cmd_draw(cmd, 3, 1, 0, 0); device.cmd_end_rendering(cmd); @@ -239,102 +237,3 @@ impl Drop for MyRenderPipelineManager { } } } - -/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. -pub struct MyRenderer { - pub device: Arc, - pub pipeline: MyRenderPipelineManager, - pub command: SingleCommandBuffer, -} - -impl MyRenderer { - pub fn new(pipeline: MyRenderPipelineManager) -> anyhow::Result { - Ok(Self { - command: SingleCommandBuffer::new(pipeline.device.clone())?, - device: pipeline.device.clone(), - pipeline, - }) - } - - pub fn render_frame( - &mut self, - frame: DrawFrame, - push_constants: ShaderConstants, - ) -> anyhow::Result<()> { - unsafe { - let device = &self.device; - let pipeline = self.pipeline.get_pipeline()?; - let cmd = self.command.cmd; - - device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; - - { - device.begin_command_buffer( - cmd, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - )?; - device.cmd_pipeline_barrier2( - cmd, - &vk::DependencyInfo::default().image_memory_barriers(&[ - vk::ImageMemoryBarrier2::default() - .image(frame.image) - .src_access_mask(vk::AccessFlags2::NONE) - .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) - .old_layout(vk::ImageLayout::UNDEFINED) - .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ), - ]), - ); - pipeline.render(device, cmd, frame.image_view, frame.extent, push_constants)?; - device.cmd_pipeline_barrier2( - cmd, - &vk::DependencyInfo::default().image_memory_barriers(&[ - vk::ImageMemoryBarrier2::default() - .image(frame.image) - .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .dst_access_mask(vk::AccessFlags2::NONE) - .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) - .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ), - ]), - ); - device.end_command_buffer(cmd)?; - } - - device.queue_submit2( - device.main_queue, - &[vk::SubmitInfo2::default() - .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() - .semaphore(frame.acquire_semaphore) - .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) - .command_buffer_infos(&[ - vk::CommandBufferSubmitInfo::default().command_buffer(cmd) - ]) - .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() - .semaphore(frame.draw_finished_semaphore) - .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], - frame.draw_finished_fence, - )?; - Ok(()) - } - } -} diff --git a/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/renderer.rs b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/renderer.rs new file mode 100644 index 0000000..99f0267 --- /dev/null +++ b/generated/graphics/ash/cargo-gpu/mygraphics/src/ash_renderer/renderer.rs @@ -0,0 +1,141 @@ +use crate::ash_renderer::buffer::{BufferCreateInfo, MyBuffer}; +use crate::ash_renderer::device::MyDevice; +use crate::ash_renderer::get_shaders; +use crate::ash_renderer::global_descriptor_set::{GlobalDescriptorSet, GlobalDescriptorSetLayout}; +use crate::ash_renderer::render_pipeline::MyRenderPipelineManager; +use crate::ash_renderer::single_command_buffer::SingleCommandBuffer; +use crate::ash_renderer::swapchain::DrawFrame; +use ash::vk; +use gpu_allocator::MemoryLocation; +use mygraphics_shaders::ShaderConstants; +use std::borrow::Cow; +use std::sync::Arc; + +/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. +pub struct MyRenderer { + pub device: Arc, + pub global_descriptor_set_layout: Arc, + pub pipeline: MyRenderPipelineManager, + pub command: SingleCommandBuffer, +} + +impl MyRenderer { + pub fn new(device: Arc, out_format: vk::Format) -> anyhow::Result { + let global_descriptor_set_layout = GlobalDescriptorSetLayout::new(device.clone())?; + let pipeline = MyRenderPipelineManager::new( + device.clone(), + global_descriptor_set_layout.clone(), + out_format, + get_shaders()?, + )?; + let command = SingleCommandBuffer::new(pipeline.device.clone())?; + Ok(Self { + device, + global_descriptor_set_layout, + pipeline, + command, + }) + } + + pub fn render_frame( + &mut self, + frame: DrawFrame, + shader_constants: &ShaderConstants, + ) -> anyhow::Result<()> { + unsafe { + let device = &self.device; + let pipeline = self.pipeline.get_pipeline()?; + let cmd = self.command.cmd; + + let mut buffer = MyBuffer::from_data( + device, + BufferCreateInfo { + usage: vk::BufferUsageFlags::STORAGE_BUFFER, + location: MemoryLocation::CpuToGpu, + name: Some(Cow::from("ShaderConstants")), + }, + shader_constants, + )?; + let mut descriptor_set = + GlobalDescriptorSet::new(&self.global_descriptor_set_layout, buffer.buffer)?; + + device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; + + { + device.begin_command_buffer( + cmd, + &vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::NONE) + .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .old_layout(vk::ImageLayout::UNDEFINED) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + pipeline.render(device, cmd, frame.image_view, frame.extent, &descriptor_set)?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .dst_access_mask(vk::AccessFlags2::NONE) + .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + device.end_command_buffer(cmd)?; + } + + device.queue_submit2( + device.main_queue, + &[vk::SubmitInfo2::default() + .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.acquire_semaphore) + .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) + .command_buffer_infos(&[ + vk::CommandBufferSubmitInfo::default().command_buffer(cmd) + ]) + .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.draw_finished_semaphore) + .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], + frame.draw_finished_fence, + )?; + + // free resources of the frame + // For a production renderer, you'd obviously don't want to immediately wait for the previous frame to + // finish rendering, but start recording the next one already while waiting. For simplicity's sake, + // we just wait immediately. + self.device.device_wait_idle()?; + descriptor_set.destroy(); + buffer.destroy(device); + Ok(()) + } + } +} diff --git a/generated/graphics/ash/spirv-builder/Cargo.toml b/generated/graphics/ash/spirv-builder/Cargo.toml index e8d1af9..20b97fd 100644 --- a/generated/graphics/ash/spirv-builder/Cargo.toml +++ b/generated/graphics/ash/spirv-builder/Cargo.toml @@ -19,6 +19,7 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spir # API ash = "0.38" ash-window = "0.13" +gpu-allocator = { version = "0.28.0", default-features = false, features = ["std", "vulkan"] } # rust-gpu # The version of the dependencies `spirv-builder` and `spirv-std` must match exactly! diff --git a/generated/graphics/ash/spirv-builder/mygraphics-shaders/src/lib.rs b/generated/graphics/ash/spirv-builder/mygraphics-shaders/src/lib.rs index acf55ee..d2b343a 100644 --- a/generated/graphics/ash/spirv-builder/mygraphics-shaders/src/lib.rs +++ b/generated/graphics/ash/spirv-builder/mygraphics-shaders/src/lib.rs @@ -23,7 +23,7 @@ pub fn main_fs(vtx_color: Vec3, output: &mut Vec4) { #[spirv(vertex)] pub fn main_vs( #[spirv(vertex_index)] vert_id: i32, - #[spirv(push_constant)] constants: &ShaderConstants, + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] constants: &ShaderConstants, #[spirv(position)] vtx_pos: &mut Vec4, vtx_color: &mut Vec3, ) { diff --git a/generated/graphics/ash/spirv-builder/mygraphics/Cargo.toml b/generated/graphics/ash/spirv-builder/mygraphics/Cargo.toml index 55ed561..2e19cf8 100644 --- a/generated/graphics/ash/spirv-builder/mygraphics/Cargo.toml +++ b/generated/graphics/ash/spirv-builder/mygraphics/Cargo.toml @@ -21,6 +21,7 @@ mygraphics-shaders = { path = "../mygraphics-shaders" } # API ash.workspace = true ash-window.workspace = true +gpu-allocator.workspace = true # other raw-window-handle.workspace = true diff --git a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/buffer.rs b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/buffer.rs new file mode 100644 index 0000000..3094c52 --- /dev/null +++ b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/buffer.rs @@ -0,0 +1,84 @@ +use crate::ash_renderer::device::MyDevice; +use ash::vk; +use bytemuck::NoUninit; +use gpu_allocator::MemoryLocation; +use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme}; +use std::borrow::Cow; +use std::sync::Arc; + +pub struct MyBuffer { + pub buffer: vk::Buffer, + pub allocation: Allocation, + pub name: String, + destroyed: bool, +} + +#[derive(Clone)] +pub struct BufferCreateInfo<'a> { + pub usage: vk::BufferUsageFlags, + pub location: MemoryLocation, + pub name: Option>, +} + +impl MyBuffer { + pub fn from_data( + device: &Arc, + info: BufferCreateInfo<'_>, + data: &T, + ) -> anyhow::Result { + Self::from_slice(device, info, bytemuck::bytes_of(data)) + } + + pub fn from_slice( + device: &Arc, + info: BufferCreateInfo<'_>, + data: &[T], + ) -> anyhow::Result { + unsafe { + let buffer = device.create_buffer( + &vk::BufferCreateInfo::default() + .size(size_of_val(data) as u64) + .usage(vk::BufferUsageFlags::STORAGE_BUFFER), + None, + )?; + let name = info.name.map(|a| a.into_owned()).unwrap_or_default(); + let mut allocation = device.borrow_allocator().allocate(&AllocationCreateDesc { + name: &name, + requirements: device.get_buffer_memory_requirements(buffer), + location: MemoryLocation::CpuToGpu, + linear: true, + allocation_scheme: AllocationScheme::GpuAllocatorManaged, + })?; + device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())?; + let mapped = &mut allocation.mapped_slice_mut().unwrap()[..size_of_val(data)]; + mapped.copy_from_slice(bytemuck::cast_slice(data)); + Ok(Self { + buffer, + allocation, + name, + destroyed: false, + }) + } + } + + /// Destroy this buffer + /// + /// # Safety + /// Buffer must not be in use + pub unsafe fn destroy(&mut self, device: &Arc) { + if !self.destroyed { + self.destroyed = true; + unsafe { + device.destroy_buffer(self.buffer, None); + } + } + } +} + +impl Drop for MyBuffer { + fn drop(&mut self) { + if !self.destroyed { + panic!("dropping Buffer {} without destroying it", &self.name); + } + } +} diff --git a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/device.rs b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/device.rs index 65066fa..3ccaa62 100644 --- a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/device.rs +++ b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/device.rs @@ -1,9 +1,10 @@ use anyhow::{Context, anyhow}; use ash::{ext, khr, vk}; +use gpu_allocator::vulkan::{Allocator, AllocatorCreateDesc}; use std::borrow::Cow; use std::ffi::{CStr, c_char}; use std::ops::Deref; -use std::sync::Arc; +use std::sync::{Arc, Mutex, MutexGuard}; /// Central struct containing the Vulkan instance and device, among others pub struct MyDevice { @@ -13,6 +14,7 @@ pub struct MyDevice { pub device: ash::Device, pub main_queue_family: u32, pub main_queue: vk::Queue, + allocator: Option>, pub debug_ext_instance: ext::debug_utils::Instance, pub debug_ext_device: ext::debug_utils::Device, pub surface_ext: khr::surface::Instance, @@ -134,6 +136,15 @@ impl MyDevice { .context("create_device")?; let main_queue = device.get_device_queue(main_queue_family, 0); + let allocator = Allocator::new(&AllocatorCreateDesc { + instance: instance.clone(), + device: device.clone(), + physical_device, + debug_settings: Default::default(), + buffer_device_address: false, + allocation_sizes: Default::default(), + })?; + Ok(Arc::new(Self { debug_ext_device: ext::debug_utils::Device::new(&instance, &device), surface_ext: khr::surface::Instance::new(&entry, &instance), @@ -144,16 +155,22 @@ impl MyDevice { device, main_queue_family, main_queue, + allocator: Some(Mutex::new(allocator)), debug_ext_instance: debug_instance, debug_callback, })) } } + + pub fn borrow_allocator(&self) -> MutexGuard<'_, Allocator> { + self.allocator.as_ref().unwrap().lock().unwrap() + } } impl Drop for MyDevice { fn drop(&mut self) { unsafe { + drop(self.allocator.take()); self.debug_ext_instance .destroy_debug_utils_messenger(self.debug_callback, None); self.device.destroy_device(None); @@ -178,6 +195,12 @@ unsafe extern "system" fn vulkan_debug_callback( .map_or(Cow::Borrowed(""), CStr::to_string_lossy); println!("{message_severity:?}: [{message_id_name}] : {message}"); - vk::FALSE + if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) + | message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) + { + vk::TRUE + } else { + vk::FALSE + } } } diff --git a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/global_descriptor_set.rs b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/global_descriptor_set.rs new file mode 100644 index 0000000..bf15fb0 --- /dev/null +++ b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/global_descriptor_set.rs @@ -0,0 +1,111 @@ +use crate::ash_renderer::device::MyDevice; +use ash::vk; +use std::sync::Arc; + +pub struct GlobalDescriptorSetLayout { + pub device: Arc, + pub layout: vk::DescriptorSetLayout, +} + +impl GlobalDescriptorSetLayout { + pub fn new(device: Arc) -> anyhow::Result> { + unsafe { + Ok(Arc::new(Self { + layout: device.create_descriptor_set_layout( + &vk::DescriptorSetLayoutCreateInfo::default().bindings(&[ + vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .stage_flags(vk::ShaderStageFlags::ALL_GRAPHICS) + .descriptor_count(1), + ]), + None, + )?, + device, + })) + } + } +} + +impl Drop for GlobalDescriptorSetLayout { + fn drop(&mut self) { + unsafe { + self.device.destroy_descriptor_set_layout(self.layout, None); + } + } +} + +/// This implementation of descriptor sets is kept simple on purpose, even at the cost of being quite inefficient. +/// Don't use this as a reference on how it should be done! +pub struct GlobalDescriptorSet { + pub layout: Arc, + pub pool: vk::DescriptorPool, + pub set: vk::DescriptorSet, + destroyed: bool, +} + +impl GlobalDescriptorSet { + /// # Safety + /// * `shader_constants` must not be dropped before `GlobalDescriptorSet` is dropped + /// * you must only drop this `GlobalDescriptorSet` when it is unused, e.g. by GPU execution + pub unsafe fn new( + layout: &Arc, + shader_constants: vk::Buffer, + ) -> anyhow::Result { + unsafe { + let device = &layout.device; + let pool = device.create_descriptor_pool( + &vk::DescriptorPoolCreateInfo::default() + .pool_sizes(&[vk::DescriptorPoolSize::default() + .ty(vk::DescriptorType::STORAGE_BUFFER) + .descriptor_count(1)]) + .max_sets(1), + None, + )?; + let set = device.allocate_descriptor_sets( + &vk::DescriptorSetAllocateInfo::default() + .descriptor_pool(pool) + .set_layouts(&[layout.layout]), + )?[0]; + device.update_descriptor_sets( + &[vk::WriteDescriptorSet::default() + .dst_set(set) + .dst_binding(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .descriptor_count(1) + .buffer_info(&[vk::DescriptorBufferInfo::default() + .buffer(shader_constants) + .offset(0) + .range(vk::WHOLE_SIZE)])], + &[], + ); + Ok(Self { + layout: layout.clone(), + pool, + set, + destroyed: false, + }) + } + } + + pub fn destroy(&mut self) { + if !self.destroyed { + self.destroyed = true; + unsafe { + let device = &self.layout.device; + device + .reset_descriptor_pool(self.pool, vk::DescriptorPoolResetFlags::empty()) + .ok(); + device.destroy_descriptor_pool(self.pool, None); + } + } + } +} + +impl Drop for GlobalDescriptorSet { + fn drop(&mut self) { + if !self.destroyed { + panic!("dropping GlobalDescriptorSet without destroying it"); + } + } +} diff --git a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/mod.rs b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/mod.rs index f72f697..289fc7d 100644 --- a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/mod.rs +++ b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/mod.rs @@ -1,5 +1,5 @@ use crate::ash_renderer::device::MyDevice; -use crate::ash_renderer::graphics::{MyRenderPipelineManager, MyRenderer}; +use crate::ash_renderer::renderer::MyRenderer; use crate::ash_renderer::swapchain::MySwapchainManager; use crate::util::enable_debug_layer; use ash::util::read_spv; @@ -11,8 +11,11 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +pub mod buffer; pub mod device; -pub mod graphics; +pub mod global_descriptor_set; +pub mod render_pipeline; +pub mod renderer; pub mod single_command_buffer; pub mod swapchain; @@ -34,24 +37,20 @@ pub fn main() -> anyhow::Result<()> { let extensions = ash_window::enumerate_required_extensions(window.display_handle()?.as_raw())?; let device = MyDevice::new(extensions, enable_debug_layer())?; let mut swapchain = MySwapchainManager::new(device.clone(), window)?; - let mut renderer = MyRenderer::new(MyRenderPipelineManager::new( - device.clone(), - swapchain.surface_format.format, - get_shaders()?, - )?)?; + let mut renderer = MyRenderer::new(device.clone(), swapchain.surface_format.format)?; let start = std::time::Instant::now(); let mut event_handler = move |event: Event<_>, event_loop_window_target: &ActiveEventLoop| match event { Event::AboutToWait => swapchain.render(|frame| { let extent = frame.extent; - let push_constants = ShaderConstants { + let shader_constants = ShaderConstants { width: extent.width, height: extent.height, time: start.elapsed().as_secs_f32(), }; - renderer.render_frame(frame, push_constants)?; + renderer.render_frame(frame, &shader_constants)?; Ok(()) }), Event::WindowEvent { event, .. } => { diff --git a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/graphics.rs b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/render_pipeline.rs similarity index 61% rename from generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/graphics.rs rename to generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/render_pipeline.rs index b860caf..dbb863e 100644 --- a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/graphics.rs +++ b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/render_pipeline.rs @@ -1,15 +1,14 @@ use crate::ash_renderer::device::MyDevice; -use crate::ash_renderer::single_command_buffer::SingleCommandBuffer; -use crate::ash_renderer::swapchain::DrawFrame; +use crate::ash_renderer::global_descriptor_set::{GlobalDescriptorSet, GlobalDescriptorSetLayout}; use anyhow::Context; use ash::vk; -use mygraphics_shaders::ShaderConstants; use std::sync::Arc; /// Manages the creation and recreation of [`MyRenderPipeline`], whenever new shader code ([`Self::set_shader_code`]) -/// is submitted or the spec constant is changed ([`Self::set_sky_fs_sun_intensity_factor`]) +/// is submitted pub struct MyRenderPipelineManager { pub device: Arc, + global_descriptor_set_layout: Arc, color_out_format: vk::Format, shader_code: Vec, pipeline: Option, @@ -24,11 +23,13 @@ pub struct MyRenderPipeline { impl MyRenderPipelineManager { pub fn new( device: Arc, + global_descriptor_set_layout: Arc, color_out_format: vk::Format, shader_code: Vec, ) -> anyhow::Result { Ok(Self { device, + global_descriptor_set_layout, color_out_format, shader_code, pipeline: None, @@ -65,12 +66,8 @@ impl MyRenderPipelineManager { )?; let pipeline_layout = self.device.create_pipeline_layout( - &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ - vk::PushConstantRange::default() - .offset(0) - .size(size_of::() as u32) - .stage_flags(vk::ShaderStageFlags::ALL), - ]), + &vk::PipelineLayoutCreateInfo::default() + .set_layouts(&[self.global_descriptor_set_layout.layout]), None, )?; @@ -179,7 +176,7 @@ impl MyRenderPipeline { cmd: vk::CommandBuffer, color_out: vk::ImageView, extent: vk::Extent2D, - push_constants: ShaderConstants, + global_descriptor_set: &GlobalDescriptorSet, ) -> anyhow::Result<()> { unsafe { let render_area = vk::Rect2D { @@ -218,12 +215,13 @@ impl MyRenderPipeline { }], ); device.cmd_set_scissor(cmd, 0, &[render_area]); - device.cmd_push_constants( + device.cmd_bind_descriptor_sets( cmd, + vk::PipelineBindPoint::GRAPHICS, self.pipeline_layout, - vk::ShaderStageFlags::ALL, 0, - bytemuck::bytes_of(&push_constants), + &[global_descriptor_set.set], + &[], ); device.cmd_draw(cmd, 3, 1, 0, 0); device.cmd_end_rendering(cmd); @@ -239,102 +237,3 @@ impl Drop for MyRenderPipelineManager { } } } - -/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. -pub struct MyRenderer { - pub device: Arc, - pub pipeline: MyRenderPipelineManager, - pub command: SingleCommandBuffer, -} - -impl MyRenderer { - pub fn new(pipeline: MyRenderPipelineManager) -> anyhow::Result { - Ok(Self { - command: SingleCommandBuffer::new(pipeline.device.clone())?, - device: pipeline.device.clone(), - pipeline, - }) - } - - pub fn render_frame( - &mut self, - frame: DrawFrame, - push_constants: ShaderConstants, - ) -> anyhow::Result<()> { - unsafe { - let device = &self.device; - let pipeline = self.pipeline.get_pipeline()?; - let cmd = self.command.cmd; - - device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; - - { - device.begin_command_buffer( - cmd, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - )?; - device.cmd_pipeline_barrier2( - cmd, - &vk::DependencyInfo::default().image_memory_barriers(&[ - vk::ImageMemoryBarrier2::default() - .image(frame.image) - .src_access_mask(vk::AccessFlags2::NONE) - .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) - .old_layout(vk::ImageLayout::UNDEFINED) - .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ), - ]), - ); - pipeline.render(device, cmd, frame.image_view, frame.extent, push_constants)?; - device.cmd_pipeline_barrier2( - cmd, - &vk::DependencyInfo::default().image_memory_barriers(&[ - vk::ImageMemoryBarrier2::default() - .image(frame.image) - .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .dst_access_mask(vk::AccessFlags2::NONE) - .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) - .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ), - ]), - ); - device.end_command_buffer(cmd)?; - } - - device.queue_submit2( - device.main_queue, - &[vk::SubmitInfo2::default() - .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() - .semaphore(frame.acquire_semaphore) - .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) - .command_buffer_infos(&[ - vk::CommandBufferSubmitInfo::default().command_buffer(cmd) - ]) - .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() - .semaphore(frame.draw_finished_semaphore) - .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], - frame.draw_finished_fence, - )?; - Ok(()) - } - } -} diff --git a/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/renderer.rs b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/renderer.rs new file mode 100644 index 0000000..99f0267 --- /dev/null +++ b/generated/graphics/ash/spirv-builder/mygraphics/src/ash_renderer/renderer.rs @@ -0,0 +1,141 @@ +use crate::ash_renderer::buffer::{BufferCreateInfo, MyBuffer}; +use crate::ash_renderer::device::MyDevice; +use crate::ash_renderer::get_shaders; +use crate::ash_renderer::global_descriptor_set::{GlobalDescriptorSet, GlobalDescriptorSetLayout}; +use crate::ash_renderer::render_pipeline::MyRenderPipelineManager; +use crate::ash_renderer::single_command_buffer::SingleCommandBuffer; +use crate::ash_renderer::swapchain::DrawFrame; +use ash::vk; +use gpu_allocator::MemoryLocation; +use mygraphics_shaders::ShaderConstants; +use std::borrow::Cow; +use std::sync::Arc; + +/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. +pub struct MyRenderer { + pub device: Arc, + pub global_descriptor_set_layout: Arc, + pub pipeline: MyRenderPipelineManager, + pub command: SingleCommandBuffer, +} + +impl MyRenderer { + pub fn new(device: Arc, out_format: vk::Format) -> anyhow::Result { + let global_descriptor_set_layout = GlobalDescriptorSetLayout::new(device.clone())?; + let pipeline = MyRenderPipelineManager::new( + device.clone(), + global_descriptor_set_layout.clone(), + out_format, + get_shaders()?, + )?; + let command = SingleCommandBuffer::new(pipeline.device.clone())?; + Ok(Self { + device, + global_descriptor_set_layout, + pipeline, + command, + }) + } + + pub fn render_frame( + &mut self, + frame: DrawFrame, + shader_constants: &ShaderConstants, + ) -> anyhow::Result<()> { + unsafe { + let device = &self.device; + let pipeline = self.pipeline.get_pipeline()?; + let cmd = self.command.cmd; + + let mut buffer = MyBuffer::from_data( + device, + BufferCreateInfo { + usage: vk::BufferUsageFlags::STORAGE_BUFFER, + location: MemoryLocation::CpuToGpu, + name: Some(Cow::from("ShaderConstants")), + }, + shader_constants, + )?; + let mut descriptor_set = + GlobalDescriptorSet::new(&self.global_descriptor_set_layout, buffer.buffer)?; + + device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; + + { + device.begin_command_buffer( + cmd, + &vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::NONE) + .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .old_layout(vk::ImageLayout::UNDEFINED) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + pipeline.render(device, cmd, frame.image_view, frame.extent, &descriptor_set)?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .dst_access_mask(vk::AccessFlags2::NONE) + .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + device.end_command_buffer(cmd)?; + } + + device.queue_submit2( + device.main_queue, + &[vk::SubmitInfo2::default() + .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.acquire_semaphore) + .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) + .command_buffer_infos(&[ + vk::CommandBufferSubmitInfo::default().command_buffer(cmd) + ]) + .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.draw_finished_semaphore) + .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], + frame.draw_finished_fence, + )?; + + // free resources of the frame + // For a production renderer, you'd obviously don't want to immediately wait for the previous frame to + // finish rendering, but start recording the next one already while waiting. For simplicity's sake, + // we just wait immediately. + self.device.device_wait_idle()?; + descriptor_set.destroy(); + buffer.destroy(device); + Ok(()) + } + } +} diff --git a/generated/graphics/wgpu/cargo-gpu/mygraphics-shaders/src/lib.rs b/generated/graphics/wgpu/cargo-gpu/mygraphics-shaders/src/lib.rs index acf55ee..d2b343a 100644 --- a/generated/graphics/wgpu/cargo-gpu/mygraphics-shaders/src/lib.rs +++ b/generated/graphics/wgpu/cargo-gpu/mygraphics-shaders/src/lib.rs @@ -23,7 +23,7 @@ pub fn main_fs(vtx_color: Vec3, output: &mut Vec4) { #[spirv(vertex)] pub fn main_vs( #[spirv(vertex_index)] vert_id: i32, - #[spirv(push_constant)] constants: &ShaderConstants, + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] constants: &ShaderConstants, #[spirv(position)] vtx_pos: &mut Vec4, vtx_color: &mut Vec3, ) { diff --git a/generated/graphics/wgpu/cargo-gpu/mygraphics/src/wgpu_renderer/render_pipeline.rs b/generated/graphics/wgpu/cargo-gpu/mygraphics/src/wgpu_renderer/render_pipeline.rs index ee2097e..ba1f793 100644 --- a/generated/graphics/wgpu/cargo-gpu/mygraphics/src/wgpu_renderer/render_pipeline.rs +++ b/generated/graphics/wgpu/cargo-gpu/mygraphics/src/wgpu_renderer/render_pipeline.rs @@ -1,3 +1,4 @@ +use crate::wgpu_renderer::renderer::{GlobalBindGroup, GlobalBindGroupLayout}; use mygraphics_shaders::ShaderConstants; use wgpu::{ ColorTargetState, ColorWrites, Device, FragmentState, FrontFace, MultisampleState, @@ -6,17 +7,22 @@ use wgpu::{ include_spirv, }; +#[derive(Debug, Clone)] pub struct MyRenderPipeline { pipeline: RenderPipeline, } impl MyRenderPipeline { - pub fn new(device: &Device, out_format: TextureFormat) -> anyhow::Result { + pub fn new( + device: &Device, + global_bind_group_layout: &GlobalBindGroupLayout, + out_format: TextureFormat, + ) -> anyhow::Result { let module = device.create_shader_module(include_spirv!(env!("SHADER_SPV_PATH"))); let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("MyRenderPipeline layout"), - bind_group_layouts: &[], + bind_group_layouts: &[&global_bind_group_layout.0], push_constant_ranges: &[PushConstantRange { stages: ShaderStages::VERTEX_FRAGMENT, range: 0..size_of::() as u32, @@ -60,13 +66,9 @@ impl MyRenderPipeline { }) } - pub fn draw(&self, rpass: &mut RenderPass<'_>, shader_constants: &ShaderConstants) { + pub fn draw(&self, rpass: &mut RenderPass<'_>, global_bind_group: &GlobalBindGroup) { rpass.set_pipeline(&self.pipeline); - rpass.set_push_constants( - ShaderStages::VERTEX_FRAGMENT, - 0, - bytemuck::bytes_of(shader_constants), - ); + rpass.set_bind_group(0, &global_bind_group.0, &[]); rpass.draw(0..3, 0..1); } } diff --git a/generated/graphics/wgpu/cargo-gpu/mygraphics/src/wgpu_renderer/renderer.rs b/generated/graphics/wgpu/cargo-gpu/mygraphics/src/wgpu_renderer/renderer.rs index c5213df..2b4349e 100644 --- a/generated/graphics/wgpu/cargo-gpu/mygraphics/src/wgpu_renderer/renderer.rs +++ b/generated/graphics/wgpu/cargo-gpu/mygraphics/src/wgpu_renderer/renderer.rs @@ -1,27 +1,38 @@ use crate::wgpu_renderer::render_pipeline::MyRenderPipeline; use mygraphics_shaders::ShaderConstants; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::wgt::CommandEncoderDescriptor; use wgpu::{ - Color, Device, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, - StoreOp, TextureFormat, TextureView, + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBinding, BufferBindingType, + BufferUsages, Color, Device, LoadOp, Operations, Queue, RenderPassColorAttachment, + RenderPassDescriptor, ShaderStages, StoreOp, TextureFormat, TextureView, }; pub struct MyRenderer { pub device: Device, pub queue: Queue, + global_bind_group_layout: GlobalBindGroupLayout, pipeline: MyRenderPipeline, } impl MyRenderer { pub fn new(device: Device, queue: Queue, out_format: TextureFormat) -> anyhow::Result { + let global_bind_group_layout = GlobalBindGroupLayout::new(&device); + let pipeline = MyRenderPipeline::new(&device, &global_bind_group_layout, out_format)?; Ok(Self { - pipeline: MyRenderPipeline::new(&device, out_format)?, + global_bind_group_layout, + pipeline, device, queue, }) } pub fn render(&self, shader_constants: &ShaderConstants, output: TextureView) { + let global_bind_group = self + .global_bind_group_layout + .create(&self.device, shader_constants); + let mut cmd = self .device .create_command_encoder(&CommandEncoderDescriptor { @@ -43,9 +54,61 @@ impl MyRenderer { timestamp_writes: None, occlusion_query_set: None, }); - self.pipeline.draw(&mut rpass, shader_constants); + self.pipeline.draw(&mut rpass, &global_bind_group); drop(rpass); self.queue.submit(std::iter::once(cmd.finish())); } } + +#[derive(Debug, Clone)] +pub struct GlobalBindGroupLayout(pub BindGroupLayout); + +impl GlobalBindGroupLayout { + pub fn new(device: &Device) -> Self { + Self(device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("GlobalBindGroupLayout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + })) + } + + pub fn create(&self, device: &Device, shader_constants: &ShaderConstants) -> GlobalBindGroup { + let shader_constants = device.create_buffer_init(&BufferInitDescriptor { + label: Some("ShaderConstants"), + contents: bytemuck::bytes_of(shader_constants), + usage: BufferUsages::STORAGE, + }); + self.create_from_buffer(device, &shader_constants) + } + + pub fn create_from_buffer( + &self, + device: &Device, + shader_constants: &Buffer, + ) -> GlobalBindGroup { + GlobalBindGroup(device.create_bind_group(&BindGroupDescriptor { + label: Some("GlobalBindGroup"), + layout: &self.0, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::Buffer(BufferBinding { + buffer: shader_constants, + offset: 0, + size: None, + }), + }], + })) + } +} + +#[derive(Debug, Clone)] +pub struct GlobalBindGroup(pub BindGroup); diff --git a/generated/graphics/wgpu/spirv-builder/mygraphics-shaders/src/lib.rs b/generated/graphics/wgpu/spirv-builder/mygraphics-shaders/src/lib.rs index acf55ee..d2b343a 100644 --- a/generated/graphics/wgpu/spirv-builder/mygraphics-shaders/src/lib.rs +++ b/generated/graphics/wgpu/spirv-builder/mygraphics-shaders/src/lib.rs @@ -23,7 +23,7 @@ pub fn main_fs(vtx_color: Vec3, output: &mut Vec4) { #[spirv(vertex)] pub fn main_vs( #[spirv(vertex_index)] vert_id: i32, - #[spirv(push_constant)] constants: &ShaderConstants, + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] constants: &ShaderConstants, #[spirv(position)] vtx_pos: &mut Vec4, vtx_color: &mut Vec3, ) { diff --git a/generated/graphics/wgpu/spirv-builder/mygraphics/src/wgpu_renderer/render_pipeline.rs b/generated/graphics/wgpu/spirv-builder/mygraphics/src/wgpu_renderer/render_pipeline.rs index ee2097e..ba1f793 100644 --- a/generated/graphics/wgpu/spirv-builder/mygraphics/src/wgpu_renderer/render_pipeline.rs +++ b/generated/graphics/wgpu/spirv-builder/mygraphics/src/wgpu_renderer/render_pipeline.rs @@ -1,3 +1,4 @@ +use crate::wgpu_renderer::renderer::{GlobalBindGroup, GlobalBindGroupLayout}; use mygraphics_shaders::ShaderConstants; use wgpu::{ ColorTargetState, ColorWrites, Device, FragmentState, FrontFace, MultisampleState, @@ -6,17 +7,22 @@ use wgpu::{ include_spirv, }; +#[derive(Debug, Clone)] pub struct MyRenderPipeline { pipeline: RenderPipeline, } impl MyRenderPipeline { - pub fn new(device: &Device, out_format: TextureFormat) -> anyhow::Result { + pub fn new( + device: &Device, + global_bind_group_layout: &GlobalBindGroupLayout, + out_format: TextureFormat, + ) -> anyhow::Result { let module = device.create_shader_module(include_spirv!(env!("SHADER_SPV_PATH"))); let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("MyRenderPipeline layout"), - bind_group_layouts: &[], + bind_group_layouts: &[&global_bind_group_layout.0], push_constant_ranges: &[PushConstantRange { stages: ShaderStages::VERTEX_FRAGMENT, range: 0..size_of::() as u32, @@ -60,13 +66,9 @@ impl MyRenderPipeline { }) } - pub fn draw(&self, rpass: &mut RenderPass<'_>, shader_constants: &ShaderConstants) { + pub fn draw(&self, rpass: &mut RenderPass<'_>, global_bind_group: &GlobalBindGroup) { rpass.set_pipeline(&self.pipeline); - rpass.set_push_constants( - ShaderStages::VERTEX_FRAGMENT, - 0, - bytemuck::bytes_of(shader_constants), - ); + rpass.set_bind_group(0, &global_bind_group.0, &[]); rpass.draw(0..3, 0..1); } } diff --git a/generated/graphics/wgpu/spirv-builder/mygraphics/src/wgpu_renderer/renderer.rs b/generated/graphics/wgpu/spirv-builder/mygraphics/src/wgpu_renderer/renderer.rs index c5213df..2b4349e 100644 --- a/generated/graphics/wgpu/spirv-builder/mygraphics/src/wgpu_renderer/renderer.rs +++ b/generated/graphics/wgpu/spirv-builder/mygraphics/src/wgpu_renderer/renderer.rs @@ -1,27 +1,38 @@ use crate::wgpu_renderer::render_pipeline::MyRenderPipeline; use mygraphics_shaders::ShaderConstants; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::wgt::CommandEncoderDescriptor; use wgpu::{ - Color, Device, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, - StoreOp, TextureFormat, TextureView, + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBinding, BufferBindingType, + BufferUsages, Color, Device, LoadOp, Operations, Queue, RenderPassColorAttachment, + RenderPassDescriptor, ShaderStages, StoreOp, TextureFormat, TextureView, }; pub struct MyRenderer { pub device: Device, pub queue: Queue, + global_bind_group_layout: GlobalBindGroupLayout, pipeline: MyRenderPipeline, } impl MyRenderer { pub fn new(device: Device, queue: Queue, out_format: TextureFormat) -> anyhow::Result { + let global_bind_group_layout = GlobalBindGroupLayout::new(&device); + let pipeline = MyRenderPipeline::new(&device, &global_bind_group_layout, out_format)?; Ok(Self { - pipeline: MyRenderPipeline::new(&device, out_format)?, + global_bind_group_layout, + pipeline, device, queue, }) } pub fn render(&self, shader_constants: &ShaderConstants, output: TextureView) { + let global_bind_group = self + .global_bind_group_layout + .create(&self.device, shader_constants); + let mut cmd = self .device .create_command_encoder(&CommandEncoderDescriptor { @@ -43,9 +54,61 @@ impl MyRenderer { timestamp_writes: None, occlusion_query_set: None, }); - self.pipeline.draw(&mut rpass, shader_constants); + self.pipeline.draw(&mut rpass, &global_bind_group); drop(rpass); self.queue.submit(std::iter::once(cmd.finish())); } } + +#[derive(Debug, Clone)] +pub struct GlobalBindGroupLayout(pub BindGroupLayout); + +impl GlobalBindGroupLayout { + pub fn new(device: &Device) -> Self { + Self(device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("GlobalBindGroupLayout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + })) + } + + pub fn create(&self, device: &Device, shader_constants: &ShaderConstants) -> GlobalBindGroup { + let shader_constants = device.create_buffer_init(&BufferInitDescriptor { + label: Some("ShaderConstants"), + contents: bytemuck::bytes_of(shader_constants), + usage: BufferUsages::STORAGE, + }); + self.create_from_buffer(device, &shader_constants) + } + + pub fn create_from_buffer( + &self, + device: &Device, + shader_constants: &Buffer, + ) -> GlobalBindGroup { + GlobalBindGroup(device.create_bind_group(&BindGroupDescriptor { + label: Some("GlobalBindGroup"), + layout: &self.0, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::Buffer(BufferBinding { + buffer: shader_constants, + offset: 0, + size: None, + }), + }], + })) + } +} + +#[derive(Debug, Clone)] +pub struct GlobalBindGroup(pub BindGroup); diff --git a/graphics/Cargo.toml.liquid b/graphics/Cargo.toml.liquid index 8342047..c85be10 100644 --- a/graphics/Cargo.toml.liquid +++ b/graphics/Cargo.toml.liquid @@ -20,6 +20,7 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spir {% if api == "ash" -%} ash = "0.38" ash-window = "0.13" +gpu-allocator = { version = "0.28.0", default-features = false, features = ["std", "vulkan"] } {%- endif -%} {%- if api == "wgpu" -%} wgpu = { version = "27.0.1", default-features = false, features = ["std", "parking_lot", "vulkan", "vulkan-portability", "spirv", "wgsl"] } diff --git a/graphics/mygraphics-shaders/src/lib.rs b/graphics/mygraphics-shaders/src/lib.rs index acf55ee..d2b343a 100644 --- a/graphics/mygraphics-shaders/src/lib.rs +++ b/graphics/mygraphics-shaders/src/lib.rs @@ -23,7 +23,7 @@ pub fn main_fs(vtx_color: Vec3, output: &mut Vec4) { #[spirv(vertex)] pub fn main_vs( #[spirv(vertex_index)] vert_id: i32, - #[spirv(push_constant)] constants: &ShaderConstants, + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] constants: &ShaderConstants, #[spirv(position)] vtx_pos: &mut Vec4, vtx_color: &mut Vec3, ) { diff --git a/graphics/mygraphics/Cargo.toml b/graphics/mygraphics/Cargo.toml index 0ce4276..84b21f5 100644 --- a/graphics/mygraphics/Cargo.toml +++ b/graphics/mygraphics/Cargo.toml @@ -17,6 +17,7 @@ mygraphics-shaders = { path = "../mygraphics-shaders" } # API ash.workspace = true ash-window.workspace = true +gpu-allocator.workspace = true wgpu.workspace = true pollster.workspace = true env_logger.workspace = true diff --git a/graphics/mygraphics/Cargo.toml.liquid b/graphics/mygraphics/Cargo.toml.liquid index 209002e..8667745 100644 --- a/graphics/mygraphics/Cargo.toml.liquid +++ b/graphics/mygraphics/Cargo.toml.liquid @@ -25,6 +25,7 @@ mygraphics-shaders = { path = "../mygraphics-shaders" } {% if api == "ash" -%} ash.workspace = true ash-window.workspace = true +gpu-allocator.workspace = true {%- endif -%} {%- if api == "wgpu" -%} wgpu.workspace = true diff --git a/graphics/mygraphics/src/ash_renderer/buffer.rs b/graphics/mygraphics/src/ash_renderer/buffer.rs new file mode 100644 index 0000000..3094c52 --- /dev/null +++ b/graphics/mygraphics/src/ash_renderer/buffer.rs @@ -0,0 +1,84 @@ +use crate::ash_renderer::device::MyDevice; +use ash::vk; +use bytemuck::NoUninit; +use gpu_allocator::MemoryLocation; +use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme}; +use std::borrow::Cow; +use std::sync::Arc; + +pub struct MyBuffer { + pub buffer: vk::Buffer, + pub allocation: Allocation, + pub name: String, + destroyed: bool, +} + +#[derive(Clone)] +pub struct BufferCreateInfo<'a> { + pub usage: vk::BufferUsageFlags, + pub location: MemoryLocation, + pub name: Option>, +} + +impl MyBuffer { + pub fn from_data( + device: &Arc, + info: BufferCreateInfo<'_>, + data: &T, + ) -> anyhow::Result { + Self::from_slice(device, info, bytemuck::bytes_of(data)) + } + + pub fn from_slice( + device: &Arc, + info: BufferCreateInfo<'_>, + data: &[T], + ) -> anyhow::Result { + unsafe { + let buffer = device.create_buffer( + &vk::BufferCreateInfo::default() + .size(size_of_val(data) as u64) + .usage(vk::BufferUsageFlags::STORAGE_BUFFER), + None, + )?; + let name = info.name.map(|a| a.into_owned()).unwrap_or_default(); + let mut allocation = device.borrow_allocator().allocate(&AllocationCreateDesc { + name: &name, + requirements: device.get_buffer_memory_requirements(buffer), + location: MemoryLocation::CpuToGpu, + linear: true, + allocation_scheme: AllocationScheme::GpuAllocatorManaged, + })?; + device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())?; + let mapped = &mut allocation.mapped_slice_mut().unwrap()[..size_of_val(data)]; + mapped.copy_from_slice(bytemuck::cast_slice(data)); + Ok(Self { + buffer, + allocation, + name, + destroyed: false, + }) + } + } + + /// Destroy this buffer + /// + /// # Safety + /// Buffer must not be in use + pub unsafe fn destroy(&mut self, device: &Arc) { + if !self.destroyed { + self.destroyed = true; + unsafe { + device.destroy_buffer(self.buffer, None); + } + } + } +} + +impl Drop for MyBuffer { + fn drop(&mut self) { + if !self.destroyed { + panic!("dropping Buffer {} without destroying it", &self.name); + } + } +} diff --git a/graphics/mygraphics/src/ash_renderer/device.rs b/graphics/mygraphics/src/ash_renderer/device.rs index 65066fa..3ccaa62 100644 --- a/graphics/mygraphics/src/ash_renderer/device.rs +++ b/graphics/mygraphics/src/ash_renderer/device.rs @@ -1,9 +1,10 @@ use anyhow::{Context, anyhow}; use ash::{ext, khr, vk}; +use gpu_allocator::vulkan::{Allocator, AllocatorCreateDesc}; use std::borrow::Cow; use std::ffi::{CStr, c_char}; use std::ops::Deref; -use std::sync::Arc; +use std::sync::{Arc, Mutex, MutexGuard}; /// Central struct containing the Vulkan instance and device, among others pub struct MyDevice { @@ -13,6 +14,7 @@ pub struct MyDevice { pub device: ash::Device, pub main_queue_family: u32, pub main_queue: vk::Queue, + allocator: Option>, pub debug_ext_instance: ext::debug_utils::Instance, pub debug_ext_device: ext::debug_utils::Device, pub surface_ext: khr::surface::Instance, @@ -134,6 +136,15 @@ impl MyDevice { .context("create_device")?; let main_queue = device.get_device_queue(main_queue_family, 0); + let allocator = Allocator::new(&AllocatorCreateDesc { + instance: instance.clone(), + device: device.clone(), + physical_device, + debug_settings: Default::default(), + buffer_device_address: false, + allocation_sizes: Default::default(), + })?; + Ok(Arc::new(Self { debug_ext_device: ext::debug_utils::Device::new(&instance, &device), surface_ext: khr::surface::Instance::new(&entry, &instance), @@ -144,16 +155,22 @@ impl MyDevice { device, main_queue_family, main_queue, + allocator: Some(Mutex::new(allocator)), debug_ext_instance: debug_instance, debug_callback, })) } } + + pub fn borrow_allocator(&self) -> MutexGuard<'_, Allocator> { + self.allocator.as_ref().unwrap().lock().unwrap() + } } impl Drop for MyDevice { fn drop(&mut self) { unsafe { + drop(self.allocator.take()); self.debug_ext_instance .destroy_debug_utils_messenger(self.debug_callback, None); self.device.destroy_device(None); @@ -178,6 +195,12 @@ unsafe extern "system" fn vulkan_debug_callback( .map_or(Cow::Borrowed(""), CStr::to_string_lossy); println!("{message_severity:?}: [{message_id_name}] : {message}"); - vk::FALSE + if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) + | message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) + { + vk::TRUE + } else { + vk::FALSE + } } } diff --git a/graphics/mygraphics/src/ash_renderer/global_descriptor_set.rs b/graphics/mygraphics/src/ash_renderer/global_descriptor_set.rs new file mode 100644 index 0000000..bf15fb0 --- /dev/null +++ b/graphics/mygraphics/src/ash_renderer/global_descriptor_set.rs @@ -0,0 +1,111 @@ +use crate::ash_renderer::device::MyDevice; +use ash::vk; +use std::sync::Arc; + +pub struct GlobalDescriptorSetLayout { + pub device: Arc, + pub layout: vk::DescriptorSetLayout, +} + +impl GlobalDescriptorSetLayout { + pub fn new(device: Arc) -> anyhow::Result> { + unsafe { + Ok(Arc::new(Self { + layout: device.create_descriptor_set_layout( + &vk::DescriptorSetLayoutCreateInfo::default().bindings(&[ + vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .stage_flags(vk::ShaderStageFlags::ALL_GRAPHICS) + .descriptor_count(1), + ]), + None, + )?, + device, + })) + } + } +} + +impl Drop for GlobalDescriptorSetLayout { + fn drop(&mut self) { + unsafe { + self.device.destroy_descriptor_set_layout(self.layout, None); + } + } +} + +/// This implementation of descriptor sets is kept simple on purpose, even at the cost of being quite inefficient. +/// Don't use this as a reference on how it should be done! +pub struct GlobalDescriptorSet { + pub layout: Arc, + pub pool: vk::DescriptorPool, + pub set: vk::DescriptorSet, + destroyed: bool, +} + +impl GlobalDescriptorSet { + /// # Safety + /// * `shader_constants` must not be dropped before `GlobalDescriptorSet` is dropped + /// * you must only drop this `GlobalDescriptorSet` when it is unused, e.g. by GPU execution + pub unsafe fn new( + layout: &Arc, + shader_constants: vk::Buffer, + ) -> anyhow::Result { + unsafe { + let device = &layout.device; + let pool = device.create_descriptor_pool( + &vk::DescriptorPoolCreateInfo::default() + .pool_sizes(&[vk::DescriptorPoolSize::default() + .ty(vk::DescriptorType::STORAGE_BUFFER) + .descriptor_count(1)]) + .max_sets(1), + None, + )?; + let set = device.allocate_descriptor_sets( + &vk::DescriptorSetAllocateInfo::default() + .descriptor_pool(pool) + .set_layouts(&[layout.layout]), + )?[0]; + device.update_descriptor_sets( + &[vk::WriteDescriptorSet::default() + .dst_set(set) + .dst_binding(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .descriptor_count(1) + .buffer_info(&[vk::DescriptorBufferInfo::default() + .buffer(shader_constants) + .offset(0) + .range(vk::WHOLE_SIZE)])], + &[], + ); + Ok(Self { + layout: layout.clone(), + pool, + set, + destroyed: false, + }) + } + } + + pub fn destroy(&mut self) { + if !self.destroyed { + self.destroyed = true; + unsafe { + let device = &self.layout.device; + device + .reset_descriptor_pool(self.pool, vk::DescriptorPoolResetFlags::empty()) + .ok(); + device.destroy_descriptor_pool(self.pool, None); + } + } + } +} + +impl Drop for GlobalDescriptorSet { + fn drop(&mut self) { + if !self.destroyed { + panic!("dropping GlobalDescriptorSet without destroying it"); + } + } +} diff --git a/graphics/mygraphics/src/ash_renderer/mod.rs b/graphics/mygraphics/src/ash_renderer/mod.rs index f72f697..289fc7d 100644 --- a/graphics/mygraphics/src/ash_renderer/mod.rs +++ b/graphics/mygraphics/src/ash_renderer/mod.rs @@ -1,5 +1,5 @@ use crate::ash_renderer::device::MyDevice; -use crate::ash_renderer::graphics::{MyRenderPipelineManager, MyRenderer}; +use crate::ash_renderer::renderer::MyRenderer; use crate::ash_renderer::swapchain::MySwapchainManager; use crate::util::enable_debug_layer; use ash::util::read_spv; @@ -11,8 +11,11 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +pub mod buffer; pub mod device; -pub mod graphics; +pub mod global_descriptor_set; +pub mod render_pipeline; +pub mod renderer; pub mod single_command_buffer; pub mod swapchain; @@ -34,24 +37,20 @@ pub fn main() -> anyhow::Result<()> { let extensions = ash_window::enumerate_required_extensions(window.display_handle()?.as_raw())?; let device = MyDevice::new(extensions, enable_debug_layer())?; let mut swapchain = MySwapchainManager::new(device.clone(), window)?; - let mut renderer = MyRenderer::new(MyRenderPipelineManager::new( - device.clone(), - swapchain.surface_format.format, - get_shaders()?, - )?)?; + let mut renderer = MyRenderer::new(device.clone(), swapchain.surface_format.format)?; let start = std::time::Instant::now(); let mut event_handler = move |event: Event<_>, event_loop_window_target: &ActiveEventLoop| match event { Event::AboutToWait => swapchain.render(|frame| { let extent = frame.extent; - let push_constants = ShaderConstants { + let shader_constants = ShaderConstants { width: extent.width, height: extent.height, time: start.elapsed().as_secs_f32(), }; - renderer.render_frame(frame, push_constants)?; + renderer.render_frame(frame, &shader_constants)?; Ok(()) }), Event::WindowEvent { event, .. } => { diff --git a/graphics/mygraphics/src/ash_renderer/graphics.rs b/graphics/mygraphics/src/ash_renderer/render_pipeline.rs similarity index 61% rename from graphics/mygraphics/src/ash_renderer/graphics.rs rename to graphics/mygraphics/src/ash_renderer/render_pipeline.rs index b860caf..dbb863e 100644 --- a/graphics/mygraphics/src/ash_renderer/graphics.rs +++ b/graphics/mygraphics/src/ash_renderer/render_pipeline.rs @@ -1,15 +1,14 @@ use crate::ash_renderer::device::MyDevice; -use crate::ash_renderer::single_command_buffer::SingleCommandBuffer; -use crate::ash_renderer::swapchain::DrawFrame; +use crate::ash_renderer::global_descriptor_set::{GlobalDescriptorSet, GlobalDescriptorSetLayout}; use anyhow::Context; use ash::vk; -use mygraphics_shaders::ShaderConstants; use std::sync::Arc; /// Manages the creation and recreation of [`MyRenderPipeline`], whenever new shader code ([`Self::set_shader_code`]) -/// is submitted or the spec constant is changed ([`Self::set_sky_fs_sun_intensity_factor`]) +/// is submitted pub struct MyRenderPipelineManager { pub device: Arc, + global_descriptor_set_layout: Arc, color_out_format: vk::Format, shader_code: Vec, pipeline: Option, @@ -24,11 +23,13 @@ pub struct MyRenderPipeline { impl MyRenderPipelineManager { pub fn new( device: Arc, + global_descriptor_set_layout: Arc, color_out_format: vk::Format, shader_code: Vec, ) -> anyhow::Result { Ok(Self { device, + global_descriptor_set_layout, color_out_format, shader_code, pipeline: None, @@ -65,12 +66,8 @@ impl MyRenderPipelineManager { )?; let pipeline_layout = self.device.create_pipeline_layout( - &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ - vk::PushConstantRange::default() - .offset(0) - .size(size_of::() as u32) - .stage_flags(vk::ShaderStageFlags::ALL), - ]), + &vk::PipelineLayoutCreateInfo::default() + .set_layouts(&[self.global_descriptor_set_layout.layout]), None, )?; @@ -179,7 +176,7 @@ impl MyRenderPipeline { cmd: vk::CommandBuffer, color_out: vk::ImageView, extent: vk::Extent2D, - push_constants: ShaderConstants, + global_descriptor_set: &GlobalDescriptorSet, ) -> anyhow::Result<()> { unsafe { let render_area = vk::Rect2D { @@ -218,12 +215,13 @@ impl MyRenderPipeline { }], ); device.cmd_set_scissor(cmd, 0, &[render_area]); - device.cmd_push_constants( + device.cmd_bind_descriptor_sets( cmd, + vk::PipelineBindPoint::GRAPHICS, self.pipeline_layout, - vk::ShaderStageFlags::ALL, 0, - bytemuck::bytes_of(&push_constants), + &[global_descriptor_set.set], + &[], ); device.cmd_draw(cmd, 3, 1, 0, 0); device.cmd_end_rendering(cmd); @@ -239,102 +237,3 @@ impl Drop for MyRenderPipelineManager { } } } - -/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. -pub struct MyRenderer { - pub device: Arc, - pub pipeline: MyRenderPipelineManager, - pub command: SingleCommandBuffer, -} - -impl MyRenderer { - pub fn new(pipeline: MyRenderPipelineManager) -> anyhow::Result { - Ok(Self { - command: SingleCommandBuffer::new(pipeline.device.clone())?, - device: pipeline.device.clone(), - pipeline, - }) - } - - pub fn render_frame( - &mut self, - frame: DrawFrame, - push_constants: ShaderConstants, - ) -> anyhow::Result<()> { - unsafe { - let device = &self.device; - let pipeline = self.pipeline.get_pipeline()?; - let cmd = self.command.cmd; - - device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; - - { - device.begin_command_buffer( - cmd, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - )?; - device.cmd_pipeline_barrier2( - cmd, - &vk::DependencyInfo::default().image_memory_barriers(&[ - vk::ImageMemoryBarrier2::default() - .image(frame.image) - .src_access_mask(vk::AccessFlags2::NONE) - .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) - .old_layout(vk::ImageLayout::UNDEFINED) - .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ), - ]), - ); - pipeline.render(device, cmd, frame.image_view, frame.extent, push_constants)?; - device.cmd_pipeline_barrier2( - cmd, - &vk::DependencyInfo::default().image_memory_barriers(&[ - vk::ImageMemoryBarrier2::default() - .image(frame.image) - .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .dst_access_mask(vk::AccessFlags2::NONE) - .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) - .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ), - ]), - ); - device.end_command_buffer(cmd)?; - } - - device.queue_submit2( - device.main_queue, - &[vk::SubmitInfo2::default() - .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() - .semaphore(frame.acquire_semaphore) - .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) - .command_buffer_infos(&[ - vk::CommandBufferSubmitInfo::default().command_buffer(cmd) - ]) - .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() - .semaphore(frame.draw_finished_semaphore) - .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], - frame.draw_finished_fence, - )?; - Ok(()) - } - } -} diff --git a/graphics/mygraphics/src/ash_renderer/renderer.rs b/graphics/mygraphics/src/ash_renderer/renderer.rs new file mode 100644 index 0000000..99f0267 --- /dev/null +++ b/graphics/mygraphics/src/ash_renderer/renderer.rs @@ -0,0 +1,141 @@ +use crate::ash_renderer::buffer::{BufferCreateInfo, MyBuffer}; +use crate::ash_renderer::device::MyDevice; +use crate::ash_renderer::get_shaders; +use crate::ash_renderer::global_descriptor_set::{GlobalDescriptorSet, GlobalDescriptorSetLayout}; +use crate::ash_renderer::render_pipeline::MyRenderPipelineManager; +use crate::ash_renderer::single_command_buffer::SingleCommandBuffer; +use crate::ash_renderer::swapchain::DrawFrame; +use ash::vk; +use gpu_allocator::MemoryLocation; +use mygraphics_shaders::ShaderConstants; +use std::borrow::Cow; +use std::sync::Arc; + +/// The renderer manages our command buffer and submits the commands, using [`MyRenderPipeline`] for drawing. +pub struct MyRenderer { + pub device: Arc, + pub global_descriptor_set_layout: Arc, + pub pipeline: MyRenderPipelineManager, + pub command: SingleCommandBuffer, +} + +impl MyRenderer { + pub fn new(device: Arc, out_format: vk::Format) -> anyhow::Result { + let global_descriptor_set_layout = GlobalDescriptorSetLayout::new(device.clone())?; + let pipeline = MyRenderPipelineManager::new( + device.clone(), + global_descriptor_set_layout.clone(), + out_format, + get_shaders()?, + )?; + let command = SingleCommandBuffer::new(pipeline.device.clone())?; + Ok(Self { + device, + global_descriptor_set_layout, + pipeline, + command, + }) + } + + pub fn render_frame( + &mut self, + frame: DrawFrame, + shader_constants: &ShaderConstants, + ) -> anyhow::Result<()> { + unsafe { + let device = &self.device; + let pipeline = self.pipeline.get_pipeline()?; + let cmd = self.command.cmd; + + let mut buffer = MyBuffer::from_data( + device, + BufferCreateInfo { + usage: vk::BufferUsageFlags::STORAGE_BUFFER, + location: MemoryLocation::CpuToGpu, + name: Some(Cow::from("ShaderConstants")), + }, + shader_constants, + )?; + let mut descriptor_set = + GlobalDescriptorSet::new(&self.global_descriptor_set_layout, buffer.buffer)?; + + device.reset_command_pool(self.command.pool, vk::CommandPoolResetFlags::default())?; + + { + device.begin_command_buffer( + cmd, + &vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::NONE) + .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .old_layout(vk::ImageLayout::UNDEFINED) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + pipeline.render(device, cmd, frame.image_view, frame.extent, &descriptor_set)?; + device.cmd_pipeline_barrier2( + cmd, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .image(frame.image) + .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .dst_access_mask(vk::AccessFlags2::NONE) + .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) + .new_layout(vk::ImageLayout::PRESENT_SRC_KHR) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + ]), + ); + device.end_command_buffer(cmd)?; + } + + device.queue_submit2( + device.main_queue, + &[vk::SubmitInfo2::default() + .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.acquire_semaphore) + .stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)]) + .command_buffer_infos(&[ + vk::CommandBufferSubmitInfo::default().command_buffer(cmd) + ]) + .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(frame.draw_finished_semaphore) + .stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)])], + frame.draw_finished_fence, + )?; + + // free resources of the frame + // For a production renderer, you'd obviously don't want to immediately wait for the previous frame to + // finish rendering, but start recording the next one already while waiting. For simplicity's sake, + // we just wait immediately. + self.device.device_wait_idle()?; + descriptor_set.destroy(); + buffer.destroy(device); + Ok(()) + } + } +} diff --git a/graphics/mygraphics/src/wgpu_renderer/render_pipeline.rs b/graphics/mygraphics/src/wgpu_renderer/render_pipeline.rs index ee2097e..ba1f793 100644 --- a/graphics/mygraphics/src/wgpu_renderer/render_pipeline.rs +++ b/graphics/mygraphics/src/wgpu_renderer/render_pipeline.rs @@ -1,3 +1,4 @@ +use crate::wgpu_renderer::renderer::{GlobalBindGroup, GlobalBindGroupLayout}; use mygraphics_shaders::ShaderConstants; use wgpu::{ ColorTargetState, ColorWrites, Device, FragmentState, FrontFace, MultisampleState, @@ -6,17 +7,22 @@ use wgpu::{ include_spirv, }; +#[derive(Debug, Clone)] pub struct MyRenderPipeline { pipeline: RenderPipeline, } impl MyRenderPipeline { - pub fn new(device: &Device, out_format: TextureFormat) -> anyhow::Result { + pub fn new( + device: &Device, + global_bind_group_layout: &GlobalBindGroupLayout, + out_format: TextureFormat, + ) -> anyhow::Result { let module = device.create_shader_module(include_spirv!(env!("SHADER_SPV_PATH"))); let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("MyRenderPipeline layout"), - bind_group_layouts: &[], + bind_group_layouts: &[&global_bind_group_layout.0], push_constant_ranges: &[PushConstantRange { stages: ShaderStages::VERTEX_FRAGMENT, range: 0..size_of::() as u32, @@ -60,13 +66,9 @@ impl MyRenderPipeline { }) } - pub fn draw(&self, rpass: &mut RenderPass<'_>, shader_constants: &ShaderConstants) { + pub fn draw(&self, rpass: &mut RenderPass<'_>, global_bind_group: &GlobalBindGroup) { rpass.set_pipeline(&self.pipeline); - rpass.set_push_constants( - ShaderStages::VERTEX_FRAGMENT, - 0, - bytemuck::bytes_of(shader_constants), - ); + rpass.set_bind_group(0, &global_bind_group.0, &[]); rpass.draw(0..3, 0..1); } } diff --git a/graphics/mygraphics/src/wgpu_renderer/renderer.rs b/graphics/mygraphics/src/wgpu_renderer/renderer.rs index c5213df..2b4349e 100644 --- a/graphics/mygraphics/src/wgpu_renderer/renderer.rs +++ b/graphics/mygraphics/src/wgpu_renderer/renderer.rs @@ -1,27 +1,38 @@ use crate::wgpu_renderer::render_pipeline::MyRenderPipeline; use mygraphics_shaders::ShaderConstants; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::wgt::CommandEncoderDescriptor; use wgpu::{ - Color, Device, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, - StoreOp, TextureFormat, TextureView, + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBinding, BufferBindingType, + BufferUsages, Color, Device, LoadOp, Operations, Queue, RenderPassColorAttachment, + RenderPassDescriptor, ShaderStages, StoreOp, TextureFormat, TextureView, }; pub struct MyRenderer { pub device: Device, pub queue: Queue, + global_bind_group_layout: GlobalBindGroupLayout, pipeline: MyRenderPipeline, } impl MyRenderer { pub fn new(device: Device, queue: Queue, out_format: TextureFormat) -> anyhow::Result { + let global_bind_group_layout = GlobalBindGroupLayout::new(&device); + let pipeline = MyRenderPipeline::new(&device, &global_bind_group_layout, out_format)?; Ok(Self { - pipeline: MyRenderPipeline::new(&device, out_format)?, + global_bind_group_layout, + pipeline, device, queue, }) } pub fn render(&self, shader_constants: &ShaderConstants, output: TextureView) { + let global_bind_group = self + .global_bind_group_layout + .create(&self.device, shader_constants); + let mut cmd = self .device .create_command_encoder(&CommandEncoderDescriptor { @@ -43,9 +54,61 @@ impl MyRenderer { timestamp_writes: None, occlusion_query_set: None, }); - self.pipeline.draw(&mut rpass, shader_constants); + self.pipeline.draw(&mut rpass, &global_bind_group); drop(rpass); self.queue.submit(std::iter::once(cmd.finish())); } } + +#[derive(Debug, Clone)] +pub struct GlobalBindGroupLayout(pub BindGroupLayout); + +impl GlobalBindGroupLayout { + pub fn new(device: &Device) -> Self { + Self(device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("GlobalBindGroupLayout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + })) + } + + pub fn create(&self, device: &Device, shader_constants: &ShaderConstants) -> GlobalBindGroup { + let shader_constants = device.create_buffer_init(&BufferInitDescriptor { + label: Some("ShaderConstants"), + contents: bytemuck::bytes_of(shader_constants), + usage: BufferUsages::STORAGE, + }); + self.create_from_buffer(device, &shader_constants) + } + + pub fn create_from_buffer( + &self, + device: &Device, + shader_constants: &Buffer, + ) -> GlobalBindGroup { + GlobalBindGroup(device.create_bind_group(&BindGroupDescriptor { + label: Some("GlobalBindGroup"), + layout: &self.0, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::Buffer(BufferBinding { + buffer: shader_constants, + offset: 0, + size: None, + }), + }], + })) + } +} + +#[derive(Debug, Clone)] +pub struct GlobalBindGroup(pub BindGroup);