diff --git a/.deny.toml b/.deny.toml
index d5c2534f64..a547102a82 100644
--- a/.deny.toml
+++ b/.deny.toml
@@ -7,6 +7,9 @@ skip-tree = [
# introduced by Deno, to be investigated
{ name = "petgraph", version = "0.6.5" },
+
+ # Winit 0.30 uses an older objc2
+ { name = "objc2-foundation", version = "0.2" },
]
skip = [
# Flume uses an old version
diff --git a/Cargo.lock b/Cargo.lock
index 5ac6df3d5d..44b0e001d5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1253,6 +1253,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+[[package]]
+name = "dispatch2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
+dependencies = [
+ "bitflags 2.9.4",
+ "objc2 0.6.3",
+]
+
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -2808,6 +2818,15 @@ dependencies = [
"objc2-encode 4.1.0",
]
+[[package]]
+name = "objc2"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
+dependencies = [
+ "objc2-encode 4.1.0",
+]
+
[[package]]
name = "objc2-app-kit"
version = "0.2.2"
@@ -2820,8 +2839,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]]
@@ -2834,7 +2853,7 @@ dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-core-location",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
]
[[package]]
@@ -2845,7 +2864,7 @@ checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
]
[[package]]
@@ -2857,7 +2876,18 @@ dependencies = [
"bitflags 2.9.4",
"block2 0.5.1",
"objc2 0.5.2",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.9.4",
+ "dispatch2",
+ "objc2 0.6.3",
]
[[package]]
@@ -2868,8 +2898,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]]
@@ -2881,7 +2911,7 @@ dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-contacts",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
]
[[package]]
@@ -2909,6 +2939,17 @@ dependencies = [
"objc2 0.5.2",
]
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+dependencies = [
+ "bitflags 2.9.4",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+]
+
[[package]]
name = "objc2-link-presentation"
version = "0.2.2"
@@ -2918,7 +2959,7 @@ dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-app-kit",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
]
[[package]]
@@ -2930,7 +2971,18 @@ dependencies = [
"bitflags 2.9.4",
"block2 0.5.1",
"objc2 0.5.2",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-metal"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
+dependencies = [
+ "bitflags 2.9.4",
+ "objc2 0.6.3",
+ "objc2-foundation 0.3.2",
]
[[package]]
@@ -2942,8 +2994,21 @@ dependencies = [
"bitflags 2.9.4",
"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.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
+dependencies = [
+ "bitflags 2.9.4",
+ "objc2 0.6.3",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "objc2-metal 0.3.2",
]
[[package]]
@@ -2953,7 +3018,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]]
@@ -2969,9 +3034,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",
@@ -2985,7 +3050,7 @@ checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
]
[[package]]
@@ -2998,7 +3063,7 @@ dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-core-location",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
]
[[package]]
@@ -3437,6 +3502,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.3",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "objc2-quartz-core 0.3.2",
+]
+
[[package]]
name = "rayon"
version = "1.11.0"
@@ -4991,6 +5068,7 @@ dependencies = [
"range-alloc",
"raw-window-handle 0.5.2",
"raw-window-handle 0.6.2",
+ "raw-window-metal",
"renderdoc-sys",
"rustc-hash 1.1.0",
"smallvec",
@@ -5699,7 +5777,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 d78e742c29..69733523f4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -202,6 +202,7 @@ metal = "0.32"
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/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml
index 3b8c37c861..edf110c511 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.
#
@@ -83,6 +83,7 @@ metal = [
"dep:objc",
"dep:parking_lot",
"dep:profiling",
+ "dep:raw-window-metal",
]
vulkan = [
"naga/spv-out",
@@ -99,6 +100,7 @@ vulkan = [
"dep:ordered-float",
"dep:parking_lot",
"dep:profiling",
+ "dep:raw-window-metal",
"dep:smallvec",
"dep:windows",
"windows/Win32",
@@ -281,6 +283,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 00223b2f77..1cbac13c58 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;
@@ -33,10 +32,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};
@@ -106,7 +106,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)
}
}
@@ -125,19 +125,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 2a705cd790..04a328894d 100644
--- a/wgpu-hal/src/metal/surface.rs
+++ b/wgpu-hal/src/metal/surface.rs
@@ -1,29 +1,22 @@
#![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 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),
@@ -32,81 +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