@@ -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,95 @@ 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+ //
107+ // Effectively, this means that the shown surface always matches the `bounds` that the user
108+ // has specified in `resize`, regardless of whether the user has rendered yet or not.
109+ layer. setContentsGravity ( unsafe { kCAGravityResize } ) ;
110+
111+ // Softbuffer uses a coordinate system with the origin in the top-left corner (doesn't
112+ // really matter unless we start setting the `position` of our layer).
113+ layer. setGeometryFlipped ( true ) ;
114+
115+ // Initialize color space here, to reduce work later on.
85116 let color_space = CGColorSpace :: create_device_rgb ( ) ;
117+
86118 Ok ( Self {
87- layer : MainThreadBound :: new ( layer, mtm ) ,
88- window : MainThreadBound :: new ( window , mtm ) ,
119+ layer : SendCALayer ( layer) ,
120+ root_layer : SendCALayer ( root_layer ) ,
89121 color_space : SendCGColorSpace ( color_space) ,
90- size : None ,
91122 _display : PhantomData ,
92123 window_handle : window_src,
93124 } )
@@ -99,17 +130,70 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for CGImpl<
99130 }
100131
101132 fn resize ( & mut self , width : NonZeroU32 , height : NonZeroU32 ) -> Result < ( ) , SoftBufferError > {
102- self . size = Some ( ( width, height) ) ;
133+ let scale_factor = self . root_layer . contentsScale ( ) ;
134+ let bounds = CGRect :: new (
135+ CGPoint :: new ( 0.0 , 0.0 ) ,
136+ CGSize :: new (
137+ width. get ( ) as CGFloat / scale_factor,
138+ height. get ( ) as CGFloat / scale_factor,
139+ ) ,
140+ ) ;
141+
142+ // _Usually_, the programmer should be resizing the surface together in lockstep with the
143+ // user action that initiated the resize, e.g. a window resize, where there would already be
144+ // a transaction ongoing.
145+ //
146+ // With the current version of Winit, though, that isn't the case, and we end up getting the
147+ // resize event emitted later, outside the callstack where the transaction was ongoing. The
148+ // user could also choose to resize e.g. on a different thread, or in loads of other
149+ // circumstances.
150+ //
151+ // This, in turn, means that the default animation with a delay of 0.25 seconds kicks in
152+ // when updating these values - this is definitely not what we want, so we disable those
153+ // animations here.
154+ CATransaction :: begin ( ) ;
155+ CATransaction :: setDisableActions ( true ) ;
156+
157+ // Set the scale factor of the layer to match the root layer / super layer, in case it
158+ // changed (e.g. if moved to a different monitor, or monitor settings changed).
159+ self . layer . setContentsScale ( scale_factor) ;
160+
161+ // Set the bounds on the layer.
162+ //
163+ // This is an explicit design decision: We set the bounds on the layer manually, instead of
164+ // letting it be automatically updated using `autoresizingMask`.
165+ //
166+ // The first reason for this is that it gives the user complete control over the size of the
167+ // layer (and underlying buffer, once properly implemented, see #83), which matches other
168+ // platforms.
169+ //
170+ // The second is that it is needed to work around a bug in macOS 14 and above, where views
171+ // using auto layout may end up setting fractional values as the bounds, and that in turn
172+ // doesn't propagate properly through the auto-resizing mask and with contents gravity.
173+ //
174+ // If we were to change this so that the layer resizes automatically, we should _not_ use
175+ // `layer.setAutoresizingMask(...)`, but instead register an observer on the super layer,
176+ // which then propagates the bounds change to the sublayer. It is unfortunate that we cannot
177+ // use the built-in functionality to do this, but not something you can avoid either way if
178+ // you're doing automatic resizing, since you'd _need_ to propagate the scale factor anyhow.
179+ self . layer . setBounds ( bounds) ;
180+
181+ // See comment on `CATransaction::begin`.
182+ CATransaction :: commit ( ) ;
183+
103184 Ok ( ( ) )
104185 }
105186
106187 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()`" ) ;
188+ let scale_factor = self . layer . contentsScale ( ) ;
189+ let bounds = self . layer . bounds ( ) ;
190+ // The bounds and scale factor are set in `resize`, and should result in integer values when
191+ // combined like this.
192+ let width = ( bounds. size . width * scale_factor) as usize ;
193+ let height = ( bounds. size . height * scale_factor) as usize ;
110194
111195 Ok ( BufferImpl {
112- buffer : vec ! [ 0 ; width. get ( ) as usize * height. get ( ) as usize ] ,
196+ buffer : vec ! [ 0 ; width * height] ,
113197 imp : self ,
114198 } )
115199 }
@@ -137,41 +221,38 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl
137221
138222 fn present ( self ) -> Result < ( ) , SoftBufferError > {
139223 let data_provider = CGDataProvider :: from_buffer ( Arc :: new ( Buffer ( self . buffer ) ) ) ;
140- let ( width, height) = self . imp . size . unwrap ( ) ;
224+
225+ let scale_factor = self . imp . layer . contentsScale ( ) ;
226+ let bounds = self . imp . layer . bounds ( ) ;
227+ // The bounds and scale factor are set in `resize`, and should result in integer values when
228+ // combined like this.
229+ let width = ( bounds. size . width * scale_factor) as usize ;
230+ let height = ( bounds. size . height * scale_factor) as usize ;
231+
141232 let image = CGImage :: new (
142- width. get ( ) as usize ,
143- height. get ( ) as usize ,
233+ width,
234+ height,
144235 8 ,
145236 32 ,
146- ( width. get ( ) * 4 ) as usize ,
237+ width * 4 ,
147238 & self . imp . color_space . 0 ,
148239 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
149240 & data_provider,
150241 false ,
151242 kCGRenderingIntentDefault,
152243 ) ;
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- ) ) ?;
244+ let contents = unsafe { ( image. as_ptr ( ) as * mut AnyObject ) . as_ref ( ) } ;
159245
160246 // The CALayer has a default action associated with a change in the layer contents, causing
161247 // a quarter second fade transition to happen every time a new buffer is applied. This can
162248 // be mitigated by wrapping the operation in a transaction and disabling all actions.
163249 CATransaction :: begin ( ) ;
164250 CATransaction :: setDisableActions ( true ) ;
165251
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- } ;
252+ // SAFETY: The contents is `CGImage`, which is a valid class for `contents`.
253+ unsafe { self . imp . layer . setContents ( contents) } ;
172254
173255 CATransaction :: commit ( ) ;
174-
175256 Ok ( ( ) )
176257 }
177258
@@ -184,3 +265,15 @@ struct SendCGColorSpace(CGColorSpace);
184265// SAFETY: `CGColorSpace` is immutable, and can freely be shared between threads.
185266unsafe impl Send for SendCGColorSpace { }
186267unsafe impl Sync for SendCGColorSpace { }
268+
269+ struct SendCALayer ( Retained < CALayer > ) ;
270+ // CALayer is thread safe
271+ unsafe impl Send for SendCALayer { }
272+ unsafe impl Sync for SendCALayer { }
273+
274+ impl Deref for SendCALayer {
275+ type Target = CALayer ;
276+ fn deref ( & self ) -> & Self :: Target {
277+ & self . 0
278+ }
279+ }
0 commit comments