use gl;
use gl::types::*;
use std::cell::RefCell;
use std::error::Error;
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_void;
use std::ops::{Deref, DerefMut};
use std::ptr;
use std::rc::Rc;
use context::GraphicsContext;
use pixel::{Pixel, PixelFormat, opengl_pixel_format, pixel_components};
use state::GraphicsState;
#[derive(Clone, Copy, Debug)]
pub enum Wrap {
ClampToEdge,
Repeat,
MirroredRepeat
}
#[derive(Clone, Copy, Debug)]
pub enum MinFilter {
Nearest,
Linear,
NearestMipmapNearest,
NearestMipmapLinear,
LinearMipmapNearest,
LinearMipmapLinear
}
#[derive(Clone, Copy, Debug)]
pub enum MagFilter {
Nearest,
Linear
}
#[derive(Clone, Copy, Debug)]
pub enum DepthComparison {
Never,
Always,
Equal,
NotEqual,
Less,
LessOrEqual,
Greater,
GreaterOrEqual
}
pub trait Dimensionable {
type Size: Copy;
type Offset: Copy;
fn dim() -> Dim;
fn width(size: Self::Size) -> u32;
fn height(_: Self::Size) -> u32 { 1 }
fn depth(_: Self::Size) -> u32 { 1 }
fn x_offset(offset: Self::Offset) -> u32;
fn y_offset(_: Self::Offset) -> u32 { 1 }
fn z_offset(_: Self::Offset) -> u32 { 1 }
fn zero_offset() -> Self::Offset;
}
fn dim_capacity<D>(size: D::Size) -> u32 where D: Dimensionable {
D::width(size) * D::height(size) * D::depth(size)
}
#[derive(Clone, Copy, Debug)]
pub enum Dim {
Dim1,
Dim2,
Dim3,
Cubemap
}
#[derive(Clone, Copy, Debug)]
pub struct Dim1;
impl Dimensionable for Dim1 {
type Size = u32;
type Offset = u32;
fn dim() -> Dim { Dim::Dim1 }
fn width(w: Self::Size) -> u32 { w }
fn x_offset(off: Self::Offset) -> u32 { off }
fn zero_offset() -> Self::Offset { 0 }
}
#[derive(Clone, Copy, Debug)]
pub struct Dim2;
impl Dimensionable for Dim2 {
type Size = [u32; 2];
type Offset = [u32; 2];
fn dim() -> Dim { Dim::Dim2 }
fn width(size: Self::Size) -> u32 { size[0] }
fn height(size: Self::Size) -> u32 { size[1] }
fn x_offset(off: Self::Offset) -> u32 { off[0] }
fn y_offset(off: Self::Offset) -> u32 { off[1] }
fn zero_offset() -> Self::Offset { [0, 0] }
}
#[derive(Clone, Copy, Debug)]
pub struct Dim3;
impl Dimensionable for Dim3 {
type Size = [u32; 3];
type Offset = [u32; 3];
fn dim() -> Dim { Dim::Dim3 }
fn width(size: Self::Size) -> u32 { size[0] }
fn height(size: Self::Size) -> u32 { size[1] }
fn depth(size: Self::Size) -> u32 { size[2] }
fn x_offset(off: Self::Offset) -> u32 { off[0] }
fn y_offset(off: Self::Offset) -> u32 { off[1] }
fn z_offset(off: Self::Offset) -> u32 { off[2] }
fn zero_offset() -> Self::Offset { [0, 0, 0] }
}
#[derive(Clone, Copy, Debug)]
pub struct Cubemap;
impl Dimensionable for Cubemap {
type Size = u32;
type Offset = ([u32; 2], CubeFace);
fn dim() -> Dim { Dim::Cubemap }
fn width(s: Self::Size) -> u32 { s }
fn height(s: Self::Size) -> u32 { s }
fn depth(_: Self::Size) -> u32 { 6 }
fn x_offset(off: Self::Offset) -> u32 { off.0[0] }
fn y_offset(off: Self::Offset) -> u32 { off.0[1] }
fn z_offset(off: Self::Offset) -> u32 {
match off.1 {
CubeFace::PositiveX => 0,
CubeFace::NegativeX => 1,
CubeFace::PositiveY => 2,
CubeFace::NegativeY => 3,
CubeFace::PositiveZ => 4,
CubeFace::NegativeZ => 5
}
}
fn zero_offset() -> Self::Offset { ([0, 0], CubeFace::PositiveX) }
}
#[derive(Clone, Copy, Debug)]
pub enum CubeFace {
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ
}
pub trait Layerable {
fn layering() -> Layering;
}
#[derive(Clone, Copy, Debug)]
pub enum Layering {
Flat,
Layered
}
#[derive(Clone, Copy, Debug)]
pub struct Flat;
impl Layerable for Flat { fn layering() -> Layering { Layering::Flat } }
#[derive(Clone, Copy, Debug)]
pub struct Layered;
impl Layerable for Layered { fn layering() -> Layering { Layering::Layered } }
pub struct RawTexture {
handle: GLuint, target: GLenum, state: Rc<RefCell<GraphicsState>>
}
impl RawTexture {
pub(crate) unsafe fn new(state: Rc<RefCell<GraphicsState>>, handle: GLuint, target: GLenum) -> Self {
RawTexture {
handle,
target,
state
}
}
#[inline]
pub(crate) fn handle(&self) -> GLuint {
self.handle
}
#[inline]
pub(crate) fn target(&self) -> GLenum {
self.target
}
}
pub struct Texture<L, D, P> where L: Layerable, D: Dimensionable, P: Pixel {
raw: RawTexture,
size: D::Size,
mipmaps: usize, _l: PhantomData<L>,
_p: PhantomData<P>
}
impl<L, D, P> Deref for Texture<L, D, P> where L: Layerable, D: Dimensionable, P: Pixel {
type Target = RawTexture;
fn deref(&self) -> &Self::Target {
&self.raw
}
}
impl<L, D, P> DerefMut for Texture<L, D, P> where L: Layerable, D: Dimensionable, P: Pixel {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.raw
}
}
impl<L, D, P> Drop for Texture<L, D, P> where L: Layerable, D: Dimensionable, P: Pixel {
fn drop(&mut self) {
unsafe { gl::DeleteTextures(1, &self.handle) }
}
}
impl<L, D, P> Texture<L, D, P>
where L: Layerable,
D: Dimensionable,
P: Pixel {
pub fn new<C>(
ctx: &mut C,
size: D::Size,
mipmaps: usize,
sampler: &Sampler
) -> Result<Self, TextureError>
where C: GraphicsContext {
let mipmaps = mipmaps + 1; let mut texture = 0;
let target = opengl_target(L::layering(), D::dim());
unsafe {
gl::GenTextures(1, &mut texture);
ctx.state().borrow_mut().bind_texture(target, texture);
create_texture::<L, D>(target, size, mipmaps, P::pixel_format(), sampler)?;
let raw = RawTexture::new(ctx.state().clone(), texture, target);
Ok(Texture {
raw,
size,
mipmaps,
_l: PhantomData,
_p: PhantomData
})
}
}
pub(crate) unsafe fn from_raw(raw: RawTexture, size: D::Size, mipmaps: usize) -> Self {
Texture {
raw,
size,
mipmaps: mipmaps + 1,
_l: PhantomData,
_p: PhantomData
}
}
pub fn to_raw(mut self) -> RawTexture {
let raw = mem::replace(&mut self.raw, unsafe { mem::uninitialized() });
mem::forget(self);
raw
}
#[inline(always)]
pub fn mipmaps(&self) -> usize {
self.mipmaps
}
pub fn clear_part(&self, gen_mipmaps: bool, offset: D::Offset, size: D::Size, pixel: P::Encoding)
where P::Encoding: Copy {
self.upload_part(gen_mipmaps, offset, size, &vec![pixel; dim_capacity::<D>(size) as usize])
}
pub fn clear(&self, gen_mipmaps: bool, pixel: P::Encoding)
where P::Encoding: Copy {
self.clear_part(gen_mipmaps, D::zero_offset(), self.size, pixel)
}
pub fn upload_part(
&self,
gen_mipmaps: bool, offset: D::Offset,
size: D::Size,
texels: &[P::Encoding]
) {
unsafe {
let mut gfx_state = self.state.borrow_mut();
gfx_state.bind_texture(self.target, self.handle);
upload_texels::<L, D, P, P::Encoding>(self.target, offset, size, texels);
if gen_mipmaps {
gl::GenerateMipmap(self.target);
}
gfx_state.bind_texture(self.target, 0);
}
}
pub fn upload(
&self,
gen_mipmaps: bool, texels: &[P::Encoding]
) {
self.upload_part(gen_mipmaps, D::zero_offset(), self.size, texels)
}
pub fn upload_part_raw(
&self,
gen_mipmaps: bool,
offset: D::Offset,
size: D::Size,
texels: &[P::RawEncoding]
) {
unsafe {
let mut gfx_state = self.state.borrow_mut();
gfx_state.bind_texture(self.target, self.handle);
upload_texels::<L, D, P, P::RawEncoding>(self.target, offset, size, texels);
if gen_mipmaps {
gl::GenerateMipmap(self.target);
}
gfx_state.bind_texture(self.target, 0);
}
}
pub fn upload_raw(&self, gen_mipmaps: bool, texels: &[P::RawEncoding]) {
self.upload_part_raw(gen_mipmaps, D::zero_offset(), self.size, texels)
}
pub fn get_raw_texels(&self) -> Vec<P::RawEncoding> where P: Pixel, P::RawEncoding: Copy {
let mut texels = Vec::new();
let pf = P::pixel_format();
let (format, _, ty) = opengl_pixel_format(pf).unwrap();
unsafe {
let mut w = 0;
let mut h = 0;
let mut gfx_state = self.state.borrow_mut();
gfx_state.bind_texture(self.target, self.handle);
gl::GetTexLevelParameteriv(self.target, 0, gl::TEXTURE_WIDTH, &mut w);
gl::GetTexLevelParameteriv(self.target, 0, gl::TEXTURE_HEIGHT, &mut h);
texels.resize((w * h) as usize * pixel_components(pf), mem::uninitialized());
gl::GetTexImage(self.target, 0, format, ty, texels.as_mut_ptr() as *mut c_void);
gfx_state.bind_texture(self.target, 0);
}
texels
}
pub fn size(&self) -> D::Size {
self.size
}
}
pub(crate) fn opengl_target(l: Layering, d: Dim) -> GLenum {
match l {
Layering::Flat => match d {
Dim::Dim1 => gl::TEXTURE_1D,
Dim::Dim2 => gl::TEXTURE_2D,
Dim::Dim3 => gl::TEXTURE_3D,
Dim::Cubemap => gl::TEXTURE_CUBE_MAP
},
Layering::Layered => match d {
Dim::Dim1 => gl::TEXTURE_1D_ARRAY,
Dim::Dim2 => gl::TEXTURE_2D_ARRAY,
Dim::Dim3 => panic!("3D textures array not supported"),
Dim::Cubemap => gl::TEXTURE_CUBE_MAP_ARRAY
}
}
}
pub(crate) unsafe fn create_texture<L, D>(
target: GLenum,
size: D::Size,
mipmaps: usize,
pf: PixelFormat,
sampler: &Sampler
) -> Result<(), TextureError>
where L: Layerable,
D: Dimensionable {
set_texture_levels(target, mipmaps);
apply_sampler_to_texture(target, sampler);
create_texture_storage::<L, D>(size, mipmaps, pf)
}
fn create_texture_storage<L, D>(
size: D::Size,
mipmaps: usize,
pf: PixelFormat
) -> Result<(), TextureError>
where L: Layerable,
D: Dimensionable {
match opengl_pixel_format(pf) {
Some(glf) => {
let (format, iformat, encoding) = glf;
match (L::layering(), D::dim()) {
(Layering::Flat, Dim::Dim1) => {
create_texture_1d_storage(format, iformat, encoding, D::width(size), mipmaps);
Ok(())
},
(Layering::Flat, Dim::Dim2) => {
create_texture_2d_storage(format, iformat, encoding, D::width(size), D::height(size), mipmaps);
Ok(())
},
(Layering::Flat, Dim::Dim3) => {
create_texture_3d_storage(format, iformat, encoding, D::width(size), D::height(size), D::depth(size), mipmaps);
Ok(())
},
(Layering::Flat, Dim::Cubemap) => {
create_cubemap_storage(format, iformat, encoding, D::width(size), mipmaps);
Ok(())
},
_ => Err(TextureError::TextureStorageCreationFailed(format!("unsupported texture OpenGL pixel format: {:?}", glf)))
}
},
None => Err(TextureError::TextureStorageCreationFailed(format!("unsupported texture pixel format: {:?}", pf)))
}
}
fn create_texture_1d_storage(format: GLenum, iformat: GLenum, encoding: GLenum, w: u32, mipmaps: usize) {
for level in 0..mipmaps {
let w = w / 2u32.pow(level as u32);
unsafe { gl::TexImage1D(gl::TEXTURE_1D, level as GLint, iformat as GLint, w as GLsizei, 0, format, encoding, ptr::null()) };
}
}
fn create_texture_2d_storage(format: GLenum, iformat: GLenum, encoding: GLenum, w: u32, h: u32, mipmaps: usize) {
for level in 0..mipmaps {
let div = 2u32.pow(level as u32);
let w = w / div;
let h = h / div;
unsafe { gl::TexImage2D(gl::TEXTURE_2D, level as GLint, iformat as GLint, w as GLsizei, h as GLsizei, 0, format, encoding, ptr::null()) };
}
}
fn create_texture_3d_storage(format: GLenum, iformat: GLenum, encoding: GLenum, w: u32, h: u32, d: u32, mipmaps: usize) {
for level in 0..mipmaps {
let div = 2u32.pow(level as u32);
let w = w / div;
let h = h / div;
let d = d / div;
unsafe { gl::TexImage3D(gl::TEXTURE_3D, level as GLint, iformat as GLint, w as GLsizei, h as GLsizei, d as GLsizei, 0, format, encoding, ptr::null()) };
}
}
fn create_cubemap_storage(format: GLenum, iformat: GLenum, encoding: GLenum, s: u32, mipmaps: usize) {
for level in 0..mipmaps {
let s = s / 2u32.pow(level as u32);
unsafe { gl::TexImage2D(gl::TEXTURE_CUBE_MAP, level as GLint, iformat as GLint, s as GLsizei, s as GLsizei, 0, format, encoding, ptr::null()) };
}
}
fn set_texture_levels(target: GLenum, mipmaps: usize) {
unsafe {
gl::TexParameteri(target, gl::TEXTURE_BASE_LEVEL, 0);
gl::TexParameteri(target, gl::TEXTURE_MAX_LEVEL, mipmaps as GLint - 1);
}
}
fn apply_sampler_to_texture(target: GLenum, sampler: &Sampler) {
unsafe {
gl::TexParameteri(target, gl::TEXTURE_WRAP_R, opengl_wrap(sampler.wrap_r) as GLint);
gl::TexParameteri(target, gl::TEXTURE_WRAP_S, opengl_wrap(sampler.wrap_s) as GLint);
gl::TexParameteri(target, gl::TEXTURE_WRAP_T, opengl_wrap(sampler.wrap_t) as GLint);
gl::TexParameteri(target, gl::TEXTURE_MIN_FILTER, opengl_min_filter(sampler.min_filter) as GLint);
gl::TexParameteri(target, gl::TEXTURE_MAG_FILTER, opengl_mag_filter(sampler.mag_filter) as GLint);
match sampler.depth_comparison {
Some(fun) => {
gl::TexParameteri(target, gl::TEXTURE_COMPARE_FUNC, opengl_depth_comparison(fun) as GLint);
gl::TexParameteri(target, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE as GLint);
},
None => {
gl::TexParameteri(target, gl::TEXTURE_COMPARE_MODE, gl::NONE as GLint);
}
}
}
}
fn opengl_wrap(wrap: Wrap) -> GLenum {
match wrap {
Wrap::ClampToEdge => gl::CLAMP_TO_EDGE,
Wrap::Repeat => gl::REPEAT,
Wrap::MirroredRepeat => gl::MIRRORED_REPEAT
}
}
fn opengl_min_filter(filter: MinFilter) -> GLenum {
match filter {
MinFilter::Nearest => gl::NEAREST,
MinFilter::Linear => gl::LINEAR,
MinFilter::NearestMipmapNearest => gl::NEAREST_MIPMAP_NEAREST,
MinFilter::NearestMipmapLinear => gl::NEAREST_MIPMAP_LINEAR,
MinFilter::LinearMipmapNearest => gl::LINEAR_MIPMAP_NEAREST,
MinFilter::LinearMipmapLinear => gl::LINEAR_MIPMAP_LINEAR
}
}
fn opengl_mag_filter(filter: MagFilter) -> GLenum {
match filter {
MagFilter::Nearest => gl::NEAREST,
MagFilter::Linear => gl::LINEAR
}
}
fn opengl_depth_comparison(fun: DepthComparison) -> GLenum {
match fun {
DepthComparison::Never => gl::NEVER,
DepthComparison::Always => gl::ALWAYS,
DepthComparison::Equal => gl::EQUAL,
DepthComparison::NotEqual => gl::NOTEQUAL,
DepthComparison::Less => gl::LESS,
DepthComparison::LessOrEqual => gl::LEQUAL,
DepthComparison::Greater => gl::GREATER,
DepthComparison::GreaterOrEqual => gl::GEQUAL
}
}
fn upload_texels<L, D, P, T>(target: GLenum, off: D::Offset, size: D::Size, texels: &[T])
where L: Layerable,
D: Dimensionable,
P: Pixel {
let pf = P::pixel_format();
match opengl_pixel_format(pf) {
Some((format, _, encoding)) => {
match L::layering() {
Layering::Flat => {
match D::dim() {
Dim::Dim1 => unsafe { gl::TexSubImage1D(target, 0, D::x_offset(off) as GLint, D::width(size) as GLsizei, format, encoding, texels.as_ptr() as *const c_void) },
Dim::Dim2 => unsafe { gl::TexSubImage2D(target, 0, D::x_offset(off) as GLint, D::y_offset(off) as GLint, D::width(size) as GLsizei, D::height(size) as GLsizei, format, encoding, texels.as_ptr() as *const c_void) },
Dim::Dim3 => unsafe { gl::TexSubImage3D(target, 0, D::x_offset(off) as GLint, D::y_offset(off) as GLint, D::z_offset(off) as GLint, D::width(size) as GLsizei, D::height(size) as GLsizei, D::depth(size) as GLsizei, format, encoding, texels.as_ptr() as *const c_void) },
Dim::Cubemap => unsafe { gl::TexSubImage3D(target, 0, D::x_offset(off) as GLint, D::y_offset(off) as GLint, (gl::TEXTURE_CUBE_MAP_POSITIVE_X + D::z_offset(off)) as GLint, D::width(size) as GLsizei, D::width(size) as GLsizei, 1, format, encoding, texels.as_ptr() as *const c_void) }
}
},
Layering::Layered => panic!("Layering::Layered not implemented yet")
}
},
None => panic!("unknown pixel format")
}
}
#[derive(Clone, Copy, Debug)]
pub struct Sampler {
pub wrap_r: Wrap,
pub wrap_s: Wrap,
pub wrap_t: Wrap,
pub min_filter: MinFilter,
pub mag_filter: MagFilter,
pub depth_comparison: Option<DepthComparison>
}
impl Default for Sampler {
fn default() -> Self {
Sampler {
wrap_r: Wrap::ClampToEdge,
wrap_s: Wrap::ClampToEdge,
wrap_t: Wrap::ClampToEdge,
min_filter: MinFilter::NearestMipmapLinear,
mag_filter: MagFilter::Linear,
depth_comparison: None
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TextureError {
TextureStorageCreationFailed(String),
}
impl fmt::Display for TextureError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
TextureError::TextureStorageCreationFailed(ref e) => {
write!(f, "texture storage creation failed: {}", e)
}
}
}
}
impl Error for TextureError {}