@@ -2,23 +2,23 @@ use crate::backend_interface::*;
22use crate :: error:: InitError ;
33use crate :: { Rect , SoftBufferError } ;
44use core_graphics:: base:: {
5- kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault,
5+ kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault, CGFloat ,
66} ;
77use core_graphics:: color_space:: CGColorSpace ;
88use core_graphics:: data_provider:: CGDataProvider ;
99use core_graphics:: image:: CGImage ;
10- use objc2:: runtime:: AnyObject ;
10+ use objc2:: runtime:: { AnyObject , Bool } ;
11+ use objc2:: { msg_send, msg_send_id} ;
1112use raw_window_handle:: { HasDisplayHandle , HasWindowHandle , RawWindowHandle } ;
1213
1314use foreign_types:: ForeignType ;
14- use objc2:: msg_send;
15- use objc2:: rc:: Id ;
16- use objc2_app_kit:: { NSAutoresizingMaskOptions , NSView , NSWindow } ;
17- use objc2_foundation:: { MainThreadBound , MainThreadMarker } ;
18- use objc2_quartz_core:: { kCAGravityTopLeft, CALayer , CATransaction } ;
15+ use objc2:: rc:: Retained ;
16+ use objc2_foundation:: { CGPoint , CGRect , CGSize , MainThreadMarker , NSObject } ;
17+ use objc2_quartz_core:: { kCAGravityResize, CALayer , CATransaction } ;
1918
2019use std:: marker:: PhantomData ;
2120use std:: num:: NonZeroU32 ;
21+ use std:: ops:: Deref ;
2222use std:: sync:: Arc ;
2323
2424struct Buffer ( Vec < u32 > ) ;
@@ -30,64 +30,92 @@ impl AsRef<[u8]> for Buffer {
3030}
3131
3232pub struct CGImpl < D , W > {
33- layer : MainThreadBound < Id < CALayer > > ,
34- window : MainThreadBound < Id < NSWindow > > ,
33+ /// Our layer.
34+ layer : SendCALayer ,
35+ /// The layer that our layer was created from.
36+ ///
37+ /// Can also be retrieved from `layer.superlayer()`.
38+ root_layer : SendCALayer ,
3539 color_space : SendCGColorSpace ,
36- size : Option < ( NonZeroU32 , NonZeroU32 ) > ,
3740 window_handle : W ,
3841 _display : PhantomData < D > ,
3942}
4043
41- // TODO(madsmtm): Expose this in `objc2_app_kit`.
42- fn set_layer ( view : & NSView , layer : & CALayer ) {
43- unsafe { msg_send ! [ view, setLayer: layer] }
44- }
45-
4644impl < D : HasDisplayHandle , W : HasWindowHandle > SurfaceInterface < D , W > for CGImpl < D , W > {
4745 type Context = D ;
4846 type Buffer < ' a > = BufferImpl < ' a , D , W > where Self : ' a ;
4947
5048 fn new ( window_src : W , _display : & D ) -> Result < Self , InitError < W > > {
51- let raw = window_src. window_handle ( ) ?. as_raw ( ) ;
52- let handle = match raw {
53- RawWindowHandle :: AppKit ( handle) => handle,
49+ // `NSView`/`UIView` can only be accessed from the main thread.
50+ let _mtm = MainThreadMarker :: new ( ) . ok_or ( SoftBufferError :: PlatformError (
51+ Some ( "can only access Core Graphics handles from the main thread" . to_string ( ) ) ,
52+ None ,
53+ ) ) ?;
54+
55+ let root_layer = match window_src. window_handle ( ) ?. as_raw ( ) {
56+ RawWindowHandle :: AppKit ( handle) => {
57+ // SAFETY: The pointer came from `WindowHandle`, which ensures that the
58+ // `AppKitWindowHandle` contains a valid pointer to an `NSView`.
59+ //
60+ // We use `NSObject` here to avoid importing `objc2-app-kit`.
61+ let view: & NSObject = unsafe { handle. ns_view . cast ( ) . as_ref ( ) } ;
62+
63+ // Force the view to become layer backed
64+ let _: ( ) = unsafe { msg_send ! [ view, setWantsLayer: Bool :: YES ] } ;
65+
66+ // SAFETY: `-[NSView layer]` returns an optional `CALayer`
67+ let layer: Option < Retained < CALayer > > = unsafe { msg_send_id ! [ view, layer] } ;
68+ layer. expect ( "failed making the view layer-backed" )
69+ }
70+ RawWindowHandle :: UiKit ( handle) => {
71+ // SAFETY: The pointer came from `WindowHandle`, which ensures that the
72+ // `UiKitWindowHandle` contains a valid pointer to an `UIView`.
73+ //
74+ // We use `NSObject` here to avoid importing `objc2-ui-kit`.
75+ let view: & NSObject = unsafe { handle. ui_view . cast ( ) . as_ref ( ) } ;
76+
77+ // SAFETY: `-[UIView layer]` returns `CALayer`
78+ let layer: Retained < CALayer > = unsafe { msg_send_id ! [ view, layer] } ;
79+ layer
80+ }
5481 _ => return Err ( InitError :: Unsupported ( window_src) ) ,
5582 } ;
5683
57- // `NSView` can only be accessed from the main thread.
58- let mtm = MainThreadMarker :: new ( ) . ok_or ( SoftBufferError :: PlatformError (
59- Some ( "can only access AppKit / macOS handles from the main thread" . to_string ( ) ) ,
60- None ,
61- ) ) ?;
62- let view = handle. ns_view . as_ptr ( ) ;
63- // SAFETY: The pointer came from `WindowHandle`, which ensures that
64- // the `AppKitWindowHandle` contains a valid pointer to an `NSView`.
65- // Unwrap is fine, since the pointer came from `NonNull`.
66- let view: Id < NSView > = unsafe { Id :: retain ( view. cast ( ) ) } . unwrap ( ) ;
84+ // Add a sublayer, to avoid interfering with the root layer, since setting the contents of
85+ // e.g. a view-controlled layer is brittle.
6786 let layer = CALayer :: new ( ) ;
68- let subview = unsafe { NSView :: initWithFrame ( mtm. alloc ( ) , view. frame ( ) ) } ;
69- layer. setContentsGravity ( unsafe { kCAGravityTopLeft } ) ;
70- layer. setNeedsDisplayOnBoundsChange ( false ) ;
71- set_layer ( & subview, & layer) ;
72- unsafe {
73- subview. setAutoresizingMask ( NSAutoresizingMaskOptions (
74- NSAutoresizingMaskOptions :: NSViewWidthSizable . 0
75- | NSAutoresizingMaskOptions :: NSViewHeightSizable . 0 ,
76- ) )
77- } ;
87+ root_layer. addSublayer ( & layer) ;
7888
79- let window = view. window ( ) . ok_or ( SoftBufferError :: PlatformError (
80- Some ( "view must be inside a window" . to_string ( ) ) ,
81- None ,
82- ) ) ?;
89+ // Set the anchor point. Used to avoid having to calculate the center point when setting
90+ // `bounds` in `resize`.
91+ layer. setAnchorPoint ( CGPoint :: new ( 0.0 , 0.0 ) ) ;
92+
93+ // Set initial scale factor. Updated in `resize`.
94+ layer. setContentsScale ( root_layer. contentsScale ( ) ) ;
95+
96+ // Set `bounds` and `position` so that the new layer is inside the superlayer.
97+ //
98+ // This differs from just setting the `bounds`, as it also takes into account any
99+ // translation that the superlayer may have that we want to preserve.
100+ layer. setFrame ( root_layer. bounds ( ) ) ;
83101
84- unsafe { view. addSubview ( & subview) } ;
102+ // Do not use auto-resizing mask, see comments in `resize` for details.
103+ // layer.setAutoresizingMask(kCALayerHeightSizable | kCALayerWidthSizable);
104+
105+ // Set the content gravity in a way that masks failure to redraw at the correct time.
106+ layer. setContentsGravity ( unsafe { kCAGravityResize } ) ;
107+
108+ // Softbuffer uses a coordinate system with the origin in the top-left corner (doesn't
109+ // really matter unless we start setting the `position` of our layer).
110+ layer. setGeometryFlipped ( true ) ;
111+
112+ // Initialize color space here, to reduce work later on.
85113 let color_space = CGColorSpace :: create_device_rgb ( ) ;
114+
86115 Ok ( Self {
87- layer : MainThreadBound :: new ( layer, mtm ) ,
88- window : MainThreadBound :: new ( window , mtm ) ,
116+ layer : SendCALayer ( layer) ,
117+ root_layer : SendCALayer ( root_layer ) ,
89118 color_space : SendCGColorSpace ( color_space) ,
90- size : None ,
91119 _display : PhantomData ,
92120 window_handle : window_src,
93121 } )
@@ -99,17 +127,70 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for CGImpl<
99127 }
100128
101129 fn resize ( & mut self , width : NonZeroU32 , height : NonZeroU32 ) -> Result < ( ) , SoftBufferError > {
102- self . size = Some ( ( width, height) ) ;
130+ let scale_factor = self . root_layer . contentsScale ( ) ;
131+ let bounds = CGRect :: new (
132+ CGPoint :: new ( 0.0 , 0.0 ) ,
133+ CGSize :: new (
134+ width. get ( ) as CGFloat / scale_factor,
135+ height. get ( ) as CGFloat / scale_factor,
136+ ) ,
137+ ) ;
138+
139+ // _Usually_, the programmer should be resizing the surface together in lockstep with the
140+ // user action that initiated the resize, e.g. a window resize, where there would already be
141+ // a transaction ongoing.
142+ //
143+ // With the current version of Winit, though, that isn't the case, and we end up getting the
144+ // resize event emitted later, outside the callstack where the transaction was ongoing. The
145+ // user could also choose to resize e.g. on a different thread, or in loads of other
146+ // circumstances.
147+ //
148+ // This, in turn, means that the default animation with a delay of 0.25 seconds kicks in
149+ // when updating these values - this is definitely not what we want, so we disable those
150+ // animations here.
151+ CATransaction :: begin ( ) ;
152+ CATransaction :: setDisableActions ( true ) ;
153+
154+ // Set the scale factor of the layer to match the root layer / super layer, in case it
155+ // changed (e.g. if moved to a different monitor, or monitor settings changed).
156+ self . layer . setContentsScale ( scale_factor) ;
157+
158+ // Set the bounds on the layer.
159+ //
160+ // This is an explicit design decision: We set the bounds on the layer manually, instead of
161+ // letting it be automatically updated using `autoresizingMask`.
162+ //
163+ // The first reason for this is that it gives the user complete control over the size of the
164+ // layer (and underlying buffer, once properly implemented, see #83), which matches other
165+ // platforms.
166+ //
167+ // The second is that it is needed to work around a bug in macOS 14 and above, where views
168+ // using auto layout may end up setting fractional values as the bounds, and that in turn
169+ // doesn't propagate properly through the auto-resizing mask and with contents gravity.
170+ //
171+ // If we were to change this so that the layer resizes automatically, we should _not_ use
172+ // `layer.setAutoresizingMask(...)`, but instead register an observer on the super layer,
173+ // which then propagates the bounds change to the sublayer. It is unfortunate that we cannot
174+ // use the built-in functionality to do this, but not something you can avoid either way if
175+ // you're doing automatic resizing, since you'd _need_ to propagate the scale factor anyhow.
176+ self . layer . setBounds ( bounds) ;
177+
178+ // See comment on `CATransaction::begin`.
179+ CATransaction :: commit ( ) ;
180+
103181 Ok ( ( ) )
104182 }
105183
106184 fn buffer_mut ( & mut self ) -> Result < BufferImpl < ' _ , D , W > , SoftBufferError > {
107- let ( width, height) = self
108- . size
109- . expect ( "Must set size of surface before calling `buffer_mut()`" ) ;
185+ let scale_factor = self . layer . contentsScale ( ) ;
186+ let bounds = self . layer . bounds ( ) ;
187+ // The bounds and scale factor are set in `resize`, and should result in integer values when
188+ // combined like this.
189+ let width = ( bounds. size . width * scale_factor) as usize ;
190+ let height = ( bounds. size . height * scale_factor) as usize ;
110191
111192 Ok ( BufferImpl {
112- buffer : vec ! [ 0 ; width. get ( ) as usize * height. get ( ) as usize ] ,
193+ buffer : vec ! [ 0 ; width * height] ,
113194 imp : self ,
114195 } )
115196 }
@@ -137,41 +218,38 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl
137218
138219 fn present ( self ) -> Result < ( ) , SoftBufferError > {
139220 let data_provider = CGDataProvider :: from_buffer ( Arc :: new ( Buffer ( self . buffer ) ) ) ;
140- let ( width, height) = self . imp . size . unwrap ( ) ;
221+
222+ let scale_factor = self . imp . layer . contentsScale ( ) ;
223+ let bounds = self . imp . layer . bounds ( ) ;
224+ // The bounds and scale factor are set in `resize`, and should result in integer values when
225+ // combined like this.
226+ let width = ( bounds. size . width * scale_factor) as usize ;
227+ let height = ( bounds. size . height * scale_factor) as usize ;
228+
141229 let image = CGImage :: new (
142- width. get ( ) as usize ,
143- height. get ( ) as usize ,
230+ width,
231+ height,
144232 8 ,
145233 32 ,
146- ( width. get ( ) * 4 ) as usize ,
234+ width * 4 ,
147235 & self . imp . color_space . 0 ,
148236 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
149237 & data_provider,
150238 false ,
151239 kCGRenderingIntentDefault,
152240 ) ;
153-
154- // TODO: Use run_on_main() instead.
155- let mtm = MainThreadMarker :: new ( ) . ok_or ( SoftBufferError :: PlatformError (
156- Some ( "can only access AppKit / macOS handles from the main thread" . to_string ( ) ) ,
157- None ,
158- ) ) ?;
241+ let contents = unsafe { ( image. as_ptr ( ) as * mut AnyObject ) . as_ref ( ) } ;
159242
160243 // The CALayer has a default action associated with a change in the layer contents, causing
161244 // a quarter second fade transition to happen every time a new buffer is applied. This can
162245 // be mitigated by wrapping the operation in a transaction and disabling all actions.
163246 CATransaction :: begin ( ) ;
164247 CATransaction :: setDisableActions ( true ) ;
165248
166- let layer = self . imp . layer . get ( mtm) ;
167- layer. setContentsScale ( self . imp . window . get ( mtm) . backingScaleFactor ( ) ) ;
168-
169- unsafe {
170- layer. setContents ( ( image. as_ptr ( ) as * mut AnyObject ) . as_ref ( ) ) ;
171- } ;
249+ // SAFETY: The contents is `CGImage`, which is a valid class for `contents`.
250+ unsafe { self . imp . layer . setContents ( contents) } ;
172251
173252 CATransaction :: commit ( ) ;
174-
175253 Ok ( ( ) )
176254 }
177255
@@ -184,3 +262,15 @@ struct SendCGColorSpace(CGColorSpace);
184262// SAFETY: `CGColorSpace` is immutable, and can freely be shared between threads.
185263unsafe impl Send for SendCGColorSpace { }
186264unsafe impl Sync for SendCGColorSpace { }
265+
266+ struct SendCALayer ( Retained < CALayer > ) ;
267+ // CALayer is thread safe
268+ unsafe impl Send for SendCALayer { }
269+ unsafe impl Sync for SendCALayer { }
270+
271+ impl Deref for SendCALayer {
272+ type Target = CALayer ;
273+ fn deref ( & self ) -> & Self :: Target {
274+ & self . 0
275+ }
276+ }
0 commit comments