Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / client / src / gpu / swapchain.rs

use std::ptr::{null, null_mut};
use std::sync::Arc;
use std::time::{Duration, Instant};

use volcanic::*;

use super::pacing::{self, Clock, Pacer};
use super::resources::view_create_info;
use super::{Device, Error, Result, Surface, check, create, zeroed};

/// Present stages that tell when an image reached the display, most accurate first.
const DISPLAY_STAGES: [VkPresentStageFlagsEXT; 3] = [
    VkPresentStageFlagsEXT::IMAGE_FIRST_PIXEL_VISIBLE_EXT,
    VkPresentStageFlagsEXT::IMAGE_FIRST_PIXEL_OUT_EXT,
    VkPresentStageFlagsEXT::REQUEST_DEQUEUED_EXT,
];
/// Time domains that can be calibrated against `Instant`, preferred first.
const TIME_DOMAINS: [VkTimeDomainKHR; 3] = [
    VkTimeDomainKHR::ClockMonotonic,
    VkTimeDomainKHR::SwapchainLocalExt,
    VkTimeDomainKHR::PresentStageLocalExt,
];
const RECALIBRATION_PRESENTS: u64 = 1024;

/// A present mode that renders like the others, unlike the shared presentable image modes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PresentMode {
    Immediate,
    Mailbox,
    Fifo,
    FifoRelaxed,
    FifoLatestReady,
}

impl PresentMode {
    pub const ALL: [Self; 5] = [
        Self::Immediate,
        Self::Mailbox,
        Self::Fifo,
        Self::FifoRelaxed,
        Self::FifoLatestReady,
    ];

    /// Whether images queue up for refresh cycles, which present timing schedules.
    pub fn is_fifo(self) -> bool {
        !matches!(self, Self::Immediate | Self::Mailbox)
    }

    fn vk(self) -> VkPresentModeKHR {
        match self {
            Self::Immediate => VkPresentModeKHR::Immediate,
            Self::Mailbox => VkPresentModeKHR::Mailbox,
            Self::Fifo => VkPresentModeKHR::Fifo,
            Self::FifoRelaxed => VkPresentModeKHR::FifoRelaxed,
            Self::FifoLatestReady => VkPresentModeKHR::FifoLatestReady,
        }
    }
}

/// How the swapchain presents frames.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Presentation {
    pub mode: PresentMode,
    /// Pace frames with present timing, which needs a FIFO present mode.
    pub pacing: bool,
}

/// An acquired swapchain image.
#[derive(Clone, Copy)]
pub struct Frame {
    pub index: u32,
    pub image: VkImage,
    pub view: VkImageView,
    /// Signal this from the frame's last submission; presentation waits on it.
    pub render_finished: VkSemaphore,
}

pub struct Swapchain {
    handle: VkSwapchainKHR,
    images: Vec<VkImage>,
    views: Vec<VkImageView>,
    render_finished: Vec<VkSemaphore>,
    extent: VkExtent2D,
    format: VkFormat,
    presentation: Presentation,
    /// The present modes the surface offers and the device may use, in `PresentMode::ALL` order.
    present_modes: Vec<PresentMode>,
    timing_support: Option<TimingSupport>,
    timing: Option<Timing>,
    surface: Surface,
    device: Arc<Device>,
}

/// What the surface offers for `VK_EXT_present_timing`.
#[derive(Clone, Copy)]
struct TimingSupport {
    display_stage: VkPresentStageFlagsEXT,
    absolute: bool,
}

/// Present timing of a swapchain created with `VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT`.
struct Timing {
    support: TimingSupport,
    /// The time domain and its id.
    domain: (VkTimeDomainKHR, u64),
    /// The time domain's clocks at the queue operations end and display stages.
    clocks: [Clock; 2],
    domains_counter: u64,
    properties_counter: u64,
    next_id: u64,
    /// Presents whose timing results are still in the swapchain's results queue.
    outstanding: u32,
    queue_size: u32,
    pacer: Pacer,
}

impl Swapchain {
    pub fn new(
        device: &Arc<Device>,
        surface: Surface,
        extent: VkExtent2D,
        presentation: Presentation,
    ) -> Result<Self> {
        let mut swapchain = Self {
            handle: VK_NULL_HANDLE,
            images: Vec::new(),
            views: Vec::new(),
            render_finished: Vec::new(),
            extent,
            format: surface_format(device, &surface)?,
            presentation: Presentation {
                mode: PresentMode::Fifo,
                pacing: false,
            },
            present_modes: present_modes(device, &surface)?,
            timing_support: timing_support(device, &surface),
            timing: None,
            surface,
            device: device.clone(),
        };
        swapchain.configure(presentation);
        swapchain.recreate(extent)?;
        Ok(swapchain)
    }

    pub fn extent(&self) -> VkExtent2D {
        self.extent
    }

    pub fn format(&self) -> VkFormat {
        self.format
    }

    pub fn present_modes(&self) -> &[PresentMode] {
        &self.present_modes
    }

    pub fn supports_pacing(&self) -> bool {
        self.timing_support.is_some()
    }

    pub fn presentation(&self) -> Presentation {
        self.presentation
    }

    /// Switches to the available presentation closest to `requested`, and returns whether the
    /// swapchain must be recreated for it. FIFO is always available.
    pub fn configure(&mut self, requested: Presentation) -> bool {
        let fallbacks: &[PresentMode] = if requested.mode.is_fifo() {
            &[]
        } else {
            &[PresentMode::Mailbox, PresentMode::Immediate]
        };
        let mode = [requested.mode]
            .iter()
            .chain(fallbacks)
            .copied()
            .find(|mode| self.present_modes.contains(mode))
            .unwrap_or(PresentMode::Fifo);
        let pacing = requested.pacing && mode.is_fifo() && self.supports_pacing();
        let presentation = Presentation { mode, pacing };
        std::mem::replace(&mut self.presentation, presentation) != presentation
    }

    /// When pacing, plans the next frame and returns when it will be displayed.
    pub fn begin_frame(&mut self, min_interval: Duration) -> Option<Instant> {
        let timing = self.timing.as_mut()?;
        timing.pacer.begin(timing.next_id, min_interval)
    }

    /// When pacing, when to start rendering the frame after the current one.
    pub fn next_frame(&self) -> Option<Instant> {
        self.timing.as_ref()?.pacer.wake()
    }

    /// Reports time the current frame spent waiting for the GPU or for a swapchain image.
    pub fn waited(&mut self, duration: Duration) {
        if let Some(timing) = &mut self.timing {
            timing.pacer.waited(duration);
        }
    }

    pub fn recreate(&mut self, requested: VkExtent2D) -> Result<()> {
        let device = self.device.clone();
        let instance = &device.instance.commands;
        device.wait_idle()?;

        let mut capabilities = zeroed::<VkSurfaceCapabilitiesKHR>();
        check(unsafe {
            instance.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
                device.physical,
                self.surface.handle,
                &mut capabilities,
            )
        })?;
        let Presentation { mode, pacing } = self.presentation;
        let timing_flags = if pacing {
            VkSwapchainCreateFlagsKHR::PRESENT_TIMING_EXT
                | VkSwapchainCreateFlagsKHR::PRESENT_ID_2_KHR
        } else {
            VkSwapchainCreateFlagsKHR::empty()
        };

        self.extent = if capabilities.currentExtent.width == u32::MAX {
            VkExtent2D {
                width: requested.width.clamp(
                    capabilities.minImageExtent.width,
                    capabilities.maxImageExtent.width,
                ),
                height: requested.height.clamp(
                    capabilities.minImageExtent.height,
                    capabilities.maxImageExtent.height,
                ),
            }
        } else {
            capabilities.currentExtent
        };
        let mut image_count = capabilities.minImageCount + 1;
        if capabilities.maxImageCount > 0 {
            image_count = image_count.min(capabilities.maxImageCount);
        }
        // Pipelines never write alpha, so every mode shows an opaque image
        let composite_alpha = [
            VkCompositeAlphaFlagsKHR::OPAQUE_KHR,
            VkCompositeAlphaFlagsKHR::PRE_MULTIPLIED_KHR,
            VkCompositeAlphaFlagsKHR::POST_MULTIPLIED_KHR,
            VkCompositeAlphaFlagsKHR::INHERIT_KHR,
        ]
        .into_iter()
        .find(|&mode| capabilities.supportedCompositeAlpha.contains(mode))
        .unwrap_or(VkCompositeAlphaFlagsKHR::OPAQUE_KHR);

        let old = self.handle;
        let info = VkSwapchainCreateInfoKHR {
            sType: VkStructureType::SwapchainCreateInfoKhr,
            flags: timing_flags,
            surface: self.surface.handle,
            minImageCount: image_count,
            imageFormat: self.format,
            imageColorSpace: VkColorSpaceKHR::SrgbNonlinear,
            imageExtent: self.extent,
            imageArrayLayers: 1,
            imageUsage: VkImageUsageFlags::COLOR_ATTACHMENT
                | VkImageUsageFlags::TRANSFER_DST
                | VkImageUsageFlags::TRANSFER_SRC,
            imageSharingMode: VkSharingMode::Exclusive,
            preTransform: capabilities.currentTransform,
            compositeAlpha: composite_alpha,
            presentMode: mode.vk(),
            clipped: VK_TRUE,
            oldSwapchain: old,
            ..zeroed()
        };
        let commands = &device.commands;
        self.timing = None;
        check(unsafe {
            commands.vkCreateSwapchainKHR(device.handle, &info, null(), &mut self.handle)
        })?;
        self.destroy_images();
        if old != VK_NULL_HANDLE {
            unsafe { commands.vkDestroySwapchainKHR(device.handle, old, null()) };
        }

        self.images = super::enumerate(|count, items| unsafe {
            commands.vkGetSwapchainImagesKHR(device.handle, self.handle, count, items)
        })?;
        for &image in &self.images {
            let view_info = view_create_info(image, self.format, VkImageAspectFlags::COLOR);
            self.views.push(create(|view| unsafe {
                commands.vkCreateImageView(device.handle, &view_info, null(), view)
            })?);
            self.render_finished.push(device.create_semaphore()?);
        }
        if let Some(support) = self.timing_support.filter(|_| pacing) {
            self.timing = Timing::new(&device, self.handle, support, 4 * self.images.len() as u32)
                .inspect_err(|error| tracing::warn!("frame pacing unavailable: {error}"))
                .ok();
        }
        tracing::debug!(
            "swapchain {}x{} with {} images, {mode:?}",
            self.extent.width,
            self.extent.height,
            self.images.len()
        );
        Ok(())
    }

    /// Returns `None` when the swapchain is out of date and must be recreated.
    pub fn acquire(&mut self, image_available: VkSemaphore) -> Result<Option<Frame>> {
        let mut index = 0;
        let result = unsafe {
            self.device.commands.vkAcquireNextImageKHR(
                self.device.handle,
                self.handle,
                u64::MAX,
                image_available,
                VK_NULL_HANDLE,
                &mut index,
            )
        };
        match result {
            VkResult::Success | VkResult::SuboptimalKhr => {
                let i = index as usize;
                Ok(Some(Frame {
                    index,
                    image: self.images[i],
                    view: self.views[i],
                    render_finished: self.render_finished[i],
                }))
            }
            VkResult::ErrorOutOfDateKhr => Ok(None),
            error => Err(Error::Vulkan(error)),
        }
    }

    /// Returns `false` when the swapchain should be recreated.
    pub fn present(&mut self, frame: &Frame) -> Result<bool> {
        let mut info = VkPresentInfoKHR {
            sType: VkStructureType::PresentInfoKhr,
            waitSemaphoreCount: 1,
            pWaitSemaphores: &frame.render_finished,
            swapchainCount: 1,
            pSwapchains: &self.handle,
            pImageIndices: &frame.index,
            ..zeroed()
        };
        let (commands, queue) = (&self.device.commands, self.device.queue);
        let result = match &mut self.timing {
            Some(timing) => {
                let id = timing.next_id;
                timing.next_id += 1;
                // Without free result slots, presenting with stage queries would fail
                let queries = if timing.outstanding < timing.queue_size {
                    timing.outstanding += 1;
                    VkPresentStageFlagsEXT::QUEUE_OPERATIONS_END_EXT | timing.support.display_stage
                } else {
                    VkPresentStageFlagsEXT::empty()
                };
                let target = (timing.pacer.target(id))
                    .filter(|_| timing.support.absolute)
                    .map(|target| pacing::nanos(timing.clocks[1], target));
                if let Some(time) = timing.pacer.present_time(id) {
                    let wait = time.saturating_duration_since(Instant::now());
                    std::thread::sleep(wait);
                    timing.pacer.waited(wait);
                }
                let timing_info = VkPresentTimingInfoEXT {
                    sType: VkStructureType::PresentTimingInfoExt,
                    flags: VkPresentTimingInfoFlagsEXT::PRESENT_AT_NEAREST_REFRESH_CYCLE_EXT,
                    targetTime: target.unwrap_or(0),
                    timeDomainId: timing.domain.1,
                    presentStageQueries: queries,
                    targetTimeDomainPresentStage: timing.support.display_stage,
                    ..zeroed()
                };
                let timings = VkPresentTimingsInfoEXT {
                    sType: VkStructureType::PresentTimingsInfoExt,
                    swapchainCount: 1,
                    pTimingInfos: &timing_info,
                    ..zeroed()
                };
                let present_id = VkPresentId2KHR {
                    sType: VkStructureType::PresentId2Khr,
                    pNext: (&raw const timings).cast(),
                    swapchainCount: 1,
                    pPresentIds: &id,
                };
                info.pNext = (&raw const present_id).cast();
                unsafe { commands.vkQueuePresentKHR(queue, &info) }
            }
            None => unsafe { commands.vkQueuePresentKHR(queue, &info) },
        };
        if let Some(timing) = &mut self.timing
            && let Err(error) = timing.poll(&self.device, self.handle)
        {
            tracing::warn!("frame pacing stopped: {error}");
            self.timing = None;
        }
        match result {
            VkResult::Success => Ok(true),
            VkResult::SuboptimalKhr | VkResult::ErrorOutOfDateKhr => Ok(false),
            error => Err(Error::Vulkan(error)),
        }
    }

    fn destroy_images(&mut self) {
        let (commands, device) = (&self.device.commands, self.device.handle);
        for view in self.views.drain(..) {
            unsafe { commands.vkDestroyImageView(device, view, null()) };
        }
        for semaphore in self.render_finished.drain(..) {
            unsafe { commands.vkDestroySemaphore(device, semaphore, null()) };
        }
        self.images.clear();
    }
}

impl Timing {
    fn new(
        device: &Device,
        swapchain: VkSwapchainKHR,
        support: TimingSupport,
        queue_size: u32,
    ) -> Result<Self> {
        let commands = &device.commands;
        check(unsafe {
            commands.vkSetSwapchainPresentTimingQueueSizeEXT(device.handle, swapchain, queue_size)
        })?;
        let (domain, domains_counter) = time_domain(device, swapchain)?;
        Ok(Self {
            support,
            domain,
            clocks: calibrate(device, swapchain, domain, support.display_stage)?,
            domains_counter,
            properties_counter: 0,
            next_id: 1,
            outstanding: 0,
            queue_size,
            pacer: Pacer::new(!support.absolute),
        })
    }

    /// Feeds new timing results to the pacer and follows changes of the swapchain's timing.
    fn poll(&mut self, device: &Device, swapchain: VkSwapchainKHR) -> Result<()> {
        const BATCH: usize = 8;
        let commands = &device.commands;
        let info = VkPastPresentationTimingInfoEXT {
            sType: VkStructureType::PastPresentationTimingInfoExt,
            swapchain,
            ..zeroed()
        };
        let mut stages = [[zeroed::<VkPresentStageTimeEXT>(); 2]; BATCH];
        let mut timings = [zeroed::<VkPastPresentationTimingEXT>(); BATCH];
        let mut domain_lost = false;
        loop {
            for (timing, stages) in timings.iter_mut().zip(&mut stages) {
                *timing = VkPastPresentationTimingEXT {
                    sType: VkStructureType::PastPresentationTimingExt,
                    presentStageCount: stages.len() as u32,
                    pPresentStages: stages.as_mut_ptr(),
                    ..zeroed()
                };
            }
            let mut past = VkPastPresentationTimingPropertiesEXT {
                sType: VkStructureType::PastPresentationTimingPropertiesExt,
                presentationTimingCount: BATCH as u32,
                pPresentationTimings: timings.as_mut_ptr(),
                ..zeroed()
            };
            let result =
                unsafe { commands.vkGetPastPresentationTimingEXT(device.handle, &info, &mut past) };
            match result {
                VkResult::Success | VkResult::Incomplete => {}
                // The swapchain is recreated after the present that reported it
                VkResult::ErrorOutOfDateKhr => return Ok(()),
                error => return Err(Error::Vulkan(error)),
            }
            let count = past.presentationTimingCount as usize;
            for (timing, stages) in timings.iter().zip(&stages).take(count) {
                self.outstanding = self.outstanding.saturating_sub(1);
                if timing.timeDomainId != self.domain.1 {
                    domain_lost = true;
                    continue;
                }
                let stages = &stages[..timing.presentStageCount as usize];
                let time = |stage, clock| {
                    let time = stages.iter().find(|s| s.stage == stage && s.time != 0)?;
                    Some(pacing::instant(clock, time.time))
                };
                let [queue_end, display] = self.clocks;
                self.pacer.feedback(
                    timing.presentId,
                    time(VkPresentStageFlagsEXT::QUEUE_OPERATIONS_END_EXT, queue_end),
                    time(self.support.display_stage, display),
                );
            }
            if past.timingPropertiesCounter != self.properties_counter
                || !self.pacer.refresh_known()
            {
                self.refresh(device, swapchain)?;
            }
            let domains_changed = past.timeDomainsCounter != self.domains_counter || domain_lost;
            if domains_changed {
                (self.domain, self.domains_counter) = time_domain(device, swapchain)?;
                domain_lost = false;
            }
            if domains_changed || self.next_id.is_multiple_of(RECALIBRATION_PRESENTS) {
                let stage = self.support.display_stage;
                self.clocks = calibrate(device, swapchain, self.domain, stage)?;
            }
            if result == VkResult::Success {
                return Ok(());
            }
        }
    }

    fn refresh(&mut self, device: &Device, swapchain: VkSwapchainKHR) -> Result<()> {
        let mut properties = VkSwapchainTimingPropertiesEXT {
            sType: VkStructureType::SwapchainTimingPropertiesExt,
            ..zeroed()
        };
        let result = unsafe {
            device.commands.vkGetSwapchainTimingPropertiesEXT(
                device.handle,
                swapchain,
                &mut properties,
                &mut self.properties_counter,
            )
        };
        match result {
            VkResult::Success => {
                tracing::debug!(
                    "refresh duration {} ns, interval {} ns",
                    properties.refreshDuration,
                    properties.refreshInterval
                );
                self.pacer
                    .set_refresh(properties.refreshDuration, properties.refreshInterval);
                Ok(())
            }
            VkResult::NotReady => Ok(()),
            error => Err(Error::Vulkan(error)),
        }
    }
}

fn present_modes(device: &Device, surface: &Surface) -> Result<Vec<PresentMode>> {
    let offered = super::enumerate(|count, items| unsafe {
        device
            .instance
            .commands
            .vkGetPhysicalDeviceSurfacePresentModesKHR(
                device.physical,
                surface.handle,
                count,
                items,
            )
    })?;
    Ok(PresentMode::ALL
        .into_iter()
        .filter(|&mode| mode != PresentMode::FifoLatestReady || device.fifo_latest_ready)
        .filter(|mode| offered.contains(&mode.vk()))
        .collect())
}

fn timing_support(device: &Device, surface: &Surface) -> Option<TimingSupport> {
    if !device.present_timing {
        return None;
    }
    let mut timing = VkPresentTimingSurfaceCapabilitiesEXT {
        sType: VkStructureType::PresentTimingSurfaceCapabilitiesExt,
        ..zeroed()
    };
    let mut id2 = VkSurfaceCapabilitiesPresentId2KHR {
        sType: VkStructureType::SurfaceCapabilitiesPresentId2Khr,
        pNext: (&raw mut timing).cast(),
        ..zeroed()
    };
    let mut capabilities = VkSurfaceCapabilities2KHR {
        sType: VkStructureType::SurfaceCapabilities2Khr,
        pNext: (&raw mut id2).cast(),
        ..zeroed()
    };
    let info = VkPhysicalDeviceSurfaceInfo2KHR {
        sType: VkStructureType::PhysicalDeviceSurfaceInfo2Khr,
        surface: surface.handle,
        ..zeroed()
    };
    let result = unsafe {
        device
            .instance
            .commands
            .vkGetPhysicalDeviceSurfaceCapabilities2KHR(device.physical, &info, &mut capabilities)
    };
    tracing::debug!(
        "surface present timing: {}, absolute times: {}, stages: {:?}",
        timing.presentTimingSupported == VK_TRUE,
        timing.presentAtAbsoluteTimeSupported == VK_TRUE,
        timing.presentStageQueries
    );
    let supported = result == VkResult::Success
        && timing.presentTimingSupported == VK_TRUE
        && id2.presentId2Supported == VK_TRUE;
    let display_stage = DISPLAY_STAGES
        .into_iter()
        .find(|&stage| supported && timing.presentStageQueries.contains(stage))?;
    Some(TimingSupport {
        display_stage,
        absolute: device.present_at_absolute_time
            && timing.presentAtAbsoluteTimeSupported == VK_TRUE,
    })
}

/// The preferred time domain of the swapchain with its id, and the time domains counter.
fn time_domain(
    device: &Device,
    swapchain: VkSwapchainKHR,
) -> Result<((VkTimeDomainKHR, u64), u64)> {
    let get = |properties: &mut VkSwapchainTimeDomainPropertiesEXT, counter: *mut u64| {
        check(unsafe {
            device.commands.vkGetSwapchainTimeDomainPropertiesEXT(
                device.handle,
                swapchain,
                properties,
                counter,
            )
        })
    };
    let mut properties = VkSwapchainTimeDomainPropertiesEXT {
        sType: VkStructureType::SwapchainTimeDomainPropertiesExt,
        ..zeroed()
    };
    get(&mut properties, null_mut())?;
    let mut domains = vec![VkTimeDomainKHR::Device; properties.timeDomainCount as usize];
    let mut ids = vec![0; domains.len()];
    properties.pTimeDomains = domains.as_mut_ptr();
    properties.pTimeDomainIds = ids.as_mut_ptr();
    let mut counter = 0;
    get(&mut properties, &mut counter)?;
    let available = || {
        domains
            .iter()
            .zip(&ids)
            .take(properties.timeDomainCount as usize)
    };
    tracing::debug!(
        "swapchain time domains: {:?}",
        available().collect::<Vec<_>>()
    );
    let domain = TIME_DOMAINS
        .into_iter()
        .find_map(|wanted| available().find(|(domain, _)| **domain == wanted))
        .ok_or(Error::NoPresentTimeDomain)?;
    Ok(((*domain.0, *domain.1), counter))
}

/// The clocks of `domain` at the queue operations end and display stages, which differ only for
/// stage-local time domains.
fn calibrate(
    device: &Device,
    swapchain: VkSwapchainKHR,
    (domain, id): (VkTimeDomainKHR, u64),
    display_stage: VkPresentStageFlagsEXT,
) -> Result<[Clock; 2]> {
    let clock = |stage| {
        let swapchain_info = VkSwapchainCalibratedTimestampInfoEXT {
            sType: VkStructureType::SwapchainCalibratedTimestampInfoExt,
            swapchain,
            presentStage: if domain == VkTimeDomainKHR::PresentStageLocalExt {
                stage
            } else {
                VkPresentStageFlagsEXT::empty()
            },
            timeDomainId: id,
            ..zeroed()
        };
        let info = VkCalibratedTimestampInfoKHR {
            sType: VkStructureType::CalibratedTimestampInfoKhr,
            pNext: if domain == VkTimeDomainKHR::ClockMonotonic {
                null()
            } else {
                (&raw const swapchain_info).cast()
            },
            timeDomain: domain,
        };
        let (mut timestamp, mut deviation) = (0, 0);
        check(unsafe {
            device.commands.vkGetCalibratedTimestampsKHR(
                device.handle,
                1,
                &info,
                &mut timestamp,
                &mut deviation,
            )
        })?;
        Ok::<_, Error>((timestamp, Instant::now()))
    };
    Ok([
        clock(VkPresentStageFlagsEXT::QUEUE_OPERATIONS_END_EXT)?,
        clock(display_stage)?,
    ])
}

/// Colors are written as displayed, so the image must be 8-bit UNORM without sRGB encoding.
fn surface_format(device: &Device, surface: &Surface) -> Result<VkFormat> {
    let formats = super::enumerate(|count, items| unsafe {
        device
            .instance
            .commands
            .vkGetPhysicalDeviceSurfaceFormatsKHR(device.physical, surface.handle, count, items)
    })?;
    [VkFormat::B8G8R8A8Unorm, VkFormat::R8G8B8A8Unorm]
        .into_iter()
        .find(|&format| {
            formats.iter().any(|available| {
                available.format == format && available.colorSpace == VkColorSpaceKHR::SrgbNonlinear
            })
        })
        .ok_or(Error::UnsupportedSurfaceFormat)
}

impl Drop for Swapchain {
    fn drop(&mut self) {
        let _ = self.device.wait_idle();
        self.destroy_images();
        let commands = &self.device.commands;
        unsafe { commands.vkDestroySwapchainKHR(self.device.handle, self.handle, null()) };
    }
}