use std::borrow::Cow; use crate::data_types::{Dimens, Vec2}; use crate::MaybeCurrentContextWithFns; #[derive(Debug)] pub struct Builder { size: Dimens, color_space: ColorSpace, mipmap_levels: u16, } impl Builder { 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_2d<'image>( &self, current_context: &MaybeCurrentContextWithFns, image: impl Into>, pixel_data_format: PixelDataFormat, ) -> Result { let image = image.into(); check_image_buffer_len_correct_for_size( image.as_ref(), [self.size.width, self.size.height, 1], pixel_data_format, )?; let size = try_convert_size([self.size.width, self.size.height])?; let texture = Texture::new(current_context, crate::sys::TEXTURE_2D); texture.alloc_2d( current_context, self.mipmap_levels, pixel_data_format, self.color_space, size, ); texture.sub_image_2d( current_context, 0, [0, 0], size, pixel_data_format, image.as_ref(), ); texture.generate_mipmap(current_context); Ok(texture) } #[must_use] pub fn create_cube_map<'image, Image>( &self, current_context: &MaybeCurrentContextWithFns, images: [(CubeMapFace, Image); 6], pixel_data_format: PixelDataFormat, ) -> Result where Image: Into>, { let images = images.map(|(face, image)| (face, image.into())); for (_, image) in &images { check_image_buffer_len_correct_for_size( image.as_ref(), [self.size.width, self.size.height, 1], pixel_data_format, )?; } let size = try_convert_size([self.size.width, self.size.height])?; let texture = Texture::new(current_context, crate::sys::TEXTURE_CUBE_MAP); texture.alloc_2d( current_context, self.mipmap_levels, pixel_data_format, self.color_space, size, ); for (face, image) in images { texture.sub_image_3d( current_context, 0, [0, 0, face as crate::sys::types::GLint], [size[0], size[1], 1], pixel_data_format, image.as_ref(), ); } texture.generate_mipmap(current_context); Ok(texture) } } impl Default for Builder { fn default() -> Self { Self { 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() -> Builder { 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_2d( &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.width, size.height, 1], pixel_data_format, )?; let offset = try_convert_offset([offset.x, offset.y])?; let size = try_convert_size([size.width, size.height])?; self.sub_image_2d( current_context, mipmap_level, offset, size, pixel_data_format, image, ); Ok(()) } pub fn store_image_3d( &self, current_context: &MaybeCurrentContextWithFns, mipmap_level: u16, offset: [u32; 3], size: [u32; 3], 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_3d( 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, target: crate::sys::types::GLenum, ) -> Self { let mut texture = crate::sys::types::GLuint::default(); unsafe { current_context .fns() .CreateTextures(target, 1, &raw mut texture); }; Self { texture } } fn alloc_2d( &self, current_context: &MaybeCurrentContextWithFns, mipmap_levels: u16, pixel_data_format: PixelDataFormat, color_space: ColorSpace, size: [crate::sys::types::GLsizei; 2], ) { unsafe { current_context.fns().TextureStorage2D( self.texture, mipmap_levels.into(), pixel_data_format.to_sized_internal_format(color_space), size[0], size[1], ); } } fn sub_image_2d( &self, current_context: &MaybeCurrentContextWithFns, mipmap_level: u16, offset: [crate::sys::types::GLint; 2], size: [crate::sys::types::GLsizei; 2], pixel_data_format: PixelDataFormat, image: &[u8], ) { unsafe { current_context.fns().TextureSubImage2D( self.texture, mipmap_level.into(), offset[0], offset[1], size[0], size[1], pixel_data_format.to_format(), crate::sys::UNSIGNED_BYTE, image.as_ptr().cast(), ); } } fn sub_image_3d( &self, current_context: &MaybeCurrentContextWithFns, mipmap_level: u16, offset: [crate::sys::types::GLint; 3], size: [crate::sys::types::GLsizei; 3], pixel_data_format: PixelDataFormat, image: &[u8], ) { unsafe { current_context.fns().TextureSubImage3D( self.texture, mipmap_level.into(), offset[0], offset[1], offset[2], size[0], size[1], size[2], 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, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum CubeMapFace { PositiveX = 0, NegativeX = 1, PositiveY = 2, NegativeY = 3, PositiveZ = 4, NegativeZ = 5, } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Size value ({value}) is too large. Must be < {max_value}")] ValueInSizeIsTooLarge { value: u32, max_value: u32 }, #[error("Offset value ({value}) is too large. Must be < {max_value}")] ValueInOffsetIsTooLarge { value: u32, max_value: u32 }, #[error( "Incorrect image buffer length for size {}x{}x{}. Expected {}, found {}", size[0], size[1], size[2], expected_buffer_len, buffer_len )] IncorrectImageBufferLengthForSize { expected_buffer_len: usize, buffer_len: usize, size: [u32; 3], }, } fn try_convert_size( size: [u32; LEN], ) -> Result<[crate::sys::types::GLsizei; LEN], Error> { let mut output = [0; LEN]; for (value, output_val) in size.into_iter().zip(&mut output) { *output_val = value.try_into().map_err(|_| Error::ValueInSizeIsTooLarge { value: value, max_value: crate::sys::types::GLsizei::MAX as u32, })?; } Ok(output) } fn try_convert_offset( offset: [u32; LEN], ) -> Result<[crate::sys::types::GLint; LEN], Error> { let mut output = [0; LEN]; for (value, output_val) in offset.into_iter().zip(&mut output) { *output_val = value .try_into() .map_err(|_| Error::ValueInOffsetIsTooLarge { value: value, max_value: crate::sys::types::GLint::MAX as u32, })?; } Ok(output) } fn check_image_buffer_len_correct_for_size( image: &[u8], size: [u32; 3], pixel_data_format: PixelDataFormat, ) -> Result<(), Error> { let pixel_width = pixel_data_format.pixel_width(); let [width, height, depth] = size; let expected_buffer_len = width as usize * height as usize * depth as usize * pixel_width; if expected_buffer_len != image.len() { return Err(Error::IncorrectImageBufferLengthForSize { expected_buffer_len, buffer_len: image.len(), size, }); } Ok(()) }