use crate::context::GpuContext;
use crate::executor::{ComputeOp, GpuOpError};
use wgpu::util::DeviceExt;
const WORKGROUP_SIZE: u32 = 64;
pub struct VectorAddInput<'a> {
pub a: &'a [f32],
pub b: &'a [f32],
}
pub struct VectorAdd;
impl ComputeOp for VectorAdd {
type Input<'a> = VectorAddInput<'a>;
type Output = Vec<f32>;
fn compute_gpu(
&self,
ctx: &GpuContext,
input: &Self::Input<'_>,
) -> Result<Self::Output, GpuOpError> {
vector_add_gpu(ctx, input.a, input.b)
}
fn compute_cpu(&self, input: &Self::Input<'_>) -> Self::Output {
vector_add_cpu(input.a, input.b)
}
}
pub fn vector_add_cpu(a: &[f32], b: &[f32]) -> Vec<f32> {
a.iter().zip(b.iter()).map(|(x, y)| x + y).collect()
}
pub fn vector_add_gpu(ctx: &GpuContext, a: &[f32], b: &[f32]) -> Result<Vec<f32>, GpuOpError> {
if a.len() != b.len() {
return Err(GpuOpError::InvalidInput(format!(
"vector_add needs equal-length inputs, got {} and {}",
a.len(),
b.len()
)));
}
let n = a.len();
if n == 0 {
return Ok(Vec::new());
}
let device = ctx.device();
let queue = ctx.queue();
let byte_len = std::mem::size_of_val(a) as wgpu::BufferAddress;
let a_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("vector_add.a"),
contents: bytemuck::cast_slice(a),
usage: wgpu::BufferUsages::STORAGE,
});
let b_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("vector_add.b"),
contents: bytemuck::cast_slice(b),
usage: wgpu::BufferUsages::STORAGE,
});
let out_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("vector_add.out"),
size: byte_len,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let staging = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("vector_add.staging"),
size: byte_len,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("vector_add.wgsl"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/vector_add.wgsl").into()),
});
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("vector_add.pipeline"),
layout: None,
module: &shader,
entry_point: Some("main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
let bind_group_layout = pipeline.get_bind_group_layout(0);
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("vector_add.bind_group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: a_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: b_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: out_buf.as_entire_binding(),
},
],
});
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("vector_add") });
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("vector_add.pass"),
timestamp_writes: None,
});
pass.set_pipeline(&pipeline);
pass.set_bind_group(0, &bind_group, &[]);
let workgroups = (n as u32).div_ceil(WORKGROUP_SIZE);
pass.dispatch_workgroups(workgroups, 1, 1);
}
encoder.copy_buffer_to_buffer(&out_buf, 0, &staging, 0, byte_len);
queue.submit(Some(encoder.finish()));
let slice = staging.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
slice.map_async(wgpu::MapMode::Read, move |res| {
let _ = tx.send(res);
});
device
.poll(wgpu::PollType::wait_indefinitely())
.map_err(|e| GpuOpError::Backend(format!("device poll failed: {e:?}")))?;
rx.recv()
.map_err(|e| GpuOpError::Backend(format!("map callback dropped: {e}")))?
.map_err(|e| GpuOpError::Backend(format!("buffer map failed: {e:?}")))?;
let data = slice
.get_mapped_range()
.map_err(|e| GpuOpError::Backend(format!("get_mapped_range failed: {e:?}")))?;
let result: Vec<f32> = bytemuck::cast_slice(&data).to_vec();
drop(data);
staging.unmap();
Ok(result)
}