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 }