use crate::shapes::{ViewShape, ViewShapeBuffers};
use crate::timestamps::GpuTimestamps;
use std::sync::Arc;
use wgpu::{
BindGroup, Buffer, CommandEncoder, ComputePass, ComputePassDescriptor, ComputePipeline, Device,
};
pub trait WorkgroupSize {
fn into_workgroups_size(self) -> [u32; 3];
}
impl WorkgroupSize for u32 {
fn into_workgroups_size(self) -> [u32; 3] {
[self, 1, 1]
}
}
impl WorkgroupSize for [u32; 3] {
fn into_workgroups_size(self) -> [u32; 3] {
self
}
}
pub struct KernelInvocationBuilder<'a, 'b> {
queue: &'b mut KernelInvocationQueue<'a>,
pipeline: &'a ComputePipeline,
bind_groups: Vec<(u32, BindGroup)>,
}
impl<'a, 'b> KernelInvocationBuilder<'a, 'b> {
pub fn new(queue: &'b mut KernelInvocationQueue<'a>, pipeline: &'a ComputePipeline) -> Self {
Self {
queue,
pipeline,
bind_groups: vec![],
}
}
pub fn bind0<const INPUTS: usize>(self, inputs: [&Buffer; INPUTS]) -> Self {
self.bind(0, inputs)
}
pub fn bind<const INPUTS: usize>(self, bind_group_id: u32, inputs: [&Buffer; INPUTS]) -> Self {
let mut inputs = inputs.map(|b| (b, 0));
for (id, input) in inputs.iter_mut().enumerate() {
input.1 = id as u32;
}
self.bind_at(bind_group_id, inputs)
}
pub fn bind_at<const INPUTS: usize>(
mut self,
bind_group_id: u32,
inputs: [(&Buffer, u32); INPUTS],
) -> Self {
let entries = inputs.map(|(input, binding)| wgpu::BindGroupEntry {
binding,
resource: input.as_entire_binding(),
});
let bind_group_layout = self.pipeline.get_bind_group_layout(bind_group_id);
let bind_group = self
.queue
.device()
.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &bind_group_layout,
entries: &entries,
});
self.bind_groups.push((bind_group_id, bind_group));
self
}
pub fn queue(self, workgroups: impl WorkgroupSize) {
let invocation = KernelInvocation {
pipeline: self.pipeline,
bind_groups: self.bind_groups,
workgroups: Workgroups::Direct(workgroups.into_workgroups_size()),
};
self.queue.push(invocation)
}
pub fn queue_indirect(self, workgroups: Arc<Buffer>) {
let invocation = KernelInvocation {
pipeline: self.pipeline,
bind_groups: self.bind_groups,
workgroups: Workgroups::Indirect(workgroups),
};
self.queue.push(invocation)
}
}
pub enum Workgroups {
Direct([u32; 3]),
Indirect(Arc<Buffer>),
}
pub struct KernelInvocation<'a> {
pub pipeline: &'a ComputePipeline,
pub bind_groups: Vec<(u32, BindGroup)>,
pub workgroups: Workgroups,
}
impl<'a> KernelInvocation<'a> {
pub fn dispatch<'b>(&'a self, pass: &mut ComputePass<'b>)
where
'a: 'b,
{
pass.set_pipeline(self.pipeline);
for (id, bind_group) in &self.bind_groups {
pass.set_bind_group(*id, bind_group, &[]);
}
match &self.workgroups {
Workgroups::Direct(workgroups) => {
pass.dispatch_workgroups(workgroups[0], workgroups[1], workgroups[2]);
}
Workgroups::Indirect(workgroups) => {
pass.dispatch_workgroups_indirect(workgroups, 0);
}
}
}
}
enum Invocation<'a> {
Kernel(KernelInvocation<'a>),
Timestamp {
query_index: u32,
},
ComputePass {
label: &'a str,
add_timestamps: bool,
},
}
pub struct KernelInvocationQueue<'a> {
device: &'a Device,
shapes: ViewShapeBuffers,
invocations: Vec<Invocation<'a>>,
}
impl<'a> KernelInvocationQueue<'a> {
pub fn new(device: &'a Device) -> Self {
Self {
device,
shapes: ViewShapeBuffers::new(),
invocations: vec![],
}
}
pub fn device(&self) -> &Device {
self.device
}
pub fn shape_buffer(&self, shape: ViewShape) -> Arc<Buffer> {
self.shapes.get(self.device, shape)
}
pub fn push(&mut self, invocation: KernelInvocation<'a>) {
self.invocations.push(Invocation::Kernel(invocation));
}
pub fn compute_pass(&mut self, label: &'a str, add_timestamps: bool) {
self.invocations.push(Invocation::ComputePass {
label,
add_timestamps,
});
}
pub fn push_timestamp(&mut self, timestamps: &mut GpuTimestamps) -> Option<u32> {
let query_index = timestamps.next_query_index();
if let Some(query_index) = query_index {
self.invocations.push(Invocation::Timestamp { query_index });
}
query_index
}
pub fn encode(&self, encoder: &mut CommandEncoder, mut timestamps: Option<&mut GpuTimestamps>) {
if self.invocations.is_empty() {
return;
}
let (mut pass, start) = if let Invocation::ComputePass {
label,
add_timestamps,
} = &self.invocations[0]
{
let desc = ComputePassDescriptor {
label: Some(*label),
timestamp_writes: timestamps
.as_deref_mut()
.filter(|_| *add_timestamps)
.and_then(|ts| ts.next_compute_pass_timestamp_writes()),
};
(encoder.begin_compute_pass(&desc), 1)
} else {
(encoder.begin_compute_pass(&Default::default()), 0)
};
for invocation in &self.invocations[start..] {
match invocation {
Invocation::Kernel(kernel) => kernel.dispatch(&mut pass),
Invocation::Timestamp { query_index } => {
if let Some(timestamps) = timestamps.as_deref_mut() {
timestamps.write_timestamp_at(&mut pass, *query_index);
}
}
Invocation::ComputePass {
label,
add_timestamps,
} => {
drop(pass);
let desc = ComputePassDescriptor {
label: Some(*label),
timestamp_writes: timestamps
.as_deref_mut()
.filter(|_| *add_timestamps)
.and_then(|ts| ts.next_compute_pass_timestamp_writes()),
};
pass = encoder.begin_compute_pass(&desc);
}
}
}
}
pub fn dispatch<'b>(
&'a self,
pass: &mut ComputePass<'b>,
mut timestamps: Option<&mut GpuTimestamps>,
) where
'a: 'b,
{
for invocation in &self.invocations {
match invocation {
Invocation::Kernel(kernel) => kernel.dispatch(pass),
Invocation::Timestamp { query_index } => {
if let Some(timestamps) = timestamps.as_deref_mut() {
timestamps.write_timestamp_at(pass, *query_index);
}
}
Invocation::ComputePass { .. } => {
}
}
}
}
pub fn clear(&mut self) {
self.invocations.clear();
}
}