diff --git a/ash-examples/src/lib.rs b/ash-examples/src/lib.rs index c1d85e450..18174e752 100644 --- a/ash-examples/src/lib.rs +++ b/ash-examples/src/lib.rs @@ -232,7 +232,7 @@ impl ExampleBase { .collect(); let mut extension_names = - ash_window::enumerate_required_extensions(window.display_handle()?.as_raw()) + ash_window::enumerate_required_extensions(event_loop.display_handle()?.as_raw()) .unwrap() .to_vec(); extension_names.push(debug_utils::NAME.as_ptr()); @@ -284,18 +284,21 @@ impl ExampleBase { let debug_call_back = debug_utils_loader .create_debug_utils_messenger(&debug_info, None) .unwrap(); - let surface = ash_window::create_surface( + + let surface_factory = ash_window::SurfaceFactory::new( &entry, &instance, - window.display_handle()?.as_raw(), - window.window_handle()?.as_raw(), - None, + event_loop.display_handle()?.as_raw(), ) .unwrap(); + let surface = surface_factory + .create_surface(window.window_handle()?.as_raw(), None) + .unwrap(); + let surface_loader = surface::Instance::load(&entry, &instance); + let pdevices = instance .enumerate_physical_devices() .expect("Physical device error"); - let surface_loader = surface::Instance::load(&entry, &instance); let (pdevice, queue_family_index) = pdevices .iter() .find_map(|pdevice| { diff --git a/ash-window/Changelog.md b/ash-window/Changelog.md index 7d7654ab0..8898ba65f 100644 --- a/ash-window/Changelog.md +++ b/ash-window/Changelog.md @@ -1,5 +1,7 @@ # Changelog +- Keep loaded `Surface` extensions around via a new `SurfaceFactory` object on which `create_surface()` is called (#1019) + ## [0.13.0] - 2024-03-31 - Bumped MSRV from 1.59 to 1.69 for `winit 0.28` and `raw-window-handle 0.5.1`, and `CStr::from_bytes_until_nul`. (#709, #716, #746) diff --git a/ash-window/examples/winit.rs b/ash-window/examples/winit.rs index ffff329ab..8df5feabb 100644 --- a/ash-window/examples/winit.rs +++ b/ash-window/examples/winit.rs @@ -30,6 +30,13 @@ fn main() -> Result<(), Box> { let instance = entry.create_instance(&instance_desc, None)?; + let surface_factory = ash_window::SurfaceFactory::new( + &entry, + &instance, + event_loop.display_handle()?.as_raw(), + ) + .unwrap(); + let window = WindowBuilder::new() .with_inner_size(PhysicalSize::::from((800, 600))) .build(&event_loop)?; @@ -56,7 +63,8 @@ fn main() -> Result<(), Box> { } Event::LoopExiting => { // This will be the last event before the loop terminates. - // TODO: How does this play with Suspended? + // TODO: Winit should issue Suspended consistently on all platforms, matching + // Resumed and creating a single uniform place to handle surface destruction. // https://github.com/rust-windowing/winit/issues/3206 if let Some(surface) = surface.take() { surface_fn.destroy_surface(surface, None); @@ -64,14 +72,9 @@ fn main() -> Result<(), Box> { } Event::Resumed => { // Create a surface from winit window. - let s = ash_window::create_surface( - &entry, - &instance, - window.display_handle().unwrap().as_raw(), - window.window_handle().unwrap().as_raw(), - None, - ) - .unwrap(); + let s = surface_factory + .create_surface(window.window_handle().unwrap().as_raw(), None) + .unwrap(); println!("surface: {s:?}"); assert!( surface.replace(s).is_none(), diff --git a/ash-window/src/lib.rs b/ash-window/src/lib.rs index 5b05fa880..285a8f6ad 100644 --- a/ash-window/src/lib.rs +++ b/ash-window/src/lib.rs @@ -9,118 +9,178 @@ use ash::{ }; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; -/// Create a surface from a raw display and window handle. -/// -/// `instance` must have created with platform specific surface extensions enabled, acquired -/// through [`enumerate_required_extensions()`]. -/// -/// # Safety -/// -/// There is a [parent/child relation] between [`Instance`] and [`Entry`], and the resulting -/// [`vk::SurfaceKHR`]. The application must not [destroy][Instance::destroy_instance()] these -/// parent objects before first [destroying][surface::Instance::destroy_surface()] the returned -/// [`vk::SurfaceKHR`] child object. [`vk::SurfaceKHR`] does _not_ implement [drop][drop()] -/// semantics and can only be destroyed via [`destroy_surface()`][surface::Instance::destroy_surface()]. -/// -/// See the [`Entry::create_instance()`] documentation for more destruction ordering rules on -/// [`Instance`]. -/// -/// The window represented by `window_handle` must be associated with the display connection -/// in `display_handle`. -/// -/// `window_handle` and `display_handle` must be associated with a valid window and display -/// connection, which must not be destroyed for the lifetime of the returned [`vk::SurfaceKHR`]. -/// -/// [parent/child relation]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fundamentals-objectmodel-lifetime -pub unsafe fn create_surface( - entry: &Entry, - instance: &Instance, - display_handle: RawDisplayHandle, - window_handle: RawWindowHandle, - allocation_callbacks: Option<&vk::AllocationCallbacks>, -) -> VkResult { - match (display_handle, window_handle) { - (RawDisplayHandle::Windows(_), RawWindowHandle::Win32(window)) => { - let surface_desc = vk::Win32SurfaceCreateInfoKHR::default() - .hwnd(window.hwnd.get()) - .hinstance( - window - .hinstance - .ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)? - .get(), - ); - let surface_fn = win32_surface::Instance::load(entry, instance); - surface_fn.create_win32_surface(&surface_desc, allocation_callbacks) - } - - (RawDisplayHandle::Wayland(display), RawWindowHandle::Wayland(window)) => { - let surface_desc = vk::WaylandSurfaceCreateInfoKHR::default() - .display(display.display.as_ptr()) - .surface(window.surface.as_ptr()); - let surface_fn = wayland_surface::Instance::load(entry, instance); - surface_fn.create_wayland_surface(&surface_desc, allocation_callbacks) - } - - (RawDisplayHandle::Xlib(display), RawWindowHandle::Xlib(window)) => { - let surface_desc = vk::XlibSurfaceCreateInfoKHR::default() - .dpy( - display - .display - .ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)? - .as_ptr(), - ) - .window(window.window); - let surface_fn = xlib_surface::Instance::load(entry, instance); - surface_fn.create_xlib_surface(&surface_desc, allocation_callbacks) - } - - (RawDisplayHandle::Xcb(display), RawWindowHandle::Xcb(window)) => { - let surface_desc = vk::XcbSurfaceCreateInfoKHR::default() - .connection( - display - .connection - .ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)? - .as_ptr(), - ) - .window(window.window.get()); - let surface_fn = xcb_surface::Instance::load(entry, instance); - surface_fn.create_xcb_surface(&surface_desc, allocation_callbacks) - } - - (RawDisplayHandle::Android(_), RawWindowHandle::AndroidNdk(window)) => { - let surface_desc = - vk::AndroidSurfaceCreateInfoKHR::default().window(window.a_native_window.as_ptr()); - let surface_fn = android_surface::Instance::load(entry, instance); - surface_fn.create_android_surface(&surface_desc, allocation_callbacks) - } - - #[cfg(target_os = "macos")] - (RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(window)) => { - use raw_window_metal::{appkit, Layer}; - - let layer = match appkit::metal_layer_from_handle(window) { - Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), - }; - - let surface_desc = vk::MetalSurfaceCreateInfoEXT::default().layer(&*layer); - let surface_fn = metal_surface::Instance::load(entry, instance); - surface_fn.create_metal_surface(&surface_desc, allocation_callbacks) - } - - #[cfg(target_os = "ios")] - (RawDisplayHandle::UiKit(_), RawWindowHandle::UiKit(window)) => { - use raw_window_metal::{uikit, Layer}; +#[derive(Clone)] +enum SurfaceExtension { + Windows(win32_surface::Instance), + Wayland( + raw_window_handle::WaylandDisplayHandle, + wayland_surface::Instance, + ), + Xlib(raw_window_handle::XlibDisplayHandle, xlib_surface::Instance), + Xcb(raw_window_handle::XcbDisplayHandle, xcb_surface::Instance), + Android(android_surface::Instance), + #[cfg(target_os = "macos")] + AppKit(metal_surface::Instance), + #[cfg(target_os = "ios")] + UiKit(metal_surface::Instance), +} - let layer = match uikit::metal_layer_from_handle(window) { - Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), - }; +/// Holder for a loaded platform-specific Vulkan `Surface` extension, used to create surfaces. +/// +/// Also stores the platform-specific [`raw_window_handle::RawDisplayHandle`] variant if necessary +/// to create surfaces, identifying the selected display server handle that was used to load the +/// relevant extension when creating a [`SurfaceFactory`]. +#[derive(Clone)] +pub struct SurfaceFactory(SurfaceExtension); + +impl SurfaceFactory { + /// Load the relevant surface extension for a given [`RawDisplayHandle`]. + /// + /// `instance` must have been created with platform specific surface extensions enabled, acquired + /// through [`enumerate_required_extensions()`]. + pub fn new( + entry: &Entry, + instance: &Instance, + display_handle: RawDisplayHandle, + ) -> VkResult { + Ok(Self(match display_handle { + RawDisplayHandle::Windows(_) => { + SurfaceExtension::Windows(win32_surface::Instance::load(entry, instance)) + } + + RawDisplayHandle::Wayland(display) => { + SurfaceExtension::Wayland(display, wayland_surface::Instance::load(entry, instance)) + } + + RawDisplayHandle::Xlib(display) => { + SurfaceExtension::Xlib(display, xlib_surface::Instance::load(entry, instance)) + } + + RawDisplayHandle::Xcb(display) => { + SurfaceExtension::Xcb(display, xcb_surface::Instance::load(entry, instance)) + } + + RawDisplayHandle::Android(_) => { + SurfaceExtension::Android(android_surface::Instance::load(entry, instance)) + } + + #[cfg(target_os = "macos")] + RawDisplayHandle::AppKit(_) => { + SurfaceExtension::AppKit(metal_surface::Instance::load(entry, instance)) + } + + #[cfg(target_os = "ios")] + RawDisplayHandle::UiKit(_) => { + SurfaceExtension::UiKit(metal_surface::Instance::load(entry, instance)) + } + + _ => return Err(vk::Result::ERROR_EXTENSION_NOT_PRESENT), + })) + } - let surface_desc = vk::MetalSurfaceCreateInfoEXT::default().layer(&*layer); - let surface_fn = metal_surface::Instance::load(entry, instance); - surface_fn.create_metal_surface(&surface_desc, allocation_callbacks) + /// Create a surface from a raw window handle that is compatible with the `display_handle` this + /// [`SurfaceFactory`] object was created with. + /// + /// # Safety + /// + /// There is a [parent/child relation] between [`Instance`] and [`Entry`], and the resulting + /// [`vk::SurfaceKHR`]. The application must not [destroy][Instance::destroy_instance()] these + /// parent objects before first [destroying][surface::Instance::destroy_surface()] the returned + /// [`vk::SurfaceKHR`] child object. [`vk::SurfaceKHR`] does _not_ implement [drop][drop()] + /// semantics and can only be destroyed via [`destroy_surface()`][surface::Instance::destroy_surface()]. + /// + /// See the [`Entry::create_instance()`] documentation for more destruction ordering rules on + /// [`Instance`]. + /// + /// The window represented by `window_handle` must be associated with the display connection + /// that was passed to `display_handle` in [`Self::new()`]. + /// + //FUTURE: This display_handle lifetime can be implicitly represented by storing DisplayHandle<'a> + /// `window_handle` and `display_handle` must be associated with a valid window and display + /// connection, which must not be destroyed for the lifetime of the returned [`vk::SurfaceKHR`]. + /// + /// [parent/child relation]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fundamentals-objectmodel-lifetime + pub unsafe fn create_surface( + &self, + window_handle: RawWindowHandle, + allocation_callbacks: Option<&vk::AllocationCallbacks>, + ) -> VkResult { + match (&self.0, window_handle) { + (SurfaceExtension::Windows(surface_fn), RawWindowHandle::Win32(window)) => { + let surface_desc = vk::Win32SurfaceCreateInfoKHR::default() + .hwnd(window.hwnd.get()) + .hinstance( + window + .hinstance + .ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)? + .get(), + ); + surface_fn.create_win32_surface(&surface_desc, allocation_callbacks) + } + + (SurfaceExtension::Wayland(display, surface_fn), RawWindowHandle::Wayland(window)) => { + let surface_desc = vk::WaylandSurfaceCreateInfoKHR::default() + .display(display.display.as_ptr()) + .surface(window.surface.as_ptr()); + surface_fn.create_wayland_surface(&surface_desc, allocation_callbacks) + } + + (SurfaceExtension::Xlib(display, surface_fn), RawWindowHandle::Xlib(window)) => { + let surface_desc = vk::XlibSurfaceCreateInfoKHR::default() + .dpy( + display + .display + .ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)? + .as_ptr(), + ) + .window(window.window); + surface_fn.create_xlib_surface(&surface_desc, allocation_callbacks) + } + + (SurfaceExtension::Xcb(display, surface_fn), RawWindowHandle::Xcb(window)) => { + let surface_desc = vk::XcbSurfaceCreateInfoKHR::default() + .connection( + display + .connection + .ok_or(vk::Result::ERROR_INITIALIZATION_FAILED)? + .as_ptr(), + ) + .window(window.window.get()); + surface_fn.create_xcb_surface(&surface_desc, allocation_callbacks) + } + + (SurfaceExtension::Android(surface_fn), RawWindowHandle::AndroidNdk(window)) => { + let surface_desc = vk::AndroidSurfaceCreateInfoKHR::default() + .window(window.a_native_window.as_ptr()); + surface_fn.create_android_surface(&surface_desc, allocation_callbacks) + } + + #[cfg(target_os = "macos")] + (SurfaceExtension::AppKit(surface_fn), RawWindowHandle::AppKit(window)) => { + use raw_window_metal::{appkit, Layer}; + + let layer = match appkit::metal_layer_from_handle(window) { + Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), + }; + + let surface_desc = vk::MetalSurfaceCreateInfoEXT::default().layer(&*layer); + surface_fn.create_metal_surface(&surface_desc, allocation_callbacks) + } + + #[cfg(target_os = "ios")] + (SurfaceExtension::UiKit(surface_fn), RawWindowHandle::UiKit(window)) => { + use raw_window_metal::{uikit, Layer}; + + let layer = match uikit::metal_layer_from_handle(window) { + Layer::Existing(layer) | Layer::Allocated(layer) => layer.cast(), + }; + + let surface_desc = vk::MetalSurfaceCreateInfoEXT::default().layer(&*layer); + surface_fn.create_metal_surface(&surface_desc, allocation_callbacks) + } + + _ => Err(vk::Result::ERROR_EXTENSION_NOT_PRESENT), } - - _ => Err(vk::Result::ERROR_EXTENSION_NOT_PRESENT), } }