From 00788d5f30de20cd5d98ae9f3cdce87f09c1c1ec Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Sun, 20 Nov 2022 00:23:18 +0100 Subject: [PATCH] Rework swapchain handling Move some more logic into the app, and avoid creating a hard dependency between narcissus-app and narcissus-gpu. --- Cargo.lock | 8 +- narcissus-app/src/button.rs | 2 +- narcissus-app/src/key.rs | 2 +- narcissus-app/src/lib.rs | 56 +- narcissus-app/src/sdl.rs | 250 ++-- narcissus-gpu/Cargo.toml | 1 - narcissus-gpu/src/backend/mod.rs | 2 + .../src/{vulkan.rs => backend/vulkan/mod.rs} | 1249 +++++++++-------- narcissus-gpu/src/lib.rs | 52 +- narcissus/src/main.rs | 33 +- 10 files changed, 885 insertions(+), 770 deletions(-) create mode 100644 narcissus-gpu/src/backend/mod.rs rename narcissus-gpu/src/{vulkan.rs => backend/vulkan/mod.rs} (92%) diff --git a/Cargo.lock b/Cargo.lock index 810d4b5..30b7efb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,12 @@ version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "narcissus" version = "0.1.0" @@ -47,6 +53,7 @@ name = "narcissus-core" version = "0.1.0" dependencies = [ "fast-float", + "memchr", "stb_image-sys", ] @@ -54,7 +61,6 @@ dependencies = [ name = "narcissus-gpu" version = "0.1.0" dependencies = [ - "narcissus-app", "narcissus-core", "vulkan-sys", ] diff --git a/narcissus-app/src/button.rs b/narcissus-app/src/button.rs index b5e5151..73da975 100644 --- a/narcissus-app/src/button.rs +++ b/narcissus-app/src/button.rs @@ -1,4 +1,4 @@ -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum Button { Left, Middle, diff --git a/narcissus-app/src/key.rs b/narcissus-app/src/key.rs index 9f01915..265f447 100644 --- a/narcissus-app/src/key.rs +++ b/narcissus-app/src/key.rs @@ -1,4 +1,4 @@ -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum Key { Unknown, diff --git a/narcissus-app/src/lib.rs b/narcissus-app/src/lib.rs index 3f971e5..55239ef 100644 --- a/narcissus-app/src/lib.rs +++ b/narcissus-app/src/lib.rs @@ -2,14 +2,14 @@ mod button; mod key; mod sdl; -use std::ffi::{c_void, CStr}; +use std::sync::Arc; -use narcissus_core::{flags_def, Handle}; +use narcissus_core::{flags_def, raw_window::AsRawWindow, Upcast}; pub use button::Button; pub use key::Key; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum PressedState { Released, Pressed, @@ -23,97 +23,95 @@ impl ModifierFlags { pub const META: Self = Self(1 << 3); } -#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug)] -pub struct Window(Handle); - -impl Window { - pub const fn is_null(&self) -> bool { - self.0.is_null() - } -} - pub struct WindowDesc<'a> { pub title: &'a str, pub width: u32, pub height: u32, } +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct WindowId(u64); + +pub trait Window: AsRawWindow + Upcast { + fn id(&self) -> WindowId; + + fn extent(&self) -> (u32, u32); +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[non_exhaustive] pub enum Event { Unknown, Quit, KeyPress { - window: Window, + window_id: WindowId, key: Key, pressed: PressedState, modifiers: ModifierFlags, }, ButtonPress { - window: Window, + window_id: WindowId, button: Button, pressed: PressedState, }, MouseMotion { - window: Window, + window_id: WindowId, x: i32, y: i32, }, /// A window has gained mouse focus. MouseEnter { - window: Window, + window_id: WindowId, x: i32, y: i32, }, /// A window has lost moust focus. MouseLeave { - window: Window, + window_id: WindowId, x: i32, y: i32, }, /// A window has gained keyboard focus. FocusIn { - window: Window, + window_id: WindowId, }, /// A window has lost keyboard focus. FocusOut { - window: Window, + window_id: WindowId, }, /// The window has been resized. Resize { - window: Window, + window_id: WindowId, width: u32, height: u32, }, // The close button has been pressed on the window. Close { - window: Window, + window_id: WindowId, }, // The window has been destroyed. Destroy { - window: Window, + window_id: WindowId, }, } pub trait App { - fn create_window(&self, desc: &WindowDesc) -> Window; - fn destroy_window(&self, window: Window); + fn create_window(&self, desc: &WindowDesc) -> Arc; + fn destroy_window(&self, window: Arc); - fn poll_event(&self) -> Option; + fn window(&self, window_id: WindowId) -> Arc; - fn vk_get_loader(&self) -> *mut c_void; - fn vk_instance_extensions(&self) -> Vec<&'static CStr>; - fn vk_create_surface(&self, window: Window, instance: u64) -> u64; - fn vk_get_surface_extent(&self, window: Window) -> (u32, u32); + fn poll_event(&self) -> Option; } pub fn create_app() -> Box { diff --git a/narcissus-app/src/sdl.rs b/narcissus-app/src/sdl.rs index 68ec527..7ce43f3 100644 --- a/narcissus-app/src/sdl.rs +++ b/narcissus-app/src/sdl.rs @@ -1,51 +1,93 @@ -use std::{ - collections::HashMap, - ffi::{c_void, CStr, CString}, - mem::MaybeUninit, - os::raw::c_char, -}; +use std::{collections::HashMap, ffi::CString, mem::MaybeUninit, sync::Arc}; -use crate::{App, Button, Event, Key, ModifierFlags, PressedState, Window}; +use crate::{App, Button, Event, Key, ModifierFlags, PressedState, Window, WindowId}; -use narcissus_core::{Handle, Mutex, Pool}; +use narcissus_core::{ + raw_window::{AsRawWindow, RawWindow, WaylandWindow, XlibWindow}, + Mutex, Upcast, +}; use sdl2_sys as sdl; -struct SdlWindow(*mut sdl::Window); +fn sdl_window_id(window_id: u32) -> WindowId { + WindowId(window_id as u64) +} + +struct SdlWindow { + window: *mut sdl::Window, +} + +impl Window for SdlWindow { + fn id(&self) -> WindowId { + sdl_window_id(unsafe { sdl::SDL_GetWindowID(self.window) }) + } + + fn extent(&self) -> (u32, u32) { + let mut width = 0; + let mut height = 0; + unsafe { + sdl::SDL_Vulkan_GetDrawableSize(self.window, &mut width, &mut height); + } + (width as u32, height as u32) + } +} + +impl AsRawWindow for SdlWindow { + fn as_raw_window(&self) -> RawWindow { + let wm_info = unsafe { + let mut wm_info = MaybeUninit::::zeroed(); + std::ptr::write( + std::ptr::addr_of_mut!((*wm_info.as_mut_ptr()).version), + sdl::Version::current(), + ); + let res = sdl::SDL_GetWindowWMInfo(self.window, wm_info.as_mut_ptr()); + assert_eq!(res, sdl::Bool::True); + wm_info.assume_init() + }; + + match wm_info.subsystem { + sdl::SysWMType::X11 => RawWindow::Xlib(XlibWindow { + display: unsafe { wm_info.info.x11.display }, + window: unsafe { wm_info.info.x11.window }, + }), + sdl::SysWMType::WAYLAND => RawWindow::Wayland(WaylandWindow { + display: unsafe { wm_info.info.wayland.display }, + surface: unsafe { wm_info.info.wayland.surface }, + }), + _ => panic!("unspported wm system"), + } + } +} + +impl Upcast for SdlWindow { + fn upcast(&self) -> &(dyn AsRawWindow + 'static) { + self + } +} pub struct SdlApp { - windows: Mutex>, - window_id_to_handle: Mutex>, + windows: Mutex>>, } impl SdlApp { pub fn new() -> Result { unsafe { sdl::SDL_Init(sdl::INIT_VIDEO) }; Ok(Self { - windows: Mutex::new(Pool::new()), - window_id_to_handle: Mutex::new(HashMap::new()), + windows: Mutex::new(HashMap::new()), }) } - - fn window_from_window_id(&self, window_id: u32) -> Window { - self.window_id_to_handle - .lock() - .get(&window_id) - .copied() - .unwrap_or_else(|| Window(Handle::null())) - } } impl Drop for SdlApp { fn drop(&mut self) { for window in self.windows.get_mut().values() { - unsafe { sdl::SDL_DestroyWindow(window.0) }; + unsafe { sdl::SDL_DestroyWindow(window.window) }; } unsafe { sdl::SDL_Quit() }; } } impl App for SdlApp { - fn create_window(&self, desc: &crate::WindowDesc) -> Window { + fn create_window(&self, desc: &crate::WindowDesc) -> Arc { let title = CString::new(desc.title).unwrap(); let window = unsafe { sdl::SDL_CreateWindow( @@ -58,79 +100,22 @@ impl App for SdlApp { ) }; assert!(!window.is_null()); - let window_id = unsafe { sdl::SDL_GetWindowID(window) }; - - let mut window_id_to_handle = self.window_id_to_handle.lock(); - let mut windows = self.windows.lock(); - - let handle = Window(windows.insert(SdlWindow(window))); - window_id_to_handle.insert(window_id, handle); - handle + let window_id = WindowId(unsafe { sdl::SDL_GetWindowID(window) } as u64); + let window = Arc::new(SdlWindow { window }); + self.windows.lock().insert(window_id, window.clone()); + window } - fn destroy_window(&self, window: Window) { - if let Some(window) = self.windows.lock().remove(window.0) { - unsafe { sdl::SDL_DestroyWindow(window.0) }; + fn destroy_window(&self, window: Arc) { + let window_id = window.id(); + drop(window); + if let Some(mut window) = self.windows.lock().remove(&window_id) { + let window = Arc::get_mut(&mut window) + .expect("tried to destroy a window while there are outstanding references"); + unsafe { sdl::SDL_DestroyWindow(window.window) }; } } - fn vk_get_loader(&self) -> *mut c_void { - unsafe { - sdl::SDL_Vulkan_LoadLibrary(std::ptr::null()); - sdl::SDL_Vulkan_GetVkGetInstanceProcAddr() - } - } - - fn vk_instance_extensions(&self) -> Vec<&'static CStr> { - let mut count: u32 = 0; - let ret = unsafe { - sdl::SDL_Vulkan_GetInstanceExtensions( - std::ptr::null_mut(), - &mut count, - std::ptr::null_mut(), - ) - }; - assert_eq!(ret, 1, "failed to query instance extensions"); - if count == 0 { - return Vec::new(); - } - - let mut names: Vec<*const c_char> = vec![std::ptr::null(); count as usize]; - let ret = unsafe { - sdl::SDL_Vulkan_GetInstanceExtensions( - std::ptr::null_mut(), - &mut count, - names.as_mut_ptr(), - ) - }; - assert_eq!(ret, 1, "failed to query instance extensions"); - - names - .iter() - .map(|&val| unsafe { CStr::from_ptr(val) }) - .collect() - } - - fn vk_create_surface(&self, window: Window, instance: u64) -> u64 { - let windows = self.windows.lock(); - let window = windows.get(window.0).unwrap(); - let mut surface = !0; - let ret = unsafe { sdl::SDL_Vulkan_CreateSurface(window.0, instance, &mut surface) }; - assert_eq!(ret, sdl::Bool::True, "failed to create vulkan surface"); - surface - } - - fn vk_get_surface_extent(&self, window: Window) -> (u32, u32) { - let windows = self.windows.lock(); - let window = windows.get(window.0).unwrap(); - let mut w = 0; - let mut h = 0; - unsafe { - sdl::SDL_Vulkan_GetDrawableSize(window.0, &mut w, &mut h); - } - (w as u32, h as u32) - } - fn poll_event(&self) -> Option { let mut event = MaybeUninit::uninit(); if unsafe { sdl::SDL_PollEvent(event.as_mut_ptr()) } == 0 { @@ -146,53 +131,40 @@ impl App for SdlApp { sdl::WindowEventId::Hidden => Event::Unknown, sdl::WindowEventId::Exposed => Event::Unknown, sdl::WindowEventId::Moved => Event::Unknown, - sdl::WindowEventId::Resized => { - let handle = self.window_from_window_id(unsafe { event.window.window_id }); - Event::Resize { - window: handle, - width: unsafe { event.window.data1 } as u32, - height: unsafe { event.window.data2 } as u32, - } - } + sdl::WindowEventId::Resized => Event::Resize { + window_id: sdl_window_id(unsafe { event.window.window_id }), + width: unsafe { event.window.data1 } as u32, + height: unsafe { event.window.data2 } as u32, + }, sdl::WindowEventId::SizeChanged => Event::Unknown, sdl::WindowEventId::Minimized => Event::Unknown, sdl::WindowEventId::Maximized => Event::Unknown, sdl::WindowEventId::Restored => Event::Unknown, - sdl::WindowEventId::Enter => { - let handle = self.window_from_window_id(unsafe { event.window.window_id }); - Event::MouseEnter { - window: handle, - x: unsafe { event.window.data1 }, - y: unsafe { event.window.data2 }, - } - } - sdl::WindowEventId::Leave => { - let handle = self.window_from_window_id(unsafe { event.window.window_id }); - Event::MouseLeave { - window: handle, - x: unsafe { event.window.data1 }, - y: unsafe { event.window.data2 }, - } - } - sdl::WindowEventId::FocusGained => { - let handle = self.window_from_window_id(unsafe { event.window.window_id }); - Event::FocusIn { window: handle } - } - sdl::WindowEventId::FocusLost => { - let handle = self.window_from_window_id(unsafe { event.window.window_id }); - Event::FocusOut { window: handle } - } - sdl::WindowEventId::Close => { - let handle = self.window_from_window_id(unsafe { event.window.window_id }); - Event::Close { window: handle } - } + sdl::WindowEventId::Enter => Event::MouseEnter { + window_id: sdl_window_id(unsafe { event.window.window_id }), + x: unsafe { event.window.data1 }, + y: unsafe { event.window.data2 }, + }, + sdl::WindowEventId::Leave => Event::MouseLeave { + window_id: sdl_window_id(unsafe { event.window.window_id }), + x: unsafe { event.window.data1 }, + y: unsafe { event.window.data2 }, + }, + sdl::WindowEventId::FocusGained => Event::FocusIn { + window_id: sdl_window_id(unsafe { event.window.window_id }), + }, + sdl::WindowEventId::FocusLost => Event::FocusOut { + window_id: sdl_window_id(unsafe { event.window.window_id }), + }, + sdl::WindowEventId::Close => Event::Close { + window_id: sdl_window_id(unsafe { event.window.window_id }), + }, sdl::WindowEventId::TakeFocus => Event::Unknown, sdl::WindowEventId::HitTest => Event::Unknown, sdl::WindowEventId::IccprofChanged => Event::Unknown, sdl::WindowEventId::DisplayChanged => Event::Unknown, }, sdl::EventType::KEYUP | sdl::EventType::KEYDOWN => { - let handle = self.window_from_window_id(unsafe { event.key.window_id }); let scancode = unsafe { event.key.keysym.scancode }; let modifiers = unsafe { event.key.keysym.modifiers }; let state = unsafe { event.key.state }; @@ -200,37 +172,37 @@ impl App for SdlApp { let modifiers = map_sdl_modifiers(modifiers); let pressed = map_sdl_pressed_state(state); Event::KeyPress { - window: handle, + window_id: sdl_window_id(unsafe { event.window.window_id }), key, pressed, modifiers, } } sdl::EventType::MOUSEBUTTONUP | sdl::EventType::MOUSEBUTTONDOWN => { - let handle = self.window_from_window_id(unsafe { event.button.window_id }); let button = unsafe { event.button.button }; let state = unsafe { event.button.state }; let button = map_sdl_button(button); let pressed = map_sdl_pressed_state(state); Event::ButtonPress { - window: handle, + window_id: sdl_window_id(unsafe { event.window.window_id }), button, pressed, } } - sdl::EventType::MOUSEMOTION => { - let handle = self.window_from_window_id(unsafe { event.window.window_id }); - Event::MouseMotion { - window: handle, - x: unsafe { event.window.data1 }, - y: unsafe { event.window.data2 }, - } - } + sdl::EventType::MOUSEMOTION => Event::MouseMotion { + window_id: sdl_window_id(unsafe { event.window.window_id }), + x: unsafe { event.window.data1 }, + y: unsafe { event.window.data2 }, + }, _ => Event::Unknown, }; Some(e) } + + fn window(&self, window_id: WindowId) -> Arc { + self.windows.lock().get(&window_id).unwrap().clone() + } } fn map_sdl_button(button: sdl::MouseButton) -> Button { diff --git a/narcissus-gpu/Cargo.toml b/narcissus-gpu/Cargo.toml index 3dff76d..77fc385 100644 --- a/narcissus-gpu/Cargo.toml +++ b/narcissus-gpu/Cargo.toml @@ -7,5 +7,4 @@ edition = "2021" [dependencies] narcissus-core = { path = "../narcissus-core" } -narcissus-app = { path = "../narcissus-app" } vulkan-sys = { path = "../ffi/vulkan-sys" } \ No newline at end of file diff --git a/narcissus-gpu/src/backend/mod.rs b/narcissus-gpu/src/backend/mod.rs new file mode 100644 index 0000000..8021465 --- /dev/null +++ b/narcissus-gpu/src/backend/mod.rs @@ -0,0 +1,2 @@ +pub mod vulkan; + diff --git a/narcissus-gpu/src/vulkan.rs b/narcissus-gpu/src/backend/vulkan/mod.rs similarity index 92% rename from narcissus-gpu/src/vulkan.rs rename to narcissus-gpu/src/backend/vulkan/mod.rs index bdd72ba..66d9468 100644 --- a/narcissus-gpu/src/vulkan.rs +++ b/narcissus-gpu/src/backend/vulkan/mod.rs @@ -1,16 +1,17 @@ use std::{ cell::UnsafeCell, - collections::{hash_map, HashMap, VecDeque}, + collections::{hash_map::Entry, HashMap, VecDeque}, marker::PhantomData, os::raw::{c_char, c_void}, ptr::NonNull, sync::atomic::{AtomicU64, AtomicUsize, Ordering}, }; -use narcissus_app::{App, Window}; use narcissus_core::{ - cstr, default, manual_arc, manual_arc::ManualArc, Arena, HybridArena, Mutex, PhantomUnsend, - Pool, + cstr, cstr_from_bytes_until_nul, default, manual_arc, + manual_arc::ManualArc, + raw_window::{AsRawWindow, RawWindow}, + Arena, HybridArena, Mutex, PhantomUnsend, Pool, }; use vulkan_sys as vk; @@ -23,8 +24,8 @@ use crate::{ ImageDimension, ImageFormat, ImageLayout, ImageSubresourceLayers, ImageSubresourceRange, ImageUsageFlags, ImageViewDesc, IndexType, LoadOp, MemoryLocation, Offset2d, Offset3d, Pipeline, PolygonMode, Sampler, SamplerAddressMode, SamplerCompareOp, SamplerDesc, - SamplerFilter, ShaderStageFlags, StencilOp, StencilOpState, StoreOp, ThreadToken, Topology, - TypedBind, + SamplerFilter, ShaderStageFlags, StencilOp, StencilOpState, StoreOp, SwapchainOutOfDateError, + ThreadToken, Topology, TypedBind, }; const NUM_FRAMES: usize = 2; @@ -34,6 +35,18 @@ const NUM_FRAMES: usize = 2; /// There's no correct answer here (spec bug) we're just picking a big number and hoping for the best. const SWAPCHAIN_DESTROY_DELAY_FRAMES: usize = 8; +mod libc { + use std::os::raw::{c_char, c_int, c_void}; + + pub const RTLD_NOW: c_int = 0x2; + pub const RTLD_LOCAL: c_int = 0; + + extern "C" { + pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void; + pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void; + } +} + macro_rules! vk_check { ($e:expr) => ({ #[allow(unused_unsafe)] @@ -669,7 +682,7 @@ struct VulkanImageShared { } struct VulkanImageSwapchain { - window: Window, + surface: vk::SurfaceKHR, image: vk::Image, view: vk::ImageView, } @@ -720,8 +733,6 @@ enum VulkanSwapchainState { } struct VulkanSwapchain { - window: Window, - surface: vk::SurfaceKHR, surface_format: vk::SurfaceFormatKHR, state: VulkanSwapchainState, @@ -761,7 +772,7 @@ struct VulkanBoundPipeline { struct VulkanCmdBuffer { command_buffer: vk::CommandBuffer, bound_pipeline: Option, - swapchains_touched: HashMap, + swapchains_touched: HashMap, } struct VulkanCmdBufferPool { @@ -836,7 +847,7 @@ struct VulkanFrame { per_thread: GpuConcurrent, - present_swapchains: Mutex>, + present_swapchains: Mutex>, destroyed_allocations: Mutex>, destroyed_buffers: Mutex>, @@ -864,16 +875,9 @@ impl VulkanFrame { } } -type SwapchainDestroyQueue = DelayQueue<( - Window, - vk::SwapchainKHR, - vk::SurfaceKHR, - Box<[vk::ImageView]>, -)>; - -pub(crate) struct VulkanDevice<'app> { - app: &'app dyn App, +type SwapchainDestroyQueue = DelayQueue<(vk::SwapchainKHR, vk::SurfaceKHR, Box<[vk::ImageView]>)>; +pub(crate) struct VulkanDevice { instance: vk::Instance, physical_device: vk::PhysicalDevice, physical_device_memory_properties: Box, @@ -887,7 +891,9 @@ pub(crate) struct VulkanDevice<'app> { frame_counter: FrameCounter, frames: Box<[UnsafeCell; NUM_FRAMES]>, - swapchains: Mutex>, + surfaces: Mutex>, + + swapchains: Mutex>, destroyed_swapchains: Mutex, image_pool: Mutex>, @@ -901,14 +907,24 @@ pub(crate) struct VulkanDevice<'app> { _global_fn: vk::GlobalFunctions, instance_fn: vk::InstanceFunctions, + xcb_surface_fn: Option, + xlib_surface_fn: Option, + wayland_surface_fn: Option, surface_fn: vk::SurfaceKHRFunctions, swapchain_fn: vk::SwapchainKHRFunctions, device_fn: vk::DeviceFunctions, } -impl<'app> VulkanDevice<'app> { - pub(crate) fn new(app: &'app dyn App) -> Self { - let get_proc_addr = app.vk_get_loader(); +impl VulkanDevice { + pub(crate) fn new() -> Self { + let get_proc_addr = unsafe { + let module = libc::dlopen( + cstr!("libvulkan.so.1").as_ptr(), + libc::RTLD_NOW | libc::RTLD_LOCAL, + ); + libc::dlsym(module, cstr!("vkGetInstanceProcAddr").as_ptr()) + }; + let global_fn = unsafe { vk::GlobalFunctions::new(get_proc_addr) }; let api_version = { @@ -926,7 +942,40 @@ impl<'app> VulkanDevice<'app> { #[cfg(not(debug_assertions))] let enabled_layers = &[]; - let enabled_extensions = app.vk_instance_extensions(); + let extension_properties = vk_vec(|count, ptr| unsafe { + global_fn.enumerate_instance_extension_properties(std::ptr::null(), count, ptr) + }); + + let mut has_wayland_support = false; + let mut has_xlib_support = false; + let mut has_xcb_support = false; + + let mut enabled_extensions = vec![]; + for extension in &extension_properties { + let extension_name = cstr_from_bytes_until_nul(&extension.extension_name).unwrap(); + + match extension_name.to_str().unwrap() { + "VK_KHR_wayland_surface" => { + has_wayland_support = true; + enabled_extensions.push(extension_name); + } + "VK_KHR_xlib_surface" => { + has_xlib_support = true; + enabled_extensions.push(extension_name); + } + "VK_KHR_xcb_surface" => { + has_xcb_support = true; + enabled_extensions.push(extension_name); + } + _ => {} + } + } + + // If we found any surface extensions, we need to additionally enable VK_KHR_surface. + if !enabled_extensions.is_empty() { + enabled_extensions.push(cstr!("VK_KHR_surface")); + } + let enabled_extensions = enabled_extensions .iter() .map(|x| x.as_ptr()) @@ -953,6 +1002,25 @@ impl<'app> VulkanDevice<'app> { }; let instance_fn = vk::InstanceFunctions::new(&global_fn, instance, vk::VERSION_1_2); + + let xcb_surface_fn = if has_xcb_support { + Some(vk::XcbSurfaceKHRFunctions::new(&global_fn, instance)) + } else { + None + }; + + let xlib_surface_fn = if has_xlib_support { + Some(vk::XlibSurfaceKHRFunctions::new(&global_fn, instance)) + } else { + None + }; + + let wayland_surface_fn = if has_wayland_support { + Some(vk::WaylandSurfaceKHRFunctions::new(&global_fn, instance)) + } else { + None + }; + let surface_fn = vk::SurfaceKHRFunctions::new(&global_fn, instance); let swapchain_fn = vk::SwapchainKHRFunctions::new(&global_fn, instance, vk::VERSION_1_1); @@ -1165,8 +1233,6 @@ impl<'app> VulkanDevice<'app> { })); Self { - app, - instance, physical_device, physical_device_memory_properties: Box::new(physical_device_memory_properties), @@ -1180,7 +1246,8 @@ impl<'app> VulkanDevice<'app> { frame_counter: FrameCounter::new(), frames, - swapchains: Mutex::new(HashMap::new()), + surfaces: default(), + swapchains: default(), destroyed_swapchains: Mutex::new(DelayQueue::new(SWAPCHAIN_DESTROY_DELAY_FRAMES)), image_pool: default(), @@ -1194,6 +1261,9 @@ impl<'app> VulkanDevice<'app> { _global_fn: global_fn, instance_fn, + xcb_surface_fn, + xlib_surface_fn, + wayland_surface_fn, surface_fn, swapchain_fn, device_fn, @@ -1395,14 +1465,12 @@ impl<'app> VulkanDevice<'app> { } } - fn destroy_swapchain( + fn destroy_swapchain_deferred( &self, - window: Window, surface: vk::SurfaceKHR, swapchain: vk::SwapchainKHR, image_views: &[vk::ImageView], ) { - let app = self.app; let device_fn = &self.device_fn; let swapchain_fn = &self.swapchain_fn; let surface_fn = &self.surface_fn; @@ -1420,13 +1488,10 @@ impl<'app> VulkanDevice<'app> { if !surface.is_null() { unsafe { surface_fn.destroy_surface(instance, surface, None) } } - if !window.is_null() { - app.destroy_window(window); - } } } -impl<'driver> Device for VulkanDevice<'driver> { +impl Device for VulkanDevice { fn create_buffer(&self, desc: &BufferDesc) -> Buffer { let mut usage = vk::BufferUsageFlags::default(); if desc.usage.contains(BufferUsageFlags::UNIFORM) { @@ -2025,466 +2090,141 @@ impl<'driver> Device for VulkanDevice<'driver> { } } - fn destroy_window(&self, window: Window) { - if let Some(VulkanSwapchain { - window: _, - surface, - surface_format: _, - state, - _formats: _, - _present_modes: _, - capabilities: _, - }) = self.swapchains.lock().remove(&window) - { - let mut image_pool = self.image_pool.lock(); + fn create_cmd_buffer(&self, frame: &Frame, thread_token: &mut ThreadToken) -> CmdBuffer { + let frame = self.frame(frame); + let per_thread = frame.per_thread.get_mut(thread_token); + let cmd_buffer_pool = &mut per_thread.cmd_buffer_pool; - if let VulkanSwapchainState::Occupied { - width: _, - height: _, - suboptimal: _, - swapchain, - image_views, - } = state - { - let mut vulkan_image_views = Vec::new(); - for &image_view in image_views.iter() { - match image_pool.remove(image_view.0) { - Some(VulkanImageHolder::Swapchain(VulkanImageSwapchain { - window: _, - image: _, - view, - })) => vulkan_image_views.push(view), - _ => panic!("swapchain image in wrong state"), - } - } + // We have consumed all available command buffers, need to allocate a new one. + if cmd_buffer_pool.next_free_index >= cmd_buffer_pool.command_buffers.len() { + let mut cmd_buffers = [vk::CommandBuffer::null(); 4]; + let allocate_info = vk::CommandBufferAllocateInfo { + command_pool: cmd_buffer_pool.command_pool, + level: vk::CommandBufferLevel::Primary, + command_buffer_count: cmd_buffers.len() as u32, + ..default() + }; + vk_check!(self.device_fn.allocate_command_buffers( + self.device, + &allocate_info, + cmd_buffers.as_mut_ptr() + )); + cmd_buffer_pool.command_buffers.extend(cmd_buffers.iter()); + } - self.destroyed_swapchains.lock().push(( - window, - swapchain, - surface, - vulkan_image_views.into_boxed_slice(), - )); + let index = cmd_buffer_pool.next_free_index; + cmd_buffer_pool.next_free_index += 1; + let command_buffer = cmd_buffer_pool.command_buffers[index]; + + vk_check!(self.device_fn.begin_command_buffer( + command_buffer, + &vk::CommandBufferBeginInfo { + flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, + ..default() } + )); + + let vulkan_cmd_buffer = per_thread.arena.alloc(VulkanCmdBuffer { + command_buffer, + bound_pipeline: None, + swapchains_touched: HashMap::new(), + }); + + CmdBuffer { + cmd_buffer_addr: vulkan_cmd_buffer as *mut _ as usize, + _phantom: &PhantomData, + phantom_unsend: PhantomUnsend {}, } } - fn acquire_swapchain( + fn cmd_barrier( &self, - frame: &Frame, - window: Window, - format: ImageFormat, - ) -> (u32, u32, Image) { - let format = vulkan_format(format); - - let mut swapchains = self.swapchains.lock(); - let mut vulkan_swapchain = swapchains.entry(window).or_insert_with(|| { - let surface = self.app.vk_create_surface(window, self.instance.as_raw()); - let surface = vk::SurfaceKHR::from_raw(surface); - - let mut supported = vk::Bool32::False; - vk_check!(self.surface_fn.get_physical_device_surface_support( - self.physical_device, - self.universal_queue_family_index, - surface, - &mut supported - )); - - assert_eq!( - supported, - vk::Bool32::True, - "universal queue does not support presenting this surface" - ); + cmd_buffer: &mut CmdBuffer, + global_barrier: Option<&GlobalBarrier>, + image_barriers: &[ImageBarrier], + ) { + let arena = HybridArena::<4096>::new(); - let formats = vk_vec(|count, ptr| unsafe { - self.surface_fn.get_physical_device_surface_formats( - self.physical_device, - surface, - count, - ptr, - ) - }) - .into_boxed_slice(); + let memory_barriers = arena.alloc_slice_fill_iter( + global_barrier + .iter() + .map(|global_barrier| vulkan_memory_barrier(global_barrier)), + ); - let present_modes = vk_vec(|count, ptr| unsafe { - self.surface_fn.get_physical_device_surface_present_modes( - self.physical_device, - surface, - count, - ptr, - ) - }) - .into_boxed_slice(); + let image_memory_barriers = + arena.alloc_slice_fill_iter(image_barriers.iter().map(|image_barrier| { + let image = self + .image_pool + .lock() + .get(image_barrier.image.0) + .expect("invalid image handle") + .image(); + let subresource_range = vulkan_subresource_range(&image_barrier.subresource_range); + vulkan_image_memory_barrier(image_barrier, image, subresource_range) + })); - let mut capabilities = vk::SurfaceCapabilitiesKHR::default(); - vk_check!(self.surface_fn.get_physical_device_surface_capabilities( - self.physical_device, - surface, - &mut capabilities - )); + let command_buffer = self.cmd_buffer_mut(cmd_buffer).command_buffer; + unsafe { + self.device_fn.cmd_pipeline_barrier2( + command_buffer, + &vk::DependencyInfo { + memory_barriers: memory_barriers.into(), + image_memory_barriers: image_memory_barriers.into(), + ..default() + }, + ) + } + } - let surface_format = formats - .iter() - .copied() - .find(|&x| x.format == format) - .expect("failed to find matching surface format"); + fn cmd_copy_buffer_to_image( + &self, + cmd_buffer: &mut CmdBuffer, + src_buffer: Buffer, + dst_image: Image, + dst_image_layout: ImageLayout, + copies: &[BufferImageCopy], + ) { + let arena = HybridArena::<4096>::new(); - VulkanSwapchain { - window, - surface, - surface_format, - state: VulkanSwapchainState::Vacant, - _formats: formats, - _present_modes: present_modes, - capabilities, - } - }); + let regions = arena.alloc_slice_fill_iter(copies.iter().map(|copy| vk::BufferImageCopy { + buffer_offset: copy.buffer_offset, + buffer_row_length: copy.buffer_row_length, + buffer_image_height: copy.buffer_image_height, + image_subresource: vulkan_subresource_layers(©.image_subresource_layers), + image_offset: copy.image_offset.into(), + image_extent: copy.image_extent.into(), + })); - assert_eq!(format, vulkan_swapchain.surface_format.format); + let src_buffer = self + .buffer_pool + .lock() + .get(src_buffer.0) + .expect("invalid buffer handle") + .buffer; - let frame = self.frame(frame); - let mut image_pool = self.image_pool.lock(); + let dst_image = self + .image_pool + .lock() + .get(dst_image.0) + .expect("invalid image handle") + .image(); - let mut present_swapchains = frame.present_swapchains.lock(); - let present_info = match present_swapchains.entry(window) { - hash_map::Entry::Occupied(_) => { - panic!("attempting to acquire the same swapchain multiple times in a frame") - } - hash_map::Entry::Vacant(entry) => entry.insert(default()), + let dst_image_layout = match dst_image_layout { + ImageLayout::Optimal => vk::ImageLayout::TransferDstOptimal, + ImageLayout::General => vk::ImageLayout::General, }; - let mut old_swapchain = vk::SwapchainKHR::null(); - let mut iters = 0; - - loop { - iters += 1; - if iters > 10 { - panic!("acquiring swapchain image took more than 10 tries"); - } - - let (desired_width, desired_height) = - self.app.vk_get_surface_extent(vulkan_swapchain.window); - - vk_check!(self.surface_fn.get_physical_device_surface_capabilities( - self.physical_device, - vulkan_swapchain.surface, - &mut vulkan_swapchain.capabilities - )); - - let desired_width = desired_width.clamp( - vulkan_swapchain.capabilities.min_image_extent.width, - vulkan_swapchain.capabilities.max_image_extent.width, - ); - let desired_height = desired_height.clamp( - vulkan_swapchain.capabilities.min_image_extent.height, - vulkan_swapchain.capabilities.max_image_extent.height, - ); - - match &mut vulkan_swapchain.state { - VulkanSwapchainState::Vacant => { - let image_extent = vk::Extent2d { - width: desired_width, - height: desired_height, - }; - let mut new_swapchain = vk::SwapchainKHR::null(); - let create_info = vk::SwapchainCreateInfoKHR { - surface: vulkan_swapchain.surface, - min_image_count: vulkan_swapchain.capabilities.min_image_count, - image_format: vulkan_swapchain.surface_format.format, - image_color_space: vulkan_swapchain.surface_format.color_space, - image_extent, - image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT, - image_array_layers: 1, - image_sharing_mode: vk::SharingMode::Exclusive, - pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY, - composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE, - present_mode: vk::PresentModeKHR::Fifo, - clipped: vk::Bool32::True, - old_swapchain, - ..default() - }; - vk_check!(self.swapchain_fn.create_swapchain( - self.device, - &create_info, - None, - &mut new_swapchain - )); - assert!(!new_swapchain.is_null()); - - let images = vk_vec(|count, ptr| unsafe { - self.swapchain_fn.get_swapchain_images( - self.device, - new_swapchain, - count, - ptr, - ) - }); - - let image_views = images - .iter() - .map(|&image| { - let create_info = vk::ImageViewCreateInfo { - image, - view_type: vk::ImageViewType::Type2d, - format: vulkan_swapchain.surface_format.format, - subresource_range: vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }, - ..default() - }; - let mut view = vk::ImageView::null(); - vk_check!(self.device_fn.create_image_view( - self.device, - &create_info, - None, - &mut view, - )); - - let handle = image_pool.insert(VulkanImageHolder::Swapchain( - VulkanImageSwapchain { - window, - image, - view, - }, - )); - Image(handle) - }) - .collect::>(); - - vulkan_swapchain.state = VulkanSwapchainState::Occupied { - width: image_extent.width, - height: image_extent.height, - suboptimal: false, - swapchain: new_swapchain, - image_views, - }; - - continue; - } - VulkanSwapchainState::Occupied { - width, - height, - suboptimal, - swapchain, - image_views, - } => { - let destroy_image_views = - |images: &mut Pool| -> Box<[vk::ImageView]> { - let mut vulkan_image_views = Vec::new(); - for &image_view in image_views.iter() { - match images.remove(image_view.0) { - Some(VulkanImageHolder::Swapchain(VulkanImageSwapchain { - window: _, - image: _, - view, - })) => vulkan_image_views.push(view), - _ => panic!("swapchain image in wrong state"), - } - } - vulkan_image_views.into_boxed_slice() - }; - - if *width != desired_width || *height != desired_height || *suboptimal { - let image_views = destroy_image_views(&mut image_pool); - old_swapchain = *swapchain; - if !old_swapchain.is_null() { - self.destroyed_swapchains.lock().push(( - Window::default(), - old_swapchain, - vk::SurfaceKHR::null(), - image_views, - )); - } - vulkan_swapchain.state = VulkanSwapchainState::Vacant; - continue; - } - - let acquire = self.request_transient_semaphore(frame); - let mut image_index = 0; - match unsafe { - self.swapchain_fn.acquire_next_image2( - self.device, - &vk::AcquireNextImageInfoKHR { - swapchain: *swapchain, - timeout: !0, - semaphore: acquire, - fence: vk::Fence::null(), - device_mask: 1, - ..default() - }, - &mut image_index, - ) - } { - vk::Result::Success => {} - vk::Result::SuboptimalKHR => { - *suboptimal = true; - } - vk::Result::ErrorOutOfDateKHR => { - old_swapchain = *swapchain; - let image_views = destroy_image_views(&mut image_pool); - if !old_swapchain.is_null() { - self.destroyed_swapchains.lock().push(( - Window::default(), - old_swapchain, - vk::SurfaceKHR::null(), - image_views, - )); - } - vulkan_swapchain.state = VulkanSwapchainState::Vacant; - continue; - } - result => vk_check!(result), - } - - present_info.acquire = acquire; - present_info.image_index = image_index; - present_info.swapchain = *swapchain; - let view = image_views[image_index as usize]; - - return (*width, *height, view); - } - } - } - } - - fn create_cmd_buffer(&self, frame: &Frame, thread_token: &mut ThreadToken) -> CmdBuffer { - let frame = self.frame(frame); - let per_thread = frame.per_thread.get_mut(thread_token); - let cmd_buffer_pool = &mut per_thread.cmd_buffer_pool; - - // We have consumed all available command buffers, need to allocate a new one. - if cmd_buffer_pool.next_free_index >= cmd_buffer_pool.command_buffers.len() { - let mut cmd_buffers = [vk::CommandBuffer::null(); 4]; - let allocate_info = vk::CommandBufferAllocateInfo { - command_pool: cmd_buffer_pool.command_pool, - level: vk::CommandBufferLevel::Primary, - command_buffer_count: cmd_buffers.len() as u32, - ..default() - }; - vk_check!(self.device_fn.allocate_command_buffers( - self.device, - &allocate_info, - cmd_buffers.as_mut_ptr() - )); - cmd_buffer_pool.command_buffers.extend(cmd_buffers.iter()); - } - - let index = cmd_buffer_pool.next_free_index; - cmd_buffer_pool.next_free_index += 1; - let command_buffer = cmd_buffer_pool.command_buffers[index]; - - vk_check!(self.device_fn.begin_command_buffer( - command_buffer, - &vk::CommandBufferBeginInfo { - flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, - ..default() - } - )); - - let vulkan_cmd_buffer = per_thread.arena.alloc(VulkanCmdBuffer { - command_buffer, - bound_pipeline: None, - swapchains_touched: HashMap::new(), - }); - - CmdBuffer { - cmd_buffer_addr: vulkan_cmd_buffer as *mut _ as usize, - _phantom: &PhantomData, - phantom_unsend: PhantomUnsend {}, - } - } - - fn cmd_barrier( - &self, - cmd_buffer: &mut CmdBuffer, - global_barrier: Option<&GlobalBarrier>, - image_barriers: &[ImageBarrier], - ) { - let arena = HybridArena::<4096>::new(); - - let memory_barriers = arena.alloc_slice_fill_iter( - global_barrier - .iter() - .map(|global_barrier| vulkan_memory_barrier(global_barrier)), - ); - - let image_memory_barriers = - arena.alloc_slice_fill_iter(image_barriers.iter().map(|image_barrier| { - let image = self - .image_pool - .lock() - .get(image_barrier.image.0) - .expect("invalid image handle") - .image(); - let subresource_range = vulkan_subresource_range(&image_barrier.subresource_range); - vulkan_image_memory_barrier(image_barrier, image, subresource_range) - })); - - let command_buffer = self.cmd_buffer_mut(cmd_buffer).command_buffer; - unsafe { - self.device_fn.cmd_pipeline_barrier2( - command_buffer, - &vk::DependencyInfo { - memory_barriers: memory_barriers.into(), - image_memory_barriers: image_memory_barriers.into(), - ..default() - }, - ) - } - } - - fn cmd_copy_buffer_to_image( - &self, - cmd_buffer: &mut CmdBuffer, - src_buffer: Buffer, - dst_image: Image, - dst_image_layout: ImageLayout, - copies: &[BufferImageCopy], - ) { - let arena = HybridArena::<4096>::new(); - - let regions = arena.alloc_slice_fill_iter(copies.iter().map(|copy| vk::BufferImageCopy { - buffer_offset: copy.buffer_offset, - buffer_row_length: copy.buffer_row_length, - buffer_image_height: copy.buffer_image_height, - image_subresource: vulkan_subresource_layers(©.image_subresource_layers), - image_offset: copy.image_offset.into(), - image_extent: copy.image_extent.into(), - })); - - let src_buffer = self - .buffer_pool - .lock() - .get(src_buffer.0) - .expect("invalid buffer handle") - .buffer; - - let dst_image = self - .image_pool - .lock() - .get(dst_image.0) - .expect("invalid image handle") - .image(); - - let dst_image_layout = match dst_image_layout { - ImageLayout::Optimal => vk::ImageLayout::TransferDstOptimal, - ImageLayout::General => vk::ImageLayout::General, - }; - - let command_buffer = self.cmd_buffer_mut(cmd_buffer).command_buffer; - unsafe { - self.device_fn.cmd_copy_buffer_to_image( - command_buffer, - src_buffer, - dst_image, - dst_image_layout, - regions, - ) - } - } + let command_buffer = self.cmd_buffer_mut(cmd_buffer).command_buffer; + unsafe { + self.device_fn.cmd_copy_buffer_to_image( + command_buffer, + src_buffer, + dst_image, + dst_image_layout, + regions, + ) + } + } fn cmd_set_bind_group( &self, @@ -2702,11 +2442,11 @@ impl<'driver> Device for VulkanDevice<'driver> { VulkanImageHolder::Shared(image) => image.view, VulkanImageHolder::Swapchain(image) => { assert!( - !cmd_buffer.swapchains_touched.contains_key(&image.window), + !cmd_buffer.swapchains_touched.contains_key(&image.surface), "swapchain attached multiple times in a command buffer" ); cmd_buffer.swapchains_touched.insert( - image.window, + image.surface, ( image.image, vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, @@ -3000,117 +2740,487 @@ impl<'driver> Device for VulkanDevice<'driver> { per_thread.arena.reset() } - self.recycled_semaphores - .lock() - .extend(frame.recycled_semaphores.get_mut().drain(..)); + self.recycled_semaphores + .lock() + .extend(frame.recycled_semaphores.get_mut().drain(..)); + + for descriptor_pool in frame.recycled_descriptor_pools.get_mut() { + vk_check!(device_fn.reset_descriptor_pool( + device, + *descriptor_pool, + vk::DescriptorPoolResetFlags::default() + )) + } + + self.recycled_descriptor_pools + .lock() + .extend(frame.recycled_descriptor_pools.get_mut().drain(..)); + + Self::destroy_deferred(device_fn, device, frame); + + self.destroyed_swapchains + .lock() + .expire(|(swapchain, surface, image_views)| { + self.destroy_swapchain_deferred(surface, swapchain, &image_views); + }); + } + + frame + } + + fn end_frame(&self, mut frame: Frame) { + let arena = HybridArena::<512>::new(); + + { + let frame = self.frame_mut(&mut frame); + + let present_swapchains = frame.present_swapchains.get_mut(); + if !present_swapchains.is_empty() { + let windows = arena.alloc_slice_fill_iter(present_swapchains.keys().copied()); + let wait_semaphores = + arena.alloc_slice_fill_iter(present_swapchains.values().map(|x| x.release)); + let swapchains = + arena.alloc_slice_fill_iter(present_swapchains.values().map(|x| x.swapchain)); + let swapchain_image_indices = + arena.alloc_slice_fill_iter(present_swapchains.values().map(|x| x.image_index)); + + present_swapchains.clear(); + + let results = arena.alloc_slice_fill_copy(swapchains.len(), vk::Result::Success); + + let present_info = vk::PresentInfoKHR { + wait_semaphores: wait_semaphores.into(), + swapchains: (swapchains, swapchain_image_indices).into(), + results: results.as_mut_ptr(), + ..default() + }; + + unsafe { + // check results below, so ignore this return value. + let _ = self + .swapchain_fn + .queue_present(self.universal_queue, &present_info); + }; + + for (i, &result) in results.iter().enumerate() { + match result { + vk::Result::Success => {} + vk::Result::SuboptimalKHR => { + // Yikes + if let VulkanSwapchainState::Occupied { + width: _, + height: _, + suboptimal, + swapchain: _, + image_views: _, + } = &mut self.swapchains.lock().get_mut(&windows[i]).unwrap().state + { + *suboptimal = true; + } + } + _ => vk_check!(result), + } + } + } + } + + self.frame_counter.release(frame); + } + + unsafe fn map_buffer(&self, buffer: Buffer) -> *mut u8 { + let mut ptr = std::ptr::null_mut(); + if let Some(buffer) = self.buffer_pool.lock().get(buffer.0) { + vk_check!(self.device_fn.map_memory( + self.device, + buffer.memory.memory, + buffer.memory.offset, + buffer.memory.size, + vk::MemoryMapFlags::default(), + &mut ptr + )) + } + std::mem::transmute::<*mut c_void, *mut u8>(ptr) + } + + unsafe fn unmap_buffer(&self, buffer: Buffer) { + if let Some(buffer) = self.buffer_pool.lock().get(buffer.0) { + self.device_fn + .unmap_memory(self.device, buffer.memory.memory) + } + } + + fn acquire_swapchain( + &self, + frame: &Frame, + window: &dyn AsRawWindow, + width: u32, + height: u32, + format: ImageFormat, + ) -> Result<(u32, u32, Image), SwapchainOutOfDateError> { + let raw_window = window.as_raw_window(); + let mut surfaces = self.surfaces.lock(); + let surface = surfaces + .entry(raw_window) + .or_insert_with(|| match raw_window { + RawWindow::Xcb(xcb) => { + let create_info = vk::XcbSurfaceCreateInfoKHR { + connection: xcb.connection, + window: xcb.window, + ..default() + }; + let mut surface = vk::SurfaceKHR::null(); + vk_check!(self.xcb_surface_fn.as_ref().unwrap().create_xcb_surface( + self.instance, + &create_info, + None, + &mut surface, + )); + surface + } + RawWindow::Xlib(xlib) => { + let create_info = vk::XlibSurfaceCreateInfoKHR { + display: xlib.display, + window: xlib.window, + ..default() + }; + let mut surface = vk::SurfaceKHR::null(); + vk_check!(self.xlib_surface_fn.as_ref().unwrap().create_xlib_surface( + self.instance, + &create_info, + None, + &mut surface, + )); + surface + } + RawWindow::Wayland(wayland) => { + let create_info = vk::WaylandSurfaceCreateInfoKHR { + display: wayland.display, + surface: wayland.surface, + ..default() + }; + let mut surface = vk::SurfaceKHR::null(); + vk_check!(self + .wayland_surface_fn + .as_ref() + .unwrap() + .create_wayland_surface(self.instance, &create_info, None, &mut surface,)); + surface + } + }); + self.acquire_swapchain(frame, *surface, width, height, format) + } + + fn destroy_swapchain(&self, window: &dyn AsRawWindow) { + let raw_window = window.as_raw_window(); + if let Some(surface) = self.surfaces.lock().remove(&raw_window) { + self.destroy_swapchain(surface) + } + } +} + +impl VulkanDevice { + fn acquire_swapchain( + &self, + frame: &Frame, + surface: vk::SurfaceKHR, + width: u32, + height: u32, + format: ImageFormat, + ) -> Result<(u32, u32, Image), SwapchainOutOfDateError> { + let format = vulkan_format(format); + + let mut swapchains = self.swapchains.lock(); + let mut vulkan_swapchain = swapchains.entry(surface).or_insert_with(|| { + let mut supported = vk::Bool32::False; + vk_check!(self.surface_fn.get_physical_device_surface_support( + self.physical_device, + self.universal_queue_family_index, + surface, + &mut supported + )); + + assert_eq!( + supported, + vk::Bool32::True, + "universal queue does not support presenting this surface" + ); + + let formats = vk_vec(|count, ptr| unsafe { + self.surface_fn.get_physical_device_surface_formats( + self.physical_device, + surface, + count, + ptr, + ) + }) + .into_boxed_slice(); + + let present_modes = vk_vec(|count, ptr| unsafe { + self.surface_fn.get_physical_device_surface_present_modes( + self.physical_device, + surface, + count, + ptr, + ) + }) + .into_boxed_slice(); + + let mut capabilities = vk::SurfaceCapabilitiesKHR::default(); + vk_check!(self.surface_fn.get_physical_device_surface_capabilities( + self.physical_device, + surface, + &mut capabilities + )); + + let surface_format = formats + .iter() + .copied() + .find(|&x| x.format == format) + .expect("failed to find matching surface format"); + + VulkanSwapchain { + surface_format, + state: VulkanSwapchainState::Vacant, + _formats: formats, + _present_modes: present_modes, + capabilities, + } + }); + + assert_eq!(format, vulkan_swapchain.surface_format.format); - for descriptor_pool in frame.recycled_descriptor_pools.get_mut() { - vk_check!(device_fn.reset_descriptor_pool( - device, - *descriptor_pool, - vk::DescriptorPoolResetFlags::default() - )) - } + let frame = self.frame(frame); + let mut image_pool = self.image_pool.lock(); - self.recycled_descriptor_pools - .lock() - .extend(frame.recycled_descriptor_pools.get_mut().drain(..)); + let mut present_swapchains = frame.present_swapchains.lock(); + let present_info = match present_swapchains.entry(surface) { + Entry::Occupied(_) => panic!("acquiring swapchain multiple times in a frame"), + Entry::Vacant(entry) => entry.insert(default()), + }; - Self::destroy_deferred(device_fn, device, frame); + vk_check!(self.surface_fn.get_physical_device_surface_capabilities( + self.physical_device, + surface, + &mut vulkan_swapchain.capabilities + )); - self.destroyed_swapchains - .lock() - .expire(|(window, swapchain, surface, image_views)| { - self.destroy_swapchain(window, surface, swapchain, &image_views); - }); - } + let width = width.clamp( + vulkan_swapchain.capabilities.min_image_extent.width, + vulkan_swapchain.capabilities.max_image_extent.width, + ); + let height = height.clamp( + vulkan_swapchain.capabilities.min_image_extent.height, + vulkan_swapchain.capabilities.max_image_extent.height, + ); - frame - } + let mut old_swapchain = vk::SwapchainKHR::null(); + loop { + match &mut vulkan_swapchain.state { + VulkanSwapchainState::Vacant => { + let mut new_swapchain = vk::SwapchainKHR::null(); + let create_info = vk::SwapchainCreateInfoKHR { + surface, + min_image_count: vulkan_swapchain.capabilities.min_image_count, + image_format: vulkan_swapchain.surface_format.format, + image_color_space: vulkan_swapchain.surface_format.color_space, + image_extent: vk::Extent2d { width, height }, + image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT, + image_array_layers: 1, + image_sharing_mode: vk::SharingMode::Exclusive, + pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY, + composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE, + present_mode: vk::PresentModeKHR::Fifo, + clipped: vk::Bool32::True, + old_swapchain, + ..default() + }; + vk_check!(self.swapchain_fn.create_swapchain( + self.device, + &create_info, + None, + &mut new_swapchain + )); + assert!(!new_swapchain.is_null()); - fn end_frame(&self, mut frame: Frame) { - let arena = HybridArena::<512>::new(); + let images = vk_vec(|count, ptr| unsafe { + self.swapchain_fn.get_swapchain_images( + self.device, + new_swapchain, + count, + ptr, + ) + }); - { - let frame = self.frame_mut(&mut frame); + let image_views = images + .iter() + .map(|&image| { + let create_info = vk::ImageViewCreateInfo { + image, + view_type: vk::ImageViewType::Type2d, + format: vulkan_swapchain.surface_format.format, + subresource_range: vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }, + ..default() + }; + let mut view = vk::ImageView::null(); + vk_check!(self.device_fn.create_image_view( + self.device, + &create_info, + None, + &mut view, + )); - let present_swapchains = frame.present_swapchains.get_mut(); - if !present_swapchains.is_empty() { - let windows = arena.alloc_slice_fill_iter(present_swapchains.keys().copied()); - let wait_semaphores = - arena.alloc_slice_fill_iter(present_swapchains.values().map(|x| x.release)); - let swapchains = - arena.alloc_slice_fill_iter(present_swapchains.values().map(|x| x.swapchain)); - let swapchain_image_indices = - arena.alloc_slice_fill_iter(present_swapchains.values().map(|x| x.image_index)); + let handle = image_pool.insert(VulkanImageHolder::Swapchain( + VulkanImageSwapchain { + surface, + image, + view, + }, + )); + Image(handle) + }) + .collect::>(); - present_swapchains.clear(); + vulkan_swapchain.state = VulkanSwapchainState::Occupied { + width, + height, + suboptimal: false, + swapchain: new_swapchain, + image_views, + }; + } + VulkanSwapchainState::Occupied { + width: current_width, + height: current_height, + suboptimal, + swapchain, + image_views, + } => { + let destroy_image_views = + |images: &mut Pool| -> Box<[vk::ImageView]> { + let mut vulkan_image_views = Vec::new(); + for &image_view in image_views.iter() { + match images.remove(image_view.0) { + Some(VulkanImageHolder::Swapchain(VulkanImageSwapchain { + surface: _, + image: _, + view, + })) => vulkan_image_views.push(view), + _ => panic!("swapchain image in wrong state"), + } + } + vulkan_image_views.into_boxed_slice() + }; - let results = arena.alloc_slice_fill_copy(swapchains.len(), vk::Result::Success); + let swapchain = *swapchain; - let present_info = vk::PresentInfoKHR { - wait_semaphores: wait_semaphores.into(), - swapchains: (swapchains, swapchain_image_indices).into(), - results: results.as_mut_ptr(), - ..default() - }; + if width != *current_width || height != *current_height || *suboptimal { + let image_views = destroy_image_views(&mut image_pool); + old_swapchain = swapchain; + self.destroyed_swapchains.lock().push(( + old_swapchain, + vk::SurfaceKHR::null(), + image_views, + )); - unsafe { - // check results below, so ignore this return value. - let _ = self - .swapchain_fn - .queue_present(self.universal_queue, &present_info); - }; + vulkan_swapchain.state = VulkanSwapchainState::Vacant; + continue; + } - for (i, &result) in results.iter().enumerate() { - match result { + let acquire = self.request_transient_semaphore(frame); + let mut image_index = 0; + match unsafe { + self.swapchain_fn.acquire_next_image2( + self.device, + &vk::AcquireNextImageInfoKHR { + swapchain, + timeout: !0, + semaphore: acquire, + fence: vk::Fence::null(), + device_mask: 1, + ..default() + }, + &mut image_index, + ) + } { vk::Result::Success => {} vk::Result::SuboptimalKHR => { - // Yikes - if let VulkanSwapchainState::Occupied { - width: _, - height: _, - suboptimal, - swapchain: _, - image_views: _, - } = &mut self.swapchains.lock().get_mut(&windows[i]).unwrap().state - { - *suboptimal = true; - } + *suboptimal = true; } - _ => vk_check!(result), + vk::Result::ErrorOutOfDateKHR => { + let image_views = destroy_image_views(&mut image_pool); + + old_swapchain = swapchain; + self.destroyed_swapchains.lock().push(( + old_swapchain, + vk::SurfaceKHR::null(), + image_views, + )); + + vulkan_swapchain.state = VulkanSwapchainState::Vacant; + return Err(SwapchainOutOfDateError(())); + } + result => vk_check!(result), } + + present_info.acquire = acquire; + present_info.image_index = image_index; + present_info.swapchain = swapchain; + let view = image_views[image_index as usize]; + + return Ok((width, height, view)); } } } - - self.frame_counter.release(frame); } - unsafe fn map_buffer(&self, buffer: Buffer) -> *mut u8 { - let mut ptr = std::ptr::null_mut(); - if let Some(buffer) = self.buffer_pool.lock().get(buffer.0) { - vk_check!(self.device_fn.map_memory( - self.device, - buffer.memory.memory, - buffer.memory.offset, - buffer.memory.size, - vk::MemoryMapFlags::default(), - &mut ptr - )) - } - std::mem::transmute::<*mut c_void, *mut u8>(ptr) - } + fn destroy_swapchain(&self, surface: vk::SurfaceKHR) { + if let Some(VulkanSwapchain { + surface_format: _, + state, + _formats: _, + _present_modes: _, + capabilities: _, + }) = self.swapchains.lock().remove(&surface) + { + let mut image_pool = self.image_pool.lock(); - unsafe fn unmap_buffer(&self, buffer: Buffer) { - if let Some(buffer) = self.buffer_pool.lock().get(buffer.0) { - self.device_fn - .unmap_memory(self.device, buffer.memory.memory) + if let VulkanSwapchainState::Occupied { + width: _, + height: _, + suboptimal: _, + swapchain, + image_views, + } = state + { + let mut vulkan_image_views = Vec::new(); + for &image_view in image_views.iter() { + match image_pool.remove(image_view.0) { + Some(VulkanImageHolder::Swapchain(VulkanImageSwapchain { + surface: _, + image: _, + view, + })) => vulkan_image_views.push(view), + _ => panic!("swapchain image in wrong state"), + } + } + + self.destroyed_swapchains.lock().push(( + swapchain, + surface, + vulkan_image_views.into_boxed_slice(), + )); + } } } } -impl<'app> Drop for VulkanDevice<'app> { +impl Drop for VulkanDevice { fn drop(&mut self) { vk_check!(self.device_fn.device_wait_idle(self.device)); @@ -3232,12 +3342,12 @@ impl<'app> Drop for VulkanDevice<'app> { .get_mut() .drain(..) .collect::>(); - for (_, (window, swapchain, surface, image_views)) in destroyed_swapchains { - self.destroy_swapchain(window, surface, swapchain, &image_views); + for (_, (swapchain, surface, image_views)) in destroyed_swapchains { + self.destroy_swapchain_deferred(surface, swapchain, &image_views); } } - for (_, swapchain) in self.swapchains.get_mut().iter() { + for (&surface, swapchain) in self.swapchains.get_mut().iter() { if let VulkanSwapchainState::Occupied { width: _, height: _, @@ -3248,10 +3358,7 @@ impl<'app> Drop for VulkanDevice<'app> { { unsafe { self.swapchain_fn.destroy_swapchain(device, swapchain, None) } } - unsafe { - self.surface_fn - .destroy_surface(instance, swapchain.surface, None) - } + unsafe { self.surface_fn.destroy_surface(instance, surface, None) } } unsafe { device_fn.destroy_device(device, None) } diff --git a/narcissus-gpu/src/lib.rs b/narcissus-gpu/src/lib.rs index da4174a..ecb8619 100644 --- a/narcissus-gpu/src/lib.rs +++ b/narcissus-gpu/src/lib.rs @@ -1,10 +1,22 @@ use std::{ffi::CStr, marker::PhantomData}; -use narcissus_app::{App, Window}; -use narcissus_core::{default, flags_def, thread_token_def, Handle, PhantomUnsend}; +use backend::vulkan; +use narcissus_core::{ + default, flags_def, raw_window::AsRawWindow, thread_token_def, Handle, PhantomUnsend, +}; +mod backend; mod delay_queue; -mod vulkan; + +pub enum DeviceBackend { + Vulkan, +} + +pub fn create_device(backend: DeviceBackend) -> Box { + match backend { + DeviceBackend::Vulkan => Box::new(vulkan::VulkanDevice::new()), + } +} #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub struct Offset2d { @@ -594,6 +606,17 @@ pub struct CmdBuffer<'a> { phantom_unsend: PhantomUnsend, } +#[derive(Debug)] +pub struct SwapchainOutOfDateError(()); + +impl std::fmt::Display for SwapchainOutOfDateError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "swapchain out of date") + } +} + +impl std::error::Error for SwapchainOutOfDateError {} + pub trait Device { fn create_buffer(&self, desc: &BufferDesc) -> Buffer; fn create_image(&self, desc: &ImageDesc) -> Image; @@ -609,6 +632,17 @@ pub trait Device { fn destroy_bind_group_layout(&self, frame: &Frame, bind_group_layout: BindGroupLayout); fn destroy_pipeline(&self, frame: &Frame, pipeline: Pipeline); + fn acquire_swapchain( + &self, + frame: &Frame, + window: &dyn AsRawWindow, + width: u32, + height: u32, + format: ImageFormat, + ) -> Result<(u32, u32, Image), SwapchainOutOfDateError>; + + fn destroy_swapchain(&self, window: &dyn AsRawWindow); + /// Map the given buffer in its entirety to system memory and return a pointer to it. /// /// # Safety @@ -624,14 +658,6 @@ pub trait Device { /// any remaining references derived from that address. unsafe fn unmap_buffer(&self, buffer: Buffer); - fn acquire_swapchain( - &self, - frame: &Frame, - window: Window, - format: ImageFormat, - ) -> (u32, u32, Image); - fn destroy_window(&self, window: Window); - #[must_use] fn create_cmd_buffer<'a, 'thread>( &'a self, @@ -708,7 +734,3 @@ pub trait Device { fn end_frame<'device>(&'device self, frame: Frame<'device>); } - -pub fn create_vulkan_device<'app>(app: &'app dyn App) -> Box { - Box::new(vulkan::VulkanDevice::new(app)) -} diff --git a/narcissus/src/main.rs b/narcissus/src/main.rs index 1d3ab69..4905ee8 100644 --- a/narcissus/src/main.rs +++ b/narcissus/src/main.rs @@ -3,7 +3,7 @@ use std::{path::Path, time::Instant}; use narcissus_app::{create_app, Event, Key, WindowDesc}; use narcissus_core::{cstr, default, obj, rand::Pcg64, Texture}; use narcissus_gpu::{ - create_vulkan_device, Access, Bind, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, + create_device, Access, Bind, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, ClearValue, CompareOp, CullingMode, Device, Extent2d, Extent3d, FrontFace, GraphicsPipelineDesc, GraphicsPipelineLayout, Image, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, ImageUsageFlags, IndexType, @@ -283,17 +283,14 @@ impl<'a> Drop for MappedBuffer<'a> { } pub fn main() { - let blÃ¥haj_image = load_texture("narcissus/data/blÃ¥haj.png"); - let (blÃ¥haj_vertices, blÃ¥haj_indices) = load_obj("narcissus/data/blÃ¥haj.obj"); - let app = create_app(); let main_window = app.create_window(&WindowDesc { title: "narcissus", width: 800, height: 600, }); + let device = create_device(narcissus_gpu::DeviceBackend::Vulkan); - let device = create_vulkan_device(app.as_ref()); let mut thread_token = ThreadToken::new(); #[repr(align(4))] @@ -368,6 +365,9 @@ pub fn main() { stencil_front: default(), }); + let blÃ¥haj_image = load_texture("narcissus/data/blÃ¥haj.png"); + let (blÃ¥haj_vertices, blÃ¥haj_indices) = load_obj("narcissus/data/blÃ¥haj.obj"); + let blÃ¥haj_vertex_buffer = create_buffer_with_data( device.as_ref(), BufferUsageFlags::STORAGE, @@ -437,7 +437,7 @@ pub fn main() { use Event::*; match event { KeyPress { - window: _, + window_id: _, key, pressed: _, modifiers: _, @@ -449,17 +449,26 @@ pub fn main() { Quit => { break 'main; } - Close { window } => { - assert_eq!(window, main_window); - device.destroy_window(window); - break 'main; + Close { window_id } => { + let window = app.window(window_id); + device.destroy_swapchain(window.upcast()); } _ => {} } } - let (width, height, swapchain_image) = - device.acquire_swapchain(&frame, main_window, ImageFormat::BGRA8_SRGB); + let (width, height, swapchain_image) = loop { + let (width, height) = main_window.extent(); + if let Ok(result) = device.acquire_swapchain( + &frame, + main_window.upcast(), + width, + height, + ImageFormat::BGRA8_SRGB, + ) { + break result; + } + }; let frame_start = Instant::now() - start_time; let frame_start = frame_start.as_secs_f32() * 0.125; -- 2.49.0