Skip to content

Commit 05e419b

Browse files
mockersfmadsmtmcwfitzgerald
authored
Add layer observer based on raw-window-metal (#7026) (#7466)
Co-authored-by: Mads Marquart <[email protected]> Co-authored-by: Connor Fitzgerald <[email protected]>
1 parent 8a38f5f commit 05e419b

File tree

4 files changed

+203
-181
lines changed

4 files changed

+203
-181
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ Bottom level categories:
4040

4141
## Unreleased
4242

43+
#### Metal
44+
- Use resize observers for smoother resizing. By @madsmtm in [#7026](https://github.com/gfx-rs/wgpu/pull/7026).
45+
4346
### v24.0.3 (2025-03-19)
4447

4548
#### Bug Fixes

wgpu-hal/src/metal/layer_observer.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
//! A rewrite of `raw-window-metal` using `objc` instead of `objc2`.
2+
//!
3+
//! See that for details: <https://docs.rs/raw-window-metal/1.1.0/>
4+
//!
5+
//! This should be temporary, see <https://github.com/gfx-rs/wgpu/pull/6210>.
6+
7+
use core::ffi::{c_void, CStr};
8+
use core_graphics_types::base::CGFloat;
9+
use core_graphics_types::geometry::CGRect;
10+
use objc::declare::ClassDecl;
11+
use objc::rc::StrongPtr;
12+
use objc::runtime::{Class, Object, Sel, BOOL, NO};
13+
use objc::{class, msg_send, sel, sel_impl};
14+
use std::sync::OnceLock;
15+
16+
#[link(name = "Foundation", kind = "framework")]
17+
extern "C" {
18+
static NSKeyValueChangeNewKey: &'static Object;
19+
}
20+
21+
#[allow(non_upper_case_globals)]
22+
const NSKeyValueObservingOptionNew: usize = 0x01;
23+
#[allow(non_upper_case_globals)]
24+
const NSKeyValueObservingOptionInitial: usize = 0x04;
25+
26+
const CONTENTS_SCALE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"contentsScale\0") };
27+
const BOUNDS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"bounds\0") };
28+
29+
/// Create a new custom layer that tracks parameters from the given super layer.
30+
///
31+
/// Same as <https://docs.rs/raw-window-metal/1.1.0/src/raw_window_metal/observer.rs.html#74-132>.
32+
pub unsafe fn new_observer_layer(root_layer: *mut Object) -> StrongPtr {
33+
let this: *mut Object = unsafe { msg_send![class(), new] };
34+
35+
// Add the layer as a sublayer of the root layer.
36+
let _: () = unsafe { msg_send![root_layer, addSublayer: this] };
37+
38+
// Register for key-value observing.
39+
let key_path: *const Object =
40+
unsafe { msg_send![class!(NSString), stringWithUTF8String: CONTENTS_SCALE.as_ptr()] };
41+
let _: () = unsafe {
42+
msg_send![
43+
root_layer,
44+
addObserver: this
45+
forKeyPath: key_path
46+
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
47+
context: context_ptr()
48+
]
49+
};
50+
51+
let key_path: *const Object =
52+
unsafe { msg_send![class!(NSString), stringWithUTF8String: BOUNDS.as_ptr()] };
53+
let _: () = unsafe {
54+
msg_send![
55+
root_layer,
56+
addObserver: this
57+
forKeyPath: key_path
58+
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
59+
context: context_ptr()
60+
]
61+
};
62+
63+
// Uncomment when debugging resize issues.
64+
// extern "C" {
65+
// static kCAGravityTopLeft: *mut Object;
66+
// }
67+
// let _: () = unsafe { msg_send![this, setContentsGravity: kCAGravityTopLeft] };
68+
69+
unsafe { StrongPtr::new(this) }
70+
}
71+
72+
/// Same as <https://docs.rs/raw-window-metal/1.1.0/src/raw_window_metal/observer.rs.html#74-132>.
73+
fn class() -> &'static Class {
74+
static CLASS: OnceLock<&'static Class> = OnceLock::new();
75+
76+
CLASS.get_or_init(|| {
77+
let superclass = class!(CAMetalLayer);
78+
let class_name = format!("WgpuObserverLayer@{:p}", &CLASS);
79+
let mut decl = ClassDecl::new(&class_name, superclass).unwrap();
80+
81+
// From NSKeyValueObserving.
82+
let sel = sel!(observeValueForKeyPath:ofObject:change:context:);
83+
let method: extern "C" fn(
84+
&Object,
85+
Sel,
86+
*mut Object,
87+
*mut Object,
88+
*mut Object,
89+
*mut c_void,
90+
) = observe_value;
91+
unsafe { decl.add_method(sel, method) };
92+
93+
let sel = sel!(dealloc);
94+
let method: extern "C" fn(&Object, Sel) = dealloc;
95+
unsafe { decl.add_method(sel, method) };
96+
97+
decl.register()
98+
})
99+
}
100+
101+
/// The unique context pointer for this class.
102+
fn context_ptr() -> *mut c_void {
103+
let ptr: *const Class = class();
104+
ptr.cast_mut().cast()
105+
}
106+
107+
/// Same as <https://docs.rs/raw-window-metal/1.1.0/src/raw_window_metal/observer.rs.html#74-132>.
108+
extern "C" fn observe_value(
109+
this: &Object,
110+
_cmd: Sel,
111+
key_path: *mut Object,
112+
object: *mut Object,
113+
change: *mut Object,
114+
context: *mut c_void,
115+
) {
116+
// An unrecognized context must belong to the super class.
117+
if context != context_ptr() {
118+
// SAFETY: The signature is correct, and it's safe to forward to
119+
// the superclass' method when we're overriding the method.
120+
return unsafe {
121+
msg_send![
122+
super(this, class!(CAMetalLayer)),
123+
observeValueForKeyPath: key_path
124+
ofObject: object
125+
change: change
126+
context: context
127+
]
128+
};
129+
}
130+
131+
assert!(!change.is_null());
132+
133+
let key = unsafe { NSKeyValueChangeNewKey };
134+
let new: *mut Object = unsafe { msg_send![change, objectForKey: key] };
135+
assert!(!new.is_null());
136+
137+
let to_compare: *const Object =
138+
unsafe { msg_send![class!(NSString), stringWithUTF8String: CONTENTS_SCALE.as_ptr()] };
139+
let is_equal: BOOL = unsafe { msg_send![key_path, isEqual: to_compare] };
140+
if is_equal != NO {
141+
// `contentsScale` is a CGFloat, and so the observed value is always a NSNumber.
142+
let scale_factor: CGFloat = if cfg!(target_pointer_width = "64") {
143+
unsafe { msg_send![new, doubleValue] }
144+
} else {
145+
unsafe { msg_send![new, floatValue] }
146+
};
147+
148+
// Set the scale factor of the layer to match the root layer.
149+
let _: () = unsafe { msg_send![this, setContentsScale: scale_factor] };
150+
return;
151+
}
152+
153+
let to_compare: *const Object =
154+
unsafe { msg_send![class!(NSString), stringWithUTF8String: BOUNDS.as_ptr()] };
155+
let is_equal: BOOL = unsafe { msg_send![key_path, isEqual: to_compare] };
156+
if is_equal != NO {
157+
// `bounds` is a CGRect, and so the observed value is always a NSNumber.
158+
let bounds: CGRect = unsafe { msg_send![new, rectValue] };
159+
160+
// Set `bounds` and `position` to match the root layer.
161+
//
162+
// This differs from just setting the `bounds`, as it also takes into account any
163+
// translation that the superlayer may have that we'd want to preserve.
164+
let _: () = unsafe { msg_send![this, setFrame: bounds] };
165+
return;
166+
}
167+
168+
panic!("unknown observed keypath {key_path:?}");
169+
}
170+
171+
extern "C" fn dealloc(this: &Object, _cmd: Sel) {
172+
// Load the root layer if it still exists, and deregister the observer.
173+
//
174+
// This is not entirely sound, as the ObserverLayer _could_ have been
175+
// moved to another layer; but Wgpu doesn't do that, so it should be fine.
176+
//
177+
// `raw-window-metal` uses a weak instance variable to do it correctly:
178+
// https://docs.rs/raw-window-metal/1.1.0/src/raw_window_metal/observer.rs.html#74-132
179+
// (but that's difficult to do with `objc`).
180+
let root_layer: *mut Object = unsafe { msg_send![this, superlayer] };
181+
if !root_layer.is_null() {
182+
let key_path: *const Object =
183+
unsafe { msg_send![class!(NSString), stringWithUTF8String: CONTENTS_SCALE.as_ptr()] };
184+
let _: () = unsafe { msg_send![root_layer, removeObserver: this forKeyPath: key_path] };
185+
186+
let key_path: *const Object =
187+
unsafe { msg_send![class!(NSString), stringWithUTF8String: BOUNDS.as_ptr()] };
188+
let _: () = unsafe { msg_send![root_layer, removeObserver: this forKeyPath: key_path] };
189+
}
190+
}

wgpu-hal/src/metal/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod adapter;
2222
mod command;
2323
mod conv;
2424
mod device;
25+
mod layer_observer;
2526
mod surface;
2627
mod time;
2728

0 commit comments

Comments
 (0)