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 } } /// Initializes this buffer with a size and a usage. /// /// # Errors /// Returns `Err` if the size (in bytes) is too large. pub fn init( &self, current_context: &CurrentContextWithFns<'_>, size: usize, usage: Usage, ) -> Result<(), Error> { let size: crate::sys::types::GLsizeiptr = size.try_into().map_err(|_| Error::SizeTooLarge { size, max_size: crate::sys::types::GLsizeiptr::MAX as usize, })?; unsafe { current_context.fns().NamedBufferData( self.buf, size, null(), usage.into_gl(), ); } Ok(()) } /// 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::SizeTooLarge { size: total_size, max_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(()) } /// Stores items in this buffer, starting at a byte offset. /// /// # Errors /// Returns `Err` if the total size (in bytes) is too large. pub fn store_at_byte_offset( &self, current_context: &CurrentContextWithFns<'_>, byte_offset: usize, items: &[Item], // usage: Usage, ) -> Result<(), Error> { let total_size = size_of_val(items); let byte_offset: crate::sys::types::GLintptr = byte_offset .try_into() .map_err(|_| Error::ByteOffsetTooLarge { byte_offset, max_byte_offset: crate::sys::types::GLintptr::MAX as usize, })?; let total_size: crate::sys::types::GLsizeiptr = total_size.try_into().map_err(|_| Error::SizeTooLarge { size: total_size, max_size: crate::sys::types::GLsizeiptr::MAX as usize, })?; unsafe { current_context.fns().NamedBufferSubData( self.buf, byte_offset, 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::SizeTooLarge { size: total_size, max_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 fn delete(&self, current_context: &CurrentContextWithFns<'_>) { unsafe { current_context.fns().DeleteBuffers(1, &raw const self.buf); } } pub fn bind_to_indexed_target( &self, current_context: &CurrentContextWithFns<'_>, target: BindingTarget, index: u32, ) { unsafe { current_context.fns().BindBufferBase( target as crate::sys::types::GLenum, index, self.buf, ); } } 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)] #[repr(u32)] pub enum BindingTarget { AtomicCounterBuffer = crate::sys::ATOMIC_COUNTER_BUFFER, TransformFeedbackBuffer = crate::sys::TRANSFORM_FEEDBACK_BUFFER, UniformBuffer = crate::sys::UNIFORM_BUFFER, ShaderStorageBuffer = crate::sys::SHADER_STORAGE_BUFFER, } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Size ({size}) is too large. Must be < {max_size}")] SizeTooLarge { size: usize, max_size: usize }, #[error("Byte offset ({byte_offset}) is too large. Must be < {max_byte_offset}")] ByteOffsetTooLarge { byte_offset: usize, max_byte_offset: usize, }, }