@@ -2,23 +2,23 @@ use crate::backend_interface::*;
2
2
use crate :: error:: InitError ;
3
3
use crate :: { Rect , SoftBufferError } ;
4
4
use core_graphics:: base:: {
5
- kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault,
5
+ kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault, CGFloat ,
6
6
} ;
7
7
use core_graphics:: color_space:: CGColorSpace ;
8
8
use core_graphics:: data_provider:: CGDataProvider ;
9
9
use core_graphics:: image:: CGImage ;
10
- use objc2:: runtime:: AnyObject ;
10
+ use objc2:: runtime:: { AnyObject , Bool } ;
11
+ use objc2:: { msg_send, msg_send_id} ;
11
12
use raw_window_handle:: { HasDisplayHandle , HasWindowHandle , RawWindowHandle } ;
12
13
13
14
use 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 } ;
19
18
20
19
use std:: marker:: PhantomData ;
21
20
use std:: num:: NonZeroU32 ;
21
+ use std:: ops:: Deref ;
22
22
use std:: sync:: Arc ;
23
23
24
24
struct Buffer ( Vec < u32 > ) ;
@@ -30,64 +30,92 @@ impl AsRef<[u8]> for Buffer {
30
30
}
31
31
32
32
pub 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 ,
35
39
color_space : SendCGColorSpace ,
36
- size : Option < ( NonZeroU32 , NonZeroU32 ) > ,
37
40
window_handle : W ,
38
41
_display : PhantomData < D > ,
39
42
}
40
43
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
-
46
44
impl < D : HasDisplayHandle , W : HasWindowHandle > SurfaceInterface < D , W > for CGImpl < D , W > {
47
45
type Context = D ;
48
46
type Buffer < ' a > = BufferImpl < ' a , D , W > where Self : ' a ;
49
47
50
48
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
+ }
54
81
_ => return Err ( InitError :: Unsupported ( window_src) ) ,
55
82
} ;
56
83
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.
67
86
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) ;
78
88
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 ( ) ) ;
83
101
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.
85
113
let color_space = CGColorSpace :: create_device_rgb ( ) ;
114
+
86
115
Ok ( Self {
87
- layer : MainThreadBound :: new ( layer, mtm ) ,
88
- window : MainThreadBound :: new ( window , mtm ) ,
116
+ layer : SendCALayer ( layer) ,
117
+ root_layer : SendCALayer ( root_layer ) ,
89
118
color_space : SendCGColorSpace ( color_space) ,
90
- size : None ,
91
119
_display : PhantomData ,
92
120
window_handle : window_src,
93
121
} )
@@ -99,17 +127,70 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for CGImpl<
99
127
}
100
128
101
129
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
+
103
181
Ok ( ( ) )
104
182
}
105
183
106
184
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 ;
110
191
111
192
Ok ( BufferImpl {
112
- buffer : vec ! [ 0 ; width. get ( ) as usize * height. get ( ) as usize ] ,
193
+ buffer : vec ! [ 0 ; width * height] ,
113
194
imp : self ,
114
195
} )
115
196
}
@@ -137,41 +218,38 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl
137
218
138
219
fn present ( self ) -> Result < ( ) , SoftBufferError > {
139
220
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
+
141
229
let image = CGImage :: new (
142
- width. get ( ) as usize ,
143
- height. get ( ) as usize ,
230
+ width,
231
+ height,
144
232
8 ,
145
233
32 ,
146
- ( width. get ( ) * 4 ) as usize ,
234
+ width * 4 ,
147
235
& self . imp . color_space . 0 ,
148
236
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
149
237
& data_provider,
150
238
false ,
151
239
kCGRenderingIntentDefault,
152
240
) ;
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 ( ) } ;
159
242
160
243
// The CALayer has a default action associated with a change in the layer contents, causing
161
244
// a quarter second fade transition to happen every time a new buffer is applied. This can
162
245
// be mitigated by wrapping the operation in a transaction and disabling all actions.
163
246
CATransaction :: begin ( ) ;
164
247
CATransaction :: setDisableActions ( true ) ;
165
248
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) } ;
172
251
173
252
CATransaction :: commit ( ) ;
174
-
175
253
Ok ( ( ) )
176
254
}
177
255
@@ -184,3 +262,15 @@ struct SendCGColorSpace(CGColorSpace);
184
262
// SAFETY: `CGColorSpace` is immutable, and can freely be shared between threads.
185
263
unsafe impl Send for SendCGColorSpace { }
186
264
unsafe 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