use super::*;
use core::num::NonZeroU32;
pub struct Texture {
ctx: Context,
pub(crate) id: GlTexture,
width: u32,
height: u32,
mipmap: bool,
}
impl Texture {
pub fn new(ctx: &Context) -> Result<Texture, GolemError> {
let ctx = Context(ctx.0.clone());
let id = unsafe { ctx.0.gl.create_texture()? };
let tex = Texture {
ctx,
id,
width: 0,
height: 0,
mipmap: false,
};
tex.set_minification(TextureFilter::Linear)
.expect("Linear textures don't require mip-maps");
Ok(tex)
}
pub fn set_active(&self, bind_point: NonZeroU32) {
let gl = &self.ctx.0.gl;
unsafe {
gl.active_texture(glow::TEXTURE0 + bind_point.get());
gl.bind_texture(glow::TEXTURE_2D, Some(self.id));
gl.active_texture(glow::TEXTURE0);
}
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn set_image(&mut self, data: Option<&[u8]>, width: u32, height: u32, color: ColorFormat) {
assert!(width > 0, "The texture width was 0",);
assert!(height > 0, "The texture width was 0",);
assert!(
width < glow::MAX_TEXTURE_SIZE,
"The texture width was bigger than the maximum size"
);
assert!(
height < glow::MAX_TEXTURE_SIZE,
"The texture height was bigger than the maximum size"
);
if let Some(data) = data {
assert!(
data.len() >= (width * height * color.bytes_per_pixel()) as usize,
"The texture data wasn't big enough for the width, height, and format supplied"
);
}
self.width = width;
self.height = height;
let format = match color {
ColorFormat::RGB => glow::RGB,
ColorFormat::RGBA => glow::RGBA,
};
let gl = &self.ctx.0.gl;
unsafe {
gl.bind_texture(glow::TEXTURE_2D, Some(self.id));
gl.tex_image_2d(
glow::TEXTURE_2D,
0,
format as i32,
width as i32,
height as i32,
0,
format,
glow::UNSIGNED_BYTE,
data,
);
if width & (width - 1) == 0 && height & (height - 1) == 0 {
gl.generate_mipmap(glow::TEXTURE_2D);
self.mipmap = true;
} else {
self.mipmap = false;
self.set_wrap_h(TextureWrap::ClampToEdge)
.expect("The texture wrap ClampToEdge is always valid");
self.set_wrap_v(TextureWrap::ClampToEdge)
.expect("The texture wrap ClampToEdge is always valid");
}
gl.bind_texture(glow::TEXTURE_2D, None);
}
}
pub fn set_subimage(
&self,
data: &[u8],
x: u32,
y: u32,
width: u32,
height: u32,
color: ColorFormat,
) {
assert!(
x + width <= self.width,
"The region over-ran the width of the texture"
);
assert!(
y + height <= self.height,
"The region over-ran the height of the texture"
);
let format = match color {
ColorFormat::RGB => glow::RGB,
ColorFormat::RGBA => glow::RGBA,
};
let required_data_len = width * height * color.bytes_per_pixel();
assert!(data.len() >= required_data_len as usize);
let gl = &self.ctx.0.gl;
unsafe {
gl.bind_texture(glow::TEXTURE_2D, Some(self.id));
gl.tex_sub_image_2d_u8_slice(
glow::TEXTURE_2D,
0,
x as i32,
y as i32,
width as i32,
height as i32,
format,
glow::UNSIGNED_BYTE,
Some(data),
);
gl.generate_mipmap(glow::TEXTURE_2D);
gl.bind_texture(glow::TEXTURE_2D, None);
}
}
fn set_texture_param(&self, param: u32, value: i32) {
let gl = &self.ctx.0.gl;
unsafe {
gl.bind_texture(glow::TEXTURE_2D, Some(self.id));
gl.tex_parameter_i32(glow::TEXTURE_2D, param, value);
}
}
pub fn set_minification(&self, min: TextureFilter) -> Result<(), GolemError> {
if !self.mipmap && min.uses_mipmap() {
Err(GolemError::MipMapsUnavailable)
} else {
self.set_texture_param(glow::TEXTURE_MIN_FILTER, min.to_gl());
Ok(())
}
}
pub fn set_magnification(&self, max: TextureFilter) -> Result<(), GolemError> {
if max.uses_mipmap() {
Err(GolemError::MipMapsUnavailable)
} else {
self.set_texture_param(glow::TEXTURE_MAG_FILTER, max.to_gl());
Ok(())
}
}
pub fn set_wrap_h(&self, wrap: TextureWrap) -> Result<(), GolemError> {
if !self.mipmap && wrap != TextureWrap::ClampToEdge {
Err(GolemError::IllegalWrapOption)
} else {
self.set_texture_param(glow::TEXTURE_WRAP_S, wrap.to_gl());
Ok(())
}
}
pub fn set_wrap_v(&self, wrap: TextureWrap) -> Result<(), GolemError> {
if !self.mipmap && wrap != TextureWrap::ClampToEdge {
Err(GolemError::IllegalWrapOption)
} else {
self.set_texture_param(glow::TEXTURE_WRAP_T, wrap.to_gl());
Ok(())
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum TextureFilter {
Linear,
Nearest,
NearestMipmapNearest,
LinearMipmapNearest,
NearestMipmapLinear,
LinearMipmapLinear,
}
impl TextureFilter {
pub(crate) fn to_gl(self) -> i32 {
match self {
TextureFilter::Linear => glow::LINEAR as i32,
TextureFilter::Nearest => glow::NEAREST as i32,
TextureFilter::NearestMipmapNearest => glow::NEAREST_MIPMAP_NEAREST as i32,
TextureFilter::NearestMipmapLinear => glow::NEAREST_MIPMAP_LINEAR as i32,
TextureFilter::LinearMipmapNearest => glow::LINEAR_MIPMAP_NEAREST as i32,
TextureFilter::LinearMipmapLinear => glow::LINEAR_MIPMAP_LINEAR as i32,
}
}
pub fn uses_mipmap(self) -> bool {
match self {
TextureFilter::Linear | TextureFilter::Nearest => false,
_ => true,
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum TextureWrap {
Repeat,
ClampToEdge,
MirroredRepeat,
}
impl TextureWrap {
pub(crate) fn to_gl(self) -> i32 {
match self {
TextureWrap::Repeat => glow::REPEAT as i32,
TextureWrap::ClampToEdge => glow::CLAMP_TO_EDGE as i32,
TextureWrap::MirroredRepeat => glow::MIRRORED_REPEAT as i32,
}
}
}
impl Drop for Texture {
fn drop(&mut self) {
unsafe {
self.ctx.0.gl.delete_texture(self.id);
}
}
}