diff --git a/.deny.toml b/.deny.toml index f307fd0334..c7e93e3df1 100644 --- a/.deny.toml +++ b/.deny.toml @@ -13,6 +13,9 @@ skip-tree = [ { name = "bit-set", version = "0.5.3" }, { name = "bit-vec", version = "0.6.3" }, { name = "capacity_builder", version = "0.1.3" }, + + # Winit 0.30 uses an older objc2 + { name = "objc2-foundation", version = "0.2" }, ] skip = [ # criterion uses an old version diff --git a/Cargo.lock b/Cargo.lock index 423ba2da46..62a8604858 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2723,6 +2723,15 @@ dependencies = [ "objc2-encode 4.1.0", ] +[[package]] +name = "objc2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" +dependencies = [ + "objc2-encode 4.1.0", +] + [[package]] name = "objc2-app-kit" version = "0.2.2" @@ -2735,8 +2744,8 @@ dependencies = [ "objc2 0.5.2", "objc2-core-data", "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -2749,7 +2758,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2760,7 +2769,7 @@ checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2772,7 +2781,17 @@ dependencies = [ "bitflags 2.9.0", "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", ] [[package]] @@ -2783,8 +2802,8 @@ checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", - "objc2-metal", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", ] [[package]] @@ -2796,7 +2815,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-contacts", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2824,6 +2843,17 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "objc2-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -2833,7 +2863,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2845,7 +2875,18 @@ dependencies = [ "bitflags 2.9.0", "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c41bc8b0e50ea7a5304a56f25e0066f526e99641b46fd7b9ad4421dd35bff6" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-foundation 0.3.0", ] [[package]] @@ -2857,8 +2898,21 @@ dependencies = [ "bitflags 2.9.0", "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", - "objc2-metal", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", + "objc2-metal 0.3.0", ] [[package]] @@ -2868,7 +2922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2884,9 +2938,9 @@ dependencies = [ "objc2-core-data", "objc2-core-image", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", @@ -2900,7 +2954,7 @@ checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2913,7 +2967,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -3340,6 +3394,18 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", + "objc2-quartz-core 0.3.0", +] + [[package]] name = "rayon" version = "1.10.0" @@ -4868,6 +4934,7 @@ dependencies = [ "range-alloc", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", + "raw-window-metal", "renderdoc-sys", "rustc-hash", "smallvec", @@ -5352,7 +5419,7 @@ dependencies = [ "ndk 0.9.0", "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index cb62dcadef..d663d6281e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -162,6 +162,7 @@ metal = "0.32.0" block = "0.1.6" core-graphics-types = "0.2" objc = "0.2.5" +raw-window-metal = "1.0" # Vulkan dependencies android_system_properties = "0.1.1" diff --git a/examples/features/src/framework.rs b/examples/features/src/framework.rs index d52c3b84ef..28d33cd5d7 100644 --- a/examples/features/src/framework.rs +++ b/examples/features/src/framework.rs @@ -391,8 +391,6 @@ async fn start(title: &str) { &context.device, &context.queue, ); - - window_loop.window.request_redraw(); } WindowEvent::KeyboardInput { event: diff --git a/examples/features/src/hello_triangle/mod.rs b/examples/features/src/hello_triangle/mod.rs index 89ef9864f5..7d8e6e24d4 100644 --- a/examples/features/src/hello_triangle/mod.rs +++ b/examples/features/src/hello_triangle/mod.rs @@ -98,8 +98,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/features/src/hello_windows/mod.rs b/examples/features/src/hello_windows/mod.rs index 9885d24022..b52fb8db43 100644 --- a/examples/features/src/hello_windows/mod.rs +++ b/examples/features/src/hello_windows/mod.rs @@ -98,8 +98,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/features/src/uniform_values/mod.rs b/examples/features/src/uniform_values/mod.rs index 3ee8676725..c9b945c318 100644 --- a/examples/features/src/uniform_values/mod.rs +++ b/examples/features/src/uniform_values/mod.rs @@ -210,7 +210,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(); } } @@ -278,7 +277,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 65b767d612..592c37ab4a 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -59,7 +59,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ # exclude the Vulkan backend on MacOS unless a separate feature `vulkan-portability` is enabled. In response # to these features, it enables features of platform specific crates. For example, the `vulkan` feature in wgpu-core # enables the `vulkan` feature in `wgpu-core-deps-windows-linux-android` which in turn enables the -# `vulkan` feature in `wgpu-hal` _only_ on those platforms. If you enable the `vulkan-portability` feature, it +# `vulkan` feature in `wgpu-hal` _only_ on those platforms. If you enable the `vulkan-portability` feature, it # will enable the `vulkan` feature in `wgpu-core-deps-apple`. The only way to do this is unfortunately to have # a separate crate for each platform category that participates in the feature unification. # @@ -82,6 +82,7 @@ metal = [ "dep:metal", "dep:objc", "dep:profiling", + "dep:raw-window-metal", ] vulkan = [ "naga/spv-out", @@ -97,6 +98,7 @@ vulkan = [ "dep:log", "dep:ordered-float", "dep:profiling", + "dep:raw-window-metal", "dep:smallvec", "dep:windows", "windows/Win32", @@ -274,6 +276,9 @@ core-graphics-types = { workspace = true, optional = true } metal = { workspace = true, optional = true } objc = { workspace = true, optional = true } +# backend: Metal + Vulkan +raw-window-metal = { workspace = true, optional = true } + ######################### ### Platform: Android ### ######################### diff --git a/wgpu-hal/src/metal/layer_observer.rs b/wgpu-hal/src/metal/layer_observer.rs deleted file mode 100644 index 8acd83b531..0000000000 --- a/wgpu-hal/src/metal/layer_observer.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! A rewrite of `raw-window-metal` using `objc` instead of `objc2`. -//! -//! See that for details: -//! -//! This should be temporary, see . - -use core::ffi::{c_void, CStr}; -use core_graphics_types::base::CGFloat; -use core_graphics_types::geometry::CGRect; -use objc::declare::ClassDecl; -use objc::rc::StrongPtr; -use objc::runtime::{Class, Object, Sel, BOOL, NO}; -use objc::{class, msg_send, sel, sel_impl}; -use std::sync::OnceLock; - -#[link(name = "Foundation", kind = "framework")] -extern "C" { - static NSKeyValueChangeNewKey: &'static Object; -} - -#[allow(non_upper_case_globals)] -const NSKeyValueObservingOptionNew: usize = 0x01; -#[allow(non_upper_case_globals)] -const NSKeyValueObservingOptionInitial: usize = 0x04; - -const CONTENTS_SCALE: &CStr = c"contentsScale"; -const BOUNDS: &CStr = c"bounds"; - -/// Create a new custom layer that tracks parameters from the given super layer. -/// -/// Same as . -pub unsafe fn new_observer_layer(root_layer: *mut Object) -> StrongPtr { - let this: *mut Object = unsafe { msg_send![class(), new] }; - - // Add the layer as a sublayer of the root layer. - let _: () = unsafe { msg_send![root_layer, addSublayer: this] }; - - // Register for key-value observing. - let key_path: *const Object = - unsafe { msg_send![class!(NSString), stringWithUTF8String: CONTENTS_SCALE.as_ptr()] }; - let _: () = unsafe { - msg_send![ - root_layer, - addObserver: this - forKeyPath: key_path - options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial - context: context_ptr() - ] - }; - - let key_path: *const Object = - unsafe { msg_send![class!(NSString), stringWithUTF8String: BOUNDS.as_ptr()] }; - let _: () = unsafe { - msg_send![ - root_layer, - addObserver: this - forKeyPath: key_path - options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial - context: context_ptr() - ] - }; - - // Uncomment when debugging resize issues. - // extern "C" { - // static kCAGravityTopLeft: *mut Object; - // } - // let _: () = unsafe { msg_send![this, setContentsGravity: kCAGravityTopLeft] }; - - unsafe { StrongPtr::new(this) } -} - -/// Same as . -fn class() -> &'static Class { - static CLASS: OnceLock<&'static Class> = OnceLock::new(); - - CLASS.get_or_init(|| { - let superclass = class!(CAMetalLayer); - let class_name = format!("WgpuObserverLayer@{:p}", &CLASS); - let mut decl = ClassDecl::new(&class_name, superclass).unwrap(); - - // From NSKeyValueObserving. - let sel = sel!(observeValueForKeyPath:ofObject:change:context:); - let method: extern "C" fn( - &Object, - Sel, - *mut Object, - *mut Object, - *mut Object, - *mut c_void, - ) = observe_value; - unsafe { decl.add_method(sel, method) }; - - let sel = sel!(dealloc); - let method: extern "C" fn(&Object, Sel) = dealloc; - unsafe { decl.add_method(sel, method) }; - - decl.register() - }) -} - -/// The unique context pointer for this class. -fn context_ptr() -> *mut c_void { - let ptr: *const Class = class(); - ptr.cast_mut().cast() -} - -/// Same as . -extern "C" fn observe_value( - this: &Object, - _cmd: Sel, - key_path: *mut Object, - object: *mut Object, - change: *mut Object, - context: *mut c_void, -) { - // An unrecognized context must belong to the super class. - if context != context_ptr() { - // SAFETY: The signature is correct, and it's safe to forward to - // the superclass' method when we're overriding the method. - return unsafe { - msg_send![ - super(this, class!(CAMetalLayer)), - observeValueForKeyPath: key_path - ofObject: object - change: change - context: context - ] - }; - } - - assert!(!change.is_null()); - - let key = unsafe { NSKeyValueChangeNewKey }; - let new: *mut Object = unsafe { msg_send![change, objectForKey: key] }; - assert!(!new.is_null()); - - let to_compare: *const Object = - unsafe { msg_send![class!(NSString), stringWithUTF8String: CONTENTS_SCALE.as_ptr()] }; - let is_equal: BOOL = unsafe { msg_send![key_path, isEqual: to_compare] }; - if is_equal != NO { - // `contentsScale` is a CGFloat, and so the observed value is always a NSNumber. - let scale_factor: CGFloat = if cfg!(target_pointer_width = "64") { - unsafe { msg_send![new, doubleValue] } - } else { - unsafe { msg_send![new, floatValue] } - }; - - // Set the scale factor of the layer to match the root layer. - let _: () = unsafe { msg_send![this, setContentsScale: scale_factor] }; - return; - } - - let to_compare: *const Object = - unsafe { msg_send![class!(NSString), stringWithUTF8String: BOUNDS.as_ptr()] }; - let is_equal: BOOL = unsafe { msg_send![key_path, isEqual: to_compare] }; - if is_equal != NO { - // `bounds` is a CGRect, and so the observed value is always a NSNumber. - let bounds: CGRect = unsafe { msg_send![new, rectValue] }; - - // Set `bounds` and `position` to match the root layer. - // - // This differs from just setting the `bounds`, as it also takes into account any - // translation that the superlayer may have that we'd want to preserve. - let _: () = unsafe { msg_send![this, setFrame: bounds] }; - return; - } - - panic!("unknown observed keypath {key_path:?}"); -} - -extern "C" fn dealloc(this: &Object, _cmd: Sel) { - // Load the root layer if it still exists, and deregister the observer. - // - // This is not entirely sound, as the ObserverLayer _could_ have been - // moved to another layer; but Wgpu doesn't do that, so it should be fine. - // - // `raw-window-metal` uses a weak instance variable to do it correctly: - // https://docs.rs/raw-window-metal/1.1.0/src/raw_window_metal/observer.rs.html#74-132 - // (but that's difficult to do with `objc`). - let root_layer: *mut Object = unsafe { msg_send![this, superlayer] }; - if !root_layer.is_null() { - let key_path: *const Object = - unsafe { msg_send![class!(NSString), stringWithUTF8String: CONTENTS_SCALE.as_ptr()] }; - let _: () = unsafe { msg_send![root_layer, removeObserver: this forKeyPath: key_path] }; - - let key_path: *const Object = - unsafe { msg_send![class!(NSString), stringWithUTF8String: BOUNDS.as_ptr()] }; - let _: () = unsafe { msg_send![root_layer, removeObserver: this forKeyPath: key_path] }; - } -} diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index da63cb1cc4..c2dd00c9bc 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -22,7 +22,6 @@ mod adapter; mod command; mod conv; mod device; -mod layer_observer; mod surface; mod time; @@ -34,10 +33,11 @@ use arrayvec::ArrayVec; use bitflags::bitflags; use hashbrown::HashMap; use metal::{ - foreign_types::ForeignTypeRef as _, MTLArgumentBuffersTier, MTLBuffer, MTLCommandBufferStatus, - MTLCullMode, MTLDepthClipMode, MTLIndexType, MTLLanguageVersion, MTLPrimitiveType, - MTLReadWriteTextureTier, MTLRenderStages, MTLResource, MTLResourceUsage, MTLSamplerState, - MTLSize, MTLTexture, MTLTextureType, MTLTriangleFillMode, MTLWinding, + foreign_types::{ForeignType as _, ForeignTypeRef as _}, + MTLArgumentBuffersTier, MTLBuffer, MTLCommandBufferStatus, MTLCullMode, MTLDepthClipMode, + MTLIndexType, MTLLanguageVersion, MTLPrimitiveType, MTLReadWriteTextureTier, MTLRenderStages, + MTLResource, MTLResourceUsage, MTLSamplerState, MTLSize, MTLTexture, MTLTextureType, + MTLTriangleFillMode, MTLWinding, }; use naga::FastHashMap; use parking_lot::{Mutex, RwLock}; @@ -105,7 +105,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) } } @@ -124,19 +124,25 @@ impl crate::Instance for Instance { _display_handle: raw_window_handle::RawDisplayHandle, window_handle: raw_window_handle::RawWindowHandle, ) -> Result { - match window_handle { - #[cfg(any(target_os = "ios", target_os = "visionos"))] - 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 0d3175c0d9..38e292d5a8 100644 --- a/wgpu-hal/src/metal/surface.rs +++ b/wgpu-hal/src/metal/surface.rs @@ -1,30 +1,23 @@ #![allow(clippy::let_unit_value)] // `let () =` being used to constrain result type use alloc::borrow::ToOwned as _; -use core::mem::ManuallyDrop; -use core::ptr::NonNull; use std::thread; use core_graphics_types::{ base::CGFloat, geometry::{CGRect, CGSize}, }; -use metal::{foreign_types::ForeignType, MTLTextureType}; +use metal::MTLTextureType; use objc::{ class, msg_send, - rc::{autoreleasepool, StrongPtr}, - runtime::{Object, BOOL, NO, YES}, + rc::autoreleasepool, + runtime::{BOOL, YES}, sel, sel_impl, }; use parking_lot::{Mutex, RwLock}; -use crate::metal::layer_observer::new_observer_layer; - -#[link(name = "QuartzCore", kind = "framework")] -extern "C" {} - 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), @@ -34,81 +27,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, - // we do the same as outlined in: - // https://docs.rs/raw-window-metal/1.1.0/raw_window_metal/#reasoning-behind-creating-a-sublayer - unsafe { new_observer_layer(root_layer) } - } - } - pub(super) fn dimensions(&self) -> wgt::Extent3d { let (size, scale): (CGSize, CGFloat) = unsafe { let render_layer_borrow = self.render_layer.lock(); diff --git a/wgpu-hal/src/vulkan/instance.rs b/wgpu-hal/src/vulkan/instance.rs index b734b689ff..070f4f1ce9 100644 --- a/wgpu-hal/src/vulkan/instance.rs +++ b/wgpu-hal/src/vulkan/instance.rs @@ -539,10 +539,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: core::ptr::NonNull, + layer: raw_window_metal::Layer, ) -> Result { if !self.shared.extensions.contains(&ext::metal_surface::NAME) { return Err(crate::InstanceError::new(String::from( @@ -550,17 +550,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() } }; @@ -904,17 +901,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(any(target_os = "ios", target_os = "visionos"), 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"