use awsm_renderer_core::{
buffers::{BufferDescriptor, BufferUsage},
error::AwsmCoreError,
renderer::AwsmRendererWebGpu,
};
use slotmap::SlotMap;
use crate::{
bind_groups::BindGroups,
decals::data::{Decal, DecalBlendMode, DecalKey},
};
pub const MAX_DECAL_COUNT: u32 = 128;
pub const DECAL_STRIDE_BYTES: usize = 80;
pub const DECAL_HEADER_BYTES: usize = 16;
const TOTAL_BUFFER_BYTES: usize =
DECAL_HEADER_BYTES + DECAL_STRIDE_BYTES * MAX_DECAL_COUNT as usize;
pub struct Decals {
decals: SlotMap<DecalKey, Decal>,
order: Vec<DecalKey>,
gpu_buffer: web_sys::GpuBuffer,
staging_bytes: Vec<u8>,
dirty: bool,
}
impl Decals {
pub fn new(gpu: &AwsmRendererWebGpu) -> Result<Self, AwsmCoreError> {
let gpu_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("Decals"),
TOTAL_BUFFER_BYTES,
BufferUsage::new().with_storage().with_copy_dst(),
)
.into(),
)?;
Ok(Self {
decals: SlotMap::with_key(),
order: Vec::new(),
gpu_buffer,
staging_bytes: vec![0u8; TOTAL_BUFFER_BYTES],
dirty: true,
})
}
pub fn gpu_buffer(&self) -> &web_sys::GpuBuffer {
&self.gpu_buffer
}
pub fn len(&self) -> usize {
self.order.len()
}
pub fn is_empty(&self) -> bool {
self.order.is_empty()
}
pub fn insert(&mut self, decal: Decal) -> Result<DecalKey, AwsmDecalError> {
if self.order.len() as u32 >= MAX_DECAL_COUNT {
return Err(AwsmDecalError::TooManyDecals(MAX_DECAL_COUNT));
}
let key = self.decals.insert(decal);
self.order.push(key);
self.dirty = true;
Ok(key)
}
pub fn get(&self, key: DecalKey) -> Option<&Decal> {
self.decals.get(key)
}
pub fn update(&mut self, key: DecalKey, f: impl FnOnce(&mut Decal)) {
if let Some(d) = self.decals.get_mut(key) {
f(d);
self.dirty = true;
}
}
pub fn remove(&mut self, key: DecalKey) -> bool {
if self.decals.remove(key).is_some() {
self.order.retain(|k| *k != key);
self.dirty = true;
true
} else {
false
}
}
pub fn iter(&self) -> impl Iterator<Item = (DecalKey, &Decal)> + '_ {
self.order
.iter()
.filter_map(|k| self.decals.get(*k).map(|d| (*k, d)))
}
pub fn write_gpu(
&mut self,
gpu: &AwsmRendererWebGpu,
bind_groups: &mut BindGroups,
) -> Result<(), AwsmCoreError> {
if !self.dirty {
return Ok(());
}
self.dirty = false;
let count = self.order.len() as u32;
self.staging_bytes[0..4].copy_from_slice(&count.to_le_bytes());
for i in 4..DECAL_HEADER_BYTES {
self.staging_bytes[i] = 0;
}
for (slot, key) in self.order.iter().enumerate() {
let Some(decal) = self.decals.get(*key) else {
continue;
};
let base = DECAL_HEADER_BYTES + slot * DECAL_STRIDE_BYTES;
let cols = decal.inverse_transform.to_cols_array();
let bytes: &[u8] =
unsafe { std::slice::from_raw_parts(cols.as_ptr() as *const u8, 64) };
self.staging_bytes[base..base + 64].copy_from_slice(bytes);
self.staging_bytes[base + 64..base + 68]
.copy_from_slice(&decal.texture_index.to_le_bytes());
self.staging_bytes[base + 68..base + 72].copy_from_slice(&decal.alpha.to_le_bytes());
let blend_mode_u32 = match decal.blend_mode {
DecalBlendMode::AlphaBlend => 0u32,
};
self.staging_bytes[base + 72..base + 76].copy_from_slice(&blend_mode_u32.to_le_bytes());
self.staging_bytes[base + 76..base + 80].copy_from_slice(&[0u8; 4]);
}
let used = DECAL_HEADER_BYTES + self.order.len() * DECAL_STRIDE_BYTES;
for i in used..self.staging_bytes.len() {
self.staging_bytes[i] = 0;
}
gpu.write_buffer(
&self.gpu_buffer,
None,
self.staging_bytes.as_slice(),
None,
None,
)?;
let _ = bind_groups;
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum AwsmDecalError {
#[error("too many decals (cap: {0})")]
TooManyDecals(u32),
#[error("decals feature is not enabled (RendererFeatures::decals = false)")]
FeatureNotEnabled,
}