1 use crate::{
2     device::{Device, PhysicalDevice},
3     internal::Channel,
4     native, Backend, QueueFamily, Shared,
5 };
6 
7 use hal::{format, image, window as w};
8 
9 use crate::CGRect;
10 use objc::rc::autoreleasepool;
11 use objc::runtime::Object;
12 use parking_lot::Mutex;
13 
14 use std::borrow::Borrow;
15 use std::ptr::NonNull;
16 use std::thread;
17 
18 #[derive(Debug)]
19 pub struct Surface {
20     view: Option<NonNull<Object>>,
21     render_layer: Mutex<metal::MetalLayer>,
22     swapchain_format: metal::MTLPixelFormat,
23     swapchain_format_desc: format::FormatDesc,
24     main_thread_id: thread::ThreadId,
25     // Useful for UI-intensive applications that are sensitive to
26     // window resizing.
27     pub present_with_transaction: bool,
28 }
29 
30 unsafe impl Send for Surface {}
31 unsafe impl Sync for Surface {}
32 
33 impl Surface {
new(view: Option<NonNull<Object>>, layer: metal::MetalLayer) -> Self34     pub fn new(view: Option<NonNull<Object>>, layer: metal::MetalLayer) -> Self {
35         Surface {
36             view,
37             render_layer: Mutex::new(layer),
38             swapchain_format: metal::MTLPixelFormat::Invalid,
39             swapchain_format_desc: format::FormatDesc {
40                 bits: 0,
41                 dim: (0, 0),
42                 packed: false,
43                 aspects: format::Aspects::empty(),
44             },
45             main_thread_id: thread::current().id(),
46             present_with_transaction: false,
47         }
48     }
49 
dispose(self)50     pub(crate) fn dispose(self) {
51         if let Some(view) = self.view {
52             let () = unsafe { msg_send![view.as_ptr(), release] };
53         }
54     }
55 
configure(&self, shared: &Shared, config: &w::SwapchainConfig) -> metal::MTLPixelFormat56     fn configure(&self, shared: &Shared, config: &w::SwapchainConfig) -> metal::MTLPixelFormat {
57         info!("build swapchain {:?}", config);
58 
59         let caps = &shared.private_caps;
60         let mtl_format = caps
61             .map_format(config.format)
62             .expect("unsupported backbuffer format");
63 
64         let render_layer = self.render_layer.lock();
65         let framebuffer_only = config.image_usage == image::Usage::COLOR_ATTACHMENT;
66         let display_sync = config.present_mode != w::PresentMode::IMMEDIATE;
67         let is_mac = caps.os_is_mac;
68         let can_set_next_drawable_timeout = if is_mac {
69             caps.has_version_at_least(10, 13)
70         } else {
71             caps.has_version_at_least(11, 0)
72         };
73         let can_set_display_sync = is_mac && caps.has_version_at_least(10, 13);
74         let drawable_size =
75             metal::CGSize::new(config.extent.width as f64, config.extent.height as f64);
76 
77         match config.composite_alpha_mode {
78             w::CompositeAlphaMode::OPAQUE => render_layer.set_opaque(true),
79             w::CompositeAlphaMode::POSTMULTIPLIED => render_layer.set_opaque(false),
80             _ => (),
81         }
82 
83         let device_raw = shared.device.lock();
84         unsafe {
85             // On iOS, unless the user supplies a view with a CAMetalLayer, we
86             // create one as a sublayer. However, when the view changes size,
87             // its sublayers are not automatically resized, and we must resize
88             // it here. The drawable size and the layer size don't correlate
89             #[cfg(target_os = "ios")]
90             {
91                 if let Some(view) = self.view {
92                     let main_layer: *mut Object = msg_send![view.as_ptr(), layer];
93                     let bounds: CGRect = msg_send![main_layer, bounds];
94                     let () = msg_send![*render_layer, setFrame: bounds];
95                 }
96             }
97             render_layer.set_device(&*device_raw);
98             render_layer.set_pixel_format(mtl_format);
99             render_layer.set_framebuffer_only(framebuffer_only as _);
100             render_layer.set_presents_with_transaction(self.present_with_transaction);
101 
102             // this gets ignored on iOS for certain OS/device combinations (iphone5s iOS 10.3)
103             let () = msg_send![*render_layer, setMaximumDrawableCount: config.image_count as u64];
104 
105             render_layer.set_drawable_size(drawable_size);
106             if can_set_next_drawable_timeout {
107                 let () = msg_send![*render_layer, setAllowsNextDrawableTimeout:false];
108             }
109             if can_set_display_sync {
110                 let () = msg_send![*render_layer, setDisplaySyncEnabled: display_sync];
111             }
112         };
113 
114         mtl_format
115     }
116 
dimensions(&self) -> w::Extent2D117     fn dimensions(&self) -> w::Extent2D {
118         let (size, scale): (metal::CGSize, metal::CGFloat) = match self.view {
119             Some(view) if !cfg!(target_os = "macos") => unsafe {
120                 let bounds: CGRect = msg_send![view.as_ptr(), bounds];
121                 let window: Option<NonNull<Object>> = msg_send![view.as_ptr(), window];
122                 let screen = window.and_then(|window| -> Option<NonNull<Object>> {
123                     msg_send![window.as_ptr(), screen]
124                 });
125                 match screen {
126                     Some(screen) => {
127                         let screen_space: *mut Object = msg_send![screen.as_ptr(), coordinateSpace];
128                         let rect: CGRect = msg_send![view.as_ptr(), convertRect:bounds toCoordinateSpace:screen_space];
129                         let scale_factor: metal::CGFloat = msg_send![screen.as_ptr(), nativeScale];
130                         (rect.size, scale_factor)
131                     }
132                     None => (bounds.size, 1.0),
133                 }
134             },
135             _ => unsafe {
136                 let render_layer_borrow = self.render_layer.lock();
137                 let render_layer = render_layer_borrow.as_ref();
138                 let bounds: CGRect = msg_send![render_layer, bounds];
139                 let contents_scale: metal::CGFloat = msg_send![render_layer, contentsScale];
140                 (bounds.size, contents_scale)
141             },
142         };
143         w::Extent2D {
144             width: (size.width * scale) as u32,
145             height: (size.height * scale) as u32,
146         }
147     }
148 }
149 
150 #[derive(Debug)]
151 pub struct SwapchainImage {
152     image: native::Image,
153     view: native::ImageView,
154     pub(crate) drawable: metal::MetalDrawable,
155     pub(crate) present_with_transaction: bool,
156 }
157 
158 unsafe impl Send for SwapchainImage {}
159 unsafe impl Sync for SwapchainImage {}
160 
161 impl Borrow<native::Image> for SwapchainImage {
borrow(&self) -> &native::Image162     fn borrow(&self) -> &native::Image {
163         &self.image
164     }
165 }
166 
167 impl Borrow<native::ImageView> for SwapchainImage {
borrow(&self) -> &native::ImageView168     fn borrow(&self) -> &native::ImageView {
169         &self.view
170     }
171 }
172 
173 impl w::Surface<Backend> for Surface {
supports_queue_family(&self, _queue_family: &QueueFamily) -> bool174     fn supports_queue_family(&self, _queue_family: &QueueFamily) -> bool {
175         // we only expose one family atm, so it's compatible
176         true
177     }
178 
capabilities(&self, physical_device: &PhysicalDevice) -> w::SurfaceCapabilities179     fn capabilities(&self, physical_device: &PhysicalDevice) -> w::SurfaceCapabilities {
180         let current_extent = if self.main_thread_id == thread::current().id() {
181             Some(self.dimensions())
182         } else {
183             warn!("Unable to get the current view dimensions on a non-main thread");
184             None
185         };
186 
187         let device_caps = &physical_device.shared.private_caps;
188 
189         let can_set_maximum_drawables_count =
190             device_caps.os_is_mac || device_caps.has_version_at_least(11, 2);
191         let can_set_display_sync =
192             device_caps.os_is_mac && device_caps.has_version_at_least(10, 13);
193 
194         w::SurfaceCapabilities {
195             present_modes: if can_set_display_sync {
196                 w::PresentMode::FIFO | w::PresentMode::IMMEDIATE
197             } else {
198                 w::PresentMode::FIFO
199             },
200             composite_alpha_modes: w::CompositeAlphaMode::OPAQUE
201                 | w::CompositeAlphaMode::POSTMULTIPLIED
202                 | w::CompositeAlphaMode::INHERIT,
203             //Note: this is hardcoded in `CAMetalLayer` documentation
204             image_count: if can_set_maximum_drawables_count {
205                 2..=3
206             } else {
207                 // 3 is the default in `CAMetalLayer` documentation
208                 // iOS 10.3 was tested to use 3 on iphone5s
209                 3..=3
210             },
211             current_extent,
212             extents: w::Extent2D {
213                 width: 4,
214                 height: 4,
215             }..=w::Extent2D {
216                 width: 4096,
217                 height: 4096,
218             },
219             max_image_layers: 1,
220             usage: image::Usage::COLOR_ATTACHMENT,
221             //| image::Usage::SAMPLED
222             //| image::Usage::TRANSFER_SRC
223             //| image::Usage::TRANSFER_DST,
224         }
225     }
226 
supported_formats(&self, _physical_device: &PhysicalDevice) -> Option<Vec<format::Format>>227     fn supported_formats(&self, _physical_device: &PhysicalDevice) -> Option<Vec<format::Format>> {
228         Some(vec![
229             format::Format::Bgra8Unorm,
230             format::Format::Bgra8Srgb,
231             format::Format::Rgba16Sfloat,
232         ])
233     }
234 }
235 
236 impl w::PresentationSurface<Backend> for Surface {
237     type SwapchainImage = SwapchainImage;
238 
configure_swapchain( &mut self, device: &Device, config: w::SwapchainConfig, ) -> Result<(), w::SwapchainError>239     unsafe fn configure_swapchain(
240         &mut self,
241         device: &Device,
242         config: w::SwapchainConfig,
243     ) -> Result<(), w::SwapchainError> {
244         if !image::Usage::COLOR_ATTACHMENT.contains(config.image_usage) {
245             warn!("Swapchain usage {:?} is not expected", config.image_usage);
246         }
247         #[cfg(target_os = "macos")]
248         {
249             if self.view.is_some() && self.main_thread_id != thread::current().id() {
250                 return Err(w::SwapchainError::WrongThread);
251             }
252         }
253         self.swapchain_format = self.configure(&device.shared, &config);
254         Ok(())
255     }
256 
unconfigure_swapchain(&mut self, _device: &Device)257     unsafe fn unconfigure_swapchain(&mut self, _device: &Device) {
258         self.swapchain_format = metal::MTLPixelFormat::Invalid;
259     }
260 
acquire_image( &mut self, _timeout_ns: u64, ) -> Result<(Self::SwapchainImage, Option<w::Suboptimal>), w::AcquireError>261     unsafe fn acquire_image(
262         &mut self,
263         _timeout_ns: u64, //TODO: use the timeout
264     ) -> Result<(Self::SwapchainImage, Option<w::Suboptimal>), w::AcquireError> {
265         let render_layer = self.render_layer.lock();
266         let (drawable, texture) = autoreleasepool(|| {
267             let drawable = render_layer.next_drawable().unwrap();
268             (drawable.to_owned(), drawable.texture().to_owned())
269         });
270         let size = render_layer.drawable_size();
271 
272         let sc_image = SwapchainImage {
273             image: native::Image {
274                 like: native::ImageLike::Texture(texture.clone()),
275                 kind: image::Kind::D2(size.width as u32, size.height as u32, 1, 1),
276                 mip_levels: 1,
277                 format_desc: self.swapchain_format_desc,
278                 shader_channel: Channel::Float,
279                 mtl_format: self.swapchain_format,
280                 mtl_type: metal::MTLTextureType::D2,
281             },
282             view: native::ImageView {
283                 texture,
284                 mtl_format: self.swapchain_format,
285             },
286             drawable,
287             present_with_transaction: self.present_with_transaction,
288         };
289         Ok((sc_image, None))
290     }
291 }
292