use crate::data_types::{Dimens, Vec2}; use crate::MaybeCurrentContextWithFns; #[derive(Debug, Clone)] pub struct Builder { size: Dimens, mipmap_levels: u16, } impl Builder { pub fn size(mut self, size: Dimens) -> Self { self.size = size; 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: Option<&[u8]>, pixel_data_format: PixelDataFormat, ) -> Result { if let Some(image) = &image { check_image_buffer_len_correct_for_size( image, [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, size); if let Some(image) = image { texture.sub_image_2d( current_context, 0, [0, 0], size, pixel_data_format, image, ); texture.generate_mipmap(current_context); } Ok(texture) } #[must_use] pub fn create_cube_map<'image>( &self, current_context: &MaybeCurrentContextWithFns, images: Option<[(CubeMapFace, &[u8]); 6]>, pixel_data_format: PixelDataFormat, ) -> Result { for (_, image) in images.iter().flatten() { 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, size); for (face, image) in images.iter().flatten() { texture.sub_image_3d( current_context, 0, [0, 0, *face as crate::sys::types::GLint], [size[0], size[1], 1], pixel_data_format, *image, ); } if images.is_some() { texture.generate_mipmap(current_context); } Ok(texture) } } impl Default for Builder { fn default() -> Self { Self { size: Dimens::default(), mipmap_levels: 1, } } } #[derive(Debug, Clone)] 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, size: [crate::sys::types::GLsizei; 2], ) { unsafe { current_context.fns().TextureStorage2D( self.texture, mipmap_levels.into(), pixel_data_format.into_gl_sized_internal_format(), 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], ) { let mut is_pixel_unpack_alignment_changed = false; if let Some(new_pixel_unpack_alignment) = pixel_data_format.requires_adjust_pixel_unpack_alignment() { set_pixel_unpack_alignment(current_context, new_pixel_unpack_alignment); is_pixel_unpack_alignment_changed = true; } unsafe { current_context.fns().TextureSubImage2D( self.texture, mipmap_level.into(), offset[0], offset[1], size[0], size[1], pixel_data_format.into_gl_format(), pixel_data_format.into_gl_data_type(), image.as_ptr().cast(), ); } if is_pixel_unpack_alignment_changed { set_pixel_unpack_alignment(current_context, PixelAlignment::default()); } } 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], ) { let mut is_pixel_unpack_alignment_changed = false; if let Some(new_pixel_unpack_alignment) = pixel_data_format.requires_adjust_pixel_unpack_alignment() { set_pixel_unpack_alignment(current_context, new_pixel_unpack_alignment); is_pixel_unpack_alignment_changed = true; } 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.into_gl_format(), pixel_data_format.into_gl_data_type(), image.as_ptr().cast(), ); } if is_pixel_unpack_alignment_changed { set_pixel_unpack_alignment(current_context, PixelAlignment::default()); } } } 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) }, } #[derive(Debug, Clone, Copy)] #[non_exhaustive] #[repr(u32)] pub enum RgbDataType { UnsignedByte = crate::sys::UNSIGNED_BYTE, } #[derive(Debug, Clone, Copy)] #[non_exhaustive] #[repr(u32)] pub enum RgbaDataType { UnsignedByte = crate::sys::UNSIGNED_BYTE, } #[derive(Debug, Clone, Copy)] #[non_exhaustive] #[repr(u32)] pub enum SrgbDataType { UnsignedByte = crate::sys::UNSIGNED_BYTE, } #[derive(Debug, Clone, Copy)] #[non_exhaustive] #[repr(u32)] pub enum SrgbaDataType { UnsignedByte = crate::sys::UNSIGNED_BYTE, } #[derive(Debug, Clone, Copy)] #[non_exhaustive] #[repr(u32)] pub enum DepthComponentDataType { Float32 = crate::sys::FLOAT, } /// Texture pixel data format. #[derive(Debug, Clone, Copy)] #[non_exhaustive] pub enum PixelDataFormat { Rgb(RgbDataType), Rgba(RgbaDataType), Srgb(SrgbDataType), Srgba(SrgbaDataType), DepthComponent(DepthComponentDataType), } impl PixelDataFormat { fn into_gl_sized_internal_format(self) -> crate::sys::types::GLenum { match self { Self::Rgb(RgbDataType::UnsignedByte) => crate::sys::RGB8, Self::Rgba(RgbaDataType::UnsignedByte) => crate::sys::RGBA8, Self::Srgb(SrgbDataType::UnsignedByte) => crate::sys::SRGB8, Self::Srgba(SrgbaDataType::UnsignedByte) => crate::sys::SRGB8_ALPHA8, Self::DepthComponent(DepthComponentDataType::Float32) => { crate::sys::DEPTH_COMPONENT32F } } } fn into_gl_format(self) -> crate::sys::types::GLenum { match self { Self::Rgb(_) | Self::Srgb(_) => crate::sys::RGB, Self::Rgba(_) | Self::Srgba(_) => crate::sys::RGBA, Self::DepthComponent(_) => crate::sys::DEPTH_COMPONENT, } } fn into_gl_data_type(self) -> crate::sys::types::GLenum { match self { Self::Rgb(ty) => ty as crate::sys::types::GLenum, Self::Srgb(ty) => ty as crate::sys::types::GLenum, Self::Rgba(ty) => ty as crate::sys::types::GLenum, Self::Srgba(ty) => ty as crate::sys::types::GLenum, Self::DepthComponent(ty) => ty as crate::sys::types::GLenum, } } fn pixel_width(&self) -> usize { match self { Self::Rgb(_) | Self::Srgb(_) => 3, Self::Rgba(_) | Self::Srgba(_) => 4, Self::DepthComponent(_) => 1, } } fn requires_adjust_pixel_unpack_alignment(&self) -> Option { if matches!( self, PixelDataFormat::Rgb(RgbDataType::UnsignedByte) | PixelDataFormat::Srgb(SrgbDataType::UnsignedByte) ) { return Some(PixelAlignment::Byte); } None } } impl Default for PixelDataFormat { fn default() -> Self { PixelDataFormat::Rgba(RgbaDataType::UnsignedByte) } } #[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(()) } fn set_pixel_unpack_alignment( current_context: &MaybeCurrentContextWithFns, alignment: PixelAlignment, ) { let alignment = match alignment { PixelAlignment::Byte => 1, // PixelAlignment::TwoBytes => 2, PixelAlignment::FourBytes => 4, // PixelAlignment::EightBytes => 8, }; unsafe { current_context .fns() .PixelStorei(crate::sys::UNPACK_ALIGNMENT, alignment); } } #[derive(Debug, Default, Clone, Copy)] enum PixelAlignment { Byte, // TwoBytes, #[default] FourBytes, // EightBytes, }