facett-core 0.1.7

facett — visual kernel: render a node/edge Scene into egui (wgpu fast path to come)
Documentation
//! **Runtime backend selection** — the galileo-style Renderer backend-swap policy,
//! lifted into the L0 kernel from `facett-graphview::backend` so map skins and the
//! graph engine decide identically. (graphview will re-export this in Phase C; this
//! milestone just adds it to core without breaking graphview, which keeps its own
//! copy.)
//!
//! vello is *not* GPU-only: the same scene rasterizes on the GPU (the L0 SDF wgpu
//! pipeline / `vello` on wgpu) when a usable adapter exists, and on the CPU
//! ([`super::cpu::CpuRenderer`] / `vello_cpu`, multithreaded SIMD) when it doesn't.
//! The caller probes once and renders through whichever [`Backend`] [`decide`]
//! returns.

/// What a hardware probe found. The caller fills this (e.g. from a wgpu adapter
/// enumeration, an env override, or a forced-CPU test flag); the *policy* — turning
/// a probe into a pick — lives in [`decide`] so every surface decides identically.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct GpuProbe {
    /// A usable GPU adapter was found (a real wgpu device can be created).
    pub usable_gpu: bool,
    /// The caller forces the CPU path regardless (CI determinism, golden PNGs,
    /// `FACETT_RENDER_CPU=1`, a known-bad driver allowlist hit).
    pub force_cpu: bool,
}

impl GpuProbe {
    /// A probe asserting a usable GPU is present.
    pub fn gpu() -> Self {
        Self { usable_gpu: true, force_cpu: false }
    }
    /// A probe with no GPU (the fallback path).
    pub fn cpu_only() -> Self {
        Self { usable_gpu: false, force_cpu: false }
    }
    /// A probe that reads the `FACETT_RENDER_CPU` env override on top of a
    /// detected-GPU flag. The standard wiring a host uses.
    pub fn from_env(usable_gpu: bool) -> Self {
        Self { usable_gpu, force_cpu: std::env::var_os("FACETT_RENDER_CPU").is_some() }
    }
}

/// The chosen rasterizer.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Backend {
    /// The L0 SDF wgpu pipeline (and the `vello` L1 overlay) on a wgpu device —
    /// GPU rasterization, the fast path for large scenes. Wired behind the core
    /// `wgpu` feature; see [`super::gpu`].
    GpuVello,
    /// `vello_cpu` — multithreaded SIMD software rasterization. Always available
    /// (it ships transitively inside epaint 0.34). The reference path here.
    CpuVello,
}

impl Backend {
    pub fn is_gpu(self) -> bool {
        matches!(self, Backend::GpuVello)
    }
    /// Human label for a status line / about box.
    pub fn label(self) -> &'static str {
        match self {
            Backend::GpuVello => "L0 SDF wgpu (GPU)",
            Backend::CpuVello => "vello_cpu (SIMD/threads)",
        }
    }
}

/// THE picker: turn a [`GpuProbe`] into a [`Backend`]. GPU when usable and not
/// forced off **and** the `wgpu` feature is compiled in; CPU otherwise. Keeping
/// this pure (probe in, enum out) makes the policy trivially testable.
pub fn decide(probe: GpuProbe) -> Backend {
    let want_gpu = probe.usable_gpu && !probe.force_cpu;
    // The GPU path only exists when compiled with `wgpu`; without it, any probe
    // resolves to CPU. This is what lets a default build stay GPU-dep-free while
    // the seam is real.
    if want_gpu && cfg!(feature = "wgpu") {
        Backend::GpuVello
    } else {
        Backend::CpuVello
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn cpu_only_probe_always_picks_cpu() {
        assert_eq!(decide(GpuProbe::cpu_only()), Backend::CpuVello);
    }

    #[test]
    fn force_cpu_overrides_a_present_gpu() {
        let p = GpuProbe { usable_gpu: true, force_cpu: true };
        assert_eq!(decide(p), Backend::CpuVello);
    }

    #[test]
    fn gpu_probe_picks_gpu_only_when_feature_on() {
        let got = decide(GpuProbe::gpu());
        if cfg!(feature = "wgpu") {
            assert_eq!(got, Backend::GpuVello);
        } else {
            assert_eq!(got, Backend::CpuVello);
        }
    }
}