use std::borrow::Cow; use crate::data_types::{Dimens, Vec2}; use crate::MaybeCurrentContextWithFns; #[derive(Debug)] pub struct Builder<'image> { image: Cow<'image, [u8]>, pixel_data_format: PixelDataFormat, size: Dimens, color_space: ColorSpace, mipmap_levels: u16, } impl<'image> Builder<'image> { pub fn image( mut self, image: impl Into>, pixel_data_format: PixelDataFormat, ) -> Self { self.image = image.into(); self.pixel_data_format = pixel_data_format; self } pub fn size(mut self, size: Dimens) -> Self { self.size = size; self } pub fn color_space(mut self, color_space: ColorSpace) -> Self { self.color_space = color_space; self } pub fn mipmap_levels(mut self, mipmap_levels: u16) -> Self { self.mipmap_levels = mipmap_levels; self } #[must_use] pub fn create( &self, current_context: &MaybeCurrentContextWithFns, ) -> Result { check_image_buffer_len_correct_for_size( self.image.as_ref(), self.size, self.pixel_data_format, )?; let size = try_convert_size(self.size.clone())?; let texture = Texture::new(current_context); texture.alloc( current_context, self.mipmap_levels, self.pixel_data_format, self.color_space, size, ); texture.sub_image( current_context, 0, Vec2 { x: 0, y: 0 }, size, self.pixel_data_format, self.image.as_ref(), ); texture.generate_mipmap(current_context); Ok(texture) } } impl<'image> Default for Builder<'image> { fn default() -> Self { Self { image: Cow::Borrowed(&[]), pixel_data_format: PixelDataFormat::default(), size: Dimens::default(), color_space: ColorSpace::default(), mipmap_levels: 1, } } } #[derive(Debug)] pub struct Texture { texture: crate::sys::types::GLuint, } impl Texture { pub fn builder<'image>() -> Builder<'image> { Builder::default() } pub fn bind_to_texture_unit( &self, current_context: &MaybeCurrentContextWithFns, texture_unit: u32, ) { unsafe { current_context .fns() .BindTextureUnit(texture_unit, self.texture); } } pub fn generate_mipmap(&self, current_context: &MaybeCurrentContextWithFns) { unsafe { current_context.fns().GenerateTextureMipmap(self.texture); } } pub fn store_image( &self, current_context: &MaybeCurrentContextWithFns, mipmap_level: u16, offset: Vec2, size: Dimens, pixel_data_format: PixelDataFormat, image: &[u8], ) -> Result<(), Error> { check_image_buffer_len_correct_for_size(image, size, pixel_data_format)?; let offset = try_convert_offset(offset)?; let size = try_convert_size(size)?; self.sub_image( current_context, mipmap_level, offset, size, pixel_data_format, image, ); Ok(()) } pub fn set_wrap( &self, current_context: &MaybeCurrentContextWithFns, wrapping: Wrapping, ) { unsafe { current_context.fns().TextureParameteri( self.texture, crate::sys::TEXTURE_WRAP_S, wrapping as i32, ); current_context.fns().TextureParameteri( self.texture, crate::sys::TEXTURE_WRAP_T, wrapping as i32, ); } } pub fn set_magnifying_filter( &self, current_context: &MaybeCurrentContextWithFns, filtering: Filtering, ) { unsafe { current_context.fns().TextureParameteri( self.texture, crate::sys::TEXTURE_MAG_FILTER, filtering as i32, ); } } pub fn set_minifying_filter( &self, current_context: &MaybeCurrentContextWithFns, filtering: Filtering, ) { unsafe { current_context.fns().TextureParameteri( self.texture, crate::sys::TEXTURE_MIN_FILTER, filtering as i32, ); } } pub fn delete(self, current_context: &MaybeCurrentContextWithFns) { unsafe { current_context .fns() .DeleteTextures(1, &raw const self.texture); } } pub fn from_raw(raw: u32) -> Self { Self { texture: raw } } pub fn into_raw(self) -> u32 { self.texture } fn new(current_context: &MaybeCurrentContextWithFns) -> Self { let mut texture = crate::sys::types::GLuint::default(); unsafe { current_context.fns().CreateTextures( crate::sys::TEXTURE_2D, 1, &raw mut texture, ); }; Self { texture } } fn alloc( &self, current_context: &MaybeCurrentContextWithFns, mipmap_levels: u16, pixel_data_format: PixelDataFormat, color_space: ColorSpace, size: Dimens, ) { unsafe { current_context.fns().TextureStorage2D( self.texture, mipmap_levels.into(), pixel_data_format.to_sized_internal_format(color_space), size.width, size.height, ); } } fn sub_image( &self, current_context: &MaybeCurrentContextWithFns, mipmap_level: u16, offset: Vec2, size: Dimens, pixel_data_format: PixelDataFormat, image: &[u8], ) { unsafe { current_context.fns().TextureSubImage2D( self.texture, mipmap_level.into(), offset.x, offset.y, size.width, size.height, pixel_data_format.to_format(), crate::sys::UNSIGNED_BYTE, image.as_ptr().cast(), ); } } } const fn try_cast_u32_to_i32(val: u32) -> i32 { assert!(val <= i32::MAX as u32); val.cast_signed() } /// Texture wrapping. #[derive(Debug, Clone, Copy)] #[repr(i32)] pub enum Wrapping { Repeat = const { try_cast_u32_to_i32(crate::sys::REPEAT) }, MirroredRepeat = const { try_cast_u32_to_i32(crate::sys::MIRRORED_REPEAT) }, ClampToEdge = const { try_cast_u32_to_i32(crate::sys::CLAMP_TO_EDGE) }, ClampToBorder = const { try_cast_u32_to_i32(crate::sys::CLAMP_TO_BORDER) }, } #[derive(Debug, Clone, Copy)] #[repr(i32)] pub enum Filtering { Nearest = const { try_cast_u32_to_i32(crate::sys::NEAREST) }, Linear = const { try_cast_u32_to_i32(crate::sys::LINEAR) }, } /// Texture pixel data format. #[derive(Debug, Clone, Copy, Default)] pub enum PixelDataFormat { Rgb8, #[default] Rgba8, } impl PixelDataFormat { fn to_sized_internal_format( self, color_space: ColorSpace, ) -> crate::sys::types::GLenum { match (self, color_space) { (Self::Rgb8, ColorSpace::Linear) => crate::sys::RGB8, (Self::Rgba8, ColorSpace::Linear) => crate::sys::RGBA8, (Self::Rgb8, ColorSpace::Srgb) => crate::sys::SRGB8, (Self::Rgba8, ColorSpace::Srgb) => crate::sys::SRGB8_ALPHA8, } } fn to_format(self) -> crate::sys::types::GLenum { match self { Self::Rgb8 => crate::sys::RGB, Self::Rgba8 => crate::sys::RGBA, } } fn pixel_width(&self) -> usize { match self { Self::Rgb8 => 3, Self::Rgba8 => 4, } } } #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] pub enum ColorSpace { #[default] Linear, Srgb, } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Size width value ({value}) is too large. Must be < {max_value}")] SizeWidthValueTooLarge { value: u32, max_value: u32 }, #[error("Size height value ({value}) is too large. Must be < {max_value}")] SizeHeightValueTooLarge { value: u32, max_value: u32 }, #[error("Offset X value ({value}) is too large. Must be < {max_value}")] OffsetXValueTooLarge { value: u32, max_value: u32 }, #[error("Offset Y value ({value}) is too large. Must be < {max_value}")] OffsetYValueTooLarge { value: u32, max_value: u32 }, #[error( "Incorrect image buffer length for size {size}. Expected {}, found {}", expected_buffer_len, buffer_len )] IncorrectImageBufferLengthForSize { expected_buffer_len: usize, buffer_len: usize, size: Dimens, }, } fn try_convert_size( size: Dimens, ) -> Result, Error> { Ok(Dimens:: { width: size .width .try_into() .map_err(|_| Error::SizeWidthValueTooLarge { value: size.width, max_value: crate::sys::types::GLsizei::MAX as u32, })?, height: size .height .try_into() .map_err(|_| Error::SizeHeightValueTooLarge { value: size.height, max_value: crate::sys::types::GLsizei::MAX as u32, })?, }) } fn try_convert_offset(offset: Vec2) -> Result, Error> { Ok(Vec2:: { x: offset .x .try_into() .map_err(|_| Error::OffsetXValueTooLarge { value: offset.x, max_value: crate::sys::types::GLint::MAX as u32, })?, y: offset .y .try_into() .map_err(|_| Error::OffsetYValueTooLarge { value: offset.y, max_value: crate::sys::types::GLint::MAX as u32, })?, }) } fn check_image_buffer_len_correct_for_size( image: &[u8], size: Dimens, pixel_data_format: PixelDataFormat, ) -> Result<(), Error> { let pixel_width = pixel_data_format.pixel_width(); let expected_buffer_len = size.width as usize * size.height as usize * pixel_width; if expected_buffer_len != image.len() { return Err(Error::IncorrectImageBufferLengthForSize { expected_buffer_len, buffer_len: image.len(), size, }); } Ok(()) }