use awsm_renderer_core::{
buffers::{BufferDescriptor, BufferUsage},
error::AwsmCoreError,
renderer::AwsmRendererWebGpu,
};
use crate::cluster_lod::{
write_cluster_cut_params, write_cluster_page_gpu, ClusterPage, CLUSTER_CUT_PARAMS_SIZE,
CLUSTER_PAGE_GPU_STRIDE,
};
use glam::{Mat4, Vec3};
pub const CLUSTER_DRAW_ARGS_SIZE: usize = 20;
pub struct ClusterLodBuffers {
pub pages_buffer: web_sys::GpuBuffer,
pub selected_buffer: web_sys::GpuBuffer,
pub params_buffer: web_sys::GpuBuffer,
pub readback_buffer: web_sys::GpuBuffer,
pub source_indices_buffer: web_sys::GpuBuffer,
pub compacted_indices_buffer: web_sys::GpuBuffer,
pub draw_args_buffer: web_sys::GpuBuffer,
pub capacity: u32,
pub index_capacity: u32,
staging: Vec<u8>,
}
impl ClusterLodBuffers {
pub fn with_capacity(
gpu: &AwsmRendererWebGpu,
capacity: u32,
index_capacity: u32,
) -> Result<Self, AwsmCoreError> {
let capacity = capacity.max(1);
let index_capacity = index_capacity.max(3);
let pages_bytes = capacity as usize * CLUSTER_PAGE_GPU_STRIDE;
let selected_bytes = capacity as usize * 4;
let index_bytes = index_capacity as usize * 4;
let pages_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("ClusterLodPages"),
pages_bytes,
BufferUsage::new().with_storage().with_copy_dst(),
)
.into(),
)?;
let selected_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("ClusterLodSelected"),
selected_bytes,
BufferUsage::new()
.with_storage()
.with_copy_dst()
.with_copy_src(),
)
.into(),
)?;
let params_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("ClusterLodParams"),
CLUSTER_CUT_PARAMS_SIZE,
BufferUsage::new().with_uniform().with_copy_dst(),
)
.into(),
)?;
let readback_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("ClusterLodReadback"),
selected_bytes,
BufferUsage::new().with_map_read().with_copy_dst(),
)
.into(),
)?;
let source_indices_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("ClusterLodSourceIndices"),
index_bytes,
BufferUsage::new().with_storage().with_copy_dst(),
)
.into(),
)?;
let compacted_indices_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("ClusterLodCompactedIndices"),
index_bytes,
BufferUsage::new()
.with_storage()
.with_index()
.with_copy_src(),
)
.into(),
)?;
let draw_args_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("ClusterLodDrawArgs"),
CLUSTER_DRAW_ARGS_SIZE,
BufferUsage::new()
.with_storage()
.with_indirect()
.with_copy_dst()
.with_copy_src(),
)
.into(),
)?;
Ok(Self {
pages_buffer,
selected_buffer,
params_buffer,
readback_buffer,
source_indices_buffer,
compacted_indices_buffer,
draw_args_buffer,
capacity,
index_capacity,
staging: vec![0u8; pages_bytes],
})
}
pub fn ensure_capacity(
&mut self,
gpu: &AwsmRendererWebGpu,
needed: u32,
needed_indices: u32,
) -> Result<bool, AwsmCoreError> {
if needed <= self.capacity && needed_indices <= self.index_capacity {
return Ok(false);
}
let new_capacity = needed.saturating_mul(2).max(needed).max(self.capacity);
let new_index_capacity = needed_indices
.saturating_mul(2)
.max(needed_indices)
.max(self.index_capacity);
*self = Self::with_capacity(gpu, new_capacity, new_index_capacity)?;
Ok(true)
}
pub fn write_source_indices(
&self,
gpu: &AwsmRendererWebGpu,
indices: &[u32],
) -> Result<(), AwsmCoreError> {
if indices.is_empty() {
return Ok(());
}
let mut bytes = Vec::with_capacity(indices.len() * 4);
for &i in indices {
bytes.extend_from_slice(&i.to_le_bytes());
}
gpu.write_buffer(
&self.source_indices_buffer,
None,
bytes.as_slice(),
None,
None,
)
}
pub fn write_pages(
&mut self,
gpu: &AwsmRendererWebGpu,
pages: &[ClusterPage],
) -> Result<(), AwsmCoreError> {
self.staging.clear();
for p in pages {
write_cluster_page_gpu(p, &mut self.staging);
}
if self.staging.is_empty() {
return Ok(());
}
gpu.write_buffer(
&self.pages_buffer,
None,
self.staging.as_slice(),
None,
None,
)
}
#[allow(clippy::too_many_arguments)]
pub fn write_params(
&self,
gpu: &AwsmRendererWebGpu,
instance_world: &Mat4,
camera_pos: Vec3,
tan_half_fov_y: f32,
viewport_h: f32,
pixel_budget: f32,
world_scale: f32,
cluster_count: u32,
) -> Result<(), AwsmCoreError> {
let mut bytes = Vec::with_capacity(CLUSTER_CUT_PARAMS_SIZE);
write_cluster_cut_params(
instance_world,
camera_pos,
tan_half_fov_y,
viewport_h,
pixel_budget,
world_scale,
cluster_count,
&mut bytes,
);
gpu.write_buffer(&self.params_buffer, None, bytes.as_slice(), None, None)
}
pub fn init_draw_args(
&self,
gpu: &AwsmRendererWebGpu,
first_instance: u32,
) -> Result<(), AwsmCoreError> {
let mut bytes = [0u8; CLUSTER_DRAW_ARGS_SIZE];
bytes[4..8].copy_from_slice(&1u32.to_le_bytes()); bytes[16..20].copy_from_slice(&first_instance.to_le_bytes());
gpu.write_buffer(&self.draw_args_buffer, None, bytes.as_slice(), None, None)
}
pub fn dispatch_groups(cluster_count: u32) -> u32 {
cluster_count.div_ceil(64)
}
}