1
1
#![ allow( clippy:: let_unit_value) ] // `let () =` being used to constrain result type
2
2
3
- use std:: { os:: raw:: c_void, ptr:: NonNull , sync:: Once , thread} ;
3
+ use std:: ffi:: c_uint;
4
+ use std:: ptr:: NonNull ;
5
+ use std:: sync:: Once ;
6
+ use std:: thread;
4
7
5
8
use core_graphics_types:: {
6
9
base:: CGFloat ,
7
10
geometry:: { CGRect , CGSize } ,
8
11
} ;
12
+ use metal:: foreign_types:: ForeignType ;
9
13
use objc:: {
10
14
class,
11
15
declare:: ClassDecl ,
@@ -16,7 +20,6 @@ use objc::{
16
20
} ;
17
21
use parking_lot:: { Mutex , RwLock } ;
18
22
19
- #[ cfg( target_os = "macos" ) ]
20
23
#[ link( name = "QuartzCore" , kind = "framework" ) ]
21
24
extern "C" {
22
25
#[ allow( non_upper_case_globals) ]
@@ -47,6 +50,7 @@ impl HalManagedMetalLayerDelegate {
47
50
let mut decl = ClassDecl :: new ( & class_name, class ! ( NSObject ) ) . unwrap ( ) ;
48
51
#[ allow( trivial_casts) ] // false positive
49
52
unsafe {
53
+ // <https://developer.apple.com/documentation/appkit/nsviewlayercontentscaledelegate/3005294-layer?language=objc>
50
54
decl. add_class_method (
51
55
sel ! ( layer: shouldInheritContentsScale: fromWindow: ) ,
52
56
layer_should_inherit_contents_scale_from_window as Fun ,
@@ -73,26 +77,15 @@ impl super::Surface {
73
77
/// If not called on the main thread, this will panic.
74
78
#[ allow( clippy:: transmute_ptr_to_ref) ]
75
79
pub unsafe fn from_view (
76
- view : * mut c_void ,
80
+ view : NonNull < Object > ,
77
81
delegate : Option < & HalManagedMetalLayerDelegate > ,
78
82
) -> Self {
79
- let view = view. cast :: < Object > ( ) ;
80
- let render_layer = {
81
- let layer = unsafe { Self :: get_metal_layer ( view, delegate) } ;
82
- let layer = layer. cast :: < metal:: MetalLayerRef > ( ) ;
83
- // SAFETY: This pointer…
84
- //
85
- // - …is properly aligned.
86
- // - …is dereferenceable to a `MetalLayerRef` as an invariant of the `metal`
87
- // field.
88
- // - …points to an _initialized_ `MetalLayerRef`.
89
- // - …is only ever aliased via an immutable reference that lives within this
90
- // lexical scope.
91
- unsafe { & * layer }
92
- }
93
- . to_owned ( ) ;
94
- let _: * mut c_void = msg_send ! [ view, retain] ;
95
- Self :: new ( NonNull :: new ( view) , render_layer)
83
+ let layer = unsafe { Self :: get_metal_layer ( view, delegate) } ;
84
+ // SAFETY: The layer is an initialized instance of `CAMetalLayer`.
85
+ let layer = unsafe { metal:: MetalLayer :: from_ptr ( layer. cast ( ) ) } ;
86
+ let view: * mut Object = msg_send ! [ view. as_ptr( ) , retain] ;
87
+ let view = NonNull :: new ( view) . expect ( "retain should return the same object" ) ;
88
+ Self :: new ( Some ( view) , layer)
96
89
}
97
90
98
91
pub unsafe fn from_layer ( layer : & metal:: MetalLayerRef ) -> Self {
@@ -102,52 +95,155 @@ impl super::Surface {
102
95
Self :: new ( None , layer. to_owned ( ) )
103
96
}
104
97
105
- /// If not called on the main thread, this will panic.
98
+ /// Get or create a new `CAMetalLayer` associated with the given `NSView`
99
+ /// or `UIView`.
100
+ ///
101
+ /// # Panics
102
+ ///
103
+ /// If called from a thread that is not the main thread, this will panic.
104
+ ///
105
+ /// # Safety
106
+ ///
107
+ /// The `view` must be a valid instance of `NSView` or `UIView`.
106
108
pub ( crate ) unsafe fn get_metal_layer (
107
- view : * mut Object ,
109
+ view : NonNull < Object > ,
108
110
delegate : Option < & HalManagedMetalLayerDelegate > ,
109
111
) -> * mut Object {
110
- if view. is_null ( ) {
111
- panic ! ( "window does not have a valid contentView" ) ;
112
- }
113
-
114
112
let is_main_thread: BOOL = msg_send ! [ class!( NSThread ) , isMainThread] ;
115
113
if is_main_thread == NO {
116
114
panic ! ( "get_metal_layer cannot be called in non-ui thread." ) ;
117
115
}
118
116
119
- let main_layer: * mut Object = msg_send ! [ view, layer] ;
120
- let class = class ! ( CAMetalLayer ) ;
121
- let is_valid_layer: BOOL = msg_send ! [ main_layer, isKindOfClass: class] ;
117
+ // Ensure that the view is layer-backed.
118
+ // Views are always layer-backed in UIKit.
119
+ #[ cfg( target_os = "macos" ) ]
120
+ let ( ) = msg_send ! [ view. as_ptr( ) , setWantsLayer: YES ] ;
121
+
122
+ let root_layer: * mut Object = msg_send ! [ view. as_ptr( ) , layer] ;
123
+ // `-[NSView layer]` can return `NULL`, while `-[UIView layer]` should
124
+ // always be available.
125
+ assert ! ( !root_layer. is_null( ) , "failed making the view layer-backed" ) ;
126
+
127
+ // NOTE: We explicitly do not touch properties such as
128
+ // `layerContentsPlacement`, `needsDisplayOnBoundsChange` and
129
+ // `contentsGravity` etc. on the root layer, both since we would like
130
+ // to give the user full control over them, and because the default
131
+ // values suit us pretty well (especially the contents placement being
132
+ // `NSViewLayerContentsRedrawDuringViewResize`, which allows the view
133
+ // to receive `drawRect:`/`updateLayer` calls).
122
134
123
- if is_valid_layer == YES {
124
- main_layer
135
+ let is_metal_layer: BOOL = msg_send ! [ root_layer, isKindOfClass: class!( CAMetalLayer ) ] ;
136
+ if is_metal_layer == YES {
137
+ // The view has a `CAMetalLayer` as the root layer, which can
138
+ // happen for example if user overwrote `-[NSView layerClass]` or
139
+ // the view is `MTKView`.
140
+ //
141
+ // This is easily handled: We take "ownership" over the layer, and
142
+ // render directly into that; after all, the user passed a view
143
+ // with an explicit Metal layer to us, so this is very likely what
144
+ // they expect us to do.
145
+ root_layer
125
146
} else {
126
- // If the main layer is not a CAMetalLayer, we create a CAMetalLayer and use it.
127
- let new_layer: * mut Object = msg_send ! [ class, new] ;
128
- let frame: CGRect = msg_send ! [ main_layer, bounds] ;
147
+ // The view does not have a `CAMetalLayer` as the root layer (this
148
+ // is the default for most views).
149
+ //
150
+ // This case is trickier! We cannot use the existing layer with
151
+ // Metal, so we must do something else. There are a few options:
152
+ //
153
+ // 1. Panic here, and require the user to pass a view with a
154
+ // `CAMetalLayer` layer.
155
+ //
156
+ // While this would "work", it doesn't solve the problem, and
157
+ // instead passes the ball onwards to the user and ecosystem to
158
+ // figure it out.
159
+ //
160
+ // 2. Override the existing layer with a newly created layer.
161
+ //
162
+ // If we overlook that this does not work in UIKit since
163
+ // `UIView`'s `layer` is `readonly`, and that as such we will
164
+ // need to do something different there anyhow, this is
165
+ // actually a fairly good solution, and was what the original
166
+ // implementation did.
167
+ //
168
+ // It has some problems though, due to:
169
+ //
170
+ // a. `wgpu` in our API design choosing not to register a
171
+ // callback with `-[CALayerDelegate displayLayer:]`, but
172
+ // instead leaves it up to the user to figure out when to
173
+ // redraw. That is, we rely on other libraries' callbacks
174
+ // telling us when to render.
175
+ //
176
+ // (If this were an API only for Metal, we would probably
177
+ // make the user provide a `render` closure that we'd call
178
+ // in the right situations. But alas, we have to be
179
+ // cross-platform here).
180
+ //
181
+ // b. Overwriting the `layer` on `NSView` makes the view
182
+ // "layer-hosting", see [wantsLayer], which disables drawing
183
+ // functionality on the view like `drawRect:`/`updateLayer`.
184
+ //
185
+ // These two in combination makes it basically impossible for
186
+ // crates like Winit to provide a robust rendering callback
187
+ // that integrates with the system's built-in mechanisms for
188
+ // redrawing, exactly because overwriting the layer would be
189
+ // implicitly disabling those mechanisms!
190
+ //
191
+ // [wantsLayer]: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer?language=objc
192
+ //
193
+ // 3. Create a sublayer.
194
+ //
195
+ // `CALayer` has the concept of "sublayers", which we can use
196
+ // instead of overriding the layer.
197
+ //
198
+ // This is also the recommended solution on UIKit, so it's nice
199
+ // that we can use (almost) the same implementation for these.
200
+ //
201
+ // It _might_, however, perform ever so slightly worse than
202
+ // overriding the layer directly.
203
+ //
204
+ // 4. Create a new `MTKView` (or a custom view), and add it as a
205
+ // subview.
206
+ //
207
+ // Similar to creating a sublayer (see above), but also
208
+ // provides a bunch of event handling that we don't need.
209
+ //
210
+ // Option 3 seems like the most robust solution, so this is what
211
+ // we're going to do.
212
+
213
+ // Create a new sublayer.
214
+ let new_layer: * mut Object = msg_send ! [ class!( CAMetalLayer ) , new] ;
215
+ let ( ) = msg_send ! [ root_layer, addSublayer: new_layer] ;
216
+
217
+ // Automatically resize the sublayer's frame to match the
218
+ // superlayer's bounds.
219
+ //
220
+ // Note that there is a somewhat hidden design decision in this:
221
+ // We define the `width` and `height` in `configure` to control
222
+ // the `drawableSize` of the layer, while `bounds` and `frame` are
223
+ // outside of the user's direct control - instead, though, they
224
+ // can control the size of the view (or root layer), and get the
225
+ // desired effect that way.
226
+ //
227
+ // We _could_ also let `configure` set the `bounds` size, however
228
+ // that would be inconsistent with using the root layer directly
229
+ // (as we may do, see above).
230
+ let width_sizable = 1 << 1 ; // kCALayerWidthSizable
231
+ let height_sizable = 1 << 4 ; // kCALayerHeightSizable
232
+ let mask: c_uint = width_sizable | height_sizable;
233
+ let ( ) = msg_send ! [ new_layer, setAutoresizingMask: mask] ;
234
+
235
+ // Specify the relative size that the auto resizing mask above
236
+ // will keep (i.e. tell it to fill out its superlayer).
237
+ let frame: CGRect = msg_send ! [ root_layer, bounds] ;
129
238
let ( ) = msg_send ! [ new_layer, setFrame: frame] ;
130
- #[ cfg( target_os = "ios" ) ]
131
- {
132
- // Unlike NSView, UIView does not allow to replace main layer.
133
- let ( ) = msg_send ! [ main_layer, addSublayer: new_layer] ;
134
- // On iOS, "from_view" may be called before the application initialization is complete,
135
- // `msg_send![view, window]` and `msg_send![window, screen]` will get null.
136
- let screen: * mut Object = msg_send ! [ class!( UIScreen ) , mainScreen] ;
137
- let scale_factor: CGFloat = msg_send ! [ screen, nativeScale] ;
138
- let ( ) = msg_send ! [ view, setContentScaleFactor: scale_factor] ;
139
- } ;
140
- #[ cfg( target_os = "macos" ) ]
141
- {
142
- let ( ) = msg_send ! [ view, setLayer: new_layer] ;
143
- let ( ) = msg_send ! [ view, setWantsLayer: YES ] ;
144
- let ( ) = msg_send ! [ new_layer, setContentsGravity: unsafe { kCAGravityTopLeft } ] ;
145
- let window: * mut Object = msg_send ! [ view, window] ;
146
- if !window. is_null ( ) {
147
- let scale_factor: CGFloat = msg_send ! [ window, backingScaleFactor] ;
148
- let ( ) = msg_send ! [ new_layer, setContentsScale: scale_factor] ;
149
- }
150
- } ;
239
+
240
+ let _: ( ) = msg_send ! [ new_layer, setContentsGravity: unsafe { kCAGravityTopLeft } ] ;
241
+
242
+ // Set initial scale factor of the layer. This is kept in sync by
243
+ // `configure` (on UIKit), and the delegate below (on AppKit).
244
+ let scale_factor: CGFloat = msg_send ! [ root_layer, contentsScale] ;
245
+ let ( ) = msg_send ! [ new_layer, setContentsScale: scale_factor] ;
246
+
151
247
if let Some ( delegate) = delegate {
152
248
let ( ) = msg_send ! [ new_layer, setDelegate: delegate. 0 ] ;
153
249
}
@@ -211,19 +307,28 @@ impl crate::Surface for super::Surface {
211
307
_ => ( ) ,
212
308
}
213
309
214
- let device_raw = device. shared . device . lock ( ) ;
215
- // On iOS, unless the user supplies a view with a CAMetalLayer, we
216
- // create one as a sublayer. However, when the view changes size,
217
- // its sublayers are not automatically resized, and we must resize
218
- // it here. The drawable size and the layer size don't correlate
219
- #[ cfg( target_os = "ios" ) ]
310
+ // AppKit / UIKit automatically sets the correct scale factor for
311
+ // layers attached to a view. Our layer, however, may not be directly
312
+ // attached to the view; in those cases, we need to set the scale
313
+ // factor ourselves.
314
+ //
315
+ // For AppKit, we do so by adding a delegate on the layer with the
316
+ // `layer:shouldInheritContentsScale:fromWindow:` method returning
317
+ // `true` - this tells the system to automatically update the scale
318
+ // factor when it changes.
319
+ //
320
+ // For UIKit, we manually update the scale factor here.
321
+ //
322
+ // TODO: Is there a way that we could listen to such changes instead?
323
+ #[ cfg( not( target_os = "macos" ) ) ]
220
324
{
221
325
if let Some ( view) = self . view {
222
- let main_layer: * mut Object = msg_send ! [ view. as_ptr( ) , layer] ;
223
- let bounds: CGRect = msg_send ! [ main_layer, bounds] ;
224
- let ( ) = msg_send ! [ * render_layer, setFrame: bounds] ;
326
+ let scale_factor: CGFloat = msg_send ! [ view. as_ptr( ) , contentScaleFactor] ;
327
+ let ( ) = msg_send ! [ render_layer. as_ptr( ) , setContentsScale: scale_factor] ;
225
328
}
226
329
}
330
+
331
+ let device_raw = device. shared . device . lock ( ) ;
227
332
render_layer. set_device ( & device_raw) ;
228
333
render_layer. set_pixel_format ( caps. map_format ( config. format ) ) ;
229
334
render_layer. set_framebuffer_only ( framebuffer_only) ;
0 commit comments