use std::{borrow::Cow, marker::PhantomData};
pub use gpu_layout_derive::AsGpuBytes;
pub trait GpuLayout {
fn write(buf: &mut GpuBytes<Self>, data: &impl AsGpuBytes) {
let data = data.as_gpu_bytes::<Self>();
if data.bytes.is_empty() {
return;
}
buf.alignment = buf.alignment.max(data.alignment);
Self::align_to(buf, data.alignment);
buf.bytes.to_mut().extend_from_slice(&data.bytes);
}
fn write_array(buf: &mut GpuBytes<Self>, data: &[impl AsGpuBytes]);
fn align_to(buf: &mut GpuBytes<Self>, alignment: usize) {
let offset = buf.bytes.len();
let padding = offset.next_multiple_of(alignment) - offset;
if padding != 0 {
buf.bytes.to_mut().extend(std::iter::repeat_n(0u8, padding));
}
}
fn align(buf: &mut GpuBytes<Self>) {
let alignment = buf.alignment;
Self::align_to(buf, alignment);
}
}
#[derive(Debug, Clone, Copy)]
pub struct Std140Layout;
impl GpuLayout for Std140Layout {
fn write_array(buf: &mut GpuBytes<Self>, data: &[impl AsGpuBytes]) {
for elem in data {
let mut elem = elem.as_gpu_bytes::<Self>();
let element_alignment = elem.alignment.next_multiple_of(16);
Self::align_to(&mut elem, element_alignment);
Self::write(buf, &elem);
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Std430Layout;
impl GpuLayout for Std430Layout {
fn write_array(buf: &mut GpuBytes<Self>, data: &[impl AsGpuBytes]) {
for elem in data {
let mut elem = elem.as_gpu_bytes::<Self>();
let element_alignment = elem.alignment;
Self::align_to(&mut elem, element_alignment);
Self::write(buf, &elem);
}
}
}
#[derive(Debug, Clone)]
pub struct GpuBytes<'a, L: GpuLayout + ?Sized> {
bytes: Cow<'a, [u8]>,
alignment: usize,
_layout: PhantomData<L>,
}
impl<'a, L: GpuLayout + ?Sized> GpuBytes<'a, L> {
pub fn empty() -> Self {
Self {
bytes: Vec::new().into(),
alignment: 4,
_layout: PhantomData,
}
}
pub fn from_slice(data: &'a [u8], alignment: usize) -> Self {
Self {
bytes: Cow::Borrowed(data),
alignment,
_layout: PhantomData,
}
}
pub fn write(&mut self, data: &impl AsGpuBytes) -> &mut Self {
L::write(self, data);
self
}
pub fn as_slice(&mut self) -> &[u8] {
L::align(self);
&self.bytes
}
}
pub trait AsGpuBytes {
fn as_gpu_bytes<L: GpuLayout + ?Sized>(&self) -> GpuBytes<'_, L>;
}
impl<L: GpuLayout + ?Sized> AsGpuBytes for GpuBytes<'_, L> {
fn as_gpu_bytes<U: GpuLayout + ?Sized>(&self) -> GpuBytes<'_, U> {
GpuBytes {
bytes: Cow::Borrowed(&self.bytes),
alignment: self.alignment,
_layout: PhantomData,
}
}
}
macro_rules! primitive_impl_gpu_bytes {
($datatype:ty, alignment = $alignment:literal) => {
impl AsGpuBytes for $datatype {
fn as_gpu_bytes<L: GpuLayout + ?Sized>(&self) -> GpuBytes<'_, L> {
GpuBytes::from_slice(bytemuck::bytes_of(self), $alignment)
}
}
};
($datatype:ty, columns = $columns:literal) => {
impl AsGpuBytes for $datatype {
fn as_gpu_bytes<L: GpuLayout + ?Sized>(&self) -> GpuBytes<'_, L> {
let mut buf = GpuBytes::empty();
for i in 0..$columns {
L::write(&mut buf, &self.col(i));
}
buf
}
}
};
}
primitive_impl_gpu_bytes!(f32, alignment = 4);
primitive_impl_gpu_bytes!(glam::Vec2, alignment = 8);
primitive_impl_gpu_bytes!(glam::Vec3, alignment = 16);
primitive_impl_gpu_bytes!(glam::Vec4, alignment = 16);
primitive_impl_gpu_bytes!(i32, alignment = 4);
primitive_impl_gpu_bytes!(glam::IVec2, alignment = 8);
primitive_impl_gpu_bytes!(glam::IVec3, alignment = 16);
primitive_impl_gpu_bytes!(glam::IVec4, alignment = 16);
primitive_impl_gpu_bytes!(u32, alignment = 4);
primitive_impl_gpu_bytes!(glam::UVec2, alignment = 8);
primitive_impl_gpu_bytes!(glam::UVec3, alignment = 16);
primitive_impl_gpu_bytes!(glam::UVec4, alignment = 16);
primitive_impl_gpu_bytes!(glam::Mat2, columns = 2);
primitive_impl_gpu_bytes!(glam::Mat3, columns = 3);
primitive_impl_gpu_bytes!(glam::Mat4, columns = 4);
impl AsGpuBytes for glam::Vec3A {
fn as_gpu_bytes<L: GpuLayout + ?Sized>(&self) -> GpuBytes<'_, L> {
let mut buf = GpuBytes::empty();
buf.write(&self.to_vec3());
buf
}
}
impl<T: AsGpuBytes> AsGpuBytes for &[T] {
fn as_gpu_bytes<L: GpuLayout + ?Sized>(&self) -> GpuBytes<'_, L> {
let mut buf = GpuBytes::empty();
L::write_array(&mut buf, self);
buf
}
}
impl<T: AsGpuBytes, const N: usize> AsGpuBytes for [T; N] {
fn as_gpu_bytes<L: GpuLayout + ?Sized>(&self) -> GpuBytes<'_, L> {
let mut buf = GpuBytes::empty();
L::write_array(&mut buf, self);
buf
}
}
impl<T: AsGpuBytes> AsGpuBytes for Vec<T> {
fn as_gpu_bytes<L: GpuLayout + ?Sized>(&self) -> GpuBytes<'_, L> {
let mut buf = GpuBytes::empty();
L::write_array(&mut buf, self);
buf
}
}