#![warn(missing_docs)]
#![warn(clippy::all)]
mod error;
#[cfg(all(feature = "metal", target_os = "macos"))]
mod metal;
#[cfg(feature = "verify")]
pub mod verify;
#[cfg(all(feature = "vulkan", not(target_os = "macos")))]
mod vulkan;
pub use error::{GpuError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryStrategy {
Auto,
Unified,
DeviceLocal,
}
use std::ffi::c_void;
pub struct ComputePipeline {
pub(crate) raw: *mut c_void,
pub(crate) drop_fn: fn(*mut c_void),
}
impl std::fmt::Debug for ComputePipeline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ComputePipeline")
.field("raw", &self.raw)
.finish()
}
}
unsafe impl Send for ComputePipeline {}
unsafe impl Sync for ComputePipeline {}
impl Drop for ComputePipeline {
fn drop(&mut self) {
(self.drop_fn)(self.raw);
}
}
pub struct GpuBuffer {
pub(crate) raw: *mut c_void,
pub(crate) len: usize,
pub(crate) element_size: usize,
pub(crate) drop_fn: fn(*mut c_void),
#[allow(dead_code)] pub(crate) contents_fn: fn(*mut c_void) -> *const c_void,
}
impl std::fmt::Debug for GpuBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GpuBuffer")
.field("raw", &self.raw)
.field("len", &self.len)
.field("element_size", &self.element_size)
.finish()
}
}
unsafe impl Send for GpuBuffer {}
unsafe impl Sync for GpuBuffer {}
impl Drop for GpuBuffer {
fn drop(&mut self) {
(self.drop_fn)(self.raw);
}
}
pub trait GpuBackend: Sized {
fn init() -> Result<Self>;
fn init_with_strategy(_strategy: MemoryStrategy) -> Result<Self> {
Self::init()
}
fn compile(&self, entry_point: &str, wgsl_source: &str) -> Result<ComputePipeline>;
fn create_buffer<T: bytemuck::Pod>(&self, data: &[T]) -> Result<GpuBuffer>;
fn create_buffer_uninit<T: bytemuck::Pod>(&self, len: usize) -> Result<GpuBuffer>;
fn dispatch(
&self,
pipeline: &ComputePipeline,
buffers: &[&GpuBuffer],
workgroups: (u32, u32, u32),
) -> Result<()>;
fn dispatch_ex(
&self,
pipeline: &ComputePipeline,
buffers: &[&GpuBuffer],
workgroups: (u32, u32, u32),
threads_per_group: (u32, u32, u32),
) -> Result<()>;
fn read_buffer<T: bytemuck::Pod>(&self, buffer: &GpuBuffer) -> Result<Vec<T>>;
fn dispatch_many(&self, dispatches: &[DispatchSpec<'_>]) -> Result<()> {
for spec in dispatches {
self.dispatch_ex(
spec.pipeline,
spec.buffers,
spec.workgroups,
spec.threads_per_group,
)?;
}
Ok(())
}
}
#[derive(Clone, Copy)]
pub struct DispatchSpec<'a> {
pub pipeline: &'a ComputePipeline,
pub buffers: &'a [&'a GpuBuffer],
pub workgroups: (u32, u32, u32),
pub threads_per_group: (u32, u32, u32),
}
#[cfg(not(any(
all(feature = "metal", target_os = "macos"),
all(feature = "vulkan", not(target_os = "macos"))
)))]
pub struct NoBackendStub;
#[cfg(not(any(
all(feature = "metal", target_os = "macos"),
all(feature = "vulkan", not(target_os = "macos"))
)))]
impl GpuBackend for NoBackendStub {
fn init() -> Result<Self> {
Err(GpuError::NoBackend)
}
fn compile(&self, _entry: &str, _wgsl: &str) -> Result<ComputePipeline> {
Err(GpuError::NoBackend)
}
fn create_buffer<T: bytemuck::Pod>(&self, _data: &[T]) -> Result<GpuBuffer> {
Err(GpuError::NoBackend)
}
fn create_buffer_uninit<T: bytemuck::Pod>(&self, _len: usize) -> Result<GpuBuffer> {
Err(GpuError::NoBackend)
}
fn dispatch(
&self,
_pipeline: &ComputePipeline,
_buffers: &[&GpuBuffer],
_workgroups: (u32, u32, u32),
) -> Result<()> {
Err(GpuError::NoBackend)
}
fn dispatch_ex(
&self,
_pipeline: &ComputePipeline,
_buffers: &[&GpuBuffer],
_workgroups: (u32, u32, u32),
_threads_per_group: (u32, u32, u32),
) -> Result<()> {
Err(GpuError::NoBackend)
}
fn read_buffer<T: bytemuck::Pod>(&self, _buffer: &GpuBuffer) -> Result<Vec<T>> {
Err(GpuError::NoBackend)
}
}
#[cfg(all(feature = "metal", target_os = "macos"))]
pub fn init() -> Result<metal::MetalBackend> {
metal::MetalBackend::init()
}
#[cfg(all(feature = "vulkan", not(target_os = "macos")))]
pub fn init() -> Result<vulkan::VulkanBackend> {
vulkan::VulkanBackend::init()
}
#[cfg(all(feature = "vulkan", not(target_os = "macos")))]
pub fn init_device_local() -> Result<vulkan::VulkanBackend> {
vulkan::VulkanBackend::init_with_strategy(MemoryStrategy::DeviceLocal)
}
#[cfg(not(any(
all(feature = "metal", target_os = "macos"),
all(feature = "vulkan", not(target_os = "macos"))
)))]
pub fn init() -> Result<NoBackendStub> {
Err(GpuError::NoBackend)
}