From 0af3fb3cb31982393bc6f0110d0be22ac5f31998 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 18:03:54 +0200 Subject: [PATCH] Use raw-window-metal to get a CAMetalLayer from raw-window-handle (#2561) The way `raw-window-metal` works is by creating a layer, and inserting that as a sublayer, just like we did on iOS before. The bounds are then kept in-sync with an observer, ensuring smooth resizing. This also fixes compilation errors on iOS, and adds preliminary support for tvOS. The implementation now solely uses `VK_EXT_metal_surface`, which was added in 2018, instead of `VK_MVK_ios_surface` / `VK_MVK_macos_surface`, which are deprecated, and only available a year and a half earlier anyhow. Note that apart from the above, there is a slight behavioral change on macOS: we no longer set `edgeAntialiasingMask` on the layer, as it's not really required, and allows us to avoid depending on `objc2` directly. It was introduced without motivation in 40e0b24, so I doubt anyone uses it, and if they do, they can change it on the layer themselves. --- Cargo.lock | 86 +++++++++++++-- Cargo.toml | 3 +- README.md | 13 ++- vulkano-util/src/context.rs | 5 +- vulkano-util/src/renderer.rs | 4 - vulkano-win/Cargo.toml | 14 +-- vulkano-win/src/raw_window_handle.rs | 76 +++++-------- vulkano-win/src/winit.rs | 74 ++----------- vulkano/Cargo.toml | 5 +- vulkano/build.rs | 4 +- vulkano/src/library.rs | 4 +- vulkano/src/swapchain/mod.rs | 2 - vulkano/src/swapchain/surface.rs | 153 +++++---------------------- 13 files changed, 167 insertions(+), 276 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index baa3dc39..b0cc2b6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,7 +196,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" dependencies = [ "block-sys", - "objc2", + "objc2 0.4.1", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", ] [[package]] @@ -928,9 +937,9 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" dependencies = [ - "block2", + "block2 0.3.0", "dispatch", - "objc2", + "objc2 0.4.1", ] [[package]] @@ -1437,7 +1446,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ "objc-sys", - "objc2-encode", + "objc2-encode 3.0.0", +] + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode 4.0.3", ] [[package]] @@ -1446,6 +1465,49 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "object" version = "0.35.0" @@ -1706,6 +1768,17 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2000e45d7daa9b6d946e88dfa1d7ae330424a81918a6545741821c989eb80a9" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation", + "objc2-quartz-core", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -2353,7 +2426,6 @@ dependencies = [ "ahash", "ash", "bytemuck", - "core-graphics-types", "crossbeam-queue", "half", "heck", @@ -2361,12 +2433,12 @@ dependencies = [ "libc", "libloading 0.8.3", "nom", - "objc", "once_cell", "parking_lot", "proc-macro2", "quote", "raw-window-handle 0.6.2", + "raw-window-metal", "serde", "serde_json", "slabbin", @@ -3052,7 +3124,7 @@ dependencies = [ "memmap2 0.9.4", "ndk 0.8.0", "ndk-sys 0.5.0+25.2.9519653", - "objc2", + "objc2 0.4.1", "once_cell", "orbclient", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index ef770dfa..b5d1fbf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,14 +47,12 @@ ahash = "0.8" ash = "0.38.0" bytemuck = "1.9" concurrent-slotmap = { git = "https://github.com/vulkano-rs/concurrent-slotmap", rev = "fa906d916d8d126d3cc3a2b4ab9a29fa27bee62d" } -core-graphics-types = "0.1" crossbeam-queue = "0.3" half = "2.0" heck = "0.4" indexmap = "2.0" libloading = "0.8" nom = "7.1" -objc = "0.2.5" once_cell = "1.17" parking_lot = "0.12" proc-macro2 = "1.0" @@ -62,6 +60,7 @@ proc-macro-crate = "2.0" quote = "1.0" rangemap = "1.5" raw-window-handle = "0.6" +raw-window-metal = "1.0" serde = "1.0" serde_json = "1.0" shaderc = "0.8.3" diff --git a/README.md b/README.md index 11245455..b4b3957c 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ Vulkano uses [shaderc-rs](https://github.com/google/shaderc-rs) for shader compi Note that in general vulkano does **not** require you to install the official Vulkan SDK. This is not something specific to vulkano (you don't need the SDK to write programs that use Vulkan, even without vulkano), but many people are unaware of that and install the SDK thinking that it is -required. However, macOS and iOS platforms do require a little more Vulkan setup since it is not +required. However, macOS, iOS and tvOS platforms do require a little more Vulkan setup since it is not natively supported. See below for more details. Unless you provide libshaderc, in order to build libshaderc with the shaderc-sys crate, the following tools must be installed and available on `PATH`: @@ -203,17 +203,16 @@ On arch based system sudo pacman -Sy base-devel git python cmake vulkan-devel --noconfirm ``` -### macOS and iOS Specific Setup +### macOS, iOS and tvOS Specific Setup -Vulkan is not natively supported by macOS and iOS. However, there exists [MoltenVK](https://github.com/KhronosGroup/MoltenVK) -an open-source Vulkan implementation on top of Apple's Metal API. This allows vulkano to build and run on macOS -and iOS platforms. +Vulkan is not natively supported by macOS, iOS and tvOS. However, there exists [MoltenVK](https://github.com/KhronosGroup/MoltenVK) +an open-source Vulkan implementation on top of Apple's Metal API. This allows vulkano to build and run on macOS, iOS and tvOS platforms. The easiest way to get vulkano up and running with MoltenVK is to install the [Vulkan SDK for macOS](https://vulkan.lunarg.com/sdk/home). There are [installation instructions](https://vulkan.lunarg.com/doc/sdk/latest/mac/getting_started.html) on the LunarG website. -On iOS, vulkano links directly to the MoltenVK framework. There is nothing else to do besides -installing it. Note that the Vulkan SDK for macOS also comes with the iOS framework. +On iOS and tvOS, vulkano links directly to the MoltenVK framework. There is nothing else to do besides +installing it. Note that the Vulkan SDK for macOS also comes with the framework for iOS and tvOS. ## License diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index 41be85bb..6cdfd469 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -120,7 +120,7 @@ impl VulkanoContext { pub fn new(mut config: VulkanoConfig) -> Self { let library = match VulkanLibrary::new() { Ok(x) => x, - #[cfg(target_os = "macos")] + #[cfg(target_vendor = "apple")] Err(vulkano::library::LoadingError::LibraryLoadFailure(err)) => panic!( "failed to load Vulkan library: {err}; did you install VulkanSDK from \ https://vulkan.lunarg.com/sdk/home?", @@ -140,8 +140,7 @@ impl VulkanoContext { khr_wayland_surface: true, khr_android_surface: true, khr_win32_surface: true, - mvk_ios_surface: true, - mvk_macos_surface: true, + ext_metal_surface: true, ..InstanceExtensions::empty() }) .union(&config.instance_create_info.enabled_extensions); diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index 1b188e1b..ae71363b 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -373,10 +373,6 @@ 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 c3bb8d35..efb4eef7 100644 --- a/vulkano-win/Cargo.toml +++ b/vulkano-win/Cargo.toml @@ -2,7 +2,10 @@ name = "vulkano-win" version = "0.34.0" edition = "2021" -authors = ["Pierre Krieger ", "The vulkano contributors"] +authors = [ + "Pierre Krieger ", + "The vulkano contributors", +] repository = "https://github.com/vulkano-rs/vulkano/tree/master/vulkano-win" description = "Link between vulkano and winit" license = "MIT OR Apache-2.0" @@ -16,8 +19,8 @@ readme = "../README.md" default = ["winit", "raw-window-handle"] raw-window-handle = ["dep:raw-window-handle"] raw-window-handle_ = ["dep:raw-window-handle"] -winit = ["dep:winit", "dep:objc", "dep:core-graphics-types"] -winit_ = ["dep:winit", "dep:objc", "dep:core-graphics-types"] +winit = ["dep:winit"] +winit_ = ["dep:winit"] # NOTE(Marc): The dependencies here are not workspace dependencies because vulkano-win is # deprecated and won't be receiving updates. @@ -27,6 +30,5 @@ raw-window-handle = { version = "0.5", optional = true } vulkano = { workspace = true } winit = { version = "0.28", 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 } +[target.'cfg(target_vendor = "apple")'.dependencies] +raw-window-metal.workspace = true diff --git a/vulkano-win/src/raw_window_handle.rs b/vulkano-win/src/raw_window_handle.rs index 15d346da..ed07eaf5 100644 --- a/vulkano-win/src/raw_window_handle.rs +++ b/vulkano-win/src/raw_window_handle.rs @@ -1,7 +1,3 @@ -#[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, }; @@ -19,29 +15,21 @@ pub fn create_surface_from_handle( RawWindowHandle::AndroidNdk(h) => { Surface::from_android(instance, h.a_native_window, Some(window)) } - RawWindowHandle::UiKit(_h) => { - #[cfg(target_os = "ios")] - { - // Ensure the layer is CAMetalLayer - let metal_layer = get_metal_layer_ios(_h.ui_view); - Surface::from_ios(instance, metal_layer.render_layer.0 as _, Some(window)) - } - #[cfg(not(target_os = "ios"))] - { - panic!("UiKit handle should only be used when target_os == 'ios'"); - } + #[cfg(target_vendor = "apple")] + RawWindowHandle::AppKit(handle) => { + let view = std::ptr::NonNull::new(handle.ns_view).unwrap(); + let layer = raw_window_metal::Layer::from_ns_view(view); + + // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation + Surface::from_metal(instance, layer.as_ptr(), Some(window)) } - RawWindowHandle::AppKit(_h) => { - #[cfg(target_os = "macos")] - { - // Ensure the layer is CAMetalLayer - let metal_layer = get_metal_layer_macos(_h.ns_view); - Surface::from_mac_os(instance, metal_layer as _, Some(window)) - } - #[cfg(not(target_os = "macos"))] - { - panic!("AppKit handle should only be used when target_os == 'macos'"); - } + #[cfg(target_vendor = "apple")] + RawWindowHandle::UiKit(handle) => { + let view = std::ptr::NonNull::new(handle.ui_view).unwrap(); + let layer = raw_window_metal::Layer::from_ui_view(view); + + // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation + Surface::from_metal(instance, layer.as_ptr(), Some(window)) } RawWindowHandle::Wayland(h) => { let d = match window.raw_display_handle() { @@ -88,29 +76,21 @@ pub unsafe fn create_surface_from_handle_ref( RawWindowHandle::AndroidNdk(h) => { Surface::from_android(instance, h.a_native_window, None) } - RawWindowHandle::UiKit(_h) => { - #[cfg(target_os = "ios")] - { - // Ensure the layer is CAMetalLayer - let metal_layer = get_metal_layer_ios(_h.ui_view); - Surface::from_ios(instance, metal_layer.render_layer.0 as _, None) - } - #[cfg(not(target_os = "ios"))] - { - panic!("UiKit handle should only be used when target_os == 'ios'"); - } + #[cfg(target_vendor = "apple")] + (RawWindowHandle::AppKit(handle), _) => { + let view = std::ptr::NonNull::new(handle.ns_view).unwrap(); + let layer = raw_window_metal::Layer::from_ns_view(view); + + // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation + Surface::from_metal(instance, layer.as_ptr(), None) } - RawWindowHandle::AppKit(_h) => { - #[cfg(target_os = "macos")] - { - // Ensure the layer is CAMetalLayer - let metal_layer = get_metal_layer_macos(_h.ns_view); - Surface::from_mac_os(instance, metal_layer as _, None) - } - #[cfg(not(target_os = "macos"))] - { - panic!("AppKit handle should only be used when target_os == 'macos'"); - } + #[cfg(target_vendor = "apple")] + (RawWindowHandle::UiKit(handle), _) => { + let view = std::ptr::NonNull::new(handle.ui_view).unwrap(); + let layer = raw_window_metal::Layer::from_ui_view(view); + + // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation + Surface::from_metal(instance, layer.as_ptr(), None) } RawWindowHandle::Wayland(h) => { let d = match window.raw_display_handle() { diff --git a/vulkano-win/src/winit.rs b/vulkano-win/src/winit.rs index da3754bd..0503ffde 100644 --- a/vulkano-win/src/winit.rs +++ b/vulkano-win/src/winit.rs @@ -22,8 +22,7 @@ pub fn required_extensions(library: &VulkanLibrary) -> InstanceExtensions { khr_wayland_surface: true, khr_android_surface: true, khr_win32_surface: true, - mvk_ios_surface: true, - mvk_macos_surface: true, + ext_metal_surface: true, khr_get_physical_device_properties2: true, khr_get_surface_capabilities2: true, ..InstanceExtensions::empty() @@ -119,12 +118,7 @@ unsafe fn winit_to_surface( } } -#[cfg(all( - unix, - not(target_os = "android"), - not(target_os = "macos"), - not(target_os = "ios") -))] +#[cfg(all(unix, not(target_os = "android"), target_vendor = "apple",))] unsafe fn winit_to_surface( instance: Arc, window: Arc, @@ -157,68 +151,15 @@ unsafe fn winit_to_surface( } } -#[cfg(any(target_os = "macos", target_os = "ios"))] -use objc::{class, msg_send, runtime::Object, sel, sel_impl}; - -/// Get (and set) `CAMetalLayer` to ns_view. -/// This is necessary to be able to render on Mac. -#[cfg(target_os = "macos")] -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 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")] unsafe fn winit_to_surface( instance: Arc, window: Arc, ) -> Result, Validated> { use winit::platform::macos::WindowExtMacOS; - let metal_layer = get_metal_layer_macos(window.ns_view()); - Surface::from_mac_os(instance, metal_layer as _, Some(window)) -} - -#[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) + let view = std::ptr::NonNull::new(window.ns_view()).unwrap(); + let layer = raw_window_metal::Layer::from_ns_view(view); + Surface::from_metal(instance, layer.as_ptr(), Some(window)) } #[cfg(target_os = "ios")] @@ -227,8 +168,9 @@ unsafe fn winit_to_surface( window: Arc, ) -> Result, Validated> { use winit::platform::ios::WindowExtIOS; - let metal_layer = get_metal_layer_ios(window.ui_view()); - Surface::from_ios(instance, metal_layer.render_layer.0 as _, Some(window)) + let view = std::ptr::NonNull::new(window.ui_view()).unwrap(); + let layer = raw_window_metal::Layer::from_ui_view(view); + Surface::from_metal(instance, layer.as_ptr(), Some(window)) } #[cfg(target_os = "windows")] diff --git a/vulkano/Cargo.toml b/vulkano/Cargo.toml index 3bb19653..328481dd 100644 --- a/vulkano/Cargo.toml +++ b/vulkano/Cargo.toml @@ -30,9 +30,8 @@ smallvec = { workspace = true } thread_local = { workspace = true } vulkano-macros = { workspace = true, optional = true } -[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -objc = { workspace = true } -core-graphics-types = { workspace = true } +[target.'cfg(target_vendor = "apple")'.dependencies] +raw-window-metal = { workspace = true } [target.'cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "hurd", target_os = "illumos", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "solaris"))'.dependencies] x11-dl = { workspace = true, optional = true } diff --git a/vulkano/build.rs b/vulkano/build.rs index aa3c5529..ee164e4a 100644 --- a/vulkano/build.rs +++ b/vulkano/build.rs @@ -4,14 +4,16 @@ mod autogen; fn main() { let target = env::var("TARGET").unwrap(); - if target.contains("apple-ios") { + if target.contains("apple-ios") || target.contains("apple-tvos") { println!("cargo:rustc-link-search=framework=/Library/Frameworks/"); println!("cargo:rustc-link-lib=c++"); println!("cargo:rustc-link-lib=framework=MoltenVK"); println!("cargo:rustc-link-lib=framework=Metal"); println!("cargo:rustc-link-lib=framework=IOSurface"); + println!("cargo:rustc-link-lib=framework=CoreGraphics"); println!("cargo:rustc-link-lib=framework=QuartzCore"); println!("cargo:rustc-link-lib=framework=UIKit"); + println!("cargo:rustc-link-lib=framework=IOKit"); println!("cargo:rustc-link-lib=framework=Foundation"); } diff --git a/vulkano/src/library.rs b/vulkano/src/library.rs index 8900b922..1cb4656e 100644 --- a/vulkano/src/library.rs +++ b/vulkano/src/library.rs @@ -40,7 +40,7 @@ pub struct VulkanLibrary { impl VulkanLibrary { /// Loads the default Vulkan library for this system. pub fn new() -> Result, LoadingError> { - #[cfg(target_os = "ios")] + #[cfg(any(target_os = "ios", target_os = "tvos"))] #[allow(non_snake_case)] fn def_loader_impl() -> Result, LoadingError> { let loader = crate::statically_linked_vulkan_loader!(); @@ -48,7 +48,7 @@ impl VulkanLibrary { Ok(Box::new(loader)) } - #[cfg(not(target_os = "ios"))] + #[cfg(not(any(target_os = "ios", target_os = "tvos")))] fn def_loader_impl() -> Result, LoadingError> { #[cfg(windows)] fn get_paths() -> [&'static Path; 1] { diff --git a/vulkano/src/swapchain/mod.rs b/vulkano/src/swapchain/mod.rs index dfbca88a..728c67e6 100644 --- a/vulkano/src/swapchain/mod.rs +++ b/vulkano/src/swapchain/mod.rs @@ -318,8 +318,6 @@ //! ``` pub use self::{acquire_present::*, surface::*}; -#[cfg(target_os = "ios")] -pub use surface::IOSMetalLayer; mod acquire_present; mod surface; diff --git a/vulkano/src/swapchain/surface.rs b/vulkano/src/swapchain/surface.rs index 5f16f24e..854cfc05 100644 --- a/vulkano/src/swapchain/surface.rs +++ b/vulkano/src/swapchain/surface.rs @@ -10,8 +10,6 @@ use crate::{ DebugWrapper, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, VulkanError, VulkanObject, }; -#[cfg(any(target_os = "macos", target_os = "ios"))] -use objc::{class, msg_send, runtime::Object, sel, sel_impl}; use raw_window_handle::{ HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, }; @@ -36,10 +34,6 @@ pub struct Surface { id: NonZeroU64, api: SurfaceApi, object: Option>, - // FIXME: This field is never set. - #[cfg(target_os = "ios")] - metal_layer: IOSMetalLayer, - // Data queried by the user at runtime, cached for faster lookups. // This is stored here rather than on `PhysicalDevice` to ensure that it's freed when the // `Surface` is destroyed. @@ -62,9 +56,8 @@ impl Surface { }; match event_loop.display_handle()?.as_raw() { RawDisplayHandle::Android(_) => extensions.khr_android_surface = true, - // FIXME: `mvk_macos_surface` and `mvk_ios_surface` are deprecated. - RawDisplayHandle::AppKit(_) => extensions.mvk_macos_surface = true, - RawDisplayHandle::UiKit(_) => extensions.mvk_ios_surface = true, + RawDisplayHandle::AppKit(_) => extensions.ext_metal_surface = true, + RawDisplayHandle::UiKit(_) => extensions.ext_metal_surface = true, RawDisplayHandle::Windows(_) => extensions.khr_win32_surface = true, RawDisplayHandle::Wayland(_) => extensions.khr_wayland_surface = true, RawDisplayHandle::Xcb(_) => extensions.khr_xcb_surface = true, @@ -107,19 +100,19 @@ impl Surface { (RawWindowHandle::AndroidNdk(window), RawDisplayHandle::Android(_display)) => { Self::from_android(instance, window.a_native_window.as_ptr().cast(), None) } - #[cfg(target_os = "macos")] - (RawWindowHandle::AppKit(window), RawDisplayHandle::AppKit(_display)) => { - // Ensure the layer is `CAMetalLayer`. - let metal_layer = get_metal_layer_macos(window.ns_view.as_ptr().cast()); + #[cfg(target_vendor = "apple")] + (RawWindowHandle::AppKit(handle), _) => { + let layer = raw_window_metal::Layer::from_ns_view(handle.ns_view); - Self::from_mac_os(instance, metal_layer.cast(), None) + // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation + Self::from_metal(instance, layer.as_ptr().as_ptr(), None) } - #[cfg(target_os = "ios")] - (RawWindowHandle::UiKit(window), RawDisplayHandle::UiKit(_display)) => { - // Ensure the layer is `CAMetalLayer`. - let metal_layer = get_metal_layer_ios(window.ui_view.as_ptr().cast()); + #[cfg(target_vendor = "apple")] + (RawWindowHandle::UiKit(handle), _) => { + let layer = raw_window_metal::Layer::from_ui_view(handle.ui_view); - Self::from_ios(instance, metal_layer.render_layer.0.cast(), None) + // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation + Self::from_metal(instance, layer.as_ptr().as_ptr(), None) } (RawWindowHandle::Wayland(window), RawDisplayHandle::Wayland(display)) => { Self::from_wayland( @@ -177,8 +170,6 @@ impl Surface { id: Self::next_id(), api, object, - #[cfg(target_os = "ios")] - metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()), surface_formats: OnceCache::new(), surface_present_modes: OnceCache::new(), surface_support: OnceCache::new(), @@ -874,13 +865,20 @@ impl Surface { ))) } - /// Creates a `Surface` from a Metal `CAMetalLayer`. + /// Create a `Surface` from a [`CAMetalLayer`]. + /// + /// If you want to create this from a `NSView` or `UIView`, it is + /// recommended that you use the `raw-window-metal` crate to get access to + /// a layer without overwriting the view's layer. + /// + /// [`CAMetalLayer`]: https://developer.apple.com/documentation/quartzcore/cametallayer /// /// # Safety /// - /// - `layer` must be a valid Metal `CAMetalLayer` handle. - /// - The object referred to by `layer` must outlive the created `Surface`. The `object` - /// parameter can be used to ensure this. + /// `layer` must point to a valid, initialized, non-NULL `CAMetalLayer`. + /// + /// The surface will retain the layer internally, so you do not need to + /// keep the source from where this came from alive. pub unsafe fn from_metal( instance: Arc, layer: *const ash::vk::CAMetalLayer, @@ -893,7 +891,7 @@ impl Surface { fn validate_from_metal( instance: &Instance, - _layer: *const ash::vk::CAMetalLayer, + layer: *const ash::vk::CAMetalLayer, ) -> Result<(), Box> { if !instance.enabled_extensions().ext_metal_surface { return Err(Box::new(ValidationError { @@ -904,6 +902,8 @@ impl Surface { })); } + assert!(!layer.is_null(), "CAMetalLayer must not be NULL"); + Ok(()) } @@ -1437,24 +1437,6 @@ impl Surface { pub fn object(&self) -> Option<&Arc> { self.object.as_ref() } - - /// 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 and 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 { @@ -1509,52 +1491,6 @@ impl Debug for Surface { impl_id_counter!(Surface); -/// Get sublayer from iOS main view (ui_view). The sublayer is created as `CAMetalLayer`. -#[cfg(target_os = "ios")] -unsafe fn get_metal_layer_ios(ui_view: *mut c_void) -> IOSMetalLayer { - use core_graphics_types::{base::CGFloat, geometry::CGRect}; - - let view: *mut Object = ui_view.cast(); - 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) -} - -/// Get (and set) `CAMetalLayer` to `ns_view`. This is necessary to be able to render on Mac. -#[cfg(target_os = "macos")] -unsafe fn get_metal_layer_macos(ns_view: *mut c_void) -> *mut Object { - use core_graphics_types::base::CGFloat; - use objc::runtime::{BOOL, NO, YES}; - - let view: *mut Object = ns_view.cast(); - 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 - } -} - /// Parameters to create a surface from a display mode and plane. #[derive(Clone, Debug)] pub struct DisplaySurfaceCreateInfo { @@ -1766,8 +1702,8 @@ impl TryFrom for SurfaceApi { fn try_from(handle: RawWindowHandle) -> Result { match handle { - RawWindowHandle::UiKit(_) => Ok(SurfaceApi::Ios), - RawWindowHandle::AppKit(_) => Ok(SurfaceApi::MacOs), + RawWindowHandle::UiKit(_) => Ok(SurfaceApi::Metal), + RawWindowHandle::AppKit(_) => Ok(SurfaceApi::Metal), RawWindowHandle::Orbital(_) => Err(()), RawWindowHandle::Xlib(_) => Ok(SurfaceApi::Xlib), RawWindowHandle::Xcb(_) => Ok(SurfaceApi::Xcb), @@ -2222,39 +2158,6 @@ impl 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 { - #[inline] - 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.