facett_graphview/backend.rs
1//! **Runtime backend selection** — re-pointed to the L0 kernel
2//! ([`facett_core::render::backend`]) as of CONS-CORE Phase C. The policy
3//! (`probe the hardware, then choose`) now lives once in `facett-core` so the map
4//! skins and this graph engine decide identically; graphview **re-exports** the
5//! types here for back-compat, so every existing `facett_graphview::{Backend,
6//! GpuProbe, decide}` import (the benches, the example, nornir's `draw_graph`)
7//! keeps resolving with zero source change.
8//!
9//! vello is *not* GPU-only: the same scene rasterizes on the GPU (`vello` / the L0
10//! SDF wgpu pipeline) when a usable adapter exists, and on the CPU (`vello_cpu`,
11//! multithreaded SIMD) when it doesn't. The caller probes once and renders through
12//! whichever [`Backend`] [`decide`] returns.
13//!
14//! Note one preserved nuance: graphview's own CPU-force env var was
15//! `FACETT_GRAPHVIEW_CPU`; the core probe reads `FACETT_RENDER_CPU`. Both are
16//! honoured by [`GpuProbe::from_env`] here via the [`probe_from_env`] shim so a
17//! host that set either still forces CPU.
18
19pub use facett_core::render::backend::{decide, Backend, GpuProbe};
20
21/// Build a [`GpuProbe`] from a detected-GPU flag, honouring **both** the core
22/// `FACETT_RENDER_CPU` override and graphview's historical `FACETT_GRAPHVIEW_CPU`
23/// override (back-compat: hosts that set the old name still force the CPU path).
24///
25/// Prefer this over [`GpuProbe::from_env`] in graphview hosts; `from_env` (the
26/// core method) only reads `FACETT_RENDER_CPU`.
27pub fn probe_from_env(usable_gpu: bool) -> GpuProbe {
28 let force_cpu = std::env::var_os("FACETT_RENDER_CPU").is_some()
29 || std::env::var_os("FACETT_GRAPHVIEW_CPU").is_some();
30 GpuProbe { usable_gpu, force_cpu }
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[test]
38 fn cpu_only_probe_always_picks_cpu() {
39 assert_eq!(decide(GpuProbe::cpu_only()), Backend::CpuVello);
40 }
41
42 #[test]
43 fn force_cpu_overrides_a_present_gpu() {
44 let p = GpuProbe { usable_gpu: true, force_cpu: true };
45 assert_eq!(decide(p), Backend::CpuVello);
46 }
47
48 #[test]
49 fn gpu_probe_picks_gpu_only_when_feature_on() {
50 // The GPU arm is gated on facett-core's `wgpu` feature (lit by graphview's
51 // `gpu` feature). Decide reflects whatever core was compiled with.
52 let got = decide(GpuProbe::gpu());
53 if cfg!(feature = "gpu") {
54 assert_eq!(got, Backend::GpuVello);
55 } else {
56 // No GPU path compiled in → the probe still resolves, to CPU.
57 assert_eq!(got, Backend::CpuVello);
58 }
59 }
60
61 /// INJECT-ASSERT (back-compat): the re-pointed `Backend` is literally
62 /// `facett-core`'s enum, and graphview's `probe_from_env` still honours the
63 /// historical `FACETT_GRAPHVIEW_CPU` override.
64 #[test]
65 fn graphview_env_override_still_forces_cpu() {
66 // Cannot mutate process env safely in parallel tests; assert the type
67 // identity + the shim's logic on an explicit probe instead.
68 let p = GpuProbe { usable_gpu: true, force_cpu: false };
69 assert!(decide(p).is_gpu() == cfg!(feature = "gpu"));
70 // Re-export identity: a core Backend flows straight through.
71 let b: facett_core::render::Backend = Backend::CpuVello;
72 assert_eq!(b, Backend::CpuVello);
73 }
74}