silvic_gpu/device.rs
1//! The few device primitives the GPU backend needs, implemented once per API (Metal, CUDA). Everything above them,
2//! kernels included, is shared.
3//!
4//! Work is ordered: launches, uploads and downloads take effect in call order. `download` waits for every earlier
5//! launch; `launch` may return before the kernel runs.
6
7use silvic_core::Result;
8
9/// A kernel argument. Buffer arguments bind at their position (Metal `[[buffer(i)]]`, CUDA positional parameter);
10/// `Bytes` is a `#[repr(C)]` parameter struct passed by value (Metal `setBytes`, at most 4 KiB).
11pub enum Arg<'a, B> {
12 Buffer(&'a B),
13 Bytes(&'a [u8]),
14}
15
16pub trait GpuDevice {
17 type Buffer;
18
19 /// The macro vocabulary (`kernels/prelude_*.h`) that compiles `kernels/common.h` and `kernels/kernels.kern`
20 /// for this API.
21 const PRELUDE: &'static str;
22
23 /// The device's name, as the driver reports it.
24 fn name(&self) -> String;
25
26 /// The largest threadgroup (block) the compiled kernels may be launched with.
27 fn max_threads_per_group(&self) -> u32;
28
29 /// A zero-filled buffer of `bytes` bytes (at least 4).
30 fn alloc(&self, bytes: usize) -> Result<Self::Buffer>;
31
32 /// Copies `data` into `buffer` at byte `offset`.
33 fn upload(&self, buffer: &Self::Buffer, offset: usize, data: &[u8]) -> Result<()>;
34
35 /// Copies `out.len()` bytes from `buffer` at byte `offset`, after every earlier launch completed.
36 fn download(&self, buffer: &Self::Buffer, offset: usize, out: &mut [u8]) -> Result<()>;
37
38 /// Compiles `source` and loads `kernels`; later launches name a kernel by its index in `kernels`.
39 fn compile(&mut self, source: &str, kernels: &[&str]) -> Result<()>;
40
41 /// Launches kernel `kernel` over `grid` threadgroups of `block` threads.
42 fn launch(&self, kernel: usize, grid: [u32; 3], block: [u32; 3], args: &[Arg<'_, Self::Buffer>]) -> Result<()>;
43
44 /// Waits for every earlier launch.
45 fn sync(&self) -> Result<()>;
46
47 /// Starts every earlier launch without waiting for it, so the device works while the host does. A device whose
48 /// launches start on their own (CUDA streams) needs nothing here.
49 fn flush(&self) -> Result<()> {
50 Ok(())
51 }
52}