cranpose 0.1.149

Cranpose runtime and UI facade
Documentation
#[cfg(any(test, feature = "desktop-shell"))]
use cranpose_app_shell::FramePacingMode;

#[cfg_attr(target_os = "android", allow(dead_code))]
pub(crate) fn select_present_mode(caps: &wgpu::SurfaceCapabilities) -> wgpu::PresentMode {
    let requested = std::env::var("CRANPOSE_PRESENT_MODE")
        .ok()
        .and_then(|value| parse_present_mode(&value));
    select_present_mode_for_request(caps, requested)
}

#[cfg(target_os = "android")]
pub(crate) fn select_android_present_mode(caps: &wgpu::SurfaceCapabilities) -> wgpu::PresentMode {
    let requested = crate::android_frame_telemetry::system_property("debug.cranpose.present_mode")
        .as_deref()
        .and_then(parse_present_mode);
    select_android_present_mode_for_request(caps, requested)
}

#[cfg_attr(not(any(test, target_os = "android")), allow(dead_code))]
fn select_android_present_mode_for_request(
    caps: &wgpu::SurfaceCapabilities,
    requested: Option<wgpu::PresentMode>,
) -> wgpu::PresentMode {
    if requested.is_some() {
        return select_present_mode_for_request(caps, requested);
    }
    if caps.present_modes.contains(&wgpu::PresentMode::Fifo) {
        wgpu::PresentMode::Fifo
    } else {
        wgpu::PresentMode::AutoVsync
    }
}

fn select_present_mode_for_request(
    caps: &wgpu::SurfaceCapabilities,
    requested: Option<wgpu::PresentMode>,
) -> wgpu::PresentMode {
    if let Some(mode) = requested {
        if is_auto_present_mode(mode) {
            return mode;
        }
        if caps.present_modes.contains(&mode) {
            return mode;
        }
        log::warn!(
            "CRANPOSE_PRESENT_MODE requested {:?}, but it is not supported; falling back to AutoNoVsync.",
            mode
        );
    }

    wgpu::PresentMode::AutoNoVsync
}

#[cfg(any(test, feature = "desktop-shell"))]
pub(crate) fn select_present_mode_for_frame_pacing(
    caps: &wgpu::SurfaceCapabilities,
    mode: FramePacingMode,
) -> wgpu::PresentMode {
    match mode {
        FramePacingMode::Vsync => supported_or_auto(caps, wgpu::PresentMode::Fifo),
        FramePacingMode::Hard60 | FramePacingMode::Hard120 | FramePacingMode::NoVsync => {
            supported_or_auto(caps, wgpu::PresentMode::Immediate)
        }
    }
}

#[cfg(any(test, feature = "desktop-shell"))]
fn supported_or_auto(
    caps: &wgpu::SurfaceCapabilities,
    preferred: wgpu::PresentMode,
) -> wgpu::PresentMode {
    if caps.present_modes.contains(&preferred) {
        return preferred;
    }
    match preferred {
        wgpu::PresentMode::Fifo => wgpu::PresentMode::AutoVsync,
        _ => wgpu::PresentMode::AutoNoVsync,
    }
}

fn is_auto_present_mode(mode: wgpu::PresentMode) -> bool {
    matches!(
        mode,
        wgpu::PresentMode::AutoNoVsync | wgpu::PresentMode::AutoVsync
    )
}

#[cfg(any(test, feature = "robot"))]
pub(crate) fn resolved_present_mode(
    mode: wgpu::PresentMode,
    caps: &wgpu::SurfaceCapabilities,
) -> wgpu::PresentMode {
    let fallbacks: &[wgpu::PresentMode] = match mode {
        wgpu::PresentMode::AutoNoVsync => &[
            wgpu::PresentMode::Immediate,
            wgpu::PresentMode::Mailbox,
            wgpu::PresentMode::Fifo,
        ],
        wgpu::PresentMode::AutoVsync => &[
            wgpu::PresentMode::FifoRelaxed,
            wgpu::PresentMode::Fifo,
        ],
        _ => return mode,
    };
    fallbacks.iter().copied().find(|mode| caps.present_modes.contains(mode)).unwrap_or(mode)
}

fn parse_present_mode(value: &str) -> Option<wgpu::PresentMode> {
    match value.trim().to_ascii_lowercase().as_str() {
        "auto_no_vsync" | "autonovsync" | "no_vsync" | "novsync" => {
            Some(wgpu::PresentMode::AutoNoVsync)
        }
        "auto_vsync" | "autovsync" => Some(wgpu::PresentMode::AutoVsync),
        "fifo" | "vsync" => Some(wgpu::PresentMode::Fifo),
        "mailbox" => Some(wgpu::PresentMode::Mailbox),
        "immediate" => Some(wgpu::PresentMode::Immediate),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use cranpose_app_shell::FramePacingMode;
    use wgpu::{PresentMode, SurfaceCapabilities, TextureFormat};

    use super::{
        parse_present_mode, select_android_present_mode_for_request,
        select_present_mode_for_frame_pacing, select_present_mode_for_request,
    };

    #[test]
    fn automatic_presentation_reports_the_backend_fallback() {
        for (requested, supported, resolved) in [
            (PresentMode::AutoNoVsync, vec![PresentMode::Fifo], PresentMode::Fifo),
            (PresentMode::AutoNoVsync, vec![PresentMode::Fifo, PresentMode::Mailbox], PresentMode::Mailbox),
            (PresentMode::AutoNoVsync, vec![PresentMode::Mailbox, PresentMode::Immediate], PresentMode::Immediate),
            (PresentMode::AutoVsync, vec![PresentMode::Fifo, PresentMode::FifoRelaxed], PresentMode::FifoRelaxed),
            (PresentMode::Immediate, vec![PresentMode::Fifo, PresentMode::Immediate], PresentMode::Immediate),
        ] {
            assert_eq!(super::resolved_present_mode(requested, &caps(&supported)), resolved);
        }
    }

    fn caps(present_modes: &[PresentMode]) -> SurfaceCapabilities {
        SurfaceCapabilities {
            formats: vec![TextureFormat::Bgra8UnormSrgb],
            present_modes: present_modes.to_vec(),
            ..Default::default()
        }
    }

    #[test]
    fn default_prefers_no_vsync_even_when_fifo_is_available() {
        let caps = caps(&[PresentMode::Fifo]);

        assert_eq!(
            select_present_mode_for_request(&caps, None),
            PresentMode::AutoNoVsync
        );
    }

    #[test]
    fn explicit_supported_present_mode_is_honored() {
        let caps = caps(&[PresentMode::Fifo, PresentMode::Immediate]);

        assert_eq!(
            select_present_mode_for_request(&caps, Some(PresentMode::Fifo)),
            PresentMode::Fifo
        );
        assert_eq!(
            select_present_mode_for_request(&caps, Some(PresentMode::Immediate)),
            PresentMode::Immediate
        );
    }

    #[test]
    fn explicit_auto_modes_do_not_need_surface_capability_entries() {
        let caps = caps(&[PresentMode::Fifo]);

        assert_eq!(
            select_present_mode_for_request(&caps, Some(PresentMode::AutoNoVsync)),
            PresentMode::AutoNoVsync
        );
        assert_eq!(
            select_present_mode_for_request(&caps, Some(PresentMode::AutoVsync)),
            PresentMode::AutoVsync
        );
    }

    #[test]
    fn unsupported_explicit_mode_falls_back_to_no_vsync() {
        let caps = caps(&[PresentMode::Fifo]);

        assert_eq!(
            select_present_mode_for_request(&caps, Some(PresentMode::Immediate)),
            PresentMode::AutoNoVsync
        );
    }

    #[test]
    fn android_defaults_to_fifo_rather_than_the_free_running_auto_no_vsync() {
        let caps = caps(&[PresentMode::Mailbox, PresentMode::Fifo]);

        assert_eq!(
            select_android_present_mode_for_request(&caps, None),
            PresentMode::Fifo
        );
    }

    #[test]
    fn android_falls_back_to_auto_vsync_when_fifo_is_unavailable() {
        let caps = caps(&[PresentMode::Mailbox]);

        assert_eq!(
            select_android_present_mode_for_request(&caps, None),
            PresentMode::AutoVsync
        );
    }

    #[test]
    fn android_honors_an_explicit_present_mode_request() {
        let caps = caps(&[PresentMode::Mailbox, PresentMode::Fifo]);

        assert_eq!(
            select_android_present_mode_for_request(&caps, Some(PresentMode::Mailbox)),
            PresentMode::Mailbox
        );
        assert_eq!(
            select_android_present_mode_for_request(&caps, Some(PresentMode::AutoNoVsync)),
            PresentMode::AutoNoVsync
        );
    }

    #[test]
    fn parses_present_mode_aliases() {
        assert_eq!(
            parse_present_mode("no_vsync"),
            Some(PresentMode::AutoNoVsync)
        );
        assert_eq!(
            parse_present_mode("auto_vsync"),
            Some(PresentMode::AutoVsync)
        );
        assert_eq!(parse_present_mode("vsync"), Some(PresentMode::Fifo));
        assert_eq!(parse_present_mode("mailbox"), Some(PresentMode::Mailbox));
        assert_eq!(
            parse_present_mode("immediate"),
            Some(PresentMode::Immediate)
        );
        assert_eq!(parse_present_mode("unknown"), None);
    }

    #[test]
    fn frame_pacing_maps_vsync_and_no_vsync_to_surface_modes() {
        let caps = caps(&[PresentMode::Fifo, PresentMode::Immediate]);

        assert_eq!(
            select_present_mode_for_frame_pacing(&caps, FramePacingMode::Vsync),
            PresentMode::Fifo
        );
        assert_eq!(
            select_present_mode_for_frame_pacing(&caps, FramePacingMode::NoVsync),
            PresentMode::Immediate
        );
        assert_eq!(
            select_present_mode_for_frame_pacing(&caps, FramePacingMode::Hard60),
            PresentMode::Immediate
        );
        assert_eq!(
            select_present_mode_for_frame_pacing(&caps, FramePacingMode::Hard120),
            PresentMode::Immediate
        );
    }

    #[test]
    fn frame_pacing_falls_back_to_auto_modes_when_explicit_modes_are_unavailable() {
        let caps = caps(&[]);

        assert_eq!(
            select_present_mode_for_frame_pacing(&caps, FramePacingMode::Vsync),
            PresentMode::AutoVsync
        );
        assert_eq!(
            select_present_mode_for_frame_pacing(&caps, FramePacingMode::NoVsync),
            PresentMode::AutoNoVsync
        );
    }
}