From 470d258614771fdb8141f525fceca03e66ec71a7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 8 Sep 2024 04:44:14 +0200 Subject: [PATCH] Use raw-window-metal to get a CAMetalLayer from raw-window-handle 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, and I've removed support for `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. --- CHANGELOG.md | 18 +- COVERAGE.md | 2 - Cargo.lock | 84 +++++++- 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 | 303 +++------------------------ 15 files changed, 174 insertions(+), 437 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4f5ffaf2d..33d3944c7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ not put changes into changelog files directly, they lead to frequent merging conflicts. Instead put incoming changelog entries into the Pull Request description. They will be transferred to this file right after the - Pull Request merge. + Pull Request merge. --> ### Public dependency updates @@ -56,6 +56,9 @@ Changes to descriptor set allocation: Changes to `Surface`: - `Surface::required_extensions` now returns a result. - `Surface::from_window[_ref]` now take `HasWindowHandle + HasDisplayHandle` as the window and return a new error type. +- `Surface::from_ios` and the corresponding `SurfaceApi::Ios` was removed, use `Surface::from_metal` instead. +- `Surface::from_mac_os` and the corresponding `SurfaceApi::MacOs` was removed, use `Surface::from_metal` instead. +- `Surface::update_ios_sublayer_on_resize` was removed as it is no longer necessary. Changes to surface creation and support functions: - Where handles to foreign window system objects are passed, Vulkano no longer takes a generic pointer, but takes the same pointer type that Ash does. @@ -144,6 +147,7 @@ Other: - Vulkano-shaders: Support for Vulkan 1.3 target environment. - Vulkano-shaders: Added `generate_structs: true` option that may be used to disable rust structs from generating. Useful in e.g. rust-gpu contexts where such functionality is not needed. - Vulkano-util: `VulkanoWindowsRenderer::swapchain_image_views` allows access to the swapchain images. +- Add support for tvOS. ### Bugs fixed @@ -164,6 +168,8 @@ Other: - Fixed non-default image view usage being ignored. - Fixed an off-by-one error in `SubpassDescription::validate`. - Vulkano-shaders: Fixed shader struct names that are invalid rust idents from panicking the shader! macro. Rust-gpu emitted struct names such as `foo::bar::MyStruct` now work. +- Make resizing smooth, and let it interoperate better with windowing libraries. +- Fix compiling on iOS. # Version 0.34.1 (2023-10-29) @@ -507,7 +513,7 @@ Changes to vulkano-shaders: ### Breaking changes Changes to queue operations: -- To do operations on a queue, you must now call `with` to gain access. This takes a closure that is passed a +- To do operations on a queue, you must now call `with` to gain access. This takes a closure that is passed a - The `wait` method of devices and queues is renamed to `wait_idle` to match Vulkan. - `Queue` now implements `VulkanObject` instead of `SynchronizedVulkanObject`, which is removed. - `Queue` now takes ownership of resources belonging to operations that you execute on it, to keep them from being destroyed while in use. @@ -855,7 +861,7 @@ Miscellaneous: - Fixed bug in `begin_render_pass` causing a panic when clearing a depth-only attachment. - Fixed bug in the `QueueFamily::supports_` methods causing a panic when querying support for a stage that needs no queue flags. - Fixed buffer overflow bug in `AutoCommandBufferBuilder::push_constants`. -- Fixed `AutoCommandBufferBuilder::push_constants` to push multiple times in case of range overlap (to accommodate VUIDs 01795 and 01796) +- Fixed `AutoCommandBufferBuilder::push_constants` to push multiple times in case of range overlap (to accommodate VUIDs 01795 and 01796) - Fixed `shader!` macro failing to compile with geometry shaders. - Refactored `VertexBuffersCollection` to allow `Arc`. - Added a `Format::texels_per_block` method.\ @@ -1049,7 +1055,7 @@ Miscellaneous: - The dynamic buffers parameter of `GraphicsPipelineBuilder::with_auto_layout` has been replaced with a closure that can be used to make tweaks to the descriptor set layouts as needed. - `ComputePipeline::new` has an additional closure parameter identical to the one described above. - **Breaking** `AttachmentImage::dimensions()` now returns `[u32; 3]` which includes the layer count. -- **Breaking** Buffers and Images that have `with_exportable_fd` use dedicated allocation, thus requiring khr_get_memory_requirements2 and khr_dedicated_allocation on top of +- **Breaking** Buffers and Images that have `with_exportable_fd` use dedicated allocation, thus requiring khr_get_memory_requirements2 and khr_dedicated_allocation on top of already needed khr_external_memory and khr_external_memory_fd. - **Breaking** `Compare` is renamed to `CompareOp` to match Vulkan. - **Breaking** Vulkano-shaders no longer generates a `Shader` struct, but instead provides `load` as a standalone function that returns `Arc` directly. @@ -1146,7 +1152,7 @@ already needed khr_external_memory and khr_external_memory_fd. - Errors checking(by unwrapping) in `MappedDeviceMemory::read_write`. - Add creation of Semaphores with exportable Linux file descriptor on. - Add method to export file descriptor corresponding to Semaphore. -- `SemaphoreBuilder` introduced. +- `SemaphoreBuilder` introduced. - Add DisplayNative enum variant to ColorSpaceEnum (AMD-specific feature). - Vulkano-shaders now provides the image format for descriptors, if the shader requires a specific format. - Vulkano-shaders now uses the `spirv_headers` crate for some of its types. @@ -1209,7 +1215,7 @@ already needed khr_external_memory and khr_external_memory_fd. - Updated winit to 0.25. - Fixed the teapot example on ArchLinux (GTX 1650). - Added support for the SPIR-V draw parameters capability. -- Added support for the VK_KHR_multiview extension. +- Added support for the VK_KHR_multiview extension. - Vulkano-shaders: Added support for MultiView SPIR-V capability. - Multiview example added showing how to utilize the VK_KHR_multiview extension to render to multiple layers of a framebuffer at once. - All Vulkan extensions supported by Ash are now provided in `InstanceExtensions` and `DeviceExtensions`. This includes all but the very newest extensions; new extensions should be added whenever Ash is updated to a new version. diff --git a/COVERAGE.md b/COVERAGE.md index 4cd841a20c..8b0fad2c13 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -182,8 +182,6 @@ Coverage of support for Vulkan core features and extensions in Vulkano, as of th - [`VK_FUCHSIA_external_semaphore`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_FUCHSIA_external_semaphore.html) - [`VK_FUCHSIA_imagepipe_surface`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_FUCHSIA_imagepipe_surface.html) - [`VK_GGP_stream_descriptor_surface`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_GGP_stream_descriptor_surface.html) -- [`VK_MVK_ios_surface`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_FUCHSIA_imagepipe_surface.html) (deprecated) -- [`VK_MVK_macos_surface`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_MVK_macos_surface.html) (deprecated) - [`VK_QNX_screen_surface`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_QNX_screen_surface.html) ### Partially supported diff --git a/Cargo.lock b/Cargo.lock index 60d44b8131..d6a280bb53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,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]] @@ -927,9 +936,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]] @@ -1436,7 +1445,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]] @@ -1445,6 +1464,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" @@ -1711,6 +1773,15 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "0.4.0" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation", + "objc2-quartz-core", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -2358,7 +2429,6 @@ dependencies = [ "ahash", "ash", "bytemuck", - "core-graphics-types", "crossbeam-queue", "half", "heck", @@ -2366,12 +2436,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", @@ -3055,7 +3125,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 1667baccce..94db41bb2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,14 +43,12 @@ ahash = "0.8" ash = "0.38.0" bytemuck = "1.9" concurrent-slotmap = { git = "https://github.com/vulkano-rs/concurrent-slotmap", rev = "bf52f0a55713bb29dde3e38bc3497b03473d1628" } -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" @@ -58,6 +56,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 11245455d1..b4b3957c80 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 41be85bb39..6cdfd46919 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 1b188e1be2..ae71363b06 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 c3bb8d3564..efb4eef794 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 15d346da16..ed07eaf507 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 da3754bd2b..0503ffdeab 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 8c59d7b088..1393bac6ef 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 } [build-dependencies] ahash = { workspace = true } diff --git a/vulkano/build.rs b/vulkano/build.rs index aa3c5529b6..ee164e4a81 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 8900b922d1..1cb4656ec2 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 dfbca88a36..728c67e6d5 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 5f16f24e32..cf14059c60 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 from_metal = 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, from_metal.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 from_metal = 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, from_metal.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(), @@ -726,161 +717,20 @@ impl Surface { ))) } - /// Creates a `Surface` from an iOS `UIView`. + /// Create a `Surface` from a [`CAMetalLayer`]. /// - /// # Safety + /// 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. /// - /// - `metal_layer` must be a valid `IOSMetalLayer` handle. - /// - The object referred to by `metal_layer` must outlive the created `Surface`. The `object` - /// parameter can be used to ensure this. - /// - The `UIView` must be backed by a `CALayer` instance of type `CAMetalLayer`. - pub unsafe fn from_ios( - instance: Arc, - view: *const c_void, - object: Option>, - ) -> Result, Validated> { - Self::validate_from_ios(&instance, view)?; - - Ok(Self::from_ios_unchecked(instance, view, object)?) - } - - fn validate_from_ios( - instance: &Instance, - _view: *const c_void, - ) -> Result<(), Box> { - if !instance.enabled_extensions().mvk_ios_surface { - return Err(Box::new(ValidationError { - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::InstanceExtension( - "mvk_ios_surface", - )])]), - ..Default::default() - })); - } - - // VUID-VkIOSSurfaceCreateInfoMVK-pView-04143 - // Can't validate, therefore unsafe - - // VUID-VkIOSSurfaceCreateInfoMVK-pView-01316 - // Can't validate, therefore unsafe - - Ok(()) - } - - #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] - pub unsafe fn from_ios_unchecked( - instance: Arc, - view: *const c_void, - object: Option>, - ) -> Result, VulkanError> { - let create_info = ash::vk::IOSSurfaceCreateInfoMVK { - flags: ash::vk::IOSSurfaceCreateFlagsMVK::empty(), - p_view: view, - ..Default::default() - }; - - let handle = { - let fns = instance.fns(); - let mut output = MaybeUninit::uninit(); - (fns.mvk_ios_surface.create_ios_surface_mvk)( - instance.handle(), - &create_info, - ptr::null(), - output.as_mut_ptr(), - ) - .result() - .map_err(VulkanError::from)?; - output.assume_init() - }; - - Ok(Arc::new(Self::from_handle( - instance, - handle, - SurfaceApi::Ios, - object, - ))) - } - - /// Creates a `Surface` from a MacOS `NSView`. + /// [`CAMetalLayer`]: https://developer.apple.com/documentation/quartzcore/cametallayer /// /// # Safety /// - /// - `view` must be a valid `CAMetalLayer` or `NSView` handle. - /// - The object referred to by `view` must outlive the created `Surface`. The `object` - /// parameter can be used to ensure this. - /// - The `NSView` must be backed by a `CALayer` instance of type `CAMetalLayer`. - pub unsafe fn from_mac_os( - instance: Arc, - view: *const c_void, - object: Option>, - ) -> Result, Validated> { - Self::validate_from_mac_os(&instance, view)?; - - Ok(Self::from_mac_os_unchecked(instance, view, object)?) - } - - fn validate_from_mac_os( - instance: &Instance, - _view: *const c_void, - ) -> Result<(), Box> { - if !instance.enabled_extensions().mvk_macos_surface { - return Err(Box::new(ValidationError { - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::InstanceExtension( - "mvk_macos_surface", - )])]), - ..Default::default() - })); - } - - // VUID-VkMacOSSurfaceCreateInfoMVK-pView-04144 - // Can't validate, therefore unsafe - - // VUID-VkMacOSSurfaceCreateInfoMVK-pView-01317 - // Can't validate, therefore unsafe - - Ok(()) - } - - #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] - pub unsafe fn from_mac_os_unchecked( - instance: Arc, - view: *const c_void, - object: Option>, - ) -> Result, VulkanError> { - let create_info = ash::vk::MacOSSurfaceCreateInfoMVK { - flags: ash::vk::MacOSSurfaceCreateFlagsMVK::empty(), - p_view: view, - ..Default::default() - }; - - let handle = { - let fns = instance.fns(); - let mut output = MaybeUninit::uninit(); - (fns.mvk_macos_surface.create_mac_os_surface_mvk)( - instance.handle(), - &create_info, - ptr::null(), - output.as_mut_ptr(), - ) - .result() - .map_err(VulkanError::from)?; - output.assume_init() - }; - - Ok(Arc::new(Self::from_handle( - instance, - handle, - SurfaceApi::MacOs, - object, - ))) - } - - /// Creates a `Surface` from a Metal `CAMetalLayer`. - /// - /// # Safety + /// `layer` must point to a valid, initialized, non-NULL `CAMetalLayer`. /// - /// - `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. + /// 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 +743,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 +754,8 @@ impl Surface { })); } + assert!(!layer.is_null(), "CAMetalLayer must not be NULL"); + Ok(()) } @@ -1437,24 +1289,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 +1343,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 { @@ -1724,14 +1512,6 @@ pub enum SurfaceApi { /// `vkCreateStreamDescriptorSurfaceGGP` Vulkan API function. GgpStreamDescriptor, - /// The surface was constructed using [`Surface::from_ios`] or the - /// `vkCreateIOSSurfaceMVK` Vulkan API function. - Ios, - - /// The surface was constructed using [`Surface::from_mac_os`] or the - /// `vkCreateMacOSSurfaceMVK` Vulkan API function. - MacOs, - /// The surface was constructed using [`Surface::from_metal`] or the /// `vkCreateMetalSurfaceEXT` Vulkan API function. Metal, @@ -1766,8 +1546,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 +2002,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.