From 4998b7e017f322969186e2cd0d1a216b116a7f3c Mon Sep 17 00:00:00 2001 From: janis Date: Tue, 31 Mar 2026 16:56:05 +0200 Subject: [PATCH] surface/swapchain refactor --- crates/renderer/src/commands.rs | 5 +- crates/renderer/src/device.rs | 274 ++++++----- crates/renderer/src/instance.rs | 135 ++++-- crates/renderer/src/lib.rs | 66 +-- crates/renderer/src/pipeline.rs | 2 +- crates/renderer/src/queue.rs | 4 + crates/renderer/src/swapchain.rs | 787 +++++++++++++++++-------------- 7 files changed, 731 insertions(+), 542 deletions(-) diff --git a/crates/renderer/src/commands.rs b/crates/renderer/src/commands.rs index 9472644..32e261d 100644 --- a/crates/renderer/src/commands.rs +++ b/crates/renderer/src/commands.rs @@ -9,7 +9,7 @@ use crate::{ util::{self, FormatExt, MutexExt}, }; -use super::{Device, Queue}; +use super::{Device, queue::Queue}; use ash::{prelude::*, vk}; use parking_lot::Mutex; @@ -643,8 +643,9 @@ mod command_pools { use thread_local::ThreadLocal; use crate::{ - Queue, define_device_owned_handle, + define_device_owned_handle, device::{Device, DeviceOwned}, + queue::Queue, sync, util::MutexExt, }; diff --git a/crates/renderer/src/device.rs b/crates/renderer/src/device.rs index ba92bbe..413b292 100644 --- a/crates/renderer/src/device.rs +++ b/crates/renderer/src/device.rs @@ -2,6 +2,7 @@ use std::{ borrow::Cow, collections::{BTreeSet, HashMap, HashSet}, ffi::CStr, + ops::Deref, sync::Arc, }; @@ -102,20 +103,21 @@ impl Drop for DeviceDrop { } struct DeviceExtensions { - debug_utils: ext::debug_utils::Device, - mesh_shader: Option, + pub(crate) debug_utils: ext::debug_utils::Device, + pub(crate) swapchain: Option, + pub(crate) mesh_shader: Option, } #[allow(unused)] pub struct DeviceInner { - alloc: vk_mem::Allocator, - raw: ash::Device, - adapter: PhysicalDeviceInfo, - instance: Arc, - queues: DeviceQueues, - sync_threadpool: sync::SyncThreadpool, - device_extensions: DeviceExtensions, - enabled_extensions: Vec<&'static CStr>, + pub(crate) alloc: vk_mem::Allocator, + pub(crate) raw: ash::Device, + pub(crate) adapter: PhysicalDeviceInfo, + pub(crate) instance: Instance, + pub(crate) queues: DeviceQueues, + pub(crate) sync_threadpool: sync::SyncThreadpool, + pub(crate) device_extensions: DeviceExtensions, + pub(crate) enabled_extensions: Vec<&'static CStr>, _drop: DeviceDrop, } @@ -157,63 +159,6 @@ impl<'a> std::hash::Hash for Extension<'a> { } } -#[derive(Debug)] -pub struct DeviceDesc<'a> { - pub app_name: Option<&'a str>, - pub app_version: u32, - pub layers: &'a [&'a CStr], - pub layer_settings: &'a [vk::LayerSettingEXT<'a>], - pub instance_extensions: &'a [Extension<'a>], - pub display_handle: Option, -} - -const VALIDATION_LAYER_NAME: &core::ffi::CStr = c"VK_LAYER_KHRONOS_validation"; -const DEBUG_LAYERS: [&core::ffi::CStr; 1] = [VALIDATION_LAYER_NAME]; -impl DeviceDesc<'_> { - fn debug_layer_settings() -> &'static [vk::LayerSettingEXT<'static>; 3] { - static SETTINGS: std::sync::LazyLock<[vk::LayerSettingEXT; 3]> = - std::sync::LazyLock::new(|| { - [ - vk::LayerSettingEXT::default() - .layer_name(VALIDATION_LAYER_NAME) - .setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES") - .ty(vk::LayerSettingTypeEXT::BOOL32) - .values(&[1]), - vk::LayerSettingEXT::default() - .layer_name(VALIDATION_LAYER_NAME) - .setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES_AMD") - .ty(vk::LayerSettingTypeEXT::BOOL32) - .values(&[1]), - vk::LayerSettingEXT::default() - .layer_name(VALIDATION_LAYER_NAME) - .setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_SYNC") - .ty(vk::LayerSettingTypeEXT::BOOL32) - .values(&[1]), - ] - }); - &SETTINGS - } -} - -impl<'a> Default for DeviceDesc<'a> { - fn default() -> Self { - Self { - app_name: Default::default(), - app_version: Default::default(), - #[cfg(debug_assertions)] - layers: &DEBUG_LAYERS, - #[cfg(debug_assertions)] - layer_settings: Self::debug_layer_settings(), - #[cfg(not(debug_assertions))] - layers: &[], - #[cfg(not(debug_assertions))] - layer_settings: &[], - instance_extensions: Default::default(), - display_handle: Default::default(), - } - } -} - pub(crate) fn get_available_extensions( entry: &ash::Entry, layers: &[&CStr], @@ -430,6 +375,11 @@ impl PhysicalDeviceInfo { let device_extensions = DeviceExtensions { debug_utils: ext::debug_utils::Device::new(&instance.inner.raw, &device), + swapchain: if enabled_extensions.contains(&khr::swapchain::NAME) { + Some(khr::swapchain::Device::new(&instance.inner.raw, &device)) + } else { + None + }, mesh_shader: if enabled_extensions.contains(&ext::mesh_shader::NAME) { Some(ext::mesh_shader::Device::new(&instance.inner.raw, &device)) } else { @@ -446,7 +396,7 @@ impl PhysicalDeviceInfo { self.pdev, ))? }, - instance: instance.inner.clone(), + instance: instance.clone(), adapter: self, queues: device_queues, device_extensions, @@ -486,6 +436,14 @@ impl PhysicalDeviceInfo { #[derive(Clone, Debug)] pub struct Device(Arc); +impl PartialEq for Device { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for Device {} + impl core::ops::Deref for Device { type Target = DeviceInner; @@ -493,54 +451,18 @@ impl core::ops::Deref for Device { &self.0 } } -pub type WeakDevice = std::sync::Weak; impl Device { - pub fn new_from_desc(desc: DeviceDesc) -> crate::Result { - let instance = Instance::new(&crate::instance::InstanceDesc { - app_name: desc.app_name, - app_version: desc.app_version, - instance_extensions: &desc - .instance_extensions - .iter() - .map(|ext| ext.name) - .collect::>(), - layers: desc.layers, - layer_settings: desc.layer_settings, - display_handle: desc.display_handle, - })?; - - // //these are required for the renderpass - // let features13 = features.physical_features_13.get_or_insert_default(); - // features13.synchronization2 = vk::TRUE; - // features13.dynamic_rendering = vk::TRUE; - // features13.maintenance4 = vk::TRUE; - let features = PhysicalDeviceFeatures { - core13: vk::PhysicalDeviceVulkan13Features { - synchronization2: vk::TRUE, - dynamic_rendering: vk::TRUE, - maintenance4: vk::TRUE, - ..Default::default() - }, - ..Default::default() - }; - - todo!() - } - pub fn sync_threadpool(&self) -> &sync::SyncThreadpool { &self.0.sync_threadpool } - pub fn weak(&self) -> WeakDevice { - Arc::downgrade(&self.0) - } pub fn alloc(&self) -> &vk_mem::Allocator { &self.0.alloc } pub fn dev(&self) -> &ash::Device { &self.0.raw } - pub fn instance(&self) -> &Arc { + pub fn instance(&self) -> &Instance { &self.0.instance } pub fn queues(&self) -> &DeviceQueues { @@ -600,18 +522,50 @@ impl Device { Ok(()) } - pub fn debug_name_object(&self, handle: T, name: &str) -> VkResult<()> { - let name = std::ffi::CString::new(name.as_bytes()).unwrap_or(c"invalid name".to_owned()); + /// # Safety + /// + /// This method inherits the safety contract from [`vkSetDebugUtilsObjectName`]. In particular: + /// + /// - `object` must be a valid handle for one of the following: + /// - An instance-level object from the same instance as this device. + /// - A physical-device-level object that descends from the same physical device as this + /// device. + /// - A device-level object that descends from this device. + /// - `object` must be externally synchronized—only the calling thread should access it during + /// this call. + /// + /// [`vkSetDebugUtilsObjectName`]: https://registry.khronos.org/vulkan/specs/latest/man/html/vkSetDebugUtilsObjectNameEXT.html + pub unsafe fn debug_name_object(&self, object: T, name: &str) { + // avoid heap allocation for short names + let mut buffer = [0u8; 64]; + let buffer_vec: Vec; + + let name_bytes = if name.len() < buffer.len() { + buffer[..name.len()].copy_from_slice(name.as_bytes()); + &buffer[..] + } else { + buffer_vec = name + .as_bytes() + .iter() + .cloned() + .chain(std::iter::once(0)) + .collect(); + &buffer_vec + }; + + let name = CStr::from_bytes_with_nul(name_bytes) + .expect("there is always a nul terminator because we added one"); + unsafe { - self.device_extensions + _ = self + .device_extensions .debug_utils .set_debug_utils_object_name( &vk::DebugUtilsObjectNameInfoEXT::default() - .object_handle(handle) - .object_name(&name), - )?; + .object_handle(object) + .object_name(name), + ); } - Ok(()) } } @@ -658,7 +612,9 @@ impl DeviceOwnedDebugObject { T: vk::Handle + Copy, { if let Some(name) = name.as_ref() { - device.debug_name_object(object, name)?; + unsafe { + device.debug_name_object(object, name); + } } Ok(Self { @@ -680,6 +636,98 @@ impl DeviceOwnedDebugObject { } } +#[derive(Debug)] +pub struct DeviceObject { + inner: T, + device: Device, + #[cfg(debug_assertions)] + name: Option>, +} + +impl Deref for DeviceObject { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DeviceObject { + pub fn new(inner: T, device: Device, name: Option>) -> Self { + unsafe { + if let Some(name) = name.as_ref() { + device.debug_name_object(inner, &name); + } + } + + Self { + inner, + device, + #[cfg(debug_assertions)] + name, + } + } + pub fn device(&self) -> &Device { + &self.device + } + pub fn name(&self) -> Option<&str> { + #[cfg(debug_assertions)] + { + self.name.as_deref().map(|cow| cow.as_ref()) + } + #[cfg(not(debug_assertions))] + { + None + } + } +} + +impl Drop for DeviceObject { + fn drop(&mut self) { + self.destroy(&self.device); + } +} + +pub trait DeviceHandle: vk::Handle + Copy { + fn destroy(self, device: &Device); +} + +impl DeviceHandle for vk::Semaphore { + fn destroy(self, device: &Device) { + unsafe { + device.dev().destroy_semaphore(self, None); + } + } +} + +impl DeviceHandle for vk::Fence { + fn destroy(self, device: &Device) { + unsafe { + device.dev().destroy_fence(self, None); + } + } +} + +impl DeviceHandle for vk::Buffer { + fn destroy(self, device: &Device) { + unsafe { + device.dev().destroy_buffer(self, None); + } + } +} + +impl DeviceHandle for vk::SwapchainKHR { + fn destroy(self, device: &Device) { + unsafe { + device + .device_extensions + .swapchain + .as_ref() + .map(|swapchain| swapchain.destroy_swapchain(self, None)); + } + } +} + pub trait DeviceOwned { fn device(&self) -> &Device; fn handle(&self) -> T; diff --git a/crates/renderer/src/instance.rs b/crates/renderer/src/instance.rs index 7b2aaf1..6bcc07d 100644 --- a/crates/renderer/src/instance.rs +++ b/crates/renderer/src/instance.rs @@ -1,4 +1,5 @@ use std::{ + cell::OnceCell, cmp::Ordering, ffi::{CStr, CString}, ops::Deref, @@ -9,7 +10,7 @@ use ash::{Entry, ext, khr, vk}; use raw_window_handle::RawDisplayHandle; use crate::{ - Error, PhysicalDeviceFeatures, PhysicalDeviceInfo, + Error, PhysicalDeviceFeatures, PhysicalDeviceInfo, SurfaceCapabilities, device::{Extension, get_extensions, get_layers}, get_physical_device_features, get_physical_device_properties, make_extension, swapchain::Surface, @@ -47,15 +48,61 @@ impl core::fmt::Debug for Instance { } } +#[derive(Debug)] pub struct InstanceDesc<'a> { pub app_name: Option<&'a str>, pub app_version: u32, - pub instance_extensions: &'a [&'a CStr], + pub instance_extensions: &'a [Extension<'a>], pub layer_settings: &'a [vk::LayerSettingEXT<'a>], pub layers: &'a [&'a CStr], pub display_handle: Option, } +pub const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation"; + +#[cfg(not(debug_assertions))] +const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[]; +#[cfg(debug_assertions)] +const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[ + vk::LayerSettingEXT { + p_layer_name: VALIDATION_LAYER.as_ptr(), + p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES".as_ptr(), + value_count: 1, + p_values: &[1u8; 1] as *const u8 as _, + ty: vk::LayerSettingTypeEXT::BOOL32, + _marker: core::marker::PhantomData, + }, + vk::LayerSettingEXT { + p_layer_name: VALIDATION_LAYER.as_ptr(), + p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES_AMD".as_ptr(), + value_count: 1, + p_values: &[1u8; 1] as *const u8 as _, + ty: vk::LayerSettingTypeEXT::BOOL32, + _marker: core::marker::PhantomData, + }, + vk::LayerSettingEXT { + p_layer_name: VALIDATION_LAYER.as_ptr(), + p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_SYNC".as_ptr(), + value_count: 1, + p_values: &[1u8; 1] as *const u8 as _, + ty: vk::LayerSettingTypeEXT::BOOL32, + _marker: core::marker::PhantomData, + }, +]; + +impl Default for InstanceDesc<'_> { + fn default() -> Self { + Self { + app_name: None, + app_version: vk::make_api_version(0, 1, 0, 0), + instance_extensions: &[make_extension!(khr::surface)], + layer_settings: DEBUG_LAYER_SETTINGS, + layers: &[VALIDATION_LAYER], + display_handle: None, + } + } +} + impl Instance { pub fn new<'a>(desc: &InstanceDesc<'a>) -> crate::Result { let entry = unsafe { ash::Entry::load()? }; @@ -95,11 +142,7 @@ impl Instance { } }; - let mut requested_extensions = desc - .instance_extensions - .iter() - .map(|name| Extension { name, version: 0 }) - .collect::>(); + let mut requested_extensions = desc.instance_extensions.to_vec(); requested_extensions.push(make_extension!(ext::debug_utils as 1)); #[cfg(debug_assertions)] @@ -170,41 +213,12 @@ impl Instance { fn choose_adapter( &self, - surface: Option<&Surface>, mut discriminator: impl FnMut(&PhysicalDeviceInfo, &PhysicalDeviceInfo) -> Ordering, ) -> crate::Result { let pdevs = unsafe { self.inner.raw.enumerate_physical_devices()? }; let mut pdevs = pdevs .into_iter() - .map(|pdev| -> crate::Result { - let properties = get_physical_device_properties(&self.inner, pdev)?; - let features = get_physical_device_features(&self.inner, pdev, &properties)?; - - let surface_capabilities = if let Some(surface) = surface { - let capabilities = unsafe { - surface - .functor - .get_physical_device_surface_capabilities(pdev, surface.raw)? - }; - - let formats = unsafe { - surface - .functor - .get_physical_device_surface_formats(pdev, surface.raw)? - }; - - Some((capabilities, formats)) - } else { - None - }; - - Ok(PhysicalDeviceInfo { - pdev, - properties, - features, - surface_capabilities, - }) - }) + .map(|pdev| -> crate::Result { self.expose_adapter(pdev) }) .filter_map(Result::ok) .collect::>(); @@ -212,13 +226,57 @@ impl Instance { pdevs.pop().ok_or(Error::NoAdapter) } + pub(crate) fn get_adapter_surface_capabilities( + &self, + pdev: vk::PhysicalDevice, + surface: &Surface, + ) -> crate::Result { + let capabilities = unsafe { + surface + .functor + .get_physical_device_surface_capabilities(pdev, surface.raw)? + }; + + let formats = unsafe { + surface + .functor + .get_physical_device_surface_formats(pdev, surface.raw)? + }; + + let present_modes = unsafe { + surface + .functor + .get_physical_device_surface_present_modes(pdev, surface.raw)? + }; + + Ok(SurfaceCapabilities { + capabilities, + formats, + present_modes, + }) + } + + pub(crate) fn expose_adapter( + &self, + pdev: vk::PhysicalDevice, + ) -> crate::Result { + let properties = get_physical_device_properties(&self.inner, pdev)?; + let features = get_physical_device_features(&self.inner, pdev, &properties)?; + + Ok(PhysicalDeviceInfo { + pdev, + properties, + features, + }) + } + pub(crate) fn choose_adapter_default( &self, surface: Option<&Surface>, required_extensions: &[Extension], required_features: Option<&PhysicalDeviceFeatures>, ) -> crate::Result { - self.choose_adapter(surface, |a, b| { + self.choose_adapter(|a, b| { // Extensions: we definitely need swapchain. match a .properties @@ -240,6 +298,7 @@ impl Instance { // Check surface compatibility // TODO + _ = surface; if let Some(ref required_features) = required_features { if b.features.superset_of(&required_features) diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 4dcc01c..ac43371 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -6,7 +6,9 @@ slice_partition_dedup )] -use std::{collections::HashMap, ffi::CStr, fmt::Debug, marker::PhantomData, sync::Arc}; +use std::{ + cell::OnceCell, collections::HashMap, ffi::CStr, fmt::Debug, marker::PhantomData, sync::Arc, +}; use bitflags::bitflags; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; @@ -103,6 +105,16 @@ pub enum Error { NoPhysicalDevice, #[error(transparent)] Io(#[from] std::io::Error), + #[error( + "Image dimensions ({width}x{height}) exceed the maximum allowed size of {max_size}x{max_size}." + )] + ImageTooLarge { + width: u32, + height: u32, + max_size: u32, + }, + #[error("Image dimensions cannot be zero.")] + ImageZeroSized, } pub type Result = core::result::Result; @@ -160,34 +172,6 @@ fn compatible_extension_properties( pub mod queue; -// Queues must be externally synchronised for calls to `vkQueueSubmit` and `vkQueuePresentKHR`. -#[derive(Clone, Debug)] -pub struct Queue(Arc>, u32); - -impl Queue { - fn new(device: &ash::Device, family: u32, index: u32) -> Self { - Self( - Arc::new(Mutex::new(unsafe { - device.get_device_queue(family, index) - })), - family, - ) - } - - pub fn family(&self) -> u32 { - self.1 - } - - pub fn with_locked T>(&self, map: F) -> T { - let lock = self.0.lock(); - map(*lock) - } - - pub fn lock(&self) -> MutexGuard<'_, vk::Queue> { - self.0.lock() - } -} - pub trait ExtendsDeviceFeatures2Debug: vk::ExtendsPhysicalDeviceFeatures2 + Debug + Send + Sync { @@ -211,7 +195,13 @@ pub struct PhysicalDeviceInfo { pub pdev: vk::PhysicalDevice, pub properties: PhysicalDeviceProperties, pub features: PhysicalDeviceFeatures, - pub surface_capabilities: Option<(SurfaceCapabilitiesKHR, Vec)>, +} + +#[derive(Debug)] +pub struct SurfaceCapabilities { + pub capabilities: SurfaceCapabilitiesKHR, + pub formats: Vec, + pub present_modes: Vec, } #[derive(Default, Debug)] @@ -960,7 +950,16 @@ pub struct Renderer2 { impl Renderer2 { pub fn new(display: RawDisplayHandle) -> Result { - let device = Device::new_from_default_desc(Some(display), &[])?; + let instance = Instance::new(&InstanceDesc { + ..Default::default() + })?; + let adapter = instance.choose_adapter_default(None, &[], None)?; + let device = adapter.create_logical_device( + &instance, + &[], + PhysicalDeviceFeatures::default(), + Some(display), + )?; Ok(Self { samplers: SamplerCache::new(device.clone()), @@ -1027,7 +1026,10 @@ impl Renderer2 { pub use vk::Extent2D; -use crate::{device::Extension, instance::InstanceInner}; +use crate::{ + device::Extension, + instance::{InstanceDesc, InstanceInner}, +}; pub mod utils { #![allow(dead_code)] diff --git a/crates/renderer/src/pipeline.rs b/crates/renderer/src/pipeline.rs index 0deade2..e87bd96 100644 --- a/crates/renderer/src/pipeline.rs +++ b/crates/renderer/src/pipeline.rs @@ -255,7 +255,7 @@ impl DescriptorPool { for (&set, desc) in sets.iter().zip(descs) { if let Some(name) = desc.name.as_ref() { - self.device().debug_name_object(set, &name)?; + unsafe { self.device().debug_name_object(set, &name) }; } } diff --git a/crates/renderer/src/queue.rs b/crates/renderer/src/queue.rs index e923cc6..81b84ae 100644 --- a/crates/renderer/src/queue.rs +++ b/crates/renderer/src/queue.rs @@ -133,6 +133,10 @@ impl DeviceQueues { &self.transfer } + pub fn swapchain_family_indices(&self) -> &[u32] { + core::slice::from_ref(&self.graphics.family.index) + } + pub unsafe fn lock(&self) { core::mem::forget(( self.graphics.inner.lock.lock(), diff --git a/crates/renderer/src/swapchain.rs b/crates/renderer/src/swapchain.rs index f4fedfc..fc7e00a 100644 --- a/crates/renderer/src/swapchain.rs +++ b/crates/renderer/src/swapchain.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, marker::PhantomData, sync::{ Arc, @@ -11,12 +12,12 @@ use ash::{ prelude::VkResult, vk::{self, Handle}, }; -use parking_lot::{RawMutex, RwLock}; +use parking_lot::{Mutex, RawMutex, RwLock}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use crate::{ - Instance, Result, define_device_owned_handle, - device::{Device, DeviceOwned}, + Instance, PhysicalDeviceInfo, Result, SurfaceCapabilities, define_device_owned_handle, + device::{Device, DeviceObject, DeviceOwned}, images, instance::InstanceInner, sync, @@ -30,6 +31,9 @@ pub struct Surface { pub(crate) raw: vk::SurfaceKHR, #[debug(skip)] pub(crate) functor: khr::surface::Instance, + pub(crate) instance: Instance, + pub(crate) capabilities: Mutex>, + pub(crate) swapchain: RwLock>>, } impl Drop for Surface { @@ -41,8 +45,48 @@ impl Drop for Surface { } impl Surface { + pub fn get_capabilities<'a>( + &'a self, + adapter: vk::PhysicalDevice, + ) -> parking_lot::MappedMutexGuard<'a, SurfaceCapabilities> { + let lock = self.capabilities.lock(); + parking_lot::MutexGuard::map( + lock, + |caps: &mut HashMap| { + caps.entry(adapter).or_insert_with(|| { + self.instance + .get_adapter_surface_capabilities(adapter, self) + .expect("surface is not compatible with the adapter") + }) + }, + ) + } + + pub fn configure(&self, device: &Device, config: SwapchainConfiguration) -> Result<()> { + let guard = self.swapchain.read(); + if let Some(swapchain) = guard.as_ref() + && swapchain.config.eq(&config) + && swapchain.swapchain.device() == device + { + // the current swapchain already matches the requested configuration, so we can skip reconfiguration. + return Ok(()); + } + + let new_swapchain = Swapchain::new( + device.clone(), + self, + config, + guard.as_ref().map(AsRef::as_ref), + )?; + + drop(guard); + self.swapchain.write().replace(Arc::new(new_swapchain)); + + todo!() + } + #[allow(dead_code)] - pub fn headless(instance: &Arc) -> Result { + pub fn headless(instance: &Instance) -> Result { let headless_instance = ash::ext::headless_surface::Instance::new(&instance.entry, &instance.raw); let functor = khr::surface::Instance::new(&instance.entry, &instance.raw); @@ -55,7 +99,13 @@ impl Surface { let raw = headless_instance .create_headless_surface(&vk::HeadlessSurfaceCreateInfoEXT::default(), None)?; - Ok(Self { raw, functor }) + Ok(Self { + raw, + functor, + capabilities: Mutex::new(HashMap::new()), + swapchain: RwLock::new(None), + instance: instance.clone(), + }) } } @@ -69,13 +119,13 @@ impl Surface { /// was created with the appropriate platform-specific surface extensions /// enabled. pub unsafe fn new_from_raw_window_handle( - instance: &Arc, + instance: &Instance, display_handle: RawDisplayHandle, window_handle: RawWindowHandle, ) -> Result { let functor = khr::surface::Instance::new(&instance.entry, &instance.raw); // SAFETY: the caller guarantees the validity of the display and window handles, and that they remain valid for the lifetime of the surface. - let surface = unsafe { + let raw = unsafe { ash_window::create_surface( &instance.entry, &instance.raw, @@ -86,30 +136,97 @@ impl Surface { }; Ok(Self { - raw: surface, + raw, functor, + capabilities: Mutex::new(HashMap::new()), + swapchain: RwLock::new(None), + instance: instance.clone(), }) } - fn get_swapchain_params( + /// Validates a swapchain configuration and possibly adjusts it to be + /// compatible with the surface capabilities by setting incompatible fields + /// to default fallbacks. + fn validate_swapchain_configuration( &self, - pdev: vk::PhysicalDevice, - requested_extent: Option, - ) -> Result { - let functor = &self.functor; - let raw = self.raw; - let caps = unsafe { functor.get_physical_device_surface_capabilities(pdev, raw)? }; - let formats = unsafe { functor.get_physical_device_surface_formats(pdev, raw)? }; - let present_modes = - unsafe { functor.get_physical_device_surface_present_modes(pdev, raw)? }; + instance: &Instance, + adapter: &PhysicalDeviceInfo, + config: &mut SwapchainConfiguration, + ) -> Result<()> { + let surface_caps = instance.get_adapter_surface_capabilities(adapter.pdev, self)?; - let present_mode = present_modes + let max_image_dim = adapter.properties.core.limits.max_image_dimension2_d; + if config.extent.width > max_image_dim || config.extent.height > max_image_dim { + return Err(crate::Error::ImageTooLarge { + width: config.extent.width, + height: config.extent.height, + max_size: max_image_dim, + }); + } + + if config.extent.width == 0 || config.extent.height == 0 { + return Err(crate::Error::ImageZeroSized); + } + + if !surface_caps.present_modes.contains(&config.present_mode) { + // find the first of these modes that is supported by the surface, in order of preference. + let fallback_modes = [ + vk::PresentModeKHR::MAILBOX, + vk::PresentModeKHR::IMMEDIATE, + vk::PresentModeKHR::FIFO, + ]; + + let fallback_mode = fallback_modes + .iter() + .find(|&&mode| surface_caps.present_modes.contains(&mode)) + .cloned().expect("FIFO is guaranteed to be supported as per Vulkan spec, so this should never happen"); + + config.present_mode = fallback_mode; + } + + if !surface_caps.formats.iter().any(|&format| { + format.format == config.format && format.color_space == config.color_space + }) { + // wgpu just rejects the swapchain if the format is not supported. is that smarter? + // find a fallback format + let format = surface_caps + .formats + .iter() + .max_by_key(|&&format| { + // prefer UNORM RGBA formats, and then SRGB color space + let is_rgba_unorm = format.format == vk::Format::R8G8B8A8_UNORM + || format.format == vk::Format::B8G8R8A8_UNORM; + let is_srgb = format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR; + is_rgba_unorm as u8 * 10 + is_srgb as u8 + }) + // fall back to the first available format + .or(surface_caps.formats.first()) + .cloned() + .expect("no surface format available!"); + + config.format = format.format; + config.color_space = format.color_space; + } + + Ok(()) + } + + fn get_fallback_swapchain_configuration( + &self, + instance: &Instance, + adapter: &PhysicalDeviceInfo, + ) -> Result { + let surface_caps = instance.get_adapter_surface_capabilities(adapter.pdev, self)?; + + let present_mode = surface_caps + .present_modes .iter() .find(|&mode| mode == &vk::PresentModeKHR::MAILBOX) .cloned() .unwrap_or(vk::PresentModeKHR::FIFO); - let format = formats + let format = surface_caps + .formats .iter() .max_by_key(|&&format| { let is_rgba_unorm = format.format == vk::Format::R8G8B8A8_UNORM @@ -117,196 +234,177 @@ impl Surface { let is_srgb = format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR; is_rgba_unorm as u8 * 10 + is_srgb as u8 }) - .or(formats.first()) + .or(surface_caps.formats.first()) .cloned() .expect("no surface format available!"); // 0 here means no limit - let max_image_count = core::num::NonZero::new(caps.max_image_count) + let max_image_count = core::num::NonZero::new(surface_caps.capabilities.max_image_count) .map(|n| n.get()) .unwrap_or(u32::MAX); // we want PREFERRED_IMAGES_IN_FLIGHT images acquired at the same time, - let image_count = - (caps.min_image_count + Swapchain::PREFERRED_IMAGES_IN_FLIGHT).min(max_image_count); + let image_count = (surface_caps.capabilities.min_image_count + + Swapchain::PREFERRED_IMAGES_IN_FLIGHT) + .min(max_image_count); let extent = current_extent_or_clamped( - &caps, - requested_extent.unwrap_or(vk::Extent2D::default().width(1).height(1)), + &surface_caps.capabilities, + vk::Extent2D::default().width(1).height(1), ); - Ok(SwapchainParams { + let composite_alpha_mode = if surface_caps + .capabilities + .supported_composite_alpha + .contains(vk::CompositeAlphaFlagsKHR::OPAQUE) + { + vk::CompositeAlphaFlagsKHR::OPAQUE + } else { + // if the surface doesn't support opaque alpha, we can still use inherit, which means the alpha will be determined by the window system. This is supported by all window systems. + vk::CompositeAlphaFlagsKHR::INHERIT + }; + + Ok(SwapchainConfiguration { present_mode, format: format.format, color_space: format.color_space, image_count, extent, - min_image_count: caps.min_image_count, + usage: vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::COLOR_ATTACHMENT, + composite_alpha_mode, }) } } -define_device_owned_handle! { - pub Swapchain(vk::SwapchainKHR) { - mutex: RawMutex, - surface: Arc, +#[derive(Debug)] +pub struct Swapchain { + // swapchain images, managed by the swapchain and must not be destroyed manually. + images: Vec, + swapchain: DeviceObject, + // this carries the device handle, however the `swapchain` field holds a ref count on the device, so it is safe to hold the pointer in the functor as well. + #[debug(skip)] + functor: khr::swapchain::Device, + /// current configuration of the swapchain. + config: SwapchainConfiguration, + /// the minimum number of images the surface permits. This is used to calculate how many images we can have in-flight at the same time. + min_image_count: u32, - #[allow(unused)] - present_mode: vk::PresentModeKHR, - #[allow(unused)] - color_space: vk::ColorSpaceKHR, - format: vk::Format, - images: Vec>, - image_views: Vec, - extent: vk::Extent2D, - min_image_count: u32, + // sync objects: + // we need two semaphores per each image, one acquire-semaphore and one release-semaphore. + // semaphores must be unique to each frame and cannot be reused per swapchain. + acquire_semaphores: Vec, + release_semaphores: Vec, - // sync objects: - // we need two semaphores per each image, one acquire-semaphore and one release-semaphore. - // semaphores must be unique to each frame and cannot be reused per swapchain. - acquire_semaphores: Vec, - release_semaphores: Vec, + // one fence per in-flight frame, to synchronize image acquisition + fences: Vec, - // one fence per in-flight frame, to synchronize image acquisition - fences: Vec>, + current_frame: AtomicU32, - current_frame: AtomicU32, + // Some of the swapchain operations require external synchronisation; this mutex allows `Swapchain` to be `Sync`. + #[debug(skip)] + guard: parking_lot::RawMutex, - // for khr_present_id/khr_present_wait - #[allow(unused)] - present_id: AtomicU64, - } => |this| unsafe { - _ = this.device().wait_queue_idle(this.device().present_queue()); - tracing::debug!("dropping swapchain {:?}", this.handle()); - for view in &this.image_views { - this.device().dev().destroy_image_view(*view, None); + // for khr_present_id/khr_present_wait + #[allow(unused)] + present_id: AtomicU64, +} + +impl Swapchain { + /// This function frees the manually managed objects associated with the swapchain. + /// This function MUST be called once and only once before the swapchain is dropped. + pub unsafe fn release_resources(&self) { + _ = self.swapchain.device().wait_idle(); + for fence in &self.fences { + unsafe { + self.swapchain.device().raw.destroy_fence(*fence, None); + } } - - this.with_locked(|swapchain| { - this.device().swapchain().destroy_swapchain(swapchain, None) - }); - - for &semaphore in this + for &semaphore in self .acquire_semaphores .iter() - .chain(&this.release_semaphores) + .chain(&self.release_semaphores) { - this.device().dev().destroy_semaphore(semaphore, None); + unsafe { + self.swapchain + .device() + .raw + .destroy_semaphore(semaphore, None); + } } } } -impl core::fmt::Debug for Swapchain { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Swapchain") - .field("inner", &self.inner) - .field("present_mode", &self.present_mode) - .field("color_space", &self.color_space) - .field("format", &self.format) - .field("images", &self.images) - .field("image_views", &self.image_views) - .field("extent", &self.extent) - .field("min_image_count", &self.min_image_count) - .field("acquire_semaphores", &self.acquire_semaphores) - .field("release_semaphores", &self.release_semaphores) - .field("fences", &self.fences) - .field("current_frame", &self.current_frame) - .field("present_id", &self.present_id) - .finish() +impl Drop for Swapchain { + fn drop(&mut self) { + unsafe { + self.release_resources(); + self.functor.destroy_swapchain(*self.swapchain, None); + } + todo!() } } impl Swapchain { const PREFERRED_IMAGES_IN_FLIGHT: u32 = 3; - pub fn new( + fn new( device: Device, - surface: Arc, - pdev: vk::PhysicalDevice, - extent: vk::Extent2D, - ) -> Result { - Self::create(device, surface, pdev, Some(extent), None) - } - - fn create( - device: Device, - surface: Arc, - pdev: vk::PhysicalDevice, - extent: Option, + surface: &Surface, + mut config: SwapchainConfiguration, old_swapchain: Option<&Self>, ) -> Result { - let SwapchainParams { - present_mode, - format, - color_space, - image_count, - min_image_count, - extent, - } = surface.get_swapchain_params(pdev, extent)?; + surface.validate_swapchain_configuration(&device.instance, &device.adapter, &mut config)?; + let surface_caps = device + .instance + .get_adapter_surface_capabilities(device.adapter.pdev, &surface)?; + + let functor = device + .device_extensions + .swapchain + .clone() + .expect("swapchain extension not loaded"); let (swapchain, images) = { - let lock = old_swapchain.as_ref().map(|handle| handle.lock()); + let _lock = old_swapchain.as_ref().map(|old| old.lock()); + let old_swapchain = old_swapchain + .map(|swp| *swp.swapchain) + .unwrap_or(vk::SwapchainKHR::null()); - Self::create_vkswapchainkhr( - &device, - surface.raw, - &device.queue_families().swapchain_family_indices(), - extent, - lock.as_ref().map(|lock| **lock), - present_mode, - format, - color_space, - image_count, - ) - }?; + let queue_families = device.queues.swapchain_family_indices(); - let images = images - .iter() - .enumerate() - .map(|(i, image)| unsafe { - images::Image::from_swapchain_image( - device.clone(), - *image, - Some(format!("swapchain-{:x}-image-{i}", swapchain.as_raw()).into()), - vk::Extent3D { - width: extent.width, - height: extent.height, - depth: 1, - }, - format, - ) - .inspect(|img| { - _ = img.get_view(images::ImageViewDesc { - // TODO: make this a function that uses debug name/surface handle - name: Some( - format!("swapchain-{:x}-image-{i}-view", swapchain.as_raw()).into(), - ), - kind: vk::ImageViewType::TYPE_2D, - format, - aspect: vk::ImageAspectFlags::COLOR, - ..Default::default() - }); + let create_info = vk::SwapchainCreateInfoKHR::default() + .surface(surface.raw) + .present_mode(config.present_mode) + .image_color_space(config.color_space) + .image_format(config.format) + .min_image_count(surface_caps.capabilities.min_image_count) + .image_usage(config.usage) + .image_array_layers(1) + .image_extent(config.extent) + .image_sharing_mode(if queue_families.len() <= 1 { + vk::SharingMode::EXCLUSIVE + } else { + vk::SharingMode::CONCURRENT }) - .map(Arc::new) - }) - .collect::>>()?; + .queue_family_indices(queue_families) + .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY) + .composite_alpha(config.composite_alpha_mode) + .old_swapchain(old_swapchain) + .clipped(true); - let image_views = images - .iter() - .enumerate() - .map(|(i, image)| { - image.get_view(images::ImageViewDesc { - name: Some(format!("swapchain-{:x}-image-{i}-view", swapchain.as_raw()).into()), - kind: vk::ImageViewType::TYPE_2D, - format, - aspect: vk::ImageAspectFlags::COLOR, - ..Default::default() - }) - }) - .collect::>>()?; + let (swapchain, images) = unsafe { + let swapchain = functor.create_swapchain(&create_info, None)?; + + let images = functor.get_swapchain_images(swapchain)?; + + (swapchain, images) + }; + + (swapchain, images) + }; let num_images = images.len() as u32; - let inflight_frames = num_images - min_image_count; + let inflight_frames = num_images - surface_caps.capabilities.min_image_count; let acquire_semaphores = { (0..inflight_frames) @@ -316,14 +414,10 @@ impl Swapchain { .create_semaphore(&vk::SemaphoreCreateInfo::default(), None) .inspect(|r| { #[cfg(debug_assertions)] - { - device - .debug_name_object( - *r, - &format!("semaphore-{:x}_{i}-acquire", swapchain.as_raw()), - ) - .unwrap(); - } + device.debug_name_object( + *r, + &format!("semaphore-{:x}_{i}-acquire", swapchain.as_raw()), + ); }) }) .collect::>>()? @@ -337,14 +431,10 @@ impl Swapchain { .create_semaphore(&vk::SemaphoreCreateInfo::default(), None) .inspect(|r| { #[cfg(debug_assertions)] - { - device - .debug_name_object( - *r, - &format!("semaphore-{:x}_{i}-release", swapchain.as_raw()), - ) - .unwrap(); - } + device.debug_name_object( + *r, + &format!("semaphore-{:x}_{i}-release", swapchain.as_raw()), + ); }) }) .collect::>>()? @@ -352,52 +442,39 @@ impl Swapchain { let fences = { (0..inflight_frames) - .map(|i| { - Ok(Arc::new(sync::Fence::create(device.clone()).inspect( - |r| { - #[cfg(debug_assertions)] - { - device - .debug_name_object( - r.fence(), - &format!("fence-{:x}_{i}", swapchain.as_raw()), - ) - .unwrap(); - } - }, - )?)) + .map(|i| unsafe { + let fence = device + .raw + .create_fence(&vk::FenceCreateInfo::default(), None)?; + device.debug_name_object(fence, &format!("fence-{:x}_{i}", swapchain.as_raw())); + Ok(fence) }) .collect::>>()? }; tracing::trace!("fences: {fences:?}"); - Ok(Self::construct( - device, - swapchain, - Some( - format!( - "swapchain-{}_{}", - surface.raw.as_raw(), - SWAPCHAIN_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) - ) - .into(), + Ok(Swapchain { + functor: device + .device_extensions + .swapchain + .clone() + .expect("swapchain extension not loaded"), + swapchain: DeviceObject::new( + swapchain, + device, + Some(format!("swapchain-{:x}", swapchain.as_raw()).into()), ), - ::INIT, - surface, - present_mode, - color_space, - format, images, - image_views, - extent, - min_image_count, + config, + guard: ::INIT, + min_image_count: surface_caps.capabilities.min_image_count, acquire_semaphores, release_semaphores, fences, - AtomicU32::new(0), - AtomicU64::new(1), - )?) + current_frame: AtomicU32::new(0), + present_id: AtomicU64::new(1), + }) } pub fn max_in_flight_images(&self) -> u32 { @@ -408,16 +485,6 @@ impl Swapchain { self.images.len() as u32 } - fn recreate(&self, extent: Option) -> Result { - Self::create( - self.device().clone(), - self.surface.clone(), - self.device().phy(), - extent, - Some(self), - ) - } - /// returns a future yielding the frame, and true if the swapchain is /// suboptimal and should be recreated. fn acquire_image( @@ -433,22 +500,17 @@ impl Swapchain { tracing::trace!(frame, "acquiring image for frame {frame}"); async move { - let fence = self.fences[frame].clone(); + let fence = self.fences[frame]; let acquire = self.acquire_semaphores[frame]; let release = self.release_semaphores[frame]; // spawn on threadpool because it might block. let (idx, suboptimal) = smol::unblock({ let this = self.clone(); - let fence = fence.clone(); move || unsafe { this.with_locked(|swapchain| { - this.device().swapchain().acquire_next_image( - swapchain, - u64::MAX, - acquire, - fence.fence(), - ) + this.functor + .acquire_next_image(swapchain, u64::MAX, acquire, fence) }) } }) @@ -465,7 +527,7 @@ impl Swapchain { SwapchainFrame { index: idx as u32, swapchain: self.clone(), - format: self.format, + format: self.config.format, image, view, acquire, @@ -498,15 +560,13 @@ impl Swapchain { // call winits pre_present_notify here unsafe { - self.device() - .swapchain() - .queue_present(*queue, &present_info)?; + self.functor.queue_present(*queue, &present_info)?; } Ok(()) } #[allow(clippy::too_many_arguments)] - fn create_vkswapchainkhr( + fn create_raw( device: &Device, surface: vk::SurfaceKHR, queue_families: &[u32], @@ -517,6 +577,11 @@ impl Swapchain { image_color_space: vk::ColorSpaceKHR, image_count: u32, ) -> Result<(vk::SwapchainKHR, Vec)> { + let swapchain_loader = device + .device_extensions + .swapchain + .as_ref() + .expect("swapchain extension not loaded"); let create_info = vk::SwapchainCreateInfoKHR::default() .surface(surface) .present_mode(present_mode) @@ -538,9 +603,9 @@ impl Swapchain { .clipped(true); let (swapchain, images) = unsafe { - let swapchain = device.swapchain().create_swapchain(&create_info, None)?; + let swapchain = swapchain_loader.create_swapchain(&create_info, None)?; - let images = device.swapchain().get_swapchain_images(swapchain)?; + let images = swapchain_loader.get_swapchain_images(swapchain)?; (swapchain, images) }; @@ -594,24 +659,31 @@ fn current_extent_or_clamped( } } -struct SwapchainParams { - present_mode: vk::PresentModeKHR, - format: vk::Format, - color_space: vk::ColorSpaceKHR, +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SwapchainConfiguration { + pub present_mode: vk::PresentModeKHR, + pub format: vk::Format, + pub color_space: vk::ColorSpaceKHR, /// the number of images to request from the device - image_count: u32, - /// the minimum number of images the surface permits - min_image_count: u32, - extent: vk::Extent2D, + pub image_count: u32, + /// The dimensions of the swapchain images. + pub extent: vk::Extent2D, + /// Alpha compositing mode. + pub composite_alpha_mode: vk::CompositeAlphaFlagsKHR, + /// Usage flags for the swapchain images. This should be a combination of + /// `vk::ImageUsageFlags::COLOR_ATTACHMENT` and + /// `vk::ImageUsageFlags::TRANSFER_DST`, but can include additional usage + /// flags if supported by the device and surface. + pub usage: vk::ImageUsageFlags, } impl Swapchain { pub fn lock(&self) -> RawMutexGuard<'_, vk::SwapchainKHR> { use parking_lot::lock_api::RawMutex; - self.mutex.lock(); + self.guard.lock(); RawMutexGuard { - mutex: &self.mutex, - value: &self.inner.object, + mutex: &self.guard, + value: &*self.swapchain, _pd: PhantomData, } } @@ -622,142 +694,145 @@ impl Swapchain { } } -#[derive(Debug)] -pub struct WindowSurface { - // window_handle: RawWindowHandle, - pub surface: Arc, - // this mutex is for guarding the swapchain against being replaced - // underneath WindowContext's functions - current_swapchain: RwLock>, -} +// impl WindowSurface { +// pub fn new( +// device: Device, +// requested_extent: vk::Extent2D, +// window: RawWindowHandle, +// display: RawDisplayHandle, +// ) -> Result { +// let surface = Arc::new(unsafe { +// Surface::new_from_raw_window_handle(device.instance(), display, window)? +// }); +// let swapchain = RwLock::new(Arc::new(Swapchain::new( +// device.clone(), +// surface.clone(), +// device.phy(), +// requested_extent, +// )?)); -impl WindowSurface { - pub fn new( - device: Device, - requested_extent: vk::Extent2D, - window: RawWindowHandle, - display: RawDisplayHandle, - ) -> Result { - let surface = Arc::new(unsafe { - Surface::new_from_raw_window_handle(device.instance(), display, window)? - }); - let swapchain = RwLock::new(Arc::new(Swapchain::new( - device.clone(), - surface.clone(), - device.phy(), - requested_extent, - )?)); +// Ok(Self { +// surface, +// // window_handle: window, +// current_swapchain: swapchain, +// }) +// } - Ok(Self { - surface, - // window_handle: window, - current_swapchain: swapchain, - }) - } +// /// spawns a task that continuously requests images from the current +// /// swapchain, sending them to a channel. returns the receiver of the +// /// channel, and a handle to the task, allowing for cancellation. +// pub fn images( +// self: Arc, +// ) -> ( +// smol::channel::Receiver, +// smol::Task>, +// ) { +// let (tx, rx) = smol::channel::bounded(8); +// let task = smol::spawn(async move { +// loop { +// let frame = self.acquire_image().await?; +// tx.send(frame) +// .await +// .expect("channel closed on swapchain acquiring frame"); +// } +// }); - /// spawns a task that continuously requests images from the current - /// swapchain, sending them to a channel. returns the receiver of the - /// channel, and a handle to the task, allowing for cancellation. - pub fn images( - self: Arc, - ) -> ( - smol::channel::Receiver, - smol::Task>, - ) { - let (tx, rx) = smol::channel::bounded(8); - let task = smol::spawn(async move { - loop { - let frame = self.acquire_image().await?; - tx.send(frame) - .await - .expect("channel closed on swapchain acquiring frame"); - } - }); +// (rx, task) +// } - (rx, task) - } +// pub async fn acquire_image(&self) -> Result { +// // clone swapchain to keep it alive +// let swapchain = self.current_swapchain.read().clone(); +// let (frame, suboptimal) = swapchain.clone().acquire_image().await?; +// if suboptimal { +// let mut lock = self.current_swapchain.write(); +// // only recreate our swapchain if it is still same, or else it might have already been recreated. +// if Arc::ptr_eq(&swapchain, &lock) { +// *lock = Arc::new(lock.recreate(None)?); +// } +// } - pub async fn acquire_image(&self) -> Result { - // clone swapchain to keep it alive - let swapchain = self.current_swapchain.read().clone(); - let (frame, suboptimal) = swapchain.clone().acquire_image().await?; - if suboptimal { - let mut lock = self.current_swapchain.write(); - // only recreate our swapchain if it is still same, or else it might have already been recreated. - if Arc::ptr_eq(&swapchain, &lock) { - *lock = Arc::new(lock.recreate(None)?); - } - } +// Ok(frame) +// } - Ok(frame) - } +// pub fn acquire_image_blocking(&self) -> Result { +// smol::block_on(self.acquire_image()) +// } - pub fn acquire_image_blocking(&self) -> Result { - smol::block_on(self.acquire_image()) - } +// pub fn recreate_with(&self, extent: Option) -> Result<()> { +// let mut swapchain = self.current_swapchain.write(); +// *swapchain = Arc::new(swapchain.recreate(extent)?); - pub fn recreate_with(&self, extent: Option) -> Result<()> { - let mut swapchain = self.current_swapchain.write(); - *swapchain = Arc::new(swapchain.recreate(extent)?); - - Ok(()) - } -} +// Ok(()) +// } +// } #[cfg(test)] mod tests { - use crate::make_extension; + use crate::{PhysicalDeviceFeatures, instance::InstanceDesc, make_extension}; use super::*; - fn create_headless_vk() -> Result<(Device, WindowSurface)> { - let device = Device::new_from_default_desc( - None, - &[ + fn create_headless_vk() -> Result<(Device, Arc)> { + let instance = Instance::new(&InstanceDesc { + instance_extensions: &[ make_extension!(ash::ext::headless_surface), make_extension!(ash::khr::surface), ], - )?; - - let surface = Arc::new(Surface::headless(device.instance())?); - - let swapchain = Arc::new(Swapchain::new( - device.clone(), - surface.clone(), - device.phy(), - vk::Extent2D::default().width(1).height(1), - )?); - - let window_ctx = WindowSurface { - // window_handle: RawWindowHandle::Web(raw_window_handle::WebWindowHandle::new(0)), - surface, - current_swapchain: RwLock::new(swapchain), + ..Default::default() + })?; + let features = PhysicalDeviceFeatures { + core13: vk::PhysicalDeviceVulkan13Features { + synchronization2: vk::TRUE, + dynamic_rendering: vk::TRUE, + maintenance4: vk::TRUE, + ..Default::default() + }, + ..Default::default() }; - Ok((device, window_ctx)) + let surface = Arc::new(Surface::headless(&instance)?); + + let adapter = instance.choose_adapter_default(Some(&surface), &[], Some(&features))?; + let device = adapter.create_logical_device(&instance, &[], features, None)?; + + surface.configure( + &device, + SwapchainConfiguration { + present_mode: vk::PresentModeKHR::FIFO, + format: vk::Format::R8G8B8A8_UNORM, + color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR, + image_count: 2, + extent: vk::Extent2D::default().width(1).height(1), + composite_alpha_mode: vk::CompositeAlphaFlagsKHR::OPAQUE, + usage: vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::COLOR_ATTACHMENT, + }, + )?; + + Ok((device, surface)) } #[tracing_test::traced_test] #[test] fn async_swapchain_acquiring() { - let (_dev, ctx) = create_headless_vk().expect("init"); - let ctx = Arc::new(ctx); - let (rx, handle) = ctx.clone().images(); + let (_dev, surface) = create_headless_vk().expect("init"); + let ctx = Arc::new(surface); + // let (rx, handle) = ctx.clone().images(); - eprintln!("hello world!"); + // eprintln!("hello world!"); - let mut count = 0; - loop { - let now = std::time::Instant::now(); - let frame = rx.recv_blocking().expect("recv"); + // let mut count = 0; + // loop { + // let now = std::time::Instant::now(); + // let frame = rx.recv_blocking().expect("recv"); - _ = frame.present(None); - tracing::info!("mspf: {:.3}ms", now.elapsed().as_micros() as f32 / 1e3); - count += 1; - if count > 1000 { - smol::block_on(handle.cancel()); - break; - } - } + // _ = frame.present(None); + // tracing::info!("mspf: {:.3}ms", now.elapsed().as_micros() as f32 / 1e3); + // count += 1; + // if count > 1000 { + // smol::block_on(handle.cancel()); + // break; + // } + // } } }