use std::ptr::null; use crate::opengl::currently_bound::CurrentlyBound; use crate::vector::Vec2; #[derive(Debug)] pub struct Texture { texture: gl::types::GLuint, } impl Texture { pub fn new() -> Self { let mut texture = gl::types::GLuint::default(); unsafe { gl::GenTextures(1, &mut texture); }; Self { texture } } pub fn bind(&self, cb: impl FnOnce(CurrentlyBound<'_, Self>)) { unsafe { gl::BindTexture(gl::TEXTURE_2D, self.texture); } // SAFETY: The buffer object is bound above let currently_bound = unsafe { CurrentlyBound::new() }; cb(currently_bound); } pub fn generate( curr_bound: &CurrentlyBound, dimens: &Vec2, data: &[u8], pixel_data_format: PixelDataFormat, ) { Self::alloc_image(curr_bound, pixel_data_format, dimens, Some(data)); unsafe { gl::GenerateMipmap(gl::TEXTURE_2D); } } pub fn set_wrap(_: CurrentlyBound, wrapping: Wrapping) { let wrapping_gl = wrapping.to_gl(); #[allow(clippy::cast_possible_wrap)] unsafe { gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, wrapping_gl as i32); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, wrapping_gl as i32); } } pub fn set_magnifying_filter(_: CurrentlyBound, filtering: Filtering) { let filtering_gl = filtering.to_gl(); #[allow(clippy::cast_possible_wrap)] unsafe { gl::TexParameteri( gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, filtering_gl as i32, ); } } pub fn set_minifying_filter(_: CurrentlyBound, filtering: Filtering) { let filtering_gl = filtering.to_gl(); #[allow(clippy::cast_possible_wrap)] unsafe { gl::TexParameteri( gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, filtering_gl as i32, ); } } /// Creates a copy of the texture & the texture images. /// /// `src_offset` and `dst_offset` are source & destination offsets from the /// bottom-left of the images. /// /// New mipmaps are generated using the largest mipmap. pub fn copy( &self, dimensions: &Vec2, pixel_data_format: PixelDataFormat, src_offset: &Vec2, dst_offset: &Vec2, ) -> Self { let new_texture = Self::new(); new_texture.bind(|curr_bound| { Self::alloc_image(&curr_bound, pixel_data_format, dimensions, None); // Mipmap have to be generated since CopyImageSubData demands that the // destination texture is completed unsafe { gl::GenerateMipmap(gl::TEXTURE_2D); } }); #[allow(clippy::cast_possible_wrap)] unsafe { gl::CopyImageSubData( self.texture, gl::TEXTURE_2D, 0, src_offset.x as i32, src_offset.y as i32, 0, new_texture.texture, gl::TEXTURE_2D, 0, dst_offset.x as i32, dst_offset.y as i32, 0, dimensions.x as i32, dimensions.y as i32, 1, ); } unsafe { gl::GenerateMipmap(gl::TEXTURE_2D); } new_texture } fn alloc_image( _: &CurrentlyBound, pixel_data_format: PixelDataFormat, dimens: &Vec2, data: Option<&[u8]>, ) { unsafe { #[allow(clippy::cast_possible_wrap)] gl::TexImage2D( gl::TEXTURE_2D, 0, pixel_data_format.to_gl() as i32, dimens.x as i32, dimens.y as i32, 0, pixel_data_format.to_gl(), gl::UNSIGNED_BYTE, data.map_or_else(null, |data| data.as_ptr().cast()), ); } } } impl Drop for Texture { fn drop(&mut self) { unsafe { gl::DeleteTextures(1, &self.texture); } } } /// Texture wrapping. #[derive(Debug, Clone, Copy)] pub enum Wrapping { Repeat, MirroredRepeat, ClampToEdge, ClampToBorder, } impl Wrapping { fn to_gl(self) -> gl::types::GLenum { match self { Self::Repeat => gl::REPEAT, Self::MirroredRepeat => gl::MIRRORED_REPEAT, Self::ClampToEdge => gl::CLAMP_TO_EDGE, Self::ClampToBorder => gl::CLAMP_TO_BORDER, } } } #[derive(Debug, Clone, Copy)] pub enum Filtering { Nearest, Linear, } impl Filtering { fn to_gl(self) -> gl::types::GLenum { match self { Self::Linear => gl::LINEAR, Self::Nearest => gl::NEAREST, } } } /// Texture pixel data format. #[derive(Debug, Clone, Copy)] pub enum PixelDataFormat { Rgb, Rgba, } impl PixelDataFormat { fn to_gl(self) -> gl::types::GLenum { match self { Self::Rgb => gl::RGB, Self::Rgba => gl::RGBA, } } }