diff --git a/src/backend/metal/src/command.rs b/src/backend/metal/src/command.rs index b9186bf50db..91b3672ce72 100644 --- a/src/backend/metal/src/command.rs +++ b/src/backend/metal/src/command.rs @@ -1197,6 +1197,22 @@ impl CommandQueue { shared, } } + + fn wait(&mut self, wait_semaphores: I) + where + I: IntoIterator, + I::Item: Borrow, + { + for semaphore in wait_semaphores { + let sem = semaphore.borrow(); + if let Some(ref system) = sem.system { + system.wait(!0); + } + if let Some(swap_image) = sem.image_ready.lock().unwrap().take() { + swap_image.wait_until_ready(); + } + } + } } impl RawCommandQueue for CommandQueue { @@ -1208,17 +1224,20 @@ impl RawCommandQueue for CommandQueue { IC::Item: Borrow, { debug!("submitting with fence {:?}", fence); - // FIXME: wait for semaphores! - // FIXME: multiple buffers signaling! - let signal_block = if !submit.signal_semaphores.is_empty() { - let semaphores_copy: Vec<_> = submit.signal_semaphores.iter().map(|semaphore| { - semaphore.0 - }).collect(); + self.wait(submit.wait_semaphores.iter().map(|&(s, _)| s)); + + let system_semaphores = submit.signal_semaphores + .into_iter() + .filter_map(|semaphore| { + semaphore.system.clone() + }) + .collect::>(); + let signal_block = if !system_semaphores.is_empty() { //Note: careful with those `ConcreteBlock::copy()` calls! Some(ConcreteBlock::new(move |_cb: *mut ()| -> () { - for semaphore in semaphores_copy.iter() { - native::dispatch_semaphore_signal(*semaphore); + for semaphore in &system_semaphores { + semaphore.signal(); } }).copy()) } else { @@ -1283,18 +1302,19 @@ impl RawCommandQueue for CommandQueue { } } - fn present(&mut self, swapchains: IS, _wait_semaphores: IW) -> Result<(), ()> + fn present(&mut self, swapchains: IS, wait_semaphores: IW) -> Result<(), ()> where IS: IntoIterator, S: Borrow, IW: IntoIterator, IW::Item: Borrow, { + self.wait(wait_semaphores); + let queue = self.shared.queue.lock().unwrap(); let command_buffer = queue.raw.new_command_buffer(); for (swapchain, index) in swapchains { - // TODO: wait for semaphores debug!("presenting frame {}", index); let drawable = swapchain.borrow().take_drawable(index); command_buffer.present_drawable(&drawable); diff --git a/src/backend/metal/src/device.rs b/src/backend/metal/src/device.rs index c476ec328dd..cf9facf60de 100644 --- a/src/backend/metal/src/device.rs +++ b/src/backend/metal/src/device.rs @@ -264,6 +264,7 @@ impl PhysicalDevice { let private_caps = { let device = &*shared.device.lock().unwrap(); PrivateCapabilities { + exposed_queues: 1, resource_heaps: Self::supports_any(device, RESOURCE_HEAP_SUPPORT), argument_buffers: Self::supports_any(device, ARGUMENT_BUFFER_SUPPORT) && false, //TODO shared_textures: !Self::is_mac(device), @@ -328,7 +329,9 @@ impl hal::PhysicalDevice for PhysicalDevice { } let mut queue_group = hal::backend::RawQueueGroup::new(family); - queue_group.add_queue(command::CommandQueue::new(self.shared.clone())); + for _ in 0 .. self.private_caps.exposed_queues { + queue_group.add_queue(command::CommandQueue::new(self.shared.clone())); + } let device = Device { shared: self.shared.clone(), @@ -1325,7 +1328,16 @@ impl hal::Device for Device { } fn create_semaphore(&self) -> n::Semaphore { - unsafe { n::Semaphore(n::dispatch_semaphore_create(1)) } // Returns retained + n::Semaphore { + // Semaphore synchronization between command buffers of the same queue + // is useless, don't bother even creating one. + system: if self.private_caps.exposed_queues > 1 { + Some(n::SystemSemaphore::new()) + } else { + None + }, + image_ready: Arc::new(Mutex::new(None)), + } } fn create_descriptor_pool(&self, _max_sets: usize, descriptor_ranges: I) -> n::DescriptorPool @@ -1524,8 +1536,7 @@ impl hal::Device for Device { fn destroy_framebuffer(&self, _buffer: n::Framebuffer) { } - fn destroy_semaphore(&self, semaphore: n::Semaphore) { - unsafe { n::dispatch_release(semaphore.0) } + fn destroy_semaphore(&self, _semaphore: n::Semaphore) { } fn allocate_memory(&self, memory_type: hal::MemoryTypeId, size: u64) -> Result { diff --git a/src/backend/metal/src/lib.rs b/src/backend/metal/src/lib.rs index 0956f6e52fe..b32d177f096 100644 --- a/src/backend/metal/src/lib.rs +++ b/src/backend/metal/src/lib.rs @@ -198,6 +198,7 @@ impl hal::Backend for Backend { #[derive(Clone, Copy, Debug)] struct PrivateCapabilities { + exposed_queues: usize, resource_heaps: bool, argument_buffers: bool, shared_textures: bool, diff --git a/src/backend/metal/src/native.rs b/src/backend/metal/src/native.rs index f7b47e4e190..2a3e8675444 100644 --- a/src/backend/metal/src/native.rs +++ b/src/backend/metal/src/native.rs @@ -1,5 +1,6 @@ use Backend; use internal::Channel; +use window::SwapchainImage; use std::collections::HashMap; use std::ops::Range; @@ -210,10 +211,10 @@ unsafe impl Send for Sampler {} unsafe impl Sync for Sampler {} #[derive(Debug)] -pub struct Semaphore(pub(crate) *mut c_void); - -unsafe impl Send for Semaphore {} -unsafe impl Sync for Semaphore {} +pub struct Semaphore { + pub(crate) system: Option, + pub(crate) image_ready: Arc>>, +} #[derive(Debug)] pub struct Buffer { @@ -481,21 +482,50 @@ pub struct FenceInner { pub type Fence = Arc; extern "C" { - #[allow(dead_code)] - pub fn dispatch_semaphore_wait( + fn dispatch_semaphore_wait( semaphore: *mut c_void, timeout: u64, ) -> c_long; - pub fn dispatch_semaphore_signal( + fn dispatch_semaphore_signal( semaphore: *mut c_void, ) -> c_long; - pub fn dispatch_semaphore_create( + fn dispatch_semaphore_create( value: c_long, ) -> *mut c_void; - pub fn dispatch_release( + fn dispatch_release( object: *mut c_void, ); } + +#[derive(Clone, Debug)] +pub struct SystemSemaphore(*mut c_void); +unsafe impl Send for SystemSemaphore {} +unsafe impl Sync for SystemSemaphore {} + +impl Drop for SystemSemaphore { + fn drop(&mut self) { + unsafe { + dispatch_release(self.0) + } + } +} +impl SystemSemaphore { + pub(crate) fn new() -> Self { + SystemSemaphore(unsafe { + dispatch_semaphore_create(1) + }) + } + pub(crate) fn signal(&self) { + unsafe { + dispatch_semaphore_signal(self.0); + } + } + pub(crate) fn wait(&self, timeout: u64) { + unsafe { + dispatch_semaphore_wait(self.0, timeout); + } + } +} diff --git a/src/backend/metal/src/window.rs b/src/backend/metal/src/window.rs index 614424c682d..e638df0c586 100644 --- a/src/backend/metal/src/window.rs +++ b/src/backend/metal/src/window.rs @@ -3,8 +3,7 @@ use internal::Channel; use native; use device::{Device, PhysicalDevice}; -use std::mem; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use hal::{self, format, image}; use hal::{Backbuffer, SwapchainConfig}; @@ -25,7 +24,7 @@ pub struct Surface { pub(crate) apply_pixel_scale: bool, } -//TODO: double-check who needs it shared +#[derive(Debug)] pub(crate) struct SurfaceInner { pub(crate) nsview: *mut Object, pub(crate) render_layer: Mutex, @@ -40,29 +39,109 @@ impl Drop for SurfaceInner { } } +impl SurfaceInner { + fn next_frame<'a>(&self, frames: &'a [Frame]) -> (usize, MutexGuard<'a, FrameInner>) { + let _ap = AutoreleasePool::new(); + let layer_ref = self.render_layer.lock().unwrap(); + + let (drawable, texture_temp): (metal::Drawable, &metal::TextureRef) = unsafe { + let drawable: &metal::DrawableRef = msg_send![*layer_ref, nextDrawable]; + (drawable.to_owned(), msg_send![drawable, texture]) + }; + + let index = frames + .iter() + .position(|f| f.texture.as_ptr() == texture_temp.as_ptr()) + .expect("Surface lost?"); + + let mut frame = frames[index].inner.lock().unwrap(); + assert!(frame.drawable.is_none()); + frame.drawable = Some(drawable); + + debug!("Surface next frame is {}", index); + (index, frame) + } +} + +#[derive(Debug)] +struct FrameInner { + drawable: Option, + /// If there is a `drawable`, availability indicates if it's free for grabs. + /// If there is `None`, `available == false` means that the frame has already + /// been acquired and the `drawable` will appear at some point. + available: bool, + last_frame: usize, +} + #[derive(Debug)] struct Frame { - drawable: Mutex>, + inner: Mutex, texture: metal::Texture, } +unsafe impl Send for Frame {} +unsafe impl Sync for Frame {} + pub struct Swapchain { - frames: Vec, + frames: Arc>, surface: Arc, _size_pixels: (u64, u64), + last_frame: usize, } -unsafe impl Send for Swapchain {} -unsafe impl Sync for Swapchain {} - impl Swapchain { + /// Returns the drawable for the specified swapchain image index, + /// marks the index as free for future use. pub(crate) fn take_drawable(&self, index: hal::SwapImageIndex) -> metal::Drawable { - self.frames[index as usize].drawable + let mut frame = self + .frames[index as usize] + .inner .lock() - .unwrap() + .unwrap(); + assert!(!frame.available); + frame.available = true; + frame.drawable .take() .expect("Drawable has not been acquired!") } + + fn signal_sync(&self, sync: hal::FrameSync) { + match sync { + hal::FrameSync::Semaphore(semaphore) => { + if let Some(ref system) = semaphore.system { + system.signal(); + } + } + hal::FrameSync::Fence(fence) => { + *fence.mutex.lock().unwrap() = true; + } + } + } +} + +#[derive(Debug)] +pub struct SwapchainImage { + frames: Arc>, + surface: Arc, + index: hal::SwapImageIndex, +} + +impl SwapchainImage { + /// Waits until the specified swapchain index is available for rendering. + pub fn wait_until_ready(&self) { + // check the target frame first + { + let frame = self.frames[self.index as usize].inner.lock().unwrap(); + assert!(!frame.available); + if frame.drawable.is_some() { + return + } + } + // wait for new frames to come until we meet the chosen one + while self.surface.next_frame(&self.frames).0 != self.index as usize { + } + debug!("Swapchain image is ready") + } } @@ -174,7 +253,11 @@ impl Device { let texture: metal::Texture = msg_send![drawable, texture]; //HACK: not retaining the texture here Frame { - drawable: Mutex::new(None), + inner: Mutex::new(FrameInner { + drawable: None, + available: true, + last_frame: 0, + }), texture, } }) @@ -198,9 +281,10 @@ impl Device { .collect(); let swapchain = Swapchain { - frames, + frames: Arc::new(frames), surface: surface.inner.clone(), _size_pixels: (pixel_width, pixel_height), + last_frame: 0, }; (swapchain, Backbuffer::Images(images)) @@ -209,30 +293,52 @@ impl Device { impl hal::Swapchain for Swapchain { fn acquire_image(&mut self, sync: hal::FrameSync) -> Result { - let _ap = AutoreleasePool::new(); // for the drawable + let mut oldest_index = 0; + let mut oldest_frame = self.last_frame; + + self.last_frame += 1; + + for (index, frame_arc) in self.frames.iter().enumerate() { + let mut frame = frame_arc.inner.lock().unwrap(); + if frame.available && frame.drawable.is_some() { + frame.available = false; + frame.last_frame = self.last_frame; + self.signal_sync(sync); + return Ok(index as _); + } + if frame.last_frame < oldest_frame { + oldest_frame = frame.last_frame; + oldest_index = index; + } + } - unsafe { + let blocking = false; + + let (index, mut frame) = if blocking { + self.surface.next_frame(&self.frames) + } else { match sync { hal::FrameSync::Semaphore(semaphore) => { - // FIXME: this is definitely wrong - native::dispatch_semaphore_signal(semaphore.0); - }, - hal::FrameSync::Fence(_fence) => unimplemented!(), + let mut sw_image = semaphore.image_ready.lock().unwrap(); + assert!(sw_image.is_none()); + *sw_image = Some(SwapchainImage { + frames: self.frames.clone(), + surface: self.surface.clone(), + index: oldest_index as _, + }); + } + hal::FrameSync::Fence(_fence) => { + //TODO: need presentation handlers always created and setting a bool + unimplemented!() + } } - } - let layer_ref = self.surface.render_layer.lock().unwrap(); - let (drawable, texture_temp): (metal::Drawable, &metal::TextureRef) = unsafe { - let drawable: &metal::DrawableRef = msg_send![*layer_ref, nextDrawable]; - (drawable.to_owned(), msg_send![drawable, texture]) + let frame = self.frames[oldest_index].inner.lock().unwrap(); + (oldest_index, frame) }; - let index = self.frames - .iter() - .position(|f| f.texture.as_ptr() == texture_temp.as_ptr()) - .expect("Surface lost?"); - let old = mem::replace(&mut *self.frames[index].drawable.lock().unwrap(), Some(drawable)); - assert!(old.is_none()); + frame.last_frame = self.last_frame; + frame.available = false; Ok(index as _) }