use std::marker::PhantomData;
use bytemuck::{Pod, Zeroable};
use crate::context::GraphicsContext;
use crate::features::GpuFeatures;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct DrawIndirect {
pub vertex_count: u32,
pub instance_count: u32,
pub first_vertex: u32,
pub first_instance: u32,
}
unsafe impl Pod for DrawIndirect {}
unsafe impl Zeroable for DrawIndirect {}
impl DrawIndirect {
pub const fn new(
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
) -> Self {
Self {
vertex_count,
instance_count,
first_vertex,
first_instance,
}
}
pub const fn single(vertex_count: u32) -> Self {
Self::new(vertex_count, 1, 0, 0)
}
pub const fn instanced(vertex_count: u32, instance_count: u32) -> Self {
Self::new(vertex_count, instance_count, 0, 0)
}
pub const fn size() -> u64 {
std::mem::size_of::<Self>() as u64
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct DrawIndexedIndirect {
pub index_count: u32,
pub instance_count: u32,
pub first_index: u32,
pub base_vertex: i32,
pub first_instance: u32,
}
unsafe impl Pod for DrawIndexedIndirect {}
unsafe impl Zeroable for DrawIndexedIndirect {}
impl DrawIndexedIndirect {
pub const fn new(
index_count: u32,
instance_count: u32,
first_index: u32,
base_vertex: i32,
first_instance: u32,
) -> Self {
Self {
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
}
}
pub const fn single(index_count: u32) -> Self {
Self::new(index_count, 1, 0, 0, 0)
}
pub const fn instanced(index_count: u32, instance_count: u32) -> Self {
Self::new(index_count, instance_count, 0, 0, 0)
}
pub const fn size() -> u64 {
std::mem::size_of::<Self>() as u64
}
}
pub trait IndirectCommand: Pod + Zeroable + Default {
const SIZE: u64;
}
impl IndirectCommand for DrawIndirect {
const SIZE: u64 = std::mem::size_of::<Self>() as u64;
}
impl IndirectCommand for DrawIndexedIndirect {
const SIZE: u64 = std::mem::size_of::<Self>() as u64;
}
pub struct IndirectBuffer<T: IndirectCommand> {
buffer: wgpu::Buffer,
capacity: usize,
_marker: PhantomData<T>,
}
impl<T: IndirectCommand> IndirectBuffer<T> {
pub fn new(context: &GraphicsContext, label: Option<&str>, capacity: usize) -> Self {
context.require_feature(GpuFeatures::INDIRECT_FIRST_INSTANCE);
let buffer = context.device().create_buffer(&wgpu::BufferDescriptor {
label,
size: T::SIZE * capacity as u64,
usage: wgpu::BufferUsages::INDIRECT
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::STORAGE,
mapped_at_creation: false,
});
Self {
buffer,
capacity,
_marker: PhantomData,
}
}
pub fn new_init(context: &GraphicsContext, label: Option<&str>, commands: &[T]) -> Self {
context.require_feature(GpuFeatures::INDIRECT_FIRST_INSTANCE);
let buffer = context.device().create_buffer(&wgpu::BufferDescriptor {
label,
size: T::SIZE * commands.len() as u64,
usage: wgpu::BufferUsages::INDIRECT
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::STORAGE,
mapped_at_creation: false,
});
context
.queue()
.write_buffer(&buffer, 0, bytemuck::cast_slice(commands));
Self {
buffer,
capacity: commands.len(),
_marker: PhantomData,
}
}
pub fn buffer(&self) -> &wgpu::Buffer {
&self.buffer
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn size_bytes(&self) -> u64 {
T::SIZE * self.capacity as u64
}
pub fn offset_of(&self, index: usize) -> u64 {
T::SIZE * index as u64
}
pub fn write_at(&self, queue: &wgpu::Queue, start_index: usize, commands: &[T]) {
assert!(
start_index + commands.len() <= self.capacity,
"Indirect buffer write would exceed capacity: {} + {} > {}",
start_index,
commands.len(),
self.capacity
);
let offset = T::SIZE * start_index as u64;
queue.write_buffer(&self.buffer, offset, bytemuck::cast_slice(commands));
}
pub fn write(&self, queue: &wgpu::Queue, commands: &[T]) {
self.write_at(queue, 0, commands);
}
pub fn clear(&self, queue: &wgpu::Queue) {
let zeros = vec![T::default(); self.capacity];
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&zeros));
}
}
pub trait RenderPassIndirectExt<'a> {
fn draw_indirect_at(&mut self, indirect_buffer: &'a IndirectBuffer<DrawIndirect>, index: usize);
fn draw_indexed_indirect_at(
&mut self,
indirect_buffer: &'a IndirectBuffer<DrawIndexedIndirect>,
index: usize,
);
}
impl<'a> RenderPassIndirectExt<'a> for wgpu::RenderPass<'a> {
fn draw_indirect_at(
&mut self,
indirect_buffer: &'a IndirectBuffer<DrawIndirect>,
index: usize,
) {
let offset = indirect_buffer.offset_of(index);
self.draw_indirect(indirect_buffer.buffer(), offset);
}
fn draw_indexed_indirect_at(
&mut self,
indirect_buffer: &'a IndirectBuffer<DrawIndexedIndirect>,
index: usize,
) {
let offset = indirect_buffer.offset_of(index);
self.draw_indexed_indirect(indirect_buffer.buffer(), offset);
}
}
pub trait RenderPassMultiDrawIndirectExt<'a> {
fn multi_draw_indirect(
&mut self,
indirect_buffer: &'a IndirectBuffer<DrawIndirect>,
start_index: usize,
count: u32,
);
fn multi_draw_indexed_indirect(
&mut self,
indirect_buffer: &'a IndirectBuffer<DrawIndexedIndirect>,
start_index: usize,
count: u32,
);
}
impl<'a> RenderPassMultiDrawIndirectExt<'a> for wgpu::RenderPass<'a> {
fn multi_draw_indirect(
&mut self,
indirect_buffer: &'a IndirectBuffer<DrawIndirect>,
start_index: usize,
count: u32,
) {
let offset = indirect_buffer.offset_of(start_index);
self.multi_draw_indirect(indirect_buffer.buffer(), offset, count);
}
fn multi_draw_indexed_indirect(
&mut self,
indirect_buffer: &'a IndirectBuffer<DrawIndexedIndirect>,
start_index: usize,
count: u32,
) {
let offset = indirect_buffer.offset_of(start_index);
self.multi_draw_indexed_indirect(indirect_buffer.buffer(), offset, count);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_draw_indirect_size() {
assert_eq!(DrawIndirect::size(), 16); assert_eq!(DrawIndirect::SIZE, 16);
}
#[test]
fn test_draw_indexed_indirect_size() {
assert_eq!(DrawIndexedIndirect::size(), 20); assert_eq!(DrawIndexedIndirect::SIZE, 20);
}
#[test]
fn test_draw_indirect_single() {
let cmd = DrawIndirect::single(36);
assert_eq!(cmd.vertex_count, 36);
assert_eq!(cmd.instance_count, 1);
assert_eq!(cmd.first_vertex, 0);
assert_eq!(cmd.first_instance, 0);
}
#[test]
fn test_draw_indexed_indirect_instanced() {
let cmd = DrawIndexedIndirect::instanced(36, 100);
assert_eq!(cmd.index_count, 36);
assert_eq!(cmd.instance_count, 100);
assert_eq!(cmd.first_index, 0);
assert_eq!(cmd.base_vertex, 0);
assert_eq!(cmd.first_instance, 0);
}
}