diff --git a/Cargo.lock b/Cargo.lock index 60a21c2230..b8f98787cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -359,7 +359,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]] @@ -1333,7 +1342,7 @@ dependencies = [ "glutin_wgl_sys 0.5.0", "icrate", "libloading", - "objc2", + "objc2 0.4.1", "once_cell", "raw-window-handle 0.5.2", "wayland-sys", @@ -1520,9 +1529,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]] @@ -2098,7 +2107,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]] @@ -2107,6 +2126,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.6.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.6.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.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "object" version = "0.36.4" @@ -2460,6 +2522,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 = "rayon" version = "1.10.0" @@ -3699,6 +3772,7 @@ dependencies = [ "range-alloc", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", + "raw-window-metal", "renderdoc-sys", "rustc-hash", "smallvec", @@ -4124,7 +4198,7 @@ dependencies = [ "memmap2", "ndk", "ndk-sys", - "objc2", + "objc2 0.4.1", "once_cell", "orbclient", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index 08bfffe816..1b741f40b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -138,6 +138,7 @@ block = "0.1" core-graphics-types = "0.1" metal = { version = "0.29.0" } objc = "0.2.5" +raw-window-metal = "1.0" # Vulkan dependencies android_system_properties = "0.1.1" diff --git a/examples/src/framework.rs b/examples/src/framework.rs index ff86cc2357..c4d4cf4a9f 100644 --- a/examples/src/framework.rs +++ b/examples/src/framework.rs @@ -422,8 +422,6 @@ async fn start(title: &str) { &context.device, &context.queue, ); - - window_loop.window.request_redraw(); } WindowEvent::KeyboardInput { event: diff --git a/examples/src/hello_triangle/mod.rs b/examples/src/hello_triangle/mod.rs index 7c82d49cf0..6d425555bf 100644 --- a/examples/src/hello_triangle/mod.rs +++ b/examples/src/hello_triangle/mod.rs @@ -81,7 +81,6 @@ async fn run(event_loop: EventLoop<()>, window: Window) { .unwrap(); surface.configure(&device, &config); - let window = &window; event_loop .run(move |event, target| { // Have the closure take ownership of the resources. @@ -100,8 +99,6 @@ async fn run(event_loop: EventLoop<()>, window: Window) { config.width = new_size.width.max(1); config.height = new_size.height.max(1); surface.configure(&device, &config); - // On macos the window needs to be redrawn manually after resizing - window.request_redraw(); } WindowEvent::RedrawRequested => { let frame = surface diff --git a/examples/src/hello_windows/mod.rs b/examples/src/hello_windows/mod.rs index b568f35d38..349d0d5f25 100644 --- a/examples/src/hello_windows/mod.rs +++ b/examples/src/hello_windows/mod.rs @@ -100,8 +100,6 @@ async fn run(event_loop: EventLoop<()>, viewports: Vec<(Arc, wgpu::Color // Recreate the swap chain with the new size if let Some(viewport) = viewports.get_mut(&window_id) { viewport.resize(&device, new_size); - // On macos the window needs to be redrawn manually after resizing - viewport.desc.window.request_redraw(); } } WindowEvent::RedrawRequested => { diff --git a/examples/src/uniform_values/mod.rs b/examples/src/uniform_values/mod.rs index 1ef58de09a..1e671056b0 100644 --- a/examples/src/uniform_values/mod.rs +++ b/examples/src/uniform_values/mod.rs @@ -217,7 +217,6 @@ impl WgpuContext { self.surface_config.width = new_size.width; self.surface_config.height = new_size.height; self.surface.configure(&self.device, &self.surface_config); - self.window.request_redraw(); } } @@ -285,7 +284,6 @@ async fn run(event_loop: EventLoop<()>, window: Arc) { WindowEvent::Resized(new_size) => { let wgpu_context_mut = wgpu_context.as_mut().unwrap(); wgpu_context_mut.resize(new_size); - wgpu_context_mut.window.request_redraw(); } WindowEvent::RedrawRequested => { let wgpu_context_ref = wgpu_context.as_ref().unwrap(); diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index d3a05f0790..49b0ce0dea 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -44,6 +44,10 @@ metal = [ # Metal is only available on Apple platforms, therefore request MSL output also only if we target an Apple platform. "naga/msl-out-if-target-apple", "dep:block", + "dep:core-graphics-types", + "dep:metal", + "dep:objc", + "dep:raw-window-metal", ] vulkan = [ "naga/spv-out", @@ -51,6 +55,7 @@ vulkan = [ "dep:gpu-alloc", "dep:gpu-descriptor", "dep:libloading", + "dep:raw-window-metal", "dep:smallvec", "dep:android_system_properties", ] @@ -61,6 +66,7 @@ gles = [ "dep:khronos-egl", "dep:libloading", "dep:ndk-sys", + "dep:objc", "windows/Win32_Graphics_OpenGL", "windows/Win32_Graphics_Gdi", "windows/Win32_System_LibraryLoader", @@ -165,10 +171,14 @@ glutin_wgl_sys = { workspace = true, optional = true } [target.'cfg(any(target_os="macos", target_os="ios"))'.dependencies] # backend: Metal block = { workspace = true, optional = true } +metal = { workspace = true, optional = true } +core-graphics-types = { workspace = true, optional = true } -metal.workspace = true -objc.workspace = true -core-graphics-types.workspace = true +# backend: Metal + GLES +objc = { workspace = true, optional = true } + +# backend: Metal + Vulkan +raw-window-metal = { workspace = true, optional = true } [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies] wasm-bindgen.workspace = true diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 1935e843ec..63dde743b0 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -34,7 +34,7 @@ use std::{ use arrayvec::ArrayVec; use bitflags::bitflags; -use metal::foreign_types::ForeignTypeRef as _; +use metal::foreign_types::{ForeignType as _, ForeignTypeRef as _}; use parking_lot::{Mutex, RwLock}; #[derive(Clone, Debug)] @@ -100,7 +100,7 @@ pub struct Instance {} impl Instance { pub fn create_surface_from_layer(&self, layer: &metal::MetalLayerRef) -> Surface { - unsafe { Surface::from_layer(layer) } + Surface::from_layer(layer) } } @@ -119,19 +119,25 @@ impl crate::Instance for Instance { _display_handle: raw_window_handle::RawDisplayHandle, window_handle: raw_window_handle::RawWindowHandle, ) -> Result { - match window_handle { - #[cfg(target_os = "ios")] - raw_window_handle::RawWindowHandle::UiKit(handle) => { - Ok(unsafe { Surface::from_view(handle.ui_view.cast()) }) + let layer = match window_handle { + raw_window_handle::RawWindowHandle::AppKit(handle) => unsafe { + raw_window_metal::Layer::from_ns_view(handle.ns_view) + }, + raw_window_handle::RawWindowHandle::UiKit(handle) => unsafe { + raw_window_metal::Layer::from_ui_view(handle.ui_view) + }, + _ => { + return Err(crate::InstanceError::new(format!( + "window handle {window_handle:?} is not a Metal-compatible handle" + ))) } - #[cfg(target_os = "macos")] - raw_window_handle::RawWindowHandle::AppKit(handle) => { - Ok(unsafe { Surface::from_view(handle.ns_view.cast()) }) - } - _ => Err(crate::InstanceError::new(format!( - "window handle {window_handle:?} is not a Metal-compatible handle" - ))), - } + }; + + // SAFETY: The layer is an initialized instance of `CAMetalLayer`, and + // we transfer the retain count to `MetalLayer` using `into_raw`. + let layer = unsafe { metal::MetalLayer::from_ptr(layer.into_raw().cast().as_ptr()) }; + + Ok(Surface::new(layer)) } unsafe fn enumerate_adapters( diff --git a/wgpu-hal/src/metal/surface.rs b/wgpu-hal/src/metal/surface.rs index 668b602474..4f34814412 100644 --- a/wgpu-hal/src/metal/surface.rs +++ b/wgpu-hal/src/metal/surface.rs @@ -1,69 +1,21 @@ #![allow(clippy::let_unit_value)] // `let () =` being used to constrain result type -use std::ffi::c_uint; -use std::mem::ManuallyDrop; -use std::ptr::NonNull; -use std::sync::Once; use std::thread; use core_graphics_types::{ base::CGFloat, geometry::{CGRect, CGSize}, }; -use metal::foreign_types::ForeignType; use objc::{ - class, - declare::ClassDecl, - msg_send, - rc::{autoreleasepool, StrongPtr}, - runtime::{Class, Object, Sel, BOOL, NO, YES}, + class, msg_send, + rc::autoreleasepool, + runtime::{BOOL, YES}, sel, sel_impl, }; use parking_lot::{Mutex, RwLock}; -#[link(name = "QuartzCore", kind = "framework")] -extern "C" { - #[allow(non_upper_case_globals)] - static kCAGravityResize: *mut Object; -} - -extern "C" fn layer_should_inherit_contents_scale_from_window( - _: &Class, - _: Sel, - _layer: *mut Object, - _new_scale: CGFloat, - _from_window: *mut Object, -) -> BOOL { - YES -} - -static CAML_DELEGATE_REGISTER: Once = Once::new(); - -#[derive(Debug)] -pub struct HalManagedMetalLayerDelegate(&'static Class); - -impl HalManagedMetalLayerDelegate { - pub fn new() -> Self { - let class_name = format!("HalManagedMetalLayerDelegate@{:p}", &CAML_DELEGATE_REGISTER); - - CAML_DELEGATE_REGISTER.call_once(|| { - type Fun = extern "C" fn(&Class, Sel, *mut Object, CGFloat, *mut Object) -> BOOL; - let mut decl = ClassDecl::new(&class_name, class!(NSObject)).unwrap(); - unsafe { - // - decl.add_class_method::( - sel!(layer:shouldInheritContentsScale:fromWindow:), - layer_should_inherit_contents_scale_from_window, - ); - } - decl.register(); - }); - Self(Class::get(&class_name).unwrap()) - } -} - impl super::Surface { - fn new(layer: metal::MetalLayer) -> Self { + pub fn new(layer: metal::MetalLayer) -> Self { Self { render_layer: Mutex::new(layer), swapchain_format: RwLock::new(None), @@ -73,188 +25,13 @@ impl super::Surface { } } - /// If not called on the main thread, this will panic. - #[allow(clippy::transmute_ptr_to_ref)] - pub unsafe fn from_view(view: NonNull) -> Self { - let layer = unsafe { Self::get_metal_layer(view) }; - let layer = ManuallyDrop::new(layer); - // SAFETY: The layer is an initialized instance of `CAMetalLayer`, and - // we transfer the retain count to `MetalLayer` using `ManuallyDrop`. - let layer = unsafe { metal::MetalLayer::from_ptr(layer.cast()) }; - Self::new(layer) - } - - pub unsafe fn from_layer(layer: &metal::MetalLayerRef) -> Self { + pub fn from_layer(layer: &metal::MetalLayerRef) -> Self { let class = class!(CAMetalLayer); - let proper_kind: BOOL = msg_send![layer, isKindOfClass: class]; + let proper_kind: BOOL = unsafe { msg_send![layer, isKindOfClass: class] }; assert_eq!(proper_kind, YES); Self::new(layer.to_owned()) } - /// Get or create a new `CAMetalLayer` associated with the given `NSView` - /// or `UIView`. - /// - /// # Panics - /// - /// If called from a thread that is not the main thread, this will panic. - /// - /// # Safety - /// - /// The `view` must be a valid instance of `NSView` or `UIView`. - pub(crate) unsafe fn get_metal_layer(view: NonNull) -> StrongPtr { - let is_main_thread: BOOL = msg_send![class!(NSThread), isMainThread]; - if is_main_thread == NO { - panic!("get_metal_layer cannot be called in non-ui thread."); - } - - // Ensure that the view is layer-backed. - // Views are always layer-backed in UIKit. - #[cfg(target_os = "macos")] - let () = msg_send![view.as_ptr(), setWantsLayer: YES]; - - let root_layer: *mut Object = msg_send![view.as_ptr(), layer]; - // `-[NSView layer]` can return `NULL`, while `-[UIView layer]` should - // always be available. - assert!(!root_layer.is_null(), "failed making the view layer-backed"); - - // NOTE: We explicitly do not touch properties such as - // `layerContentsPlacement`, `needsDisplayOnBoundsChange` and - // `contentsGravity` etc. on the root layer, both since we would like - // to give the user full control over them, and because the default - // values suit us pretty well (especially the contents placement being - // `NSViewLayerContentsRedrawDuringViewResize`, which allows the view - // to receive `drawRect:`/`updateLayer` calls). - - let is_metal_layer: BOOL = msg_send![root_layer, isKindOfClass: class!(CAMetalLayer)]; - if is_metal_layer == YES { - // The view has a `CAMetalLayer` as the root layer, which can - // happen for example if user overwrote `-[NSView layerClass]` or - // the view is `MTKView`. - // - // This is easily handled: We take "ownership" over the layer, and - // render directly into that; after all, the user passed a view - // with an explicit Metal layer to us, so this is very likely what - // they expect us to do. - unsafe { StrongPtr::retain(root_layer) } - } else { - // The view does not have a `CAMetalLayer` as the root layer (this - // is the default for most views). - // - // This case is trickier! We cannot use the existing layer with - // Metal, so we must do something else. There are a few options: - // - // 1. Panic here, and require the user to pass a view with a - // `CAMetalLayer` layer. - // - // While this would "work", it doesn't solve the problem, and - // instead passes the ball onwards to the user and ecosystem to - // figure it out. - // - // 2. Override the existing layer with a newly created layer. - // - // If we overlook that this does not work in UIKit since - // `UIView`'s `layer` is `readonly`, and that as such we will - // need to do something different there anyhow, this is - // actually a fairly good solution, and was what the original - // implementation did. - // - // It has some problems though, due to: - // - // a. `wgpu` in our API design choosing not to register a - // callback with `-[CALayerDelegate displayLayer:]`, but - // instead leaves it up to the user to figure out when to - // redraw. That is, we rely on other libraries' callbacks - // telling us when to render. - // - // (If this were an API only for Metal, we would probably - // make the user provide a `render` closure that we'd call - // in the right situations. But alas, we have to be - // cross-platform here). - // - // b. Overwriting the `layer` on `NSView` makes the view - // "layer-hosting", see [wantsLayer], which disables drawing - // functionality on the view like `drawRect:`/`updateLayer`. - // - // These two in combination makes it basically impossible for - // crates like Winit to provide a robust rendering callback - // that integrates with the system's built-in mechanisms for - // redrawing, exactly because overwriting the layer would be - // implicitly disabling those mechanisms! - // - // [wantsLayer]: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer?language=objc - // - // 3. Create a sublayer. - // - // `CALayer` has the concept of "sublayers", which we can use - // instead of overriding the layer. - // - // This is also the recommended solution on UIKit, so it's nice - // that we can use (almost) the same implementation for these. - // - // It _might_, however, perform ever so slightly worse than - // overriding the layer directly. - // - // 4. Create a new `MTKView` (or a custom view), and add it as a - // subview. - // - // Similar to creating a sublayer (see above), but also - // provides a bunch of event handling that we don't need. - // - // Option 3 seems like the most robust solution, so this is what - // we're going to do. - - // Create a new sublayer. - let new_layer: *mut Object = msg_send![class!(CAMetalLayer), new]; - let () = msg_send![root_layer, addSublayer: new_layer]; - - // Automatically resize the sublayer's frame to match the - // superlayer's bounds. - // - // Note that there is a somewhat hidden design decision in this: - // We define the `width` and `height` in `configure` to control - // the `drawableSize` of the layer, while `bounds` and `frame` are - // outside of the user's direct control - instead, though, they - // can control the size of the view (or root layer), and get the - // desired effect that way. - // - // We _could_ also let `configure` set the `bounds` size, however - // that would be inconsistent with using the root layer directly - // (as we may do, see above). - let width_sizable = 1 << 1; // kCALayerWidthSizable - let height_sizable = 1 << 4; // kCALayerHeightSizable - let mask: c_uint = width_sizable | height_sizable; - let () = msg_send![new_layer, setAutoresizingMask: mask]; - - // Specify the relative size that the auto resizing mask above - // will keep (i.e. tell it to fill out its superlayer). - let frame: CGRect = msg_send![root_layer, bounds]; - let () = msg_send![new_layer, setFrame: frame]; - - // The gravity to use when the layer's `drawableSize` isn't the - // same as the bounds rectangle. - // - // The desired content gravity is `kCAGravityResize`, because it - // masks / alleviates issues with resizing when - // `present_with_transaction` is disabled, and behaves better when - // moving the window between monitors. - // - // Unfortunately, it also makes it harder to see changes to - // `width` and `height` in `configure`. When debugging resize - // issues, swap this for `kCAGravityTopLeft` instead. - let _: () = msg_send![new_layer, setContentsGravity: unsafe { kCAGravityResize }]; - - // Set initial scale factor of the layer. This is kept in sync by - // `configure` (on UIKit), and the delegate below (on AppKit). - let scale_factor: CGFloat = msg_send![root_layer, contentsScale]; - let () = msg_send![new_layer, setContentsScale: scale_factor]; - - let delegate = HalManagedMetalLayerDelegate::new(); - let () = msg_send![new_layer, setDelegate: delegate.0]; - - unsafe { StrongPtr::new(new_layer) } - } - } - pub(super) fn dimensions(&self) -> wgt::Extent3d { let (size, scale): (CGSize, CGFloat) = unsafe { let render_layer_borrow = self.render_layer.lock(); @@ -301,29 +78,6 @@ impl crate::Surface for super::Surface { _ => (), } - // AppKit / UIKit automatically sets the correct scale factor for - // layers attached to a view. Our layer, however, may not be directly - // attached to a view; in those cases, we need to set the scale - // factor ourselves. - // - // For AppKit, we do so by adding a delegate on the layer with the - // `layer:shouldInheritContentsScale:fromWindow:` method returning - // `true` - this tells the system to automatically update the scale - // factor when it changes. - // - // For UIKit, we manually update the scale factor from the super layer - // here, if there is one. - // - // TODO: Is there a way that we could listen to such changes instead? - #[cfg(not(target_os = "macos"))] - { - let superlayer: *mut Object = msg_send![render_layer.as_ptr(), superlayer]; - if !superlayer.is_null() { - let scale_factor: CGFloat = msg_send![superlayer, contentsScale]; - let () = msg_send![render_layer.as_ptr(), setContentsScale: scale_factor]; - } - } - let device_raw = device.shared.device.lock(); render_layer.set_device(&device_raw); render_layer.set_pixel_format(caps.map_format(config.format)); diff --git a/wgpu-hal/src/vulkan/instance.rs b/wgpu-hal/src/vulkan/instance.rs index 5673859e45..1721a92eb4 100644 --- a/wgpu-hal/src/vulkan/instance.rs +++ b/wgpu-hal/src/vulkan/instance.rs @@ -511,10 +511,10 @@ impl super::Instance { Ok(self.create_surface_from_vk_surface_khr(surface)) } - #[cfg(metal)] - fn create_surface_from_view( + #[cfg(target_vendor = "apple")] + fn create_surface_from_layer( &self, - view: std::ptr::NonNull, + layer: raw_window_metal::Layer, ) -> Result { if !self.shared.extensions.contains(&ext::metal_surface::NAME) { return Err(crate::InstanceError::new(String::from( @@ -522,17 +522,14 @@ impl super::Instance { ))); } - let layer = unsafe { crate::metal::Surface::get_metal_layer(view.cast()) }; // NOTE: The layer is retained by Vulkan's `vkCreateMetalSurfaceEXT`, // so no need to retain it beyond the scope of this function. - let layer_ptr = (*layer).cast(); - let surface = { let metal_loader = ext::metal_surface::Instance::new(&self.shared.entry, &self.shared.raw); let vk_info = vk::MetalSurfaceCreateInfoEXT::default() .flags(vk::MetalSurfaceCreateFlagsEXT::empty()) - .layer(layer_ptr); + .layer(layer.as_ptr().as_ptr()); unsafe { metal_loader.create_metal_surface(&vk_info, None).unwrap() } }; @@ -870,17 +867,19 @@ impl crate::Instance for super::Instance { })?; self.create_surface_from_hwnd(hinstance.get(), handle.hwnd.get()) } - #[cfg(all(target_os = "macos", feature = "metal"))] + #[cfg(target_vendor = "apple")] (Rwh::AppKit(handle), _) if self.shared.extensions.contains(&ext::metal_surface::NAME) => { - self.create_surface_from_view(handle.ns_view) + let layer = unsafe { raw_window_metal::Layer::from_ns_view(handle.ns_view) }; + self.create_surface_from_layer(layer) } - #[cfg(all(target_os = "ios", feature = "metal"))] + #[cfg(target_vendor = "apple")] (Rwh::UiKit(handle), _) if self.shared.extensions.contains(&ext::metal_surface::NAME) => { - self.create_surface_from_view(handle.ui_view) + let layer = unsafe { raw_window_metal::Layer::from_ui_view(handle.ui_view) }; + self.create_surface_from_layer(layer) } (_, _) => Err(crate::InstanceError::new(format!( "window handle {window_handle:?} is not a Vulkan-compatible handle"