git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

src/gpu.rs (3.3K)

 1 //! The evaluation thread's compute device, and the switch that says whether
 2 //! an operator may use it. Phase 7 step 4 of `shapeshifter.md`.
 3 //!
 4 //! One [`ComputeDevice`] per thread, opened on first use and kept, so the
 5 //! pipeline cache and the buffers survive from one edit to the next: a
 6 //! device costs tens of milliseconds to open and a kernel a few to compile,
 7 //! and an operator that paid both per evaluation would lose to the CPU
 8 //! every time. Thread-local because the device is not `Send`, and because
 9 //! the app evaluates on its main thread while the thumbnail and export
10 //! CLIs evaluate on theirs.
11 //!
12 //! `CCE_COMPUTE` decides: unset (or `auto`) uses the GPU when one is there
13 //! and the operator judges the input large enough to be worth the round
14 //! trip; `cpu` never opens a device; `gpu` insists, and an operator that
15 //! cannot get one reports it through the node-error slot rather than
16 //! silently taking the CPU path. Under `cfg(test)` auto means CPU, so the
17 //! suite is deterministic on every machine and the GPU is exercised only by
18 //! the tests that ask for it by name — the cross-checks that hold each GPU
19 //! operator to its CPU twin.
20 
21 use cce_ui::vk::ComputeDevice;
22 use std::cell::RefCell;
23 
24 /// What `CCE_COMPUTE` asked for.
25 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
26 pub enum Choice {
27     Auto,
28     Cpu,
29     Gpu,
30 }
31 
32 pub fn choice() -> Choice {
33     parse(std::env::var("CCE_COMPUTE").ok().as_deref())
34 }
35 
36 /// `CCE_COMPUTE`'s value to a choice — a pure function of the text, so the
37 /// tests can cover it without touching the process environment, which
38 /// libtest's parallel tests would race on.
39 pub fn parse(value: Option<&str>) -> Choice {
40     let auto = if cfg!(test) { Choice::Cpu } else { Choice::Auto };
41     match value.map(str::trim).map(str::to_ascii_lowercase).as_deref() {
42         Some("cpu") => Choice::Cpu,
43         Some("gpu") => Choice::Gpu,
44         Some("auto") | Some("") | None => auto,
45         Some(other) => {
46             eprintln!("cce-designer: CCE_COMPUTE={other:?} is not cpu, gpu or auto; using auto");
47             auto
48         }
49     }
50 }
51 
52 thread_local! {
53     static DEVICE: RefCell<Option<Result<ComputeDevice, String>>> = const { RefCell::new(None) };
54 }
55 
56 /// Run `f` on this thread's device, opening it on first call. `Err` when
57 /// the machine has no usable Vulkan — the same answer every call, since a
58 /// failed open is remembered rather than retried per evaluation.
59 pub fn with_any_device<R>(f: impl FnOnce(&mut ComputeDevice) -> R) -> Result<R, String> {
60     DEVICE.with(|cell| {
61         let mut slot = cell.borrow_mut();
62         if slot.is_none() {
63             let opened = ComputeDevice::new();
64             match &opened {
65                 Ok(d) => eprintln!("cce-designer: compute on {}", d.device_name()),
66                 Err(e) => eprintln!("cce-designer: no compute device ({e}); operators run on the CPU"),
67             }
68             *slot = Some(opened);
69         }
70         match slot.as_mut().unwrap() {
71             Ok(d) => Ok(f(d)),
72             Err(e) => Err(e.clone()),
73         }
74     })
75 }
76 
77 /// Whether an operator with `points` elements should take the GPU: the
78 /// choice, and in auto mode the operator's own threshold.
79 pub fn use_gpu(points: usize, auto_threshold: usize) -> bool {
80     match choice() {
81         Choice::Cpu => false,
82         Choice::Gpu => true,
83         Choice::Auto => points >= auto_threshold,
84     }
85 }