diff --git a/CHANGELOG.md b/CHANGELOG.md index c6ac81e69c..9917ab16eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ --> - **Breaking** Public dependency updates: - Winit 0.27 + - raw-window-handle 0.5 - **Breaking** Changes to `Instance` and Vulkan initialization: - `FunctionPointers` is renamed to `VulkanLibrary`, and now resides in a separate `library` module. It is re-exported from the crate root. - The `Loader` trait is now in the `library` module. @@ -36,6 +37,12 @@ - **Breaking** Changes to memory pools: - Renamed `StdMemoryPool[Alloc]`, `StdHostVisibleMemoryTypePool[Alloc]`, `StdNonHostVisibleMemoryTypePool[Alloc]` to `Standard{...}`. - Removed `Device::standard_pool` in favor of `Device::standard_memory_pool`, which returns `&Arc`. +- **Potentially Breaking** Fix iOS compilation: + - Removed dependency to `cocoa` and `metal` + - Fixed iOS compilation errors + - Added `winit_to_surface` method for iOS, ensuring we can draw to a sub `CAMetalLayer` layer + - Added `Surface::update_ios_sublayer_on_resize` to ensure iOS sublayer is fullscreen if initial window size was not the same as device's + - Ensure both iOS and MacOS have `CAMetalLayer` when using `create_surface_from_handle` - Bugs fixed: - [#1896](https://github.com/vulkano-rs/vulkano/issues/1896): Vulkano-shaders generates invalid struct definitions when struct field names are stripped out by the compiler. diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index 1092446880..7c6bc5aa31 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -339,6 +339,10 @@ impl VulkanoWindowRenderer { self.remove_additional_image_view(i); self.add_additional_image_view(i, format, usage); } + #[cfg(target_os = "ios")] + unsafe { + self.surface.update_ios_sublayer_on_resize(); + } self.recreate_swapchain = false; } } diff --git a/vulkano-win/Cargo.toml b/vulkano-win/Cargo.toml index 8b3c714d0d..776537d4d6 100644 --- a/vulkano-win/Cargo.toml +++ b/vulkano-win/Cargo.toml @@ -13,15 +13,14 @@ categories = ["rendering::graphics-api"] [features] default = ["winit_", "raw-window-handle_"] -winit_ = ["winit", "metal", "cocoa", "objc"] +winit_ = ["winit", "objc", "core-graphics-types"] raw-window-handle_ = ["raw-window-handle"] [dependencies] -raw-window-handle = { version = "0.4", optional = true } +raw-window-handle = { version = "0.5", optional = true } vulkano = { version = "0.30.0", path = "../vulkano" } winit = { version = "0.27", optional = true } -[target.'cfg(target_os = "macos")'.dependencies] -cocoa = { version = "0.24", optional = true } -metal = { version = "0.23", optional = true } -objc = { version = "0.2", optional = true } +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +objc = { version = "0.2.5", optional = true } +core-graphics-types = { version = "0.1", optional = true } \ No newline at end of file diff --git a/vulkano-win/src/raw_window_handle.rs b/vulkano-win/src/raw_window_handle.rs index 5c55d8c848..d575b309b8 100644 --- a/vulkano-win/src/raw_window_handle.rs +++ b/vulkano-win/src/raw_window_handle.rs @@ -1,4 +1,10 @@ -use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +#[cfg(target_os = "ios")] +use crate::get_metal_layer_ios; +#[cfg(target_os = "macos")] +use crate::get_metal_layer_macos; +use raw_window_handle::{ + HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, +}; use std::sync::Arc; use vulkano::instance::Instance; use vulkano::swapchain::Surface; @@ -6,29 +12,64 @@ use vulkano::swapchain::SurfaceCreationError; /// Creates a vulkan surface from a generic window /// which implements HasRawWindowHandle and thus can reveal the os-dependent handle. -/// - Note that if you wish to use this function with MacOS, you will need to ensure that the -/// `CAMetalLayer` is set to the ns_view. An example of how one might do that can be found in -/// `vulkano_win::set_ca_metal_layer_to_winit` pub fn create_surface_from_handle( window: W, instance: Arc, ) -> Result>, SurfaceCreationError> where - W: HasRawWindowHandle, + W: HasRawWindowHandle + HasRawDisplayHandle, { unsafe { match window.raw_window_handle() { RawWindowHandle::AndroidNdk(h) => { Surface::from_android(instance, h.a_native_window, window) } - RawWindowHandle::UiKit(h) => Surface::from_ios(instance, h.ui_view, window), - RawWindowHandle::AppKit(h) => Surface::from_mac_os(instance, h.ns_view, window), + RawWindowHandle::UiKit(_h) => { + #[cfg(target_os = "ios")] + { + // Ensure the layer is CAMetalLayer + let layer = get_metal_layer_ios(_h.ui_view); + Surface::from_ios(instance, layer, window) + } + #[cfg(not(target_os = "ios"))] + { + panic!("UiKit handle should only be used when target_os == 'ios'"); + } + } + RawWindowHandle::AppKit(_h) => { + #[cfg(target_os = "macos")] + { + // Ensure the layer is CAMetalLayer + let layer = get_metal_layer_macos(_h.ns_view); + Surface::from_mac_os(instance, layer as *const (), window) + } + #[cfg(not(target_os = "macos"))] + { + panic!("AppKit handle should only be used when target_os == 'macos'"); + } + } RawWindowHandle::Wayland(h) => { - Surface::from_wayland(instance, h.display, h.surface, window) + let d = match window.raw_display_handle() { + RawDisplayHandle::Wayland(d) => d, + _ => panic!("Invalid RawDisplayHandle"), + }; + Surface::from_wayland(instance, d.display, h.surface, window) } RawWindowHandle::Win32(h) => Surface::from_win32(instance, h.hinstance, h.hwnd, window), - RawWindowHandle::Xcb(h) => Surface::from_xcb(instance, h.connection, h.window, window), - RawWindowHandle::Xlib(h) => Surface::from_xlib(instance, h.display, h.window, window), + RawWindowHandle::Xcb(h) => { + let d = match window.raw_display_handle() { + RawDisplayHandle::Xcb(d) => d, + _ => panic!("Invalid RawDisplayHandle"), + }; + Surface::from_xcb(instance, d.connection, h.window, window) + } + RawWindowHandle::Xlib(h) => { + let d = match window.raw_display_handle() { + RawDisplayHandle::Xlib(d) => d, + _ => panic!("Invalid RawDisplayHandle"), + }; + Surface::from_xlib(instance, d.display, h.window, window) + } RawWindowHandle::Web(_) => unimplemented!(), _ => unimplemented!(), } diff --git a/vulkano-win/src/winit.rs b/vulkano-win/src/winit.rs index 0637b7ad07..a208517e75 100644 --- a/vulkano-win/src/winit.rs +++ b/vulkano-win/src/winit.rs @@ -123,7 +123,12 @@ unsafe fn winit_to_surface>( } } -#[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))] +#[cfg(all( + unix, + not(target_os = "android"), + not(target_os = "macos"), + not(target_os = "ios") +))] unsafe fn winit_to_surface>( instance: Arc, win: W, @@ -157,36 +162,37 @@ unsafe fn winit_to_surface>( } } -#[cfg(target_os = "macos")] -use cocoa::{ - appkit::{NSView, NSWindow}, - base::id as cocoa_id, -}; -#[cfg(target_os = "macos")] -use metal::MetalLayer; -#[cfg(target_os = "macos")] -use objc::runtime::YES; -#[cfg(target_os = "macos")] -use std::mem; +#[cfg(any(target_os = "macos", target_os = "ios"))] +use objc::{class, msg_send, runtime::Object, sel, sel_impl}; -/// Ensure `CAMetalLayer` (native rendering surface on MacOs) is used by the ns_view. +/// Get (and set) `CAMetalLayer` to ns_view. /// This is necessary to be able to render on Mac. #[cfg(target_os = "macos")] -unsafe fn set_ca_metal_layer_to_winit>(win: W) { - use winit::platform::macos::WindowExtMacOS; +pub(crate) unsafe fn get_metal_layer_macos(view: *mut std::ffi::c_void) -> *mut Object { + use core_graphics_types::base::CGFloat; + use objc::runtime::YES; + use objc::runtime::{BOOL, NO}; - let wnd: cocoa_id = mem::transmute(win.borrow().ns_window()); - let layer = MetalLayer::new(); - - layer.set_edge_antialiasing_mask(0); - layer.set_presents_with_transaction(false); - layer.remove_all_animations(); - - let view = wnd.contentView(); - - layer.set_contents_scale(view.backingScaleFactor()); - view.setLayer(mem::transmute(layer.as_ref())); // Bombs here with out of memory - view.setWantsLayer(YES); + let view: *mut Object = std::mem::transmute(view); + let main_layer: *mut Object = msg_send![view, layer]; + let class = class!(CAMetalLayer); + let is_valid_layer: BOOL = msg_send![main_layer, isKindOfClass: class]; + if is_valid_layer == NO { + let new_layer: *mut Object = msg_send![class, new]; + let () = msg_send![new_layer, setEdgeAntialiasingMask: 0]; + let () = msg_send![new_layer, setPresentsWithTransaction: false]; + let () = msg_send![new_layer, removeAllAnimations]; + let () = msg_send![view, setLayer: new_layer]; + let () = msg_send![view, setWantsLayer: YES]; + let window: *mut Object = msg_send![view, window]; + if !window.is_null() { + let scale_factor: CGFloat = msg_send![window, backingScaleFactor]; + let () = msg_send![new_layer, setContentsScale: scale_factor]; + } + new_layer + } else { + main_layer + } } #[cfg(target_os = "macos")] @@ -195,9 +201,39 @@ unsafe fn winit_to_surface>( win: W, ) -> Result>, SurfaceCreationError> { use winit::platform::macos::WindowExtMacOS; + let layer = get_metal_layer_macos(win.borrow().ns_view()); + Surface::from_mac_os(instance, layer as *const (), win) +} + +#[cfg(target_os = "ios")] +use vulkano::swapchain::IOSMetalLayer; + +/// Get sublayer from iOS main view (ui_view). The sublayer is created as CAMetalLayer +#[cfg(target_os = "ios")] +pub(crate) unsafe fn get_metal_layer_ios(view: *mut std::ffi::c_void) -> IOSMetalLayer { + use core_graphics_types::{base::CGFloat, geometry::CGRect}; + + let view: *mut Object = std::mem::transmute(view); + let main_layer: *mut Object = msg_send![view, layer]; + let class = class!(CAMetalLayer); + let new_layer: *mut Object = msg_send![class, new]; + let frame: CGRect = msg_send![main_layer, bounds]; + let () = msg_send![new_layer, setFrame: frame]; + let () = msg_send![main_layer, addSublayer: new_layer]; + let screen: *mut Object = msg_send![class!(UIScreen), mainScreen]; + let scale_factor: CGFloat = msg_send![screen, nativeScale]; + let () = msg_send![view, setContentScaleFactor: scale_factor]; + IOSMetalLayer::new(view, new_layer) +} - set_ca_metal_layer_to_winit(win.borrow()); - Surface::from_mac_os(instance, win.borrow().ns_view() as *const (), win) +#[cfg(target_os = "ios")] +unsafe fn winit_to_surface>( + instance: Arc, + win: W, +) -> Result>, SurfaceCreationError> { + use winit::platform::ios::WindowExtIOS; + let layer = get_metal_layer_ios(win.borrow().ui_view()); + Surface::from_ios(instance, layer, win) } #[cfg(target_os = "windows")] diff --git a/vulkano/Cargo.toml b/vulkano/Cargo.toml index dc43bbc8da..2b3879accd 100644 --- a/vulkano/Cargo.toml +++ b/vulkano/Cargo.toml @@ -26,6 +26,10 @@ once_cell = { version = "1.13", features = ["parking_lot"] } parking_lot = { version = "0.12", features = ["send_guard"] } smallvec = "1.8" +[target.'cfg(target_os = "ios")'.dependencies] +objc = "0.2.5" +core-graphics-types = "0.1" + [build-dependencies] heck = "0.4" indexmap = "1.8" diff --git a/vulkano/src/library.rs b/vulkano/src/library.rs index fb5cad013a..62de5dbb35 100644 --- a/vulkano/src/library.rs +++ b/vulkano/src/library.rs @@ -42,8 +42,8 @@ impl VulkanLibrary { pub fn new() -> Result, LoadingError> { #[cfg(target_os = "ios")] #[allow(non_snake_case)] - fn def_loader_impl() -> Result, LoadingError> { - let loader = statically_linked_vulkan_loader!(); + fn def_loader_impl() -> Result, LoadingError> { + let loader = crate::statically_linked_vulkan_loader!(); Ok(Box::new(loader)) } @@ -84,7 +84,6 @@ impl VulkanLibrary { .get_instance_proc_addr(ash::vk::Instance::null(), name.as_ptr()) .map_or(ptr::null(), |func| func as _) }); - // Per the Vulkan spec: // If the vkGetInstanceProcAddr returns NULL for vkEnumerateInstanceVersion, it is a // Vulkan 1.0 implementation. Otherwise, the application can call vkEnumerateInstanceVersion @@ -325,12 +324,12 @@ macro_rules! statically_linked_vulkan_loader { struct StaticallyLinkedVulkanLoader; unsafe impl Loader for StaticallyLinkedVulkanLoader { - fn get_instance_proc_addr( + unsafe fn get_instance_proc_addr( &self, instance: ash::vk::Instance, name: *const c_char, - ) -> extern "system" fn() -> () { - unsafe { vkGetInstanceProcAddr(instance, name) } + ) -> ash::vk::PFN_vkVoidFunction { + vkGetInstanceProcAddr(instance, name) } } diff --git a/vulkano/src/swapchain/mod.rs b/vulkano/src/swapchain/mod.rs index dabfb55c21..5b9e124581 100644 --- a/vulkano/src/swapchain/mod.rs +++ b/vulkano/src/swapchain/mod.rs @@ -330,6 +330,9 @@ pub use self::{ SwapchainAcquireFuture, SwapchainCreateInfo, SwapchainCreationError, Win32Monitor, }, }; +#[cfg(target_os = "ios")] +pub use surface::IOSMetalLayer; + use std::sync::atomic::AtomicBool; pub mod display; diff --git a/vulkano/src/swapchain/surface.rs b/vulkano/src/swapchain/surface.rs index 31b67d8565..f0bdee1102 100644 --- a/vulkano/src/swapchain/surface.rs +++ b/vulkano/src/swapchain/surface.rs @@ -18,6 +18,10 @@ use crate::{ }, Error, OomError, VulkanObject, }; + +#[cfg(target_os = "ios")] +use objc::{class, msg_send, runtime::Object, sel, sel_impl}; + use std::{ error, fmt, hash::{Hash, Hasher}, @@ -35,10 +39,11 @@ pub struct Surface { instance: Arc, api: SurfaceApi, window: W, - // If true, a swapchain has been associated to this surface, and that any new swapchain // creation should be forbidden. has_swapchain: AtomicBool, + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer, } impl Surface { @@ -63,6 +68,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), } } @@ -131,6 +138,8 @@ impl Surface { window: (), has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -177,6 +186,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -188,9 +199,10 @@ impl Surface { /// - The object referred to by `view` must outlive the created `Surface`. /// The `win` parameter can be used to ensure this. /// - The `UIView` must be backed by a `CALayer` instance of type `CAMetalLayer`. - pub unsafe fn from_ios( + #[cfg(target_os = "ios")] + pub unsafe fn from_ios( instance: Arc, - view: *const T, + metal_layer: IOSMetalLayer, win: W, ) -> Result>, SurfaceCreationError> { if !instance.enabled_extensions().mvk_ios_surface { @@ -201,7 +213,7 @@ impl Surface { let create_info = ash::vk::IOSSurfaceCreateInfoMVK { flags: ash::vk::IOSSurfaceCreateFlagsMVK::empty(), - p_view: view as *const _, + p_view: metal_layer.render_layer.0 as *const _, ..Default::default() }; @@ -224,6 +236,7 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + metal_layer, })) } @@ -235,6 +248,7 @@ impl Surface { /// - The object referred to by `view` must outlive the created `Surface`. /// The `win` parameter can be used to ensure this. /// - The `NSView` must be backed by a `CALayer` instance of type `CAMetalLayer`. + #[cfg(target_os = "macos")] pub unsafe fn from_mac_os( instance: Arc, view: *const T, @@ -271,6 +285,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -317,6 +333,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -363,6 +381,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -413,6 +433,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -463,6 +485,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -513,6 +537,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -563,6 +589,8 @@ impl Surface { window: win, has_swapchain: AtomicBool::new(false), + #[cfg(target_os = "ios")] + metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), })) } @@ -583,6 +611,24 @@ impl Surface { pub fn window(&self) -> &W { &self.window } + + /// Resizes the sublayer bounds on iOS. + /// It may not be necessary if original window size matches device's, but often it does not. + /// Thus this should be called after a resize has occurred abd swapchain has been recreated. + /// + /// On iOS, we've created CAMetalLayer as a sublayer. However, when the view changes size, + /// its sublayers are not automatically resized, and we must resize + /// it here. + #[cfg(target_os = "ios")] + #[inline] + pub unsafe fn update_ios_sublayer_on_resize(&self) { + use core_graphics_types::geometry::CGRect; + let class = class!(CAMetalLayer); + let main_layer: *mut Object = self.metal_layer.main_layer.0; + let bounds: CGRect = msg_send![main_layer, bounds]; + let render_layer: *mut Object = self.metal_layer.render_layer.0; + let () = msg_send![render_layer, setFrame: bounds]; + } } impl Drop for Surface { @@ -616,8 +662,8 @@ impl fmt::Debug for Surface { instance, api, window: _, - has_swapchain, + .. } = self; fmt.debug_struct("Surface") @@ -1226,6 +1272,38 @@ impl Default for SurfaceInfo { } } +#[cfg(target_os = "ios")] +struct LayerHandle(*mut Object); + +#[cfg(target_os = "ios")] +unsafe impl Send for LayerHandle {} + +#[cfg(target_os = "ios")] +unsafe impl Sync for LayerHandle {} + +/// Represents the metal layer for IOS +#[cfg(target_os = "ios")] +pub struct IOSMetalLayer { + main_layer: LayerHandle, + render_layer: LayerHandle, +} + +#[cfg(target_os = "ios")] +impl IOSMetalLayer { + pub fn new(main_layer: *mut Object, render_layer: *mut Object) -> Self { + Self { + main_layer: LayerHandle(main_layer), + render_layer: LayerHandle(render_layer), + } + } +} + +#[cfg(target_os = "ios")] +unsafe impl Send for IOSMetalLayer {} + +#[cfg(target_os = "ios")] +unsafe impl Sync for IOSMetalLayer {} + /// The capabilities of a surface when used by a physical device. /// /// You have to match these capabilities when you create a swapchain.