ugli/texture/
mod.rs

1use super::*;
2
3/// # Safety
4/// Don't implement yourself
5pub unsafe trait TexturePixel {
6    const INTERNAL_FORMAT: raw::Enum;
7    const FORMAT: raw::Enum;
8    const TYPE: raw::Enum;
9}
10
11unsafe impl TexturePixel for Rgba<f32> {
12    const INTERNAL_FORMAT: raw::Enum = raw::RGBA;
13    const FORMAT: raw::Enum = raw::RGBA;
14    const TYPE: raw::Enum = raw::UNSIGNED_BYTE;
15}
16
17unsafe impl TexturePixel for u8 {
18    #[cfg(target_arch = "wasm32")]
19    const INTERNAL_FORMAT: raw::Enum = raw::ALPHA;
20    #[cfg(not(target_arch = "wasm32"))]
21    const INTERNAL_FORMAT: raw::Enum = raw::RGBA;
22    const FORMAT: raw::Enum = raw::ALPHA;
23    const TYPE: raw::Enum = raw::UNSIGNED_BYTE;
24}
25
26#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
27pub enum WrapMode {
28    Repeat = raw::REPEAT as _,
29    Clamp = raw::CLAMP_TO_EDGE as _,
30}
31
32#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
33pub enum Filter {
34    Nearest = raw::NEAREST as _,
35    Linear = raw::LINEAR as _,
36}
37
38pub struct Texture2d<P: TexturePixel> {
39    pub(crate) ugli: Ugli,
40    pub(crate) handle: raw::Texture,
41    size: Cell<vec2<usize>>,
42    phantom_data: PhantomData<*mut P>,
43}
44
45impl<P: TexturePixel> Drop for Texture2d<P> {
46    fn drop(&mut self) {
47        let gl = &self.ugli.inner.raw;
48        gl.delete_texture(&self.handle);
49    }
50}
51
52pub type Texture = Texture2d<Rgba<f32>>;
53
54impl<P: TexturePixel> Texture2d<P> {
55    fn new_raw(ugli: &Ugli, size: vec2<usize>) -> Self {
56        let gl = &ugli.inner.raw;
57        let handle = gl.create_texture().unwrap();
58        gl.bind_texture(raw::TEXTURE_2D, &handle);
59        gl.tex_parameteri(
60            raw::TEXTURE_2D,
61            raw::TEXTURE_MIN_FILTER,
62            raw::LINEAR as raw::Int,
63        );
64        let mut texture = Self {
65            ugli: ugli.clone(),
66            handle,
67            size: Cell::new(size),
68            phantom_data: PhantomData,
69        };
70        texture.set_filter(Filter::Linear);
71        texture.set_wrap_mode(WrapMode::Clamp);
72        ugli.debug_check();
73        texture
74    }
75
76    pub fn is_pot(&self) -> bool {
77        let size = self.size.get();
78        size.x & (size.x - 1) == 0 && size.y & (size.y - 1) == 0
79    }
80
81    pub fn new_uninitialized(ugli: &Ugli, size: vec2<usize>) -> Self {
82        let texture = Self::new_raw(ugli, size);
83        let gl = &ugli.inner.raw;
84        gl.tex_image_2d::<u8>(
85            raw::TEXTURE_2D,
86            0,
87            P::INTERNAL_FORMAT as raw::Int,
88            size.x as raw::SizeI,
89            size.y as raw::SizeI,
90            0,
91            P::FORMAT,
92            P::TYPE,
93            None,
94        );
95        ugli.debug_check();
96        texture
97    }
98    pub fn set_wrap_mode(&mut self, wrap_mode: WrapMode) {
99        self.set_wrap_mode_separate(wrap_mode, wrap_mode);
100    }
101
102    pub fn set_wrap_mode_separate(&mut self, wrap_mode_x: WrapMode, wrap_mode_y: WrapMode) {
103        if wrap_mode_x == WrapMode::Repeat || wrap_mode_y == WrapMode::Repeat {
104            assert!(
105                self.is_pot(),
106                "Repeat wrap mode only supported for power of two textures"
107            ); // Because of webgl
108        }
109        let gl = &self.ugli.inner.raw;
110        gl.bind_texture(raw::TEXTURE_2D, &self.handle);
111        gl.tex_parameteri(
112            raw::TEXTURE_2D,
113            raw::TEXTURE_WRAP_S,
114            wrap_mode_x as raw::Int,
115        );
116        gl.tex_parameteri(
117            raw::TEXTURE_2D,
118            raw::TEXTURE_WRAP_T,
119            wrap_mode_y as raw::Int,
120        );
121        self.ugli.debug_check();
122    }
123
124    pub fn set_filter(&mut self, filter: Filter) {
125        assert!(self.is_pot() || filter == Filter::Nearest || filter == Filter::Linear);
126        let gl = &self.ugli.inner.raw;
127        gl.bind_texture(raw::TEXTURE_2D, &self.handle);
128        gl.tex_parameteri(raw::TEXTURE_2D, raw::TEXTURE_MAG_FILTER, filter as raw::Int);
129        gl.tex_parameteri(raw::TEXTURE_2D, raw::TEXTURE_MIN_FILTER, filter as raw::Int);
130        self.ugli.debug_check();
131    }
132
133    pub fn size(&self) -> vec2<usize> {
134        self.size.get()
135    }
136
137    // TODO: use like Matrix<Color>?
138    pub fn sub_image(&mut self, pos: vec2<usize>, size: vec2<usize>, data: &[u8]) {
139        assert_eq!(
140            size.x
141                * size.y
142                * match P::FORMAT {
143                    raw::RGBA => 4,
144                    raw::ALPHA => 1,
145                    _ => unreachable!(),
146                },
147            data.len()
148        );
149        let gl = &self.ugli.inner.raw;
150        gl.pixel_store_flip_y(false);
151        gl.bind_texture(raw::TEXTURE_2D, &self.handle);
152        gl.tex_sub_image_2d(
153            raw::TEXTURE_2D,
154            0,
155            pos.x as raw::Int,
156            pos.y as raw::Int,
157            size.x as raw::SizeI,
158            size.y as raw::SizeI,
159            P::FORMAT,
160            P::TYPE,
161            data,
162        );
163        self.ugli.debug_check();
164    }
165}
166
167impl Texture {
168    pub fn gen_mipmaps(&mut self) {
169        assert!(self.is_pot());
170        let gl = &self.ugli.inner.raw;
171        gl.bind_texture(raw::TEXTURE_2D, &self.handle);
172        gl.generate_mipmap(raw::TEXTURE_2D);
173        gl.tex_parameteri(
174            raw::TEXTURE_2D,
175            raw::TEXTURE_MIN_FILTER,
176            raw::LINEAR_MIPMAP_LINEAR as raw::Int,
177        );
178        self.ugli.debug_check();
179    }
180
181    pub fn new_with<F: FnMut(vec2<usize>) -> Rgba<f32>>(
182        ugli: &Ugli,
183        size: vec2<usize>,
184        mut f: F,
185    ) -> Self {
186        let texture = Texture2d::new_raw(ugli, size);
187        let mut data: Vec<u8> = Vec::with_capacity(size.x * size.y * 4);
188        for y in 0..size.y {
189            for x in 0..size.x {
190                let color = f(vec2(x, y));
191                data.push((color.r * 255.0) as u8);
192                data.push((color.g * 255.0) as u8);
193                data.push((color.b * 255.0) as u8);
194                data.push((color.a * 255.0) as u8);
195            }
196        }
197        let gl = &ugli.inner.raw;
198        gl.pixel_store_flip_y(false);
199        gl.tex_image_2d(
200            raw::TEXTURE_2D,
201            0,
202            raw::RGBA as raw::Int,
203            size.x as raw::SizeI,
204            size.y as raw::SizeI,
205            0,
206            raw::RGBA as raw::Enum,
207            raw::UNSIGNED_BYTE,
208            Some(&data),
209        );
210        ugli.debug_check();
211        texture
212    }
213
214    pub fn from_image_image(ugli: &Ugli, mut image: image::RgbaImage) -> Self {
215        let size = vec2(image.width() as usize, image.height() as usize);
216        let mut texture = Texture2d::new_raw(ugli, size);
217        let gl = &ugli.inner.raw;
218        image::imageops::flip_vertical_in_place(&mut image);
219        gl.pixel_store_flip_y(false);
220        gl.tex_image_2d(
221            raw::TEXTURE_2D,
222            0,
223            raw::RGBA as raw::Int,
224            size.x as raw::SizeI,
225            size.y as raw::SizeI,
226            0,
227            raw::RGBA as raw::Enum,
228            raw::UNSIGNED_BYTE,
229            Some(&image.into_raw()),
230        );
231        if texture.is_pot() {
232            texture.gen_mipmaps();
233        }
234        ugli.debug_check();
235        texture
236    }
237
238    #[cfg(target_arch = "wasm32")]
239    pub fn from_html_image_element(
240        ugli: &Ugli,
241        image: &web_sys::HtmlImageElement,
242        premultiply_alpha: bool,
243    ) -> Self {
244        let mut texture =
245            Texture2d::new_raw(ugli, vec2(image.width() as usize, image.height() as usize));
246        let gl = &ugli.inner.raw;
247        gl.pixel_store_flip_y(true);
248        gl.pixel_store_premultiply_alpha(premultiply_alpha);
249        gl.tex_image_2d_image(
250            raw::TEXTURE_2D,
251            0,
252            raw::RGBA as raw::Int,
253            raw::RGBA,
254            raw::UNSIGNED_BYTE,
255            image,
256        );
257        if texture.is_pot() {
258            texture.gen_mipmaps();
259        }
260        ugli.debug_check();
261        texture
262    }
263}