diff --git a/Cargo.toml b/Cargo.toml index 783b176..e03758d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ vk-sync = "0.1.6" winit = "0.30.5" tinyvec = "1.8" rand = "0.8.5" +tokio = "1.42.0" futures = "0.3" smol = "2.0" diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index 6db2b27..6ce57cc 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] tinyvec = {workspace = true} rand = {workspace = true} +tokio = {workspace = true, features = ["rt", "sync"]} dyn-clone = "1" anyhow = "1.0.89" ash = "0.38.0" diff --git a/crates/renderer/src/commands.rs b/crates/renderer/src/commands.rs new file mode 100644 index 0000000..fa5978a --- /dev/null +++ b/crates/renderer/src/commands.rs @@ -0,0 +1,205 @@ +use std::{future::Future, marker::PhantomData}; + +use super::{Device2 as Device, Queue}; +use ash::{prelude::*, vk}; + +pub struct FenceFuture<'a> { + device: Device, + fence: vk::Fence, + _pd: PhantomData<&'a ()>, +} + +impl Drop for FenceFuture<'_> { + fn drop(&mut self) { + unsafe { self.device.dev().destroy_fence(self.fence, None) }; + } +} + +impl FenceFuture<'_> { + /// Unsafe because `fence` must not be destroyed while this future is live. + pub unsafe fn from_fence(device: Device, fence: vk::Fence) -> Self { + Self { + device, + fence, + _pd: PhantomData, + } + } + pub fn block_until(&self) -> VkResult<()> { + unsafe { + self.device + .dev() + .wait_for_fences(&[self.fence], true, u64::MAX) + } + } +} + +impl Future for FenceFuture<'_> { + type Output = (); + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + let signaled = unsafe { + self.device + .dev() + .get_fence_status(self.fence) + .unwrap_or(false) + }; + + if signaled { + std::task::Poll::Ready(()) + } else { + tokio::task::spawn_blocking(move || {}); + std::task::Poll::Pending + } + } +} + +pub struct SingleUseCommandPool { + device: Device, + pool: vk::CommandPool, +} + +impl Drop for SingleUseCommandPool { + fn drop(&mut self) { + unsafe { + self.device.dev().destroy_command_pool(self.pool, None); + } + } +} + +impl SingleUseCommandPool { + pub fn new(device: Device, family_index: u32) -> VkResult { + let pool_info = vk::CommandPoolCreateInfo::default() + .queue_family_index(family_index) + .flags(vk::CommandPoolCreateFlags::TRANSIENT); + + let pool = + unsafe { device.dev().create_command_pool(&pool_info, None)? }; + + Ok(Self { device, pool }) + } + + pub fn pool(&self) -> vk::CommandPool { + self.pool + } +} + +pub struct SingleUseCommand { + device: Device, + pool: vk::CommandPool, + buffer: vk::CommandBuffer, +} + +impl Drop for SingleUseCommand { + fn drop(&mut self) { + unsafe { + self.device + .dev() + .free_command_buffers(self.pool, &[self.buffer]) + }; + } +} + +impl SingleUseCommand { + pub fn new(device: Device, pool: vk::CommandPool) -> VkResult { + let buffer = unsafe { + let alloc_info = vk::CommandBufferAllocateInfo::default() + .command_buffer_count(1) + .command_pool(pool) + .level(vk::CommandBufferLevel::PRIMARY); + let buffer = device.dev().allocate_command_buffers(&alloc_info)?[0]; + + let begin_info = vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); + device.dev().begin_command_buffer(buffer, &begin_info)?; + + buffer + }; + Ok(Self { + device, + pool, + buffer, + }) + } + + pub fn command_buffer(&self) -> vk::CommandBuffer { + self.buffer + } + + fn submit_fence( + &self, + queue: Queue, + wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>, + signal: Option, + ) -> VkResult { + unsafe { self.device.dev().end_command_buffer(self.buffer)? }; + + let buffers = [self.buffer]; + let mut submit_info = + vk::SubmitInfo::default().command_buffers(&buffers); + + if let Some(semaphore) = signal.as_ref() { + // SAFETY: T and [T;1] have the same layout + submit_info = submit_info.signal_semaphores(unsafe { + core::mem::transmute::<&vk::Semaphore, &[vk::Semaphore; 1]>( + semaphore, + ) + }); + } + if let Some((semaphore, stage)) = wait.as_ref() { + submit_info = submit_info + .wait_semaphores(core::slice::from_ref(semaphore)) + .wait_dst_stage_mask(core::slice::from_ref(stage)); + } + + let fence = { + let create_info = vk::FenceCreateInfo::default(); + unsafe { self.device.dev().create_fence(&create_info, None)? } + }; + + queue.with_locked(|queue| unsafe { + self.device.dev().queue_submit(queue, &[submit_info], fence) + })?; + + Ok(fence) + } + + pub fn submit_async<'a>( + &'a self, + queue: Queue, + wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>, + signal: Option, + ) -> VkResult> { + let device = self.device.clone(); + let fence = self.submit_fence(queue, wait, signal)?; + + Ok(unsafe { FenceFuture::from_fence(device, fence) }) + } + + pub fn submit_blocking( + self, + queue: Queue, + wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>, + signal: Option, + ) -> VkResult<()> { + let device = self.device.clone(); + let fence = self.submit_fence(queue, wait, signal)?; + + unsafe { + device.dev().wait_for_fences(&[fence], true, u64::MAX)?; + device.dev().destroy_fence(fence, None); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + async fn async_submit(cmd: SingleUseCommand, queue: Queue) { + cmd.submit_async(queue, None, None).unwrap().await; + } +} diff --git a/crates/renderer/src/images.rs b/crates/renderer/src/images.rs new file mode 100644 index 0000000..d407b77 --- /dev/null +++ b/crates/renderer/src/images.rs @@ -0,0 +1,137 @@ +use super::{Device2 as Device, Queue, VkAllocator}; +use ash::{prelude::*, vk}; +use vk_mem::Alloc; + +pub struct Image2D { + alloc: VkAllocator, + image: vk::Image, + size: vk::Extent2D, + mip_levels: u32, + format: vk::Format, + allocation: vk_mem::Allocation, +} + +impl Image2D { + pub fn new_exclusive( + alloc: VkAllocator, + extent: vk::Extent2D, + mip_levels: u32, + array_layers: u32, + format: vk::Format, + tiling: vk::ImageTiling, + usage: vk::ImageUsageFlags, + memory_usage: vk_mem::MemoryUsage, + alloc_flags: vk_mem::AllocationCreateFlags, + ) -> VkResult { + let create_info = vk::ImageCreateInfo::default() + .array_layers(array_layers) + .mip_levels(mip_levels) + .extent(vk::Extent3D { + width: extent.width, + height: extent.height, + depth: 1, + }) + .image_type(vk::ImageType::TYPE_2D) + .format(format) + .tiling(tiling) + .initial_layout(vk::ImageLayout::UNDEFINED) + .usage(usage) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .samples(vk::SampleCountFlags::TYPE_1); + + let alloc_info = vk_mem::AllocationCreateInfo { + usage: memory_usage, + flags: alloc_flags, + ..Default::default() + }; + + let (image, allocation) = + unsafe { alloc.create_image(&create_info, &alloc_info)? }; + + Ok(Self { + alloc, + image, + size: extent, + mip_levels, + format, + allocation, + }) + } + + pub fn view( + &self, + device: &Device, + aspect: vk::ImageAspectFlags, + ) -> VkResult { + let create_info = vk::ImageViewCreateInfo::default() + .image(self.image) + .view_type(vk::ImageViewType::TYPE_2D) + .format(self.format) + .components(vk::ComponentMapping::default()) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(aspect) + .base_mip_level(0) + .level_count(self.mip_levels) + .base_array_layer(0) + .layer_count(1), + ); + + let view = + unsafe { device.dev().create_image_view(&create_info, None)? }; + + Ok(view) + } + + pub fn image(&self) -> vk::Image { + self.image + } +} + +impl Drop for Image2D { + fn drop(&mut self) { + unsafe { + self.alloc.destroy_image(self.image, &mut self.allocation); + } + } +} + +pub struct QueueOwnership { + src: u32, + dst: u32, +} + +pub fn image_barrier<'a>( + image: vk::Image, + aspects: vk::ImageAspectFlags, + src_stage: vk::PipelineStageFlags2, + src_access: vk::AccessFlags2, + dst_stage: vk::PipelineStageFlags2, + dst_access: vk::AccessFlags2, + old_layout: vk::ImageLayout, + new_layout: vk::ImageLayout, + queue_ownership_op: Option, +) -> vk::ImageMemoryBarrier2<'a> { + let (src_family, dst_family) = queue_ownership_op + .map(|t| (t.src, t.dst)) + .unwrap_or((vk::QUEUE_FAMILY_IGNORED, vk::QUEUE_FAMILY_IGNORED)); + + vk::ImageMemoryBarrier2::default() + .image(image) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(aspects) + .base_mip_level(0) + .base_array_layer(0) + .level_count(vk::REMAINING_MIP_LEVELS) + .layer_count(vk::REMAINING_ARRAY_LAYERS), + ) + .src_stage_mask(src_stage) + .src_access_mask(src_access) + .dst_stage_mask(dst_stage) + .dst_access_mask(dst_access) + .dst_queue_family_index(dst_family) + .src_queue_family_index(src_family) + .old_layout(old_layout) + .new_layout(new_layout) +} diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index cbac9af..2aebf0f 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -5,7 +5,7 @@ use std::{ ffi::{CStr, CString}, fmt::Debug, marker::PhantomData, - sync::{Arc, Mutex}, + sync::{atomic::AtomicU64, Arc, Mutex}, }; use ash::{ @@ -967,6 +967,22 @@ impl Vulkan { .mesh_shader(true) .task_shader(true), ) + .with_extension( + make_extention_properties( + ash::khr::present_id::NAME, + ash::khr::present_id::SPEC_VERSION, + ), + vk::PhysicalDevicePresentIdFeaturesKHR::default() + .present_id(true), + ) + .with_extension( + make_extention_properties( + ash::khr::present_wait::NAME, + ash::khr::present_wait::SPEC_VERSION, + ), + vk::PhysicalDevicePresentWaitFeaturesKHR::default() + .present_wait(true), + ) .with_extension( make_extention_properties( ash::ext::index_type_uint8::NAME, @@ -1649,16 +1665,31 @@ impl Renderer { let wait_semaphores = [signal_semaphore.semaphore()]; let swapchains = [ctx.current_swapchain.swapchain]; let indices = [swapchain_index]; + static PRESENT_ID: AtomicU64 = AtomicU64::new(1); + let mut present_ids = [PRESENT_ID + .fetch_add(1, std::sync::atomic::Ordering::Release)]; + let mut present_id = + vk::PresentIdKHR::default().present_ids(&present_ids); let present_info = vk::PresentInfoKHR::default() .image_indices(&indices) .swapchains(&swapchains) - .wait_semaphores(&wait_semaphores); + .wait_semaphores(&wait_semaphores) + .push_next(&mut present_id); dev.present_queue().with_locked(|queue| { dev.swapchain().queue_present(queue, &present_info) })?; future.block_until()?; - dev.dev().device_wait_idle(); + khr::present_wait::Device::new( + &self.vulkan.instance.instance, + &self.vulkan.device.device, + ) + .wait_for_present( + ctx.current_swapchain.swapchain, + present_ids[0], + u64::MAX, + )?; + // dev.dev().device_wait_idle(); drop(ready_semaphore); drop(signal_semaphore); } diff --git a/crates/renderer/src/sync.rs b/crates/renderer/src/sync.rs new file mode 100644 index 0000000..5fc93aa --- /dev/null +++ b/crates/renderer/src/sync.rs @@ -0,0 +1,42 @@ +use super::Device2 as Device; +use ash::{prelude::*, vk}; + +pub struct Semaphore { + device: Device, + inner: vk::Semaphore, +} + +impl Semaphore { + pub fn new(device: Device) -> VkResult { + let mut type_info = vk::SemaphoreTypeCreateInfo::default() + .semaphore_type(vk::SemaphoreType::BINARY); + let create_info = + vk::SemaphoreCreateInfo::default().push_next(&mut type_info); + let inner = + unsafe { device.dev().create_semaphore(&create_info, None)? }; + + Ok(Self { device, inner }) + } + pub fn new_timeline(device: Device, value: u64) -> VkResult { + let mut type_info = vk::SemaphoreTypeCreateInfo::default() + .semaphore_type(vk::SemaphoreType::TIMELINE) + .initial_value(value); + let create_info = + vk::SemaphoreCreateInfo::default().push_next(&mut type_info); + let inner = + unsafe { device.dev().create_semaphore(&create_info, None)? }; + + Ok(Self { device, inner }) + } + pub fn semaphore(&self) -> vk::Semaphore { + self.inner + } +} + +impl Drop for Semaphore { + fn drop(&mut self) { + unsafe { + self.device.dev().destroy_semaphore(self.inner, None); + } + } +}