use crate::Gpu;
use fidget_core::render::{ImageSize, VoxelSize};
use fidget_raster::RenderSize;
use zerocopy::{FromBytes, IntoBytes};
pub struct FlexBuffer<T: BufferTag> {
size: T::S,
data: wgpu::Buffer,
name: String,
_t: std::marker::PhantomData<T>,
}
pub trait BufferTag {
type T;
type S: BufferItemCount + Copy;
fn usage() -> u32;
}
pub struct MappedBufferTag<T: BufferTag> {
_t: std::marker::PhantomData<T>,
}
impl<T: BufferTag> BufferTag for MappedBufferTag<T> {
type T = T::T;
type S = T::S;
fn usage() -> u32 {
wgpu::BufferUsages::COPY_DST.bits()
| wgpu::BufferUsages::MAP_READ.bits()
}
}
#[macro_export]
macro_rules! tag {
($vis:vis $name:ident, $t:ty, $s:ty, $($flag:ident)|+ $(,$doc:expr)?) => {
$(#[doc = $doc])?
$vis struct $name;
impl $crate::buf::BufferTag for $name {
type T = $t;
type S = $s;
fn usage() -> u32 {
$( wgpu::BufferUsages::$flag.bits() )|+
}
}
}
}
pub trait BufferItemCount {
fn item_count(&self) -> usize;
}
impl BufferItemCount for usize {
fn item_count(&self) -> usize {
*self
}
}
impl BufferItemCount for ImageSize {
fn item_count(&self) -> usize {
usize::try_from(self.width())
.unwrap()
.checked_mul(usize::try_from(self.height()).unwrap())
.unwrap()
}
}
impl BufferItemCount for VoxelSize {
fn item_count(&self) -> usize {
ImageSize::from(*self).item_count()
}
}
pub(crate) struct FlexConfigSize<C, T> {
count: usize,
_c: std::marker::PhantomData<C>,
_t: std::marker::PhantomData<T>,
}
impl<C, T> From<usize> for FlexConfigSize<C, T> {
fn from(value: usize) -> Self {
Self {
count: value,
_c: std::marker::PhantomData,
_t: std::marker::PhantomData,
}
}
}
impl<C, T> Clone for FlexConfigSize<C, T> {
fn clone(&self) -> Self {
*self
}
}
impl<C, T> Copy for FlexConfigSize<C, T> {}
impl<C, T> BufferItemCount for FlexConfigSize<C, T> {
fn item_count(&self) -> usize {
self.count * std::mem::size_of::<T>() + std::mem::size_of::<C>()
}
}
pub(crate) struct FlexConfigTag<C, T> {
_c: std::marker::PhantomData<C>,
_t: std::marker::PhantomData<T>,
}
impl<C, T> BufferTag for FlexConfigTag<C, T>
where
T: Copy,
{
type T = u8;
type S = FlexConfigSize<C, T>;
fn usage() -> u32 {
wgpu::BufferUsages::COPY_DST.bits() | wgpu::BufferUsages::STORAGE.bits()
}
}
pub(crate) type FlexConfigBuffer<C, T> = FlexBuffer<FlexConfigTag<C, T>>;
impl<C, T> FlexConfigBuffer<C, T>
where
C: zerocopy::IntoBytes + zerocopy::Immutable,
T: zerocopy::IntoBytes + zerocopy::Immutable + Copy,
{
pub(crate) fn write(
&mut self,
gpu: &Gpu,
c: &C,
data: &[T],
) -> Result<ConfigBufferWrite, BufferSizeError> {
let r = self.grow_to_fit(&gpu.device, data.len().into())?;
let config_len = std::mem::size_of::<C>();
let data_len = std::mem::size_of_val(data);
let mut writer = gpu
.queue
.write_buffer_with(
&self.data,
0,
((config_len + data_len) as u64).try_into().unwrap(),
)
.unwrap();
writer.slice(..config_len).copy_from_slice(c.as_bytes());
writer.slice(config_len..).copy_from_slice(data.as_bytes());
Ok(match r {
std::cmp::Ordering::Equal => ConfigBufferWrite::BufferUnchanged,
std::cmp::Ordering::Less | std::cmp::Ordering::Greater => {
ConfigBufferWrite::BufferChanged
}
})
}
}
#[derive(Copy, Clone, Debug)]
#[must_use]
pub(crate) enum ConfigBufferWrite {
BufferChanged,
BufferUnchanged,
}
impl<T: BufferTag> FlexBuffer<T> {
pub(crate) fn new(
device: &wgpu::Device,
name: impl AsRef<str>,
size: T::S,
) -> Result<Self, BufferSizeError> {
Self::check_size(size)?;
let size_bytes = Self::calculate_buffer_size(size);
let usage = wgpu::BufferUsages::from_bits(T::usage()).unwrap();
let data = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(name.as_ref()),
size: size_bytes,
usage,
mapped_at_creation: false,
});
Ok(Self {
data,
size,
name: name.as_ref().to_owned(),
_t: std::marker::PhantomData,
})
}
pub fn calculate_buffer_size(item_count: T::S) -> u64 {
let out = u64::try_from(item_count.item_count())
.unwrap()
.checked_mul(u64::try_from(std::mem::size_of::<T::T>()).unwrap())
.unwrap();
out.next_multiple_of(4)
}
pub fn size_bytes(&self) -> u64 {
Self::calculate_buffer_size(self.size)
}
pub(crate) fn check_size(size: T::S) -> Result<(), BufferSizeError> {
let size = Self::calculate_buffer_size(size);
let usage = wgpu::BufferUsages::from_bits(T::usage()).unwrap();
let buf_ty = if usage.contains(wgpu::BufferUsages::STORAGE) {
BufferType::Storage
} else if usage.contains(wgpu::BufferUsages::UNIFORM) {
BufferType::Uniform
} else {
BufferType::Generic
};
buf_ty.check(size)
}
pub(crate) fn grow_to_fit(
&mut self,
device: &wgpu::Device,
size: T::S,
) -> Result<std::cmp::Ordering, BufferSizeError> {
Self::check_size(size)?;
let r = self.size.item_count().cmp(&size.item_count());
let new_size = Self::calculate_buffer_size(size);
if new_size > self.capacity() {
let usage = self.data.usage();
self.data = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(self.name.as_str()),
size: new_size,
usage,
mapped_at_creation: false,
});
}
self.size = size;
Ok(r)
}
pub fn bind_active(&self) -> wgpu::BindingResource<'_> {
self.data.slice(0..self.size_bytes()).try_into().unwrap()
}
pub(crate) fn capacity(&self) -> u64 {
self.data.size()
}
pub fn name(&self) -> &str {
&self.name
}
pub(crate) fn map_async(
&self,
callback: impl FnOnce(Result<(), wgpu::BufferAsyncError>)
+ wgpu::WasmNotSend
+ 'static,
) -> wgpu::BufferSlice<'_> {
let slice = self.data.slice(0..self.size_bytes());
slice.map_async(wgpu::MapMode::Read, callback);
slice
}
pub(crate) fn clear(&self, encoder: &mut wgpu::CommandEncoder) {
encoder.clear_buffer(&self.data, 0, Some(self.size_bytes()));
}
pub fn data(&self) -> &wgpu::Buffer {
&self.data
}
pub fn size(&self) -> T::S {
self.size
}
}
pub type ReadBuffer<T> = FlexBuffer<MappedBufferTag<T>>;
pub struct MappedBuffer<'a, T: BufferTag> {
buf: &'a ReadBuffer<T>,
slice: wgpu::BufferSlice<'a>,
}
impl<T: BufferTag> Drop for MappedBuffer<'_, T> {
fn drop(&mut self) {
self.buf.data().unmap();
}
}
impl<'a, T> MappedBuffer<'a, T>
where
T: BufferTag,
T::T: zerocopy::FromBytes + zerocopy::Immutable + Copy,
{
pub(crate) fn new(
buf: &'a ReadBuffer<T>,
slice: wgpu::BufferSlice<'a>,
) -> Self {
Self { buf, slice }
}
pub fn to_vec(&self) -> Vec<T::T> {
let slice = self.slice.get_mapped_range().unwrap();
let n = self.buf.size().item_count() * std::mem::size_of::<T::T>();
<[T::T]>::ref_from_bytes(&slice[..n]).unwrap().to_owned()
}
}
pub struct MappedImage<'a, T: BufferTag>(MappedBuffer<'a, T>)
where
T::S: RenderSize;
impl<'a, T> From<MappedBuffer<'a, T>> for MappedImage<'a, T>
where
T: BufferTag,
T::S: RenderSize,
{
fn from(value: MappedBuffer<'a, T>) -> Self {
Self(value)
}
}
impl<'a, T> MappedImage<'a, T>
where
T: BufferTag,
T::T: zerocopy::FromBytes + zerocopy::Immutable + Copy,
T::S: RenderSize,
{
pub fn image(&self) -> fidget_raster::Image<T::T, T::S> {
fidget_raster::Image::build(self.0.to_vec(), self.0.buf.size()).unwrap()
}
}
#[derive(Debug, thiserror::Error)]
pub enum BufferSizeError {
#[error(
"requested size {requested_size} exceeds maximum {} for \
{buffer_type} buffer",
buffer_type.max_size()
)]
TooLarge {
requested_size: u64,
buffer_type: BufferType,
},
}
#[derive(Copy, Clone, Debug)]
pub enum BufferType {
Uniform,
Storage,
Generic,
}
impl std::fmt::Display for BufferType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
BufferType::Uniform => "uniform",
BufferType::Storage => "storage",
BufferType::Generic => "generic",
};
s.fmt(f)
}
}
impl BufferType {
pub const fn max_size(&self) -> u64 {
match self {
BufferType::Uniform => 64 * 1024,
BufferType::Storage => 128 * 1024 * 1024,
BufferType::Generic => 256 * 1024 * 1024,
}
}
fn check(&self, requested_size: u64) -> Result<(), BufferSizeError> {
if requested_size > self.max_size() {
Err(BufferSizeError::TooLarge {
requested_size,
buffer_type: *self,
})
} else {
Ok(())
}
}
}
pub(crate) fn buffer_uniform(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
pub(crate) fn buffer_ro(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
pub(crate) fn buffer_ro_dyn(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: true,
min_binding_size: None,
},
count: None,
}
}
pub(crate) fn buffer_rw(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}