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.
This commit is contained in:
Mads Marquart 2024-09-11 18:03:54 +02:00 committed by GitHub
parent db4657d0f0
commit 0af3fb3cb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 167 additions and 276 deletions

86
Cargo.lock generated
View File

@ -196,7 +196,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68"
dependencies = [ dependencies = [
"block-sys", "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]] [[package]]
@ -928,9 +937,9 @@ version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319"
dependencies = [ dependencies = [
"block2", "block2 0.3.0",
"dispatch", "dispatch",
"objc2", "objc2 0.4.1",
] ]
[[package]] [[package]]
@ -1437,7 +1446,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d"
dependencies = [ dependencies = [
"objc-sys", "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]] [[package]]
@ -1446,6 +1465,49 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" 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]] [[package]]
name = "object" name = "object"
version = "0.35.0" version = "0.35.0"
@ -1706,6 +1768,17 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" 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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.3.5" version = "0.3.5"
@ -2353,7 +2426,6 @@ dependencies = [
"ahash", "ahash",
"ash", "ash",
"bytemuck", "bytemuck",
"core-graphics-types",
"crossbeam-queue", "crossbeam-queue",
"half", "half",
"heck", "heck",
@ -2361,12 +2433,12 @@ dependencies = [
"libc", "libc",
"libloading 0.8.3", "libloading 0.8.3",
"nom", "nom",
"objc",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"proc-macro2", "proc-macro2",
"quote", "quote",
"raw-window-handle 0.6.2", "raw-window-handle 0.6.2",
"raw-window-metal",
"serde", "serde",
"serde_json", "serde_json",
"slabbin", "slabbin",
@ -3052,7 +3124,7 @@ dependencies = [
"memmap2 0.9.4", "memmap2 0.9.4",
"ndk 0.8.0", "ndk 0.8.0",
"ndk-sys 0.5.0+25.2.9519653", "ndk-sys 0.5.0+25.2.9519653",
"objc2", "objc2 0.4.1",
"once_cell", "once_cell",
"orbclient", "orbclient",
"percent-encoding", "percent-encoding",

View File

@ -47,14 +47,12 @@ ahash = "0.8"
ash = "0.38.0" ash = "0.38.0"
bytemuck = "1.9" bytemuck = "1.9"
concurrent-slotmap = { git = "https://github.com/vulkano-rs/concurrent-slotmap", rev = "fa906d916d8d126d3cc3a2b4ab9a29fa27bee62d" } concurrent-slotmap = { git = "https://github.com/vulkano-rs/concurrent-slotmap", rev = "fa906d916d8d126d3cc3a2b4ab9a29fa27bee62d" }
core-graphics-types = "0.1"
crossbeam-queue = "0.3" crossbeam-queue = "0.3"
half = "2.0" half = "2.0"
heck = "0.4" heck = "0.4"
indexmap = "2.0" indexmap = "2.0"
libloading = "0.8" libloading = "0.8"
nom = "7.1" nom = "7.1"
objc = "0.2.5"
once_cell = "1.17" once_cell = "1.17"
parking_lot = "0.12" parking_lot = "0.12"
proc-macro2 = "1.0" proc-macro2 = "1.0"
@ -62,6 +60,7 @@ proc-macro-crate = "2.0"
quote = "1.0" quote = "1.0"
rangemap = "1.5" rangemap = "1.5"
raw-window-handle = "0.6" raw-window-handle = "0.6"
raw-window-metal = "1.0"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
shaderc = "0.8.3" shaderc = "0.8.3"

View File

@ -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 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 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 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. 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`: 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 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) 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 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.
and iOS platforms.
The easiest way to get vulkano up and running with MoltenVK is to install the 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. [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 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 iOS framework. installing it. Note that the Vulkan SDK for macOS also comes with the framework for iOS and tvOS.
## License ## License

View File

@ -120,7 +120,7 @@ impl VulkanoContext {
pub fn new(mut config: VulkanoConfig) -> Self { pub fn new(mut config: VulkanoConfig) -> Self {
let library = match VulkanLibrary::new() { let library = match VulkanLibrary::new() {
Ok(x) => x, Ok(x) => x,
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
Err(vulkano::library::LoadingError::LibraryLoadFailure(err)) => panic!( Err(vulkano::library::LoadingError::LibraryLoadFailure(err)) => panic!(
"failed to load Vulkan library: {err}; did you install VulkanSDK from \ "failed to load Vulkan library: {err}; did you install VulkanSDK from \
https://vulkan.lunarg.com/sdk/home?", https://vulkan.lunarg.com/sdk/home?",
@ -140,8 +140,7 @@ impl VulkanoContext {
khr_wayland_surface: true, khr_wayland_surface: true,
khr_android_surface: true, khr_android_surface: true,
khr_win32_surface: true, khr_win32_surface: true,
mvk_ios_surface: true, ext_metal_surface: true,
mvk_macos_surface: true,
..InstanceExtensions::empty() ..InstanceExtensions::empty()
}) })
.union(&config.instance_create_info.enabled_extensions); .union(&config.instance_create_info.enabled_extensions);

View File

@ -373,10 +373,6 @@ impl VulkanoWindowRenderer {
self.remove_additional_image_view(i); self.remove_additional_image_view(i);
self.add_additional_image_view(i, format, usage); self.add_additional_image_view(i, format, usage);
} }
#[cfg(target_os = "ios")]
unsafe {
self.surface.update_ios_sublayer_on_resize();
}
self.recreate_swapchain = false; self.recreate_swapchain = false;
} }
} }

View File

@ -2,7 +2,10 @@
name = "vulkano-win" name = "vulkano-win"
version = "0.34.0" version = "0.34.0"
edition = "2021" edition = "2021"
authors = ["Pierre Krieger <pierre.krieger1708@gmail.com>", "The vulkano contributors"] authors = [
"Pierre Krieger <pierre.krieger1708@gmail.com>",
"The vulkano contributors",
]
repository = "https://github.com/vulkano-rs/vulkano/tree/master/vulkano-win" repository = "https://github.com/vulkano-rs/vulkano/tree/master/vulkano-win"
description = "Link between vulkano and winit" description = "Link between vulkano and winit"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@ -16,8 +19,8 @@ readme = "../README.md"
default = ["winit", "raw-window-handle"] default = ["winit", "raw-window-handle"]
raw-window-handle = ["dep:raw-window-handle"] raw-window-handle = ["dep: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"]
winit_ = ["dep:winit", "dep:objc", "dep:core-graphics-types"] winit_ = ["dep:winit"]
# NOTE(Marc): The dependencies here are not workspace dependencies because vulkano-win is # NOTE(Marc): The dependencies here are not workspace dependencies because vulkano-win is
# deprecated and won't be receiving updates. # deprecated and won't be receiving updates.
@ -27,6 +30,5 @@ raw-window-handle = { version = "0.5", optional = true }
vulkano = { workspace = true } vulkano = { workspace = true }
winit = { version = "0.28", optional = true } winit = { version = "0.28", optional = true }
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] [target.'cfg(target_vendor = "apple")'.dependencies]
objc = { version = "0.2.5", optional = true } raw-window-metal.workspace = true
core-graphics-types = { version = "0.1", optional = true }

View File

@ -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::{ use raw_window_handle::{
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
}; };
@ -19,29 +15,21 @@ pub fn create_surface_from_handle(
RawWindowHandle::AndroidNdk(h) => { RawWindowHandle::AndroidNdk(h) => {
Surface::from_android(instance, h.a_native_window, Some(window)) Surface::from_android(instance, h.a_native_window, Some(window))
} }
RawWindowHandle::UiKit(_h) => { #[cfg(target_vendor = "apple")]
#[cfg(target_os = "ios")] RawWindowHandle::AppKit(handle) => {
{ let view = std::ptr::NonNull::new(handle.ns_view).unwrap();
// Ensure the layer is CAMetalLayer let layer = raw_window_metal::Layer::from_ns_view(view);
let metal_layer = get_metal_layer_ios(_h.ui_view);
Surface::from_ios(instance, metal_layer.render_layer.0 as _, Some(window)) // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation
} Surface::from_metal(instance, layer.as_ptr(), Some(window))
#[cfg(not(target_os = "ios"))]
{
panic!("UiKit handle should only be used when target_os == 'ios'");
}
} }
RawWindowHandle::AppKit(_h) => { #[cfg(target_vendor = "apple")]
#[cfg(target_os = "macos")] RawWindowHandle::UiKit(handle) => {
{ let view = std::ptr::NonNull::new(handle.ui_view).unwrap();
// Ensure the layer is CAMetalLayer let layer = raw_window_metal::Layer::from_ui_view(view);
let metal_layer = get_metal_layer_macos(_h.ns_view);
Surface::from_mac_os(instance, metal_layer as _, Some(window)) // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation
} Surface::from_metal(instance, layer.as_ptr(), Some(window))
#[cfg(not(target_os = "macos"))]
{
panic!("AppKit handle should only be used when target_os == 'macos'");
}
} }
RawWindowHandle::Wayland(h) => { RawWindowHandle::Wayland(h) => {
let d = match window.raw_display_handle() { let d = match window.raw_display_handle() {
@ -88,29 +76,21 @@ pub unsafe fn create_surface_from_handle_ref(
RawWindowHandle::AndroidNdk(h) => { RawWindowHandle::AndroidNdk(h) => {
Surface::from_android(instance, h.a_native_window, None) Surface::from_android(instance, h.a_native_window, None)
} }
RawWindowHandle::UiKit(_h) => { #[cfg(target_vendor = "apple")]
#[cfg(target_os = "ios")] (RawWindowHandle::AppKit(handle), _) => {
{ let view = std::ptr::NonNull::new(handle.ns_view).unwrap();
// Ensure the layer is CAMetalLayer let layer = raw_window_metal::Layer::from_ns_view(view);
let metal_layer = get_metal_layer_ios(_h.ui_view);
Surface::from_ios(instance, metal_layer.render_layer.0 as _, None) // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation
} Surface::from_metal(instance, layer.as_ptr(), None)
#[cfg(not(target_os = "ios"))]
{
panic!("UiKit handle should only be used when target_os == 'ios'");
}
} }
RawWindowHandle::AppKit(_h) => { #[cfg(target_vendor = "apple")]
#[cfg(target_os = "macos")] (RawWindowHandle::UiKit(handle), _) => {
{ let view = std::ptr::NonNull::new(handle.ui_view).unwrap();
// Ensure the layer is CAMetalLayer let layer = raw_window_metal::Layer::from_ui_view(view);
let metal_layer = get_metal_layer_macos(_h.ns_view);
Surface::from_mac_os(instance, metal_layer as _, None) // Vulkan retains the CAMetalLayer, so no need to retain it past this invocation
} Surface::from_metal(instance, layer.as_ptr(), None)
#[cfg(not(target_os = "macos"))]
{
panic!("AppKit handle should only be used when target_os == 'macos'");
}
} }
RawWindowHandle::Wayland(h) => { RawWindowHandle::Wayland(h) => {
let d = match window.raw_display_handle() { let d = match window.raw_display_handle() {

View File

@ -22,8 +22,7 @@ pub fn required_extensions(library: &VulkanLibrary) -> InstanceExtensions {
khr_wayland_surface: true, khr_wayland_surface: true,
khr_android_surface: true, khr_android_surface: true,
khr_win32_surface: true, khr_win32_surface: true,
mvk_ios_surface: true, ext_metal_surface: true,
mvk_macos_surface: true,
khr_get_physical_device_properties2: true, khr_get_physical_device_properties2: true,
khr_get_surface_capabilities2: true, khr_get_surface_capabilities2: true,
..InstanceExtensions::empty() ..InstanceExtensions::empty()
@ -119,12 +118,7 @@ unsafe fn winit_to_surface(
} }
} }
#[cfg(all( #[cfg(all(unix, not(target_os = "android"), target_vendor = "apple",))]
unix,
not(target_os = "android"),
not(target_os = "macos"),
not(target_os = "ios")
))]
unsafe fn winit_to_surface( unsafe fn winit_to_surface(
instance: Arc<Instance>, instance: Arc<Instance>,
window: Arc<Window>, window: Arc<Window>,
@ -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")] #[cfg(target_os = "macos")]
unsafe fn winit_to_surface( unsafe fn winit_to_surface(
instance: Arc<Instance>, instance: Arc<Instance>,
window: Arc<Window>, window: Arc<Window>,
) -> Result<Arc<Surface>, Validated<VulkanError>> { ) -> Result<Arc<Surface>, Validated<VulkanError>> {
use winit::platform::macos::WindowExtMacOS; use winit::platform::macos::WindowExtMacOS;
let metal_layer = get_metal_layer_macos(window.ns_view()); let view = std::ptr::NonNull::new(window.ns_view()).unwrap();
Surface::from_mac_os(instance, metal_layer as _, Some(window)) let layer = raw_window_metal::Layer::from_ns_view(view);
} Surface::from_metal(instance, layer.as_ptr(), 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)
} }
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
@ -227,8 +168,9 @@ unsafe fn winit_to_surface(
window: Arc<Window>, window: Arc<Window>,
) -> Result<Arc<Surface>, Validated<VulkanError>> { ) -> Result<Arc<Surface>, Validated<VulkanError>> {
use winit::platform::ios::WindowExtIOS; use winit::platform::ios::WindowExtIOS;
let metal_layer = get_metal_layer_ios(window.ui_view()); let view = std::ptr::NonNull::new(window.ui_view()).unwrap();
Surface::from_ios(instance, metal_layer.render_layer.0 as _, Some(window)) let layer = raw_window_metal::Layer::from_ui_view(view);
Surface::from_metal(instance, layer.as_ptr(), Some(window))
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]

View File

@ -30,9 +30,8 @@ smallvec = { workspace = true }
thread_local = { workspace = true } thread_local = { workspace = true }
vulkano-macros = { workspace = true, optional = true } vulkano-macros = { workspace = true, optional = true }
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] [target.'cfg(target_vendor = "apple")'.dependencies]
objc = { workspace = true } raw-window-metal = { workspace = true }
core-graphics-types = { 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] [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 } x11-dl = { workspace = true, optional = true }

View File

@ -4,14 +4,16 @@ mod autogen;
fn main() { fn main() {
let target = env::var("TARGET").unwrap(); 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-search=framework=/Library/Frameworks/");
println!("cargo:rustc-link-lib=c++"); println!("cargo:rustc-link-lib=c++");
println!("cargo:rustc-link-lib=framework=MoltenVK"); println!("cargo:rustc-link-lib=framework=MoltenVK");
println!("cargo:rustc-link-lib=framework=Metal"); println!("cargo:rustc-link-lib=framework=Metal");
println!("cargo:rustc-link-lib=framework=IOSurface"); 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=QuartzCore");
println!("cargo:rustc-link-lib=framework=UIKit"); println!("cargo:rustc-link-lib=framework=UIKit");
println!("cargo:rustc-link-lib=framework=IOKit");
println!("cargo:rustc-link-lib=framework=Foundation"); println!("cargo:rustc-link-lib=framework=Foundation");
} }

View File

@ -40,7 +40,7 @@ pub struct VulkanLibrary {
impl VulkanLibrary { impl VulkanLibrary {
/// Loads the default Vulkan library for this system. /// Loads the default Vulkan library for this system.
pub fn new() -> Result<Arc<Self>, LoadingError> { pub fn new() -> Result<Arc<Self>, LoadingError> {
#[cfg(target_os = "ios")] #[cfg(any(target_os = "ios", target_os = "tvos"))]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn def_loader_impl() -> Result<Box<dyn Loader>, LoadingError> { fn def_loader_impl() -> Result<Box<dyn Loader>, LoadingError> {
let loader = crate::statically_linked_vulkan_loader!(); let loader = crate::statically_linked_vulkan_loader!();
@ -48,7 +48,7 @@ impl VulkanLibrary {
Ok(Box::new(loader)) Ok(Box::new(loader))
} }
#[cfg(not(target_os = "ios"))] #[cfg(not(any(target_os = "ios", target_os = "tvos")))]
fn def_loader_impl() -> Result<Box<dyn Loader>, LoadingError> { fn def_loader_impl() -> Result<Box<dyn Loader>, LoadingError> {
#[cfg(windows)] #[cfg(windows)]
fn get_paths() -> [&'static Path; 1] { fn get_paths() -> [&'static Path; 1] {

View File

@ -318,8 +318,6 @@
//! ``` //! ```
pub use self::{acquire_present::*, surface::*}; pub use self::{acquire_present::*, surface::*};
#[cfg(target_os = "ios")]
pub use surface::IOSMetalLayer;
mod acquire_present; mod acquire_present;
mod surface; mod surface;

View File

@ -10,8 +10,6 @@ use crate::{
DebugWrapper, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, VulkanError, DebugWrapper, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, VulkanError,
VulkanObject, VulkanObject,
}; };
#[cfg(any(target_os = "macos", target_os = "ios"))]
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
use raw_window_handle::{ use raw_window_handle::{
HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle,
}; };
@ -36,10 +34,6 @@ pub struct Surface {
id: NonZeroU64, id: NonZeroU64,
api: SurfaceApi, api: SurfaceApi,
object: Option<Arc<dyn Any + Send + Sync>>, object: Option<Arc<dyn Any + Send + Sync>>,
// FIXME: This field is never set.
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer,
// Data queried by the user at runtime, cached for faster lookups. // 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 // This is stored here rather than on `PhysicalDevice` to ensure that it's freed when the
// `Surface` is destroyed. // `Surface` is destroyed.
@ -62,9 +56,8 @@ impl Surface {
}; };
match event_loop.display_handle()?.as_raw() { match event_loop.display_handle()?.as_raw() {
RawDisplayHandle::Android(_) => extensions.khr_android_surface = true, RawDisplayHandle::Android(_) => extensions.khr_android_surface = true,
// FIXME: `mvk_macos_surface` and `mvk_ios_surface` are deprecated. RawDisplayHandle::AppKit(_) => extensions.ext_metal_surface = true,
RawDisplayHandle::AppKit(_) => extensions.mvk_macos_surface = true, RawDisplayHandle::UiKit(_) => extensions.ext_metal_surface = true,
RawDisplayHandle::UiKit(_) => extensions.mvk_ios_surface = true,
RawDisplayHandle::Windows(_) => extensions.khr_win32_surface = true, RawDisplayHandle::Windows(_) => extensions.khr_win32_surface = true,
RawDisplayHandle::Wayland(_) => extensions.khr_wayland_surface = true, RawDisplayHandle::Wayland(_) => extensions.khr_wayland_surface = true,
RawDisplayHandle::Xcb(_) => extensions.khr_xcb_surface = true, RawDisplayHandle::Xcb(_) => extensions.khr_xcb_surface = true,
@ -107,19 +100,19 @@ impl Surface {
(RawWindowHandle::AndroidNdk(window), RawDisplayHandle::Android(_display)) => { (RawWindowHandle::AndroidNdk(window), RawDisplayHandle::Android(_display)) => {
Self::from_android(instance, window.a_native_window.as_ptr().cast(), None) Self::from_android(instance, window.a_native_window.as_ptr().cast(), None)
} }
#[cfg(target_os = "macos")] #[cfg(target_vendor = "apple")]
(RawWindowHandle::AppKit(window), RawDisplayHandle::AppKit(_display)) => { (RawWindowHandle::AppKit(handle), _) => {
// Ensure the layer is `CAMetalLayer`. let layer = raw_window_metal::Layer::from_ns_view(handle.ns_view);
let metal_layer = get_metal_layer_macos(window.ns_view.as_ptr().cast());
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")] #[cfg(target_vendor = "apple")]
(RawWindowHandle::UiKit(window), RawDisplayHandle::UiKit(_display)) => { (RawWindowHandle::UiKit(handle), _) => {
// Ensure the layer is `CAMetalLayer`. let layer = raw_window_metal::Layer::from_ui_view(handle.ui_view);
let metal_layer = get_metal_layer_ios(window.ui_view.as_ptr().cast());
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)) => { (RawWindowHandle::Wayland(window), RawDisplayHandle::Wayland(display)) => {
Self::from_wayland( Self::from_wayland(
@ -177,8 +170,6 @@ impl Surface {
id: Self::next_id(), id: Self::next_id(),
api, api,
object, object,
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
surface_formats: OnceCache::new(), surface_formats: OnceCache::new(),
surface_present_modes: OnceCache::new(), surface_present_modes: OnceCache::new(),
surface_support: 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 /// # Safety
/// ///
/// - `layer` must be a valid Metal `CAMetalLayer` handle. /// `layer` must point to a valid, initialized, non-NULL `CAMetalLayer`.
/// - 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( pub unsafe fn from_metal(
instance: Arc<Instance>, instance: Arc<Instance>,
layer: *const ash::vk::CAMetalLayer, layer: *const ash::vk::CAMetalLayer,
@ -893,7 +891,7 @@ impl Surface {
fn validate_from_metal( fn validate_from_metal(
instance: &Instance, instance: &Instance,
_layer: *const ash::vk::CAMetalLayer, layer: *const ash::vk::CAMetalLayer,
) -> Result<(), Box<ValidationError>> { ) -> Result<(), Box<ValidationError>> {
if !instance.enabled_extensions().ext_metal_surface { if !instance.enabled_extensions().ext_metal_surface {
return Err(Box::new(ValidationError { return Err(Box::new(ValidationError {
@ -904,6 +902,8 @@ impl Surface {
})); }));
} }
assert!(!layer.is_null(), "CAMetalLayer must not be NULL");
Ok(()) Ok(())
} }
@ -1437,24 +1437,6 @@ impl Surface {
pub fn object(&self) -> Option<&Arc<dyn Any + Send + Sync>> { pub fn object(&self) -> Option<&Arc<dyn Any + Send + Sync>> {
self.object.as_ref() 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 { impl Drop for Surface {
@ -1509,52 +1491,6 @@ impl Debug for Surface {
impl_id_counter!(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. /// Parameters to create a surface from a display mode and plane.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DisplaySurfaceCreateInfo { pub struct DisplaySurfaceCreateInfo {
@ -1766,8 +1702,8 @@ impl TryFrom<RawWindowHandle> for SurfaceApi {
fn try_from(handle: RawWindowHandle) -> Result<Self, Self::Error> { fn try_from(handle: RawWindowHandle) -> Result<Self, Self::Error> {
match handle { match handle {
RawWindowHandle::UiKit(_) => Ok(SurfaceApi::Ios), RawWindowHandle::UiKit(_) => Ok(SurfaceApi::Metal),
RawWindowHandle::AppKit(_) => Ok(SurfaceApi::MacOs), RawWindowHandle::AppKit(_) => Ok(SurfaceApi::Metal),
RawWindowHandle::Orbital(_) => Err(()), RawWindowHandle::Orbital(_) => Err(()),
RawWindowHandle::Xlib(_) => Ok(SurfaceApi::Xlib), RawWindowHandle::Xlib(_) => Ok(SurfaceApi::Xlib),
RawWindowHandle::Xcb(_) => Ok(SurfaceApi::Xcb), 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. /// The capabilities of a surface when used by a physical device.
/// ///
/// You have to match these capabilities when you create a swapchain. /// You have to match these capabilities when you create a swapchain.