Skip to content

Commit b1d22cd

Browse files
committed
Add layer observer based on raw-window-metal
1 parent d977735 commit b1d22cd

File tree

3 files changed

+196
-181
lines changed

3 files changed

+196
-181
lines changed

wgpu-hal/src/metal/layer_observer.rs

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

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)