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