lampshade 0.13.0

Fast, composable GPU primitives for Rust applications using wgpu and WGSL.
Documentation
use crate::{common, profiling};

pub(super) const BLOCK_SIZE: u32 = 256;
pub(super) const MAX_WORKGROUPS_X: u32 = 65_535;

pub(super) struct ArgminPipelines {
    pub(super) fixed_layout: wgpu::BindGroupLayout,
    pub(super) counted_layout: wgpu::BindGroupLayout,
    pub(super) fixed: wgpu::ComputePipeline,
    pub(super) counted: wgpu::ComputePipeline,
}

impl ArgminPipelines {
    pub(super) fn new(device: &wgpu::Device) -> Self {
        let fixed_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("Argmin-by-Key Layout"),
            entries: &[
                common::buffers::bind_entry(0, true, false),
                common::buffers::bind_entry(1, false, false),
                common::buffers::bind_entry(2, true, true),
            ],
        });
        let counted_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("Counted Argmin-by-Key Layout"),
            entries: &[
                common::buffers::bind_entry(0, true, false),
                common::buffers::bind_entry(1, false, false),
                common::buffers::bind_entry(2, true, false),
                common::buffers::bind_entry(3, true, true),
            ],
        });
        let fixed = create_pipeline(
            device,
            &fixed_layout,
            include_str!("argmin.wgsl"),
            "Argmin-by-Key Pipeline",
        );
        let counted = create_pipeline(
            device,
            &counted_layout,
            include_str!("argmin_counted.wgsl"),
            "Counted Argmin-by-Key Pipeline",
        );
        Self {
            fixed_layout,
            counted_layout,
            fixed,
            counted,
        }
    }

    pub(super) fn record_pass(
        &self,
        encoder: &mut wgpu::CommandEncoder,
        bind_group: &wgpu::BindGroup,
        output_items: u32,
        level: u32,
        counted: bool,
        profiler: Option<&mut profiling::TimestampRecorder>,
    ) {
        let (groups_x, groups_y) = dispatch_dimensions(output_items);
        let pipeline = if counted { &self.counted } else { &self.fixed };
        let profile_label = profiler
            .is_some()
            .then(|| format!("argmin_by_key.level.{level}"));
        profiling::record_compute_pass(encoder, "Argmin by Key", profile_label, profiler, |pass| {
            pass.set_pipeline(pipeline);
            pass.set_bind_group(0, bind_group, &[]);
            pass.dispatch_workgroups(groups_x, groups_y, 1);
        });
    }
}

fn create_pipeline(
    device: &wgpu::Device,
    layout: &wgpu::BindGroupLayout,
    source: &str,
    label: &str,
) -> wgpu::ComputePipeline {
    let source = source
        .replace("{{BLOCK_SIZE}}", &BLOCK_SIZE.to_string())
        .replace("{{MAX_WORKGROUPS_X}}", &MAX_WORKGROUPS_X.to_string());
    common::shader::create_compute_pipeline(device, layout, &source, label, "main", None)
}

pub(super) const fn output_items(input_items: u32) -> u32 {
    input_items.div_ceil(BLOCK_SIZE)
}

pub(super) fn pass_count(mut input_items: u32) -> u32 {
    let mut passes = 0;
    while input_items > 0 {
        passes += 1;
        input_items = output_items(input_items);
        if input_items == 1 {
            break;
        }
    }
    passes
}

fn dispatch_dimensions(output_items: u32) -> (u32, u32) {
    (
        output_items.min(MAX_WORKGROUPS_X),
        output_items.div_ceil(MAX_WORKGROUPS_X),
    )
}

#[cfg(test)]
mod tests {
    use super::{BLOCK_SIZE, MAX_WORKGROUPS_X, dispatch_dimensions, pass_count};

    #[test]
    fn hierarchy_and_two_dimensional_dispatch_cover_tails() {
        assert_eq!(pass_count(0), 0);
        assert_eq!(pass_count(1), 1);
        assert_eq!(pass_count(BLOCK_SIZE), 1);
        assert_eq!(pass_count(BLOCK_SIZE + 1), 2);
        assert_eq!(
            dispatch_dimensions(MAX_WORKGROUPS_X + 1),
            (MAX_WORKGROUPS_X, 2)
        );
    }
}