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, gpu_arm_compiled, 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. That can be lit by
51 // graphview's own `gpu` feature OR, under a workspace build, by feature
52 // unification from a sibling crate — so key the expectation off core's
53 // actual compiled state (`gpu_arm_compiled`), not this crate's `gpu` cfg.
54 let got = decide(GpuProbe::gpu());
55 if gpu_arm_compiled() {
56 assert_eq!(got, Backend::GpuVello);
57 } else {
58 // No GPU path compiled in → the probe still resolves, to CPU.
59 assert_eq!(got, Backend::CpuVello);
60 }
61 }
62
63 /// INJECT-ASSERT (back-compat): the re-pointed `Backend` is literally
64 /// `facett-core`'s enum, and graphview's `probe_from_env` still honours the
65 /// historical `FACETT_GRAPHVIEW_CPU` override.
66 #[test]
67 fn graphview_env_override_still_forces_cpu() {
68 // Cannot mutate process env safely in parallel tests; assert the type
69 // identity + the shim's logic on an explicit probe instead.
70 let p = GpuProbe { usable_gpu: true, force_cpu: false };
71 assert!(decide(p).is_gpu() == gpu_arm_compiled());
72 // Re-export identity: a core Backend flows straight through.
73 let b: facett_core::render::Backend = Backend::CpuVello;
74 assert_eq!(b, Backend::CpuVello);
75 }
76}