rafx_api/extra/
swapchain_helper.rs

1use crate::{
2    RafxCommandBuffer, RafxDeviceContext, RafxError, RafxFence, RafxFormat,
3    RafxPresentSuccessResult, RafxQueue, RafxResult, RafxSemaphore, RafxSwapchain,
4    RafxSwapchainColorSpace, RafxSwapchainDef, RafxSwapchainImage, RafxTexture,
5};
6use crossbeam_channel::{Receiver, Sender};
7use std::sync::atomic::{AtomicUsize, Ordering};
8use std::sync::{Arc, Mutex};
9
10/// May be implemented to get callbacks related to the swapchain being created/destroyed. This is
11/// optional.
12pub trait RafxSwapchainEventListener {
13    /// Called whenever the swapchain needs to be created (the first time, and in cases where the
14    /// swapchain needs to be recreated)
15    fn swapchain_created(
16        &mut self,
17        device_context: &RafxDeviceContext,
18        swapchain: &RafxSwapchain,
19    ) -> RafxResult<()>;
20
21    /// Called whenever the swapchain will be destroyed (when VkSurface is dropped, and also in cases
22    /// where the swapchain needs to be recreated)
23    fn swapchain_destroyed(
24        &mut self,
25        device_context: &RafxDeviceContext,
26        swapchain: &RafxSwapchain,
27    ) -> RafxResult<()>;
28}
29
30// This is shared state held within an Arc between the SwapchainHelper and the PresentableFrame.
31// It contains the swapchain, sync primitives required to wait for the GPU to complete work, and
32// sync primitives to allow the helper/presentable frame to communicate.
33struct RafxSwapchainHelperSharedState {
34    global_frame_index: AtomicUsize,
35    sync_frame_index: AtomicUsize,
36    image_available_semaphores: Vec<RafxSemaphore>,
37    render_finished_semaphores: Vec<RafxSemaphore>,
38    in_flight_fences: Vec<RafxFence>,
39    result_tx: Sender<RafxResult<RafxPresentSuccessResult>>,
40    result_rx: Receiver<RafxResult<RafxPresentSuccessResult>>,
41    // Arc so that we can move the swapchain to a new RafxSwapchainHelperSharedState
42    swapchain: Arc<Mutex<RafxSwapchain>>,
43}
44
45impl RafxSwapchainHelperSharedState {
46    fn new(
47        device_context: &RafxDeviceContext,
48        swapchain: Arc<Mutex<RafxSwapchain>>,
49    ) -> RafxResult<Self> {
50        let image_count = swapchain.lock().unwrap().image_count();
51        let mut image_available_semaphores = Vec::with_capacity(image_count);
52        let mut render_finished_semaphores = Vec::with_capacity(image_count);
53        let mut in_flight_fences = Vec::with_capacity(image_count);
54
55        for _ in 0..image_count {
56            image_available_semaphores.push(device_context.create_semaphore()?);
57            render_finished_semaphores.push(device_context.create_semaphore()?);
58            in_flight_fences.push(device_context.create_fence()?);
59        }
60
61        let (result_tx, result_rx) = crossbeam_channel::unbounded();
62
63        Ok(RafxSwapchainHelperSharedState {
64            global_frame_index: AtomicUsize::new(0),
65            sync_frame_index: AtomicUsize::new(0),
66            image_available_semaphores,
67            render_finished_semaphores,
68            in_flight_fences,
69            result_tx,
70            result_rx,
71            swapchain,
72        })
73    }
74}
75
76/// Represents an acquired image from a swapchain. It can move between threads and be sumitted by
77/// any thread. The swapchain helper will not provide another image until this one is presented.
78///
79/// To ease error handling, the swapchain may be submitted with an error. This error will be
80/// returned on the next attempt to acquire a swapchain image (i.e. the main thread).
81pub struct RafxPresentableFrame {
82    // State that's shared among the swapchain helper and the presentable frame. Mostly immutable,
83    // but the swapchain itself is stored in it, wrapped by a mutex
84    shared_state: Option<Arc<RafxSwapchainHelperSharedState>>,
85    swapchain_image: RafxSwapchainImage,
86    global_frame_index: usize,
87    sync_frame_index: usize,
88}
89
90impl RafxPresentableFrame {
91    /// An index that starts at 0 on the first present and increments every frame, wrapping back to
92    /// 0 after each swapchain image has been presented once. (See image_count on
93    /// RafxSwapchainHelper). WARNING: This is not always the returned swapchain image. Swapchain
94    /// images may be acquired in any order.
95    pub fn rotating_frame_index(&self) -> usize {
96        // The sync_frame_index can be used as-is for this purpose
97        self.sync_frame_index
98    }
99
100    pub fn incrementing_frame_index(&self) -> usize {
101        self.global_frame_index
102    }
103
104    /// Returns the acquired swapchain image
105    pub fn swapchain_texture(&self) -> &RafxTexture {
106        &self.swapchain_image.texture
107    }
108
109    /// Returns the swapchain. The swapchain helper code generally assumes ownership/management of
110    /// the swapchain. Messing with it may invalidate assumptions in the swapchain helper code, so
111    /// mutating operations on it are not recommended
112    pub fn swapchain(&self) -> &Arc<Mutex<RafxSwapchain>> {
113        &self.shared_state.as_ref().unwrap().swapchain
114    }
115
116    /// Submits the given command buffers and schedules the swapchain image to be presented after
117    /// their completion
118    pub fn present(
119        mut self,
120        queue: &RafxQueue,
121        command_buffers: &[&RafxCommandBuffer],
122    ) -> RafxResult<RafxPresentSuccessResult> {
123        log::trace!(
124            "Calling RafxPresentableFrame::present with {} command buffers",
125            command_buffers.len()
126        );
127        let result = self.do_present(queue, command_buffers);
128
129        // Let the shared state arc drop, this will unblock the next frame
130        let shared_state = self.shared_state.take().unwrap();
131        shared_state.result_tx.send(result.clone()).unwrap();
132
133        result
134    }
135
136    /// Presents the current swapchain image and returns the given error during the next image
137    /// acquisition attempt
138    pub fn present_with_error(
139        mut self,
140        queue: &RafxQueue,
141        error: RafxError,
142    ) {
143        log::warn!(
144            "Calling RafxPresentableFrame::present_with_error {:?}",
145            error
146        );
147
148        //TODO: AFAIK there is no way to simply trigger the semaphore and skip calling do_present
149        // with no command buffers. The downside of doing this is that we end up with both the
150        // end user's result and a result from do_present and have no sensible way of merging them
151
152        //TODO: Might be able to do this without presenting by having command buffers that can be
153        // submitted that trigger the semaphore.
154        let _ = self.do_present(queue, &mut []);
155
156        // Let the shared state arc drop, this will unblock the next frame
157        let shared_state = self.shared_state.take().unwrap();
158        shared_state.result_tx.send(Err(error)).unwrap();
159    }
160
161    pub fn do_present(
162        &mut self,
163        queue: &RafxQueue,
164        command_buffers: &[&RafxCommandBuffer],
165    ) -> RafxResult<RafxPresentSuccessResult> {
166        // A present can only occur using the result from the previous acquire_next_image call
167        let shared_state = self.shared_state.as_ref().unwrap();
168        let sync_frame_index = shared_state.sync_frame_index.load(Ordering::Relaxed);
169        assert!(self.sync_frame_index == sync_frame_index);
170
171        let frame_fence = &shared_state.in_flight_fences[sync_frame_index];
172        let wait_semaphores = [&shared_state.image_available_semaphores[sync_frame_index]];
173        let signal_semaphores = [&shared_state.render_finished_semaphores[sync_frame_index]];
174
175        queue.submit(
176            command_buffers,
177            &wait_semaphores,
178            &signal_semaphores,
179            Some(frame_fence),
180        )?;
181
182        let swapchain = shared_state.swapchain.lock().unwrap();
183
184        //NOTE: Don't bail for error until we update the frame indices
185        let result = queue.present(
186            &*swapchain,
187            &signal_semaphores,
188            self.swapchain_image.swapchain_image_index,
189        );
190
191        shared_state.sync_frame_index.store(
192            (sync_frame_index + 1) % shared_state.in_flight_fences.len(),
193            Ordering::Relaxed,
194        );
195        shared_state
196            .global_frame_index
197            .fetch_add(1, Ordering::Relaxed);
198
199        result
200    }
201}
202
203impl Drop for RafxPresentableFrame {
204    fn drop(&mut self) {
205        if self.shared_state.is_some() {
206            self.shared_state.take().unwrap().result_tx.send(Err(RafxError::StringError("SwapchainHelperPresentableFrame was dropped without calling present or present_with_error".to_string()))).unwrap();
207        }
208    }
209}
210
211pub enum TryAcquireNextImageResult {
212    Success(RafxPresentableFrame),
213
214    // While this is an "error" being returned as success, it is expected and recoverable while
215    // other errors usually aren't. This way the ? operator can still be used to bail out the
216    // unrecoverable errors and the different flavors of "success" should be explicitly handled
217    // in a match
218    DeviceReset,
219}
220
221pub struct RafxSwapchainHelper {
222    device_context: RafxDeviceContext,
223    shared_state: Option<Arc<RafxSwapchainHelperSharedState>>,
224    format: RafxFormat,
225    color_space: RafxSwapchainColorSpace,
226    swapchain_def: RafxSwapchainDef,
227    image_count: usize,
228
229    // False initially, set to true when we produce the first presentable frame to indicate that
230    // future frames need to wait for its result to be sent via the result_tx/result_rx channel
231    expect_result_from_previous_frame: bool,
232}
233
234impl RafxSwapchainHelper {
235    pub fn new(
236        device_context: &RafxDeviceContext,
237        swapchain: RafxSwapchain,
238        mut event_listener: Option<&mut dyn RafxSwapchainEventListener>,
239    ) -> RafxResult<Self> {
240        let format = swapchain.format();
241        let color_space = swapchain.color_space();
242        let image_count = swapchain.image_count();
243        let swapchain_def = swapchain.swapchain_def().clone();
244
245        let shared_state = Arc::new(RafxSwapchainHelperSharedState::new(
246            device_context,
247            Arc::new(Mutex::new(swapchain)),
248        )?);
249
250        if let Some(event_listener) = event_listener.as_mut() {
251            let swapchain = shared_state.swapchain.lock().unwrap();
252            event_listener.swapchain_created(device_context, &*swapchain)?;
253        }
254
255        Ok(RafxSwapchainHelper {
256            device_context: device_context.clone(),
257            shared_state: Some(shared_state),
258            format,
259            color_space,
260            image_count,
261            swapchain_def,
262            expect_result_from_previous_frame: false,
263        })
264    }
265
266    pub fn destroy(
267        &mut self,
268        mut event_listener: Option<&mut dyn RafxSwapchainEventListener>,
269    ) -> RafxResult<()> {
270        log::debug!("Destroying swapchain helper");
271
272        // If there is a frame in flight, wait until it is submitted. This hopefully means we are
273        // the only holder of this arc and we can unwrap it
274        self.wait_until_previous_frame_submitted()?;
275
276        if let Some(shared_state) = self.shared_state.take() {
277            let begin_wait_time = rafx_base::Instant::now();
278            while Arc::strong_count(&shared_state) > 1 {
279                // It's possible the previous frame has not finished dropping. Wait until this
280                // occurs.
281                if (rafx_base::Instant::now() - begin_wait_time).as_secs_f32() > 1.0 {
282                    // Bail early, we won't properly clean up
283                    log::error!("A presentable frame was submitted but still isn't dropped. Can't clean up the swapchain");
284                    break;
285                }
286            }
287
288            match Arc::try_unwrap(shared_state) {
289                Ok(shared_state) => {
290                    log::debug!("wait for all fences to complete");
291                    let fences: Vec<_> = shared_state.in_flight_fences.iter().map(|x| x).collect();
292                    self.device_context.wait_for_fences(&fences)?;
293
294                    if let Some(event_listener) = event_listener.as_mut() {
295                        let old_swapchain = shared_state.swapchain.lock().unwrap();
296                        log::debug!("destroy the swapchain");
297                        event_listener
298                            .swapchain_destroyed(&self.device_context, &*old_swapchain)?;
299                    }
300                }
301                Err(_arc) => {
302                    let error = "The swapchain could not be destroyed, a PresentableFrame exists that is using it";
303                    log::error!("{}", error);
304                    return Err(error)?;
305                }
306            }
307        }
308
309        Ok(())
310    }
311
312    pub fn format(&self) -> RafxFormat {
313        self.format
314    }
315
316    pub fn color_space(&self) -> RafxSwapchainColorSpace {
317        self.color_space
318    }
319
320    pub fn image_count(&self) -> usize {
321        self.image_count
322    }
323
324    pub fn swapchain_def(&self) -> &RafxSwapchainDef {
325        &self.swapchain_def
326    }
327
328    pub fn wait_until_previous_frame_submitted(
329        &mut self
330    ) -> RafxResult<Option<RafxPresentSuccessResult>> {
331        if self.expect_result_from_previous_frame {
332            self.expect_result_from_previous_frame = false;
333
334            Ok(Some(
335                self.shared_state
336                    .as_ref()
337                    .unwrap()
338                    .result_rx
339                    .recv()
340                    .unwrap()?,
341            ))
342        } else {
343            Ok(None)
344        }
345    }
346
347    pub fn wait_until_sync_frame_idle(
348        &mut self,
349        sync_frame_index: usize,
350    ) -> RafxResult<()> {
351        self.shared_state.as_ref().unwrap().in_flight_fences[sync_frame_index].wait()
352    }
353
354    pub fn acquire_next_image(
355        &mut self,
356        window_width: u32,
357        window_height: u32,
358        event_listener: Option<&mut dyn RafxSwapchainEventListener>,
359    ) -> RafxResult<RafxPresentableFrame> {
360        //
361        // Block until the previous frame completes being submitted to GPU
362        //
363        let previous_frame_result = self.wait_until_previous_frame_submitted();
364
365        //
366        // Block until the next sync frame index finishes submitting. It's not safe to modify
367        // resources associated with it until the last execution of it fully completes.
368        //
369        let next_sync_frame = self
370            .shared_state
371            .as_ref()
372            .unwrap()
373            .sync_frame_index
374            .load(Ordering::Relaxed);
375        self.wait_until_sync_frame_idle(next_sync_frame)?;
376
377        //
378        // Check the result of the previous frame. Possible outcomes:
379        //  - Previous frame was successful: immediately try rendering again with the same swapchain
380        //  - We've never tried rendering before: try rendering with the initial swapchain
381        //  - Previous frame failed but resolvable by rebuilding the swapchain - skip trying to
382        //    render again with the same swapchain
383        //  - Previous frame failed with unrecoverable error: bail
384        //
385        let rebuild_swapchain = match &previous_frame_result {
386            Ok(result) => {
387                match result {
388                    // We tried to render, check the previous render result
389                    Some(result) => match result {
390                        RafxPresentSuccessResult::Success => false,
391                        RafxPresentSuccessResult::SuccessSuboptimal => {
392                            log::debug!("Swapchain is sub-optimal, rebuilding");
393                            //TODO: This can occur persistently when the app is minimized, so ignore
394                            // if the size has not changed. However, we could also consider adding
395                            // a counter to limit the frequency. (A sensible case for this is
396                            // resizing a window - to avoid rebuilding swapchain every frame during
397                            // the resize.
398                            if window_height != self.swapchain_def.height
399                                || window_width != self.swapchain_def.width
400                            {
401                                true
402                            } else {
403                                false
404                            }
405                        }
406                        RafxPresentSuccessResult::DeviceReset => {
407                            log::debug!("Swapchain sent DeviceReset, rebuilding");
408                            true
409                        }
410                    },
411                    // We have not rendered yet, so assume the swapchain we have is fine
412                    None => false,
413                }
414            }
415            // An unrecoverable failure occurred, bail
416            Err(e) => return Err(e.clone()),
417        };
418
419        //
420        // If we don't have any reason yet to rebuild the swapchain, try to render
421        //
422        if !rebuild_swapchain {
423            // This case is taken if we have never rendered a frame or if the previous render was successful
424            let result = self.try_acquire_next_image(window_width, window_height)?;
425            if let TryAcquireNextImageResult::Success(presentable_frame) = result {
426                return Ok(presentable_frame);
427            }
428
429            // if not successful (TryAcquireNextImageResult::DeviceReset), fall through to
430            // try to recreate the swapchain
431        };
432
433        //
434        // Rebuild the swapchain and try again. Any failure after a rebuild will be fatal
435        //
436        self.rebuild_swapchain(window_width, window_height, event_listener)?;
437
438        let result = self.try_acquire_next_image(window_width, window_height)?;
439        if let TryAcquireNextImageResult::Success(presentable_frame) = result {
440            Ok(presentable_frame)
441        } else {
442            Err(RafxError::StringError(
443                "Failed to recreate swapchain".to_string(),
444            ))
445        }
446    }
447
448    pub fn try_acquire_next_image(
449        &mut self,
450        window_width: u32,
451        window_height: u32,
452    ) -> RafxResult<TryAcquireNextImageResult> {
453        match self.do_try_acquire_next_image(window_width, window_height) {
454            #[cfg(feature = "rafx-vulkan")]
455            Err(RafxError::VkError(ash::vk::Result::ERROR_OUT_OF_DATE_KHR)) => {
456                Ok(TryAcquireNextImageResult::DeviceReset)
457            }
458            result @ _ => result,
459        }
460    }
461
462    fn do_try_acquire_next_image(
463        &mut self,
464        window_width: u32,
465        window_height: u32,
466    ) -> RafxResult<TryAcquireNextImageResult> {
467        // If a frame is still outstanding from a previous acquire_next_swapchain_image call, wait
468        // to receive the result of that frame. If the result was an error, return that error now.
469        // This allows us to handle errors from the render thread in the main thread. This wait is
470        // only blocking on getting the previous frame submitted. It's possible the GPU is still
471        // processing it, and even the frame before it.
472        self.wait_until_previous_frame_submitted()?;
473
474        // check if window size changed and we are out of date
475        let shared_state = self.shared_state.as_ref().unwrap();
476        let mut swapchain = shared_state.swapchain.lock().unwrap();
477        let swapchain_def = swapchain.swapchain_def();
478
479        if swapchain_def.width != window_width || swapchain_def.height != window_height {
480            log::debug!("Force swapchain rebuild due to changed window size");
481            return Ok(TryAcquireNextImageResult::DeviceReset);
482        }
483
484        // This index iterates from 0..max_num_frames, wrapping around to 0. This ensures we use a
485        // different set of sync primitives per frame in flight
486        let sync_frame_index = shared_state.sync_frame_index.load(Ordering::Relaxed);
487        let global_frame_index = shared_state.global_frame_index.load(Ordering::Relaxed);
488
489        // If this swapchain image is still being process on the GPU, block until it is flushed
490        let frame_fence = &shared_state.in_flight_fences[sync_frame_index];
491        self.device_context.wait_for_fences(&[frame_fence]).unwrap();
492
493        // Acquire the next image and signal the image available semaphore when it's ready to use
494        let image_available_semaphore = &shared_state.image_available_semaphores[sync_frame_index];
495        let swapchain_image = swapchain.acquire_next_image_semaphore(image_available_semaphore)?;
496
497        self.expect_result_from_previous_frame = true;
498
499        Ok(TryAcquireNextImageResult::Success(RafxPresentableFrame {
500            shared_state: Some(shared_state.clone()),
501            swapchain_image,
502            sync_frame_index,
503            global_frame_index,
504        }))
505    }
506
507    fn rebuild_swapchain(
508        &mut self,
509        window_width: u32,
510        window_height: u32,
511        mut event_listener: Option<&mut dyn RafxSwapchainEventListener>,
512    ) -> RafxResult<()> {
513        log::info!("Rebuild Swapchain");
514
515        let shared_state = self.shared_state.take().unwrap();
516        {
517            let mut swapchain = shared_state.swapchain.lock().unwrap();
518            if let Some(event_listener) = event_listener.as_mut() {
519                event_listener.swapchain_destroyed(&self.device_context, &*swapchain)?;
520            }
521
522            let mut swapchain_def = swapchain.swapchain_def().clone();
523            swapchain_def.width = window_width;
524            swapchain_def.height = window_height;
525
526            swapchain.rebuild(&swapchain_def)?;
527
528            if let Some(event_listener) = event_listener.as_mut() {
529                event_listener.swapchain_created(&self.device_context, &swapchain)?;
530            }
531
532            self.format = swapchain.format();
533            self.image_count = swapchain.image_count();
534            self.swapchain_def = swapchain_def;
535        }
536
537        self.shared_state = Some(Arc::new(RafxSwapchainHelperSharedState::new(
538            &self.device_context,
539            shared_state.swapchain.clone(),
540        )?));
541        Ok(())
542    }
543}
544
545impl Drop for RafxSwapchainHelper {
546    fn drop(&mut self) {
547        // This will be a no-op if destroy() was already called
548        self.destroy(None).unwrap();
549    }
550}