#[cfg(feature = "std")]
use std::cell::RefCell;
#[cfg(feature = "std")]
use std::fmt;
#[cfg(feature = "std")]
use std::marker::PhantomData;
#[cfg(feature = "std")]
use std::mem;
#[cfg(feature = "std")]
use std::ops::{Deref, DerefMut};
#[cfg(feature = "std")]
use std::os::raw::c_void;
#[cfg(feature = "std")]
use std::ptr;
#[cfg(feature = "std")]
use std::rc::Rc;
#[cfg(not(feature = "std"))]
use alloc::rc::Rc;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use core::cell::RefCell;
#[cfg(not(feature = "std"))]
use core::fmt::{self, Write};
#[cfg(not(feature = "std"))]
use core::marker::PhantomData;
#[cfg(not(feature = "std"))]
use core::mem;
#[cfg(not(feature = "std"))]
use core::ops::{Deref, DerefMut};
#[cfg(not(feature = "std"))]
use core::ptr;
use crate::context::GraphicsContext;
pub use crate::depth_test::DepthComparison;
use crate::metagl::*;
use crate::pixel::{opengl_pixel_format, Pixel, PixelFormat};
use crate::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,
}
pub trait Dimensionable {
type Size: Copy;
type Offset: Copy;
const ZERO_OFFSET: Self::Offset;
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 count(size: Self::Size) -> usize;
}
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 Offset = u32;
type Size = u32;
const ZERO_OFFSET: Self::Offset = 0;
fn dim() -> Dim {
Dim::Dim1
}
fn width(w: Self::Size) -> u32 {
w
}
fn x_offset(off: Self::Offset) -> u32 {
off
}
fn count(size: Self::Size) -> usize {
size as usize
}
}
#[derive(Clone, Copy, Debug)]
pub struct Dim2;
impl Dimensionable for Dim2 {
type Offset = [u32; 2];
type Size = [u32; 2];
const ZERO_OFFSET: Self::Offset = [0, 0];
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 count([width, height]: Self::Size) -> usize {
width as usize * height as usize
}
}
#[derive(Clone, Copy, Debug)]
pub struct Dim3;
impl Dimensionable for Dim3 {
type Offset = [u32; 3];
type Size = [u32; 3];
const ZERO_OFFSET: Self::Offset = [0, 0, 0];
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 count([width, height, depth]: Self::Size) -> usize {
width as usize * height as usize * depth as usize
}
}
#[derive(Clone, Copy, Debug)]
pub struct Cubemap;
impl Dimensionable for Cubemap {
type Offset = ([u32; 2], CubeFace);
type Size = u32;
const ZERO_OFFSET: Self::Offset = ([0, 0], CubeFace::PositiveX);
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 count(size: Self::Size) -> usize {
let size = size as usize;
size * size
}
}
#[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 into_raw(self) -> RawTexture {
let raw = unsafe { ptr::read(&self.raw) };
mem::forget(self);
raw
}
#[inline(always)]
pub fn mipmaps(&self) -> usize {
self.mipmaps
}
pub fn clear_part(
&self,
gen_mipmaps: GenMipmaps,
offset: D::Offset,
size: D::Size,
pixel: P::Encoding
) -> Result<(), TextureError>
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: GenMipmaps, pixel: P::Encoding) -> Result<(), TextureError>
where P::Encoding: Copy {
self.clear_part(gen_mipmaps, D::ZERO_OFFSET, self.size, pixel)
}
pub fn upload_part(
&self,
gen_mipmaps: GenMipmaps,
offset: D::Offset,
size: D::Size,
texels: &[P::Encoding],
) -> Result<(), TextureError> {
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 == GenMipmaps::Yes {
gl::GenerateMipmap(self.target);
}
gfx_state.bind_texture(self.target, 0);
}
Ok(())
}
pub fn upload(
&self,
gen_mipmaps: GenMipmaps,
texels: &[P::Encoding],
) -> Result<(), TextureError> {
self.upload_part(gen_mipmaps, D::ZERO_OFFSET, self.size, texels)
}
pub fn upload_part_raw(
&self,
gen_mipmaps: GenMipmaps,
offset: D::Offset,
size: D::Size,
texels: &[P::RawEncoding],
) -> Result<(), TextureError> {
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 == GenMipmaps::Yes {
gl::GenerateMipmap(self.target);
}
gfx_state.bind_texture(self.target, 0);
}
Ok(())
}
pub fn upload_raw(
&self,
gen_mipmaps: GenMipmaps,
texels: &[P::RawEncoding]
) -> Result<(), TextureError> {
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 + Default {
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);
let skip_bytes = (pf.format.size() * w as usize) % 8;
set_pack_alignment(skip_bytes);
texels.resize_with((w * h) as usize * pf.canals_len(), Default::default);
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
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum GenMipmaps {
Yes,
No
}
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 => unimplemented!("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(())
}
_ => {
#[cfg(feature = "std")]
{
Err(TextureError::TextureStorageCreationFailed(format!(
"unsupported texture OpenGL pixel format: {:?}",
glf
)))
}
#[cfg(not(feature = "std"))]
{
let mut reason = String::new();
let _ = write!(&mut reason, "unsupported texture OpenGL pixel format: {:?}", glf);
Err(TextureError::TextureStorageCreationFailed(reason))
}
}
}
}
None => {
#[cfg(feature = "std")]
{
Err(TextureError::TextureStorageCreationFailed(format!(
"unsupported texture pixel format: {:?}",
pf
)))
}
#[cfg(not(feature = "std"))]
{
let mut reason = String::new();
let _ = write!(&mut reason, "unsupported texture pixel format: {:?}", pf);
Err(TextureError::TextureStorageCreationFailed(reason))
}
}
}
}
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);
for face in 0..6 {
unsafe {
gl::TexImage2D(
gl::TEXTURE_CUBE_MAP_POSITIVE_X + face,
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,
fun.to_glenum() 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 set_unpack_alignment(skip_bytes: usize) {
let unpack_alignment = match skip_bytes {
0 => 8,
2 => 2,
4 => 4,
_ => 1
};
unsafe { gl::PixelStorei(gl::UNPACK_ALIGNMENT, unpack_alignment) };
}
fn set_pack_alignment(skip_bytes: usize) {
let pack_alignment = match skip_bytes {
0 => 8,
2 => 2,
4 => 4,
_ => 1
};
unsafe { gl::PixelStorei(gl::PACK_ALIGNMENT, pack_alignment) };
}
fn upload_texels<L, D, P, T>(
target: GLenum,
off: D::Offset,
size: D::Size,
texels: &[T]
) -> Result<(), TextureError>
where L: Layerable,
D: Dimensionable,
P: Pixel {
let input_bytes = texels.len() * mem::size_of::<T>();
let pf = P::pixel_format();
let pf_size = pf.format.size();
let expected_bytes = D::count(size) * pf_size;
if input_bytes < expected_bytes {
return Err(TextureError::NotEnoughPixels(expected_bytes, input_bytes));
}
let skip_bytes = (D::width(size) as usize * pf_size) % 8;
set_unpack_alignment(skip_bytes);
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::TexSubImage2D(
gl::TEXTURE_CUBE_MAP_POSITIVE_X + D::z_offset(off),
0,
D::x_offset(off) as GLint,
D::y_offset(off) as GLint,
D::width(size) as GLsizei,
D::width(size) as GLsizei,
format,
encoding,
texels.as_ptr() as *const c_void,
)
}
}
Layering::Layered => unimplemented!("Layering::Layered not implemented yet"),
}
None => return Err(TextureError::UnsupportedPixelFormat(pf))
}
Ok(())
}
#[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),
NotEnoughPixels(usize, usize),
UnsupportedPixelFormat(PixelFormat)
}
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)
}
TextureError::NotEnoughPixels(expected, provided) => {
write!(f, "not enough texels provided: expected {} bytes, provided {} bytes", expected, provided)
}
TextureError::UnsupportedPixelFormat(fmt) => {
write!(f, "unsupported pixel format: {:?}", fmt)
}
}
}
}