use crate::backend::BackendError;
use std::sync::Arc;
use wgpu::util::DeviceExt;
pub struct Device {
pub device: Arc<wgpu::Device>,
pub queue: Arc<wgpu::Queue>,
}
impl Device {
pub fn new() -> Result<Self, BackendError> {
pollster::block_on(Self::init())
}
async fn init() -> Result<Self, BackendError> {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
..Default::default()
})
.await
.ok_or_else(|| BackendError::BackendInit {
backend: "wgpu+rs",
reason: "no GPU adapter".into(),
})?;
let mut limits = wgpu::Limits::default();
limits.max_buffer_size = 4u64 << 30;
limits.max_storage_buffer_binding_size = 1u32 << 30;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("run"),
required_features: wgpu::Features::empty(),
required_limits: limits,
memory_hints: Default::default(),
},
None,
)
.await
.map_err(|e| BackendError::BackendInit {
backend: "wgpu+rs",
reason: e.to_string(),
})?;
Ok(Self {
device: Arc::new(device),
queue: Arc::new(queue),
})
}
pub fn upload_bytes(&self, bytes: &[u8]) -> wgpu::Buffer {
let mut padded = bytes.to_vec();
while padded.len() % 4 != 0 {
padded.push(0);
}
if padded.is_empty() {
padded = vec![0u8; 4];
}
self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: &padded,
usage: wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_SRC
| wgpu::BufferUsages::COPY_DST,
})
}
pub fn alloc_f32(&self, n: usize) -> wgpu::Buffer {
let size = (n.max(1) * 4) as u64;
self.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size,
usage: wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_SRC
| wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
pub fn upload_uniform(&self, bytes: &[u8]) -> wgpu::Buffer {
let mut padded = bytes.to_vec();
while padded.len() % 16 != 0 {
padded.push(0);
}
self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: &padded,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})
}
pub fn read_f32(&self, buffer: &wgpu::Buffer, count: usize) -> Vec<f32> {
let size = (count * 4) as u64;
let staging = self.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut enc = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
enc.copy_buffer_to_buffer(buffer, 0, &staging, 0, size);
self.queue.submit(std::iter::once(enc.finish()));
let slice = staging.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
slice.map_async(wgpu::MapMode::Read, move |r| {
tx.send(r).ok();
});
self.device.poll(wgpu::Maintain::Wait);
rx.recv().unwrap().unwrap();
let data = slice.get_mapped_range();
let out: Vec<f32> = bytemuck::cast_slice(&data).to_vec();
drop(data);
staging.unmap();
out
}
}
pub fn compute_pipeline(
device: &wgpu::Device,
shader_src: &str,
bindings: &[wgpu::BindGroupLayoutEntry],
) -> (wgpu::ComputePipeline, wgpu::BindGroupLayout) {
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(shader_src.into()),
});
let bg_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: bindings,
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&bg_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
module: &module,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
(pipeline, bg_layout)
}
pub fn storage_ro(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
pub fn storage_rw(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
pub fn uniform(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}