use std::marker::PhantomData; use std::mem::size_of_val; use std::ptr::null; use safer_ffi::layout::ReprC; use crate::CurrentContextWithFns; #[derive(Debug)] pub struct Buffer { buf: crate::sys::types::GLuint, _pd: PhantomData, } impl Buffer { #[must_use] pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self { let mut buffer = crate::sys::types::GLuint::default(); unsafe { current_context.fns().CreateBuffers(1, &raw mut buffer); }; Self { buf: buffer, _pd: PhantomData } } /// Stores items in this buffer. /// /// # Errors /// Returns `Err` if the total size (in bytes) is too large. pub fn store( &self, current_context: &CurrentContextWithFns<'_>, items: &[Item], usage: Usage, ) -> Result<(), Error> { let total_size = size_of_val(items); let total_size: crate::sys::types::GLsizeiptr = total_size .try_into() .map_err(|_| Error::TotalItemsSizeTooLarge { total_size, max_total_size: crate::sys::types::GLsizeiptr::MAX as usize, })?; unsafe { current_context.fns().NamedBufferData( self.buf, total_size, items.as_ptr().cast(), usage.into_gl(), ); } Ok(()) } /// Maps the values in the `values` slice into `Item`s which is stored into this /// buffer. /// /// # Errors /// Returns `Err` if the total size (in bytes) is too large. pub fn store_mapped( &self, current_context: &CurrentContextWithFns<'_>, values: &[Value], usage: Usage, mut map_func: impl FnMut(&Value) -> Item, ) -> Result<(), Error> { let item_size: crate::sys::types::GLsizeiptr = const { assert!(size_of::() <= crate::sys::types::GLsizeiptr::MAX as usize); size_of::().cast_signed() }; let total_size = size_of::() * values.len(); let total_size: crate::sys::types::GLsizeiptr = total_size .try_into() .map_err(|_| Error::TotalItemsSizeTooLarge { total_size, max_total_size: crate::sys::types::GLsizeiptr::MAX as usize, })?; unsafe { current_context.fns().NamedBufferData( self.buf, total_size, null(), usage.into_gl(), ); } for (index, value) in values.iter().enumerate() { let item = map_func(value); let offset = index * size_of::(); let Ok(offset_casted) = crate::sys::types::GLintptr::try_from(offset) else { unreachable!(); // Reason: The total size can be casted to a GLintptr // (done above) so offsets should be castable as well }; unsafe { current_context.fns().NamedBufferSubData( self.buf, offset_casted, item_size, (&raw const item).cast(), ); } } Ok(()) } pub(crate) fn object(&self) -> crate::sys::types::GLuint { self.buf } } /// Buffer usage. #[derive(Debug)] pub enum Usage { /// The buffer data is set only once and used by the GPU at most a few times. Stream, /// The buffer data is set only once and used many times. Static, /// The buffer data is changed a lot and used many times. Dynamic, } impl Usage { fn into_gl(self) -> crate::sys::types::GLenum { match self { Self::Stream => crate::sys::STREAM_DRAW, Self::Static => crate::sys::STATIC_DRAW, Self::Dynamic => crate::sys::DYNAMIC_DRAW, } } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error( "Total size of items ({total_size}) is too large. Must be < {max_total_size}" )] TotalItemsSizeTooLarge { total_size: usize, max_total_size: usize, }, }