use super::{OccTable, BLOCK_SIZE, SUPERBLOCK_SIZE};
use crate::alphabet::ALPHABET_SIZE;
use crate::bwt::Bwt;
use crate::gpu::GpuContext;
const SHADER: &str = include_str!("../../shaders/occ_scan.wgsl");
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Params {
n: u32,
num_blocks: u32,
}
pub struct OccPipelines {
block_pipeline: wgpu::ComputePipeline,
}
impl OccPipelines {
pub fn new(ctx: &GpuContext) -> Self {
Self {
block_pipeline: ctx.create_compute_pipeline("occ_block", SHADER, "occ_block"),
}
}
pub async fn build_occ_table(&self, ctx: &GpuContext, bwt: &Bwt) -> OccTable {
let n = bwt.len() as u32;
let num_blocks = n.div_ceil(BLOCK_SIZE);
let alpha = ALPHABET_SIZE as u32;
let bwt_u32: Vec<u32> = bwt.to_u32_vec();
let bwt_buf = ctx.create_buffer_init("occ_bwt", &bwt_u32);
let block_counts_buf = ctx.create_buffer_empty("occ_block_counts", num_blocks * alpha);
let bitvectors_buf = ctx.create_buffer_empty("occ_bitvectors", num_blocks * alpha * 2);
let params = Params { n, num_blocks };
let params_buf = ctx.create_uniform_buffer("occ_params", ¶ms);
let bg = ctx.create_bind_group(
&self.block_pipeline,
0,
&[
wgpu::BindGroupEntry {
binding: 0,
resource: bwt_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: block_counts_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: bitvectors_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: params_buf.as_entire_binding(),
},
],
);
ctx.dispatch(&self.block_pipeline, &bg, (num_blocks, 1, 1));
let block_counts = ctx
.download_buffer(&block_counts_buf, num_blocks * alpha)
.await;
let bitvec_flat = ctx
.download_buffer(&bitvectors_buf, num_blocks * alpha * 2)
.await;
let num_blocks_usize = num_blocks as usize;
let num_superblocks = n.div_ceil(SUPERBLOCK_SIZE) as usize;
let blocks_per_sb = (SUPERBLOCK_SIZE / BLOCK_SIZE) as usize;
let alpha_usize = ALPHABET_SIZE;
let mut symbol_to_lane = [u8::MAX; ALPHABET_SIZE];
for (c, lane) in symbol_to_lane.iter_mut().enumerate() {
*lane = c as u8;
}
let num_planes = (u8::BITS - (ALPHABET_SIZE as u8 - 1).leading_zeros()) as usize;
let mut superblock_checkpoints: Vec<u32> =
Vec::with_capacity(num_superblocks * alpha_usize);
let mut block_deltas: Vec<u16> = Vec::with_capacity(num_blocks_usize * alpha_usize);
let mut planes: Vec<u64> = Vec::with_capacity(num_blocks_usize * num_planes);
let mut cumulative = [0u32; ALPHABET_SIZE];
let mut sb_base = [0u32; ALPHABET_SIZE];
for b in 0..num_blocks_usize {
if b % blocks_per_sb == 0 {
superblock_checkpoints.extend_from_slice(&cumulative);
sb_base = cumulative;
}
let mut block_planes = vec![0u64; num_planes];
for c in 0..alpha_usize {
block_deltas.push((cumulative[c] - sb_base[c]) as u16);
let lo = bitvec_flat[(b * alpha_usize + c) * 2];
let hi = bitvec_flat[(b * alpha_usize + c) * 2 + 1];
let bits = (hi as u64) << 32 | lo as u64;
for (p, plane) in block_planes.iter_mut().enumerate() {
if (c >> p) & 1 == 1 {
*plane |= bits;
}
}
cumulative[c] += block_counts[b * alpha_usize + c];
}
planes.extend_from_slice(&block_planes);
}
OccTable::from_parts(
ALPHABET_SIZE as u8,
symbol_to_lane,
superblock_checkpoints,
block_deltas,
planes,
n,
super::OccEncoding::Bitplane,
)
}
}