use gl;
use image;
use maths::Vector2u;
use resources::Loadable;
use std::{cmp::Ordering, error, fmt, io};
pub type TextureID = gl::types::GLuint;
#[derive(Debug)]
pub enum TextureError {
Io(io::Error),
ImageError(image::ImageError),
InvalidTextureData(u32, u32, u32, usize),
}
impl From<io::Error> for TextureError {
fn from(error: io::Error) -> Self {
TextureError::Io(error)
}
}
impl error::Error for TextureError {}
impl fmt::Display for TextureError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TextureError::Io(error) => write!(f, "{}", error),
TextureError::ImageError(error) => write!(f, "{}", error),
TextureError::InvalidTextureData(pixel_size, width, height, len) => write!(
f,
"TextureError: {}x{}x{} != {}",
pixel_size, width, height, len
),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum TextureFormat {
Rgb = gl::RGB as isize,
Rgba = gl::RGBA as isize,
}
impl TextureFormat {
pub fn pixel_length(self) -> u32 {
match self {
TextureFormat::Rgb => 3,
TextureFormat::Rgba => 4,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum WrapMode {
ClampToEdge = gl::CLAMP_TO_EDGE as isize,
ClampToBorder = gl::CLAMP_TO_BORDER as isize,
MirroredRepeat = gl::MIRRORED_REPEAT as isize,
Repeat = gl::REPEAT as isize,
MirrorClampToEdge = gl::MIRROR_CLAMP_TO_EDGE as isize,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum MinFilterMode {
Nearest = gl::NEAREST as isize,
Linear = gl::LINEAR as isize,
NearestMipmapNearest = gl::NEAREST_MIPMAP_NEAREST as isize,
LinearMipmapNearest = gl::LINEAR_MIPMAP_NEAREST as isize,
NearestMipmapLinear = gl::NEAREST_MIPMAP_LINEAR as isize,
LinearMipmapLinear = gl::LINEAR_MIPMAP_LINEAR as isize,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum MaxFilterMode {
Nearest = gl::NEAREST as isize,
Linear = gl::LINEAR as isize,
}
#[derive(Debug, Copy, Clone)]
pub struct TextureOptions {
pub format: TextureFormat,
pub h_wrap_mode: WrapMode,
pub v_wrap_mode: WrapMode,
pub min_filter_mode: MinFilterMode,
pub max_filter_mode: MaxFilterMode,
}
impl Default for TextureOptions {
fn default() -> Self {
Self {
format: TextureFormat::Rgba,
h_wrap_mode: WrapMode::Repeat,
v_wrap_mode: WrapMode::Repeat,
min_filter_mode: MinFilterMode::NearestMipmapNearest,
max_filter_mode: MaxFilterMode::Nearest,
}
}
}
#[derive(Debug)]
pub struct Texture {
id: TextureID,
size: Vector2u,
options: TextureOptions,
}
impl Loadable for Texture {
type LoadOptions = TextureOptions;
type LoadError = TextureError;
fn load_from_bytes(data: &[u8], options: TextureOptions) -> Result<Self, TextureError> {
let img = image::load_from_memory(data)
.map_err(TextureError::ImageError)?
.to_rgba();
let (width, height) = img.dimensions();
Self::from_bytes(img.as_ref(), options, width, height)
}
}
impl PartialEq for Texture {
fn eq(&self, other: &Texture) -> bool {
self.id == other.id
}
}
impl Eq for Texture {}
impl PartialOrd for Texture {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Texture {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl Drop for Texture {
fn drop(&mut self) {
unsafe { gl::DeleteTextures(1, &self.id) }
}
}
impl Texture {
pub fn id(&self) -> TextureID {
self.id
}
pub fn size(&self) -> Vector2u {
self.size
}
pub fn width(&self) -> u32 {
self.size.x
}
pub fn height(&self) -> u32 {
self.size.y
}
pub fn options(&self) -> &TextureOptions {
&self.options
}
pub fn from_bytes(
data: &[u8],
options: TextureOptions,
width: u32,
height: u32,
) -> Result<Self, TextureError> {
if data.len() != (options.format.pixel_length() * width * height) as usize {
return Err(TextureError::InvalidTextureData(
options.format.pixel_length(),
width,
height,
data.len(),
));
}
let mut id = 0;
unsafe {
gl::GenTextures(1, &mut id);
gl::BindTexture(gl::TEXTURE_2D, id);
gl::TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGBA as gl::types::GLint,
width as gl::types::GLint,
height as gl::types::GLint,
0,
options.format as gl::types::GLenum,
gl::UNSIGNED_BYTE,
data.as_ptr() as *const gl::types::GLvoid,
);
gl::TexParameteri(
gl::TEXTURE_2D,
gl::TEXTURE_WRAP_S,
options.h_wrap_mode as gl::types::GLint,
);
gl::TexParameteri(
gl::TEXTURE_2D,
gl::TEXTURE_WRAP_T,
options.v_wrap_mode as gl::types::GLint,
);
gl::TexParameteri(
gl::TEXTURE_2D,
gl::TEXTURE_MIN_FILTER,
options.min_filter_mode as gl::types::GLint,
);
gl::TexParameteri(
gl::TEXTURE_2D,
gl::TEXTURE_MAG_FILTER,
options.max_filter_mode as gl::types::GLint,
);
match options.min_filter_mode {
MinFilterMode::NearestMipmapNearest
| MinFilterMode::LinearMipmapNearest
| MinFilterMode::NearestMipmapLinear
| MinFilterMode::LinearMipmapLinear => gl::GenerateMipmap(gl::TEXTURE_2D),
MinFilterMode::Nearest | MinFilterMode::Linear => {}
}
gl::BindTexture(gl::TEXTURE_2D, 0);
}
Ok(Self {
id,
size: Vector2u::new(width, height),
options,
})
}
}