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