use std::sync::LazyLock;
use awsm_renderer_core::{
buffers::{BufferDescriptor, BufferUsage},
error::AwsmCoreError,
renderer::AwsmRendererWebGpu,
};
pub const RING_LEN: usize = 3;
static STAGING_BUFFER_USAGE: LazyLock<BufferUsage> =
LazyLock::new(|| BufferUsage::new().with_map_write().with_copy_src());
pub struct StagingRing {
buffers: [web_sys::GpuBuffer; RING_LEN],
slot_capacity: usize,
cursor: usize,
label: &'static str,
}
impl StagingRing {
pub fn new(
gpu: &AwsmRendererWebGpu,
initial_capacity: usize,
label: &'static str,
) -> Result<Self, AwsmCoreError> {
let cap = initial_capacity.max(64);
let buffers = [
create_slot(gpu, cap, label)?,
create_slot(gpu, cap, label)?,
create_slot(gpu, cap, label)?,
];
Ok(Self {
buffers,
slot_capacity: cap,
cursor: 0,
label,
})
}
pub fn current(&self) -> &web_sys::GpuBuffer {
&self.buffers[self.cursor]
}
pub fn slot_capacity(&self) -> usize {
self.slot_capacity
}
pub fn ensure_capacity(
&mut self,
gpu: &AwsmRendererWebGpu,
requested_bytes: usize,
) -> Result<bool, AwsmCoreError> {
if requested_bytes <= self.slot_capacity {
return Ok(false);
}
let new_cap = (requested_bytes * 2).max(self.slot_capacity * 2);
for slot in &mut self.buffers {
*slot = create_slot(gpu, new_cap, self.label)?;
}
self.slot_capacity = new_cap;
Ok(true)
}
pub fn advance(&mut self) {
self.cursor = (self.cursor + 1) % RING_LEN;
}
pub fn total_bytes(&self) -> usize {
RING_LEN * self.slot_capacity
}
}
fn create_slot(
gpu: &AwsmRendererWebGpu,
capacity: usize,
label: &'static str,
) -> Result<web_sys::GpuBuffer, AwsmCoreError> {
gpu.create_buffer(&BufferDescriptor::new(Some(label), capacity, *STAGING_BUFFER_USAGE).into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cursor_wraps_after_ring_len() {
let mut cursor = 0_usize;
let mut seen = Vec::new();
for _ in 0..RING_LEN * 2 {
seen.push(cursor);
cursor = (cursor + 1) % RING_LEN;
}
assert_eq!(seen, vec![0, 1, 2, 0, 1, 2]);
}
}