]> git.nega.tv - josh/narcissus/commitdiff
Rework swapchain handling
authorJoshua Simmons <josh@nega.tv>
Sat, 19 Nov 2022 23:23:18 +0000 (00:23 +0100)
committerJoshua Simmons <josh@nega.tv>
Sat, 19 Nov 2022 23:23:18 +0000 (00:23 +0100)
Move some more logic into the app, and avoid creating a hard dependency
between narcissus-app and narcissus-gpu.

Cargo.lock
narcissus-app/src/button.rs
narcissus-app/src/key.rs
narcissus-app/src/lib.rs
narcissus-app/src/sdl.rs
narcissus-gpu/Cargo.toml
narcissus-gpu/src/backend/mod.rs [new file with mode: 0644]
narcissus-gpu/src/backend/vulkan/mod.rs [moved from narcissus-gpu/src/vulkan.rs with 92% similarity]
narcissus-gpu/src/lib.rs
narcissus/src/main.rs

index 810d4b59d28d3738334bc35e343cb13efdf0c48d..30b7efb8e53b8f45b874228ed380a4e5d6daccdc 100644 (file)
@@ -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",
 ]
index b5e5151d8e55a8a15b2b9325f04965c312e54190..73da9752c5816b53372cec7bcaa04a648fa9ccaa 100644 (file)
@@ -1,4 +1,4 @@
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub enum Button {
     Left,
     Middle,
index 9f01915471965a693b719d1e3b68ecbae095d3d9..265f447c7cf14308a43ad49701c5c119e8454ca2 100644 (file)
@@ -1,4 +1,4 @@
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub enum Key {
     Unknown,
 
index 3f971e5aa36d4e039cc499786620c3a01f1f5775..55239ef9b36985dffcad0afb6b94e3393b1677fb 100644 (file)
@@ -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<dyn AsRawWindow> {
+    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<dyn Window>;
+    fn destroy_window(&self, window: Arc<dyn Window>);
 
-    fn poll_event(&self) -> Option<Event>;
+    fn window(&self, window_id: WindowId) -> Arc<dyn Window>;
 
-    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<Event>;
 }
 
 pub fn create_app() -> Box<dyn App> {
index 68ec52786dcb5c64b02dbaa3038fabd047d3b228..7ce43f3a3713e7360d9af43b6f5ee084dec8c8fd 100644 (file)
@@ -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::<sdl::SysWMinfo>::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<dyn AsRawWindow> for SdlWindow {
+    fn upcast(&self) -> &(dyn AsRawWindow + 'static) {
+        self
+    }
+}
 
 pub struct SdlApp {
-    windows: Mutex<Pool<SdlWindow>>,
-    window_id_to_handle: Mutex<HashMap<u32, Window>>,
+    windows: Mutex<HashMap<WindowId, Arc<SdlWindow>>>,
 }
 
 impl SdlApp {
     pub fn new() -> Result<Self, ()> {
         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<dyn Window> {
         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<dyn Window>) {
+        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<Event> {
         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<dyn Window> {
+        self.windows.lock().get(&window_id).unwrap().clone()
+    }
 }
 
 fn map_sdl_button(button: sdl::MouseButton) -> Button {
index 3dff76d33322474ea620b6d1b25eed1675c21649..77fc3855d37032ed49f70ad39819e864a90ef89d 100644 (file)
@@ -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 (file)
index 0000000..8021465
--- /dev/null
@@ -0,0 +1,2 @@
+pub mod vulkan;
+
similarity index 92%
rename from narcissus-gpu/src/vulkan.rs
rename to narcissus-gpu/src/backend/vulkan/mod.rs
index bdd72bae78f615ca6f99ec8678ef1801dc124109..66d94689b4191211ea7efcc8ba973a48612a5fe2 100644 (file)
@@ -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<VulkanBoundPipeline>,
-    swapchains_touched: HashMap<Window, (vk::Image, vk::PipelineStageFlags2)>,
+    swapchains_touched: HashMap<vk::SurfaceKHR, (vk::Image, vk::PipelineStageFlags2)>,
 }
 
 struct VulkanCmdBufferPool {
@@ -836,7 +847,7 @@ struct VulkanFrame {
 
     per_thread: GpuConcurrent<VulkanPerThread>,
 
-    present_swapchains: Mutex<HashMap<Window, VulkanPresentInfo>>,
+    present_swapchains: Mutex<HashMap<vk::SurfaceKHR, VulkanPresentInfo>>,
 
     destroyed_allocations: Mutex<VecDeque<VulkanMemory>>,
     destroyed_buffers: Mutex<VecDeque<vk::Buffer>>,
@@ -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<vk::PhysicalDeviceMemoryProperties>,
@@ -887,7 +891,9 @@ pub(crate) struct VulkanDevice<'app> {
     frame_counter: FrameCounter,
     frames: Box<[UnsafeCell<VulkanFrame>; NUM_FRAMES]>,
 
-    swapchains: Mutex<HashMap<Window, VulkanSwapchain>>,
+    surfaces: Mutex<HashMap<RawWindow, vk::SurfaceKHR>>,
+
+    swapchains: Mutex<HashMap<vk::SurfaceKHR, VulkanSwapchain>>,
     destroyed_swapchains: Mutex<SwapchainDestroyQueue>,
 
     image_pool: Mutex<Pool<VulkanImageHolder>>,
@@ -901,14 +907,24 @@ pub(crate) struct VulkanDevice<'app> {
 
     _global_fn: vk::GlobalFunctions,
     instance_fn: vk::InstanceFunctions,
+    xcb_surface_fn: Option<vk::XcbSurfaceKHRFunctions>,
+    xlib_surface_fn: Option<vk::XlibSurfaceKHRFunctions>,
+    wayland_surface_fn: Option<vk::WaylandSurfaceKHRFunctions>,
     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(&copy.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::<Box<_>>();
-
-                    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<VulkanImageHolder>| -> 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(&copy.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::<Box<_>>();
 
-                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<VulkanImageHolder>| -> 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::<Vec<_>>();
-            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) }
index da4174a945e084072d3a07bd8aff333e5512e5ab..ecb86193b6a643122f2528955581999f7c6455b7 100644 (file)
@@ -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<dyn Device> {
+    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<dyn Device + 'app> {
-    Box::new(vulkan::VulkanDevice::new(app))
-}
index 1d3ab695771350dff41a54a6574778773c28127d..4905ee844547bd8a9bedc2df74bf8f4c1aca0b54 100644 (file)
@@ -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;