use std::sync::Arc;
use wgpu::{Buffer, BufferAddress, BufferDescriptor, BufferUsages, ComputePipeline};
use crate::context::GpuContext;
use crate::error::{GpuError, GpuResult};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DispatchIndirectArgs {
pub x: u32,
pub y: u32,
pub z: u32,
}
impl DispatchIndirectArgs {
#[inline]
pub fn new(x: u32, y: u32, z: u32) -> Self {
Self { x, y, z }
}
#[inline]
pub fn for_1d(total: u32, workgroup_size: u32) -> Self {
Self {
x: workgroup_count_1d(total, workgroup_size),
y: 1,
z: 1,
}
}
#[inline]
pub fn as_bytes(&self) -> [u8; 12] {
let mut buf = [0u8; 12];
buf[0..4].copy_from_slice(&self.x.to_le_bytes());
buf[4..8].copy_from_slice(&self.y.to_le_bytes());
buf[8..12].copy_from_slice(&self.z.to_le_bytes());
buf
}
}
pub struct IndirectDispatchBuffer {
buffer: Arc<Buffer>,
offset: BufferAddress,
capacity: u32,
}
impl IndirectDispatchBuffer {
pub fn new(ctx: &GpuContext, args: DispatchIndirectArgs) -> GpuResult<Self> {
let buffer = ctx.device().create_buffer(&BufferDescriptor {
label: Some("indirect_dispatch"),
size: 12,
usage: BufferUsages::INDIRECT | BufferUsages::COPY_DST | BufferUsages::STORAGE,
mapped_at_creation: false,
});
let buf = Arc::new(buffer);
ctx.queue().write_buffer(&buf, 0, &args.as_bytes());
Ok(Self {
buffer: buf,
offset: 0,
capacity: 1,
})
}
pub fn new_with_capacity(ctx: &GpuContext, max_dispatches: u32) -> GpuResult<Self> {
if max_dispatches == 0 {
return Err(GpuError::InvalidKernelParams {
reason: "max_dispatches must be > 0".to_string(),
});
}
let size = 12u64 * max_dispatches as u64;
let buffer = ctx.device().create_buffer(&BufferDescriptor {
label: Some("indirect_dispatch_multi"),
size,
usage: BufferUsages::INDIRECT | BufferUsages::COPY_DST | BufferUsages::STORAGE,
mapped_at_creation: false,
});
Ok(Self {
buffer: Arc::new(buffer),
offset: 0,
capacity: max_dispatches,
})
}
pub fn update(&self, ctx: &GpuContext, args: DispatchIndirectArgs) {
ctx.queue()
.write_buffer(&self.buffer, self.offset, &args.as_bytes());
}
pub fn update_at(
&self,
ctx: &GpuContext,
slot_idx: u32,
args: DispatchIndirectArgs,
) -> GpuResult<()> {
if slot_idx >= self.capacity {
return Err(GpuError::InvalidKernelParams {
reason: format!(
"slot_idx {} is out of range for capacity {}",
slot_idx, self.capacity
),
});
}
let slot_offset = self.offset + 12 * slot_idx as u64;
ctx.queue()
.write_buffer(&self.buffer, slot_offset, &args.as_bytes());
Ok(())
}
#[inline]
pub fn buffer(&self) -> &Buffer {
&self.buffer
}
#[inline]
pub fn offset(&self) -> BufferAddress {
self.offset
}
#[inline]
pub fn capacity(&self) -> u32 {
self.capacity
}
}
pub fn dispatch_indirect_on_pass<'a>(
pass: &mut wgpu::ComputePass<'a>,
pipeline: &'a ComputePipeline,
bind_groups: &[&'a wgpu::BindGroup],
indirect: &'a IndirectDispatchBuffer,
) {
pass.set_pipeline(pipeline);
for (i, bg) in bind_groups.iter().enumerate() {
pass.set_bind_group(i as u32, *bg, &[]);
}
pass.dispatch_workgroups_indirect(indirect.buffer(), indirect.offset());
}
#[inline]
pub fn workgroup_count_1d(total_elements: u32, workgroup_size: u32) -> u32 {
if workgroup_size == 0 {
return 0;
}
total_elements.saturating_add(workgroup_size - 1) / workgroup_size
}
#[inline]
pub fn workgroup_count_2d(width: u32, height: u32, wg_x: u32, wg_y: u32) -> (u32, u32) {
(
workgroup_count_1d(width, wg_x),
workgroup_count_1d(height, wg_y),
)
}
#[inline]
pub fn workgroup_count_3d(dims: (u32, u32, u32), wg_dims: (u32, u32, u32)) -> (u32, u32, u32) {
(
workgroup_count_1d(dims.0, wg_dims.0),
workgroup_count_1d(dims.1, wg_dims.1),
workgroup_count_1d(dims.2, wg_dims.2),
)
}
#[inline]
pub fn args_for_elements(total: u32, workgroup_size: u32) -> DispatchIndirectArgs {
DispatchIndirectArgs {
x: workgroup_count_1d(total, workgroup_size),
y: 1,
z: 1,
}
}