webgl_rc/
texture.rs

1use std::cell::Cell;
2use std::rc::Rc;
3
4use js_sys::{Error, JsString, Uint8Array};
5use num_enum::{IntoPrimitive, TryFromPrimitive};
6use web_sys::{
7    HtmlImageElement, OesTextureHalfFloat, WebGlRenderingContext as Context, WebGlTexture,
8};
9
10use super::gl::Gl;
11use super::gl::GlError;
12use super::settings::Settings;
13
14#[repr(i32)]
15#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Eq)]
16pub enum TextureFilter {
17    Nearest = Context::NEAREST as i32,
18    Linear = Context::LINEAR as i32,
19}
20
21impl Default for TextureFilter {
22    fn default() -> Self {
23        TextureFilter::Linear
24    }
25}
26
27#[repr(u32)]
28#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Eq)]
29pub enum TextureType {
30    Byte = Context::UNSIGNED_BYTE,
31    Float = Context::FLOAT,
32    HalfFloat = OesTextureHalfFloat::HALF_FLOAT_OES,
33}
34
35#[repr(u32)]
36#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Eq)]
37pub enum TextureFormat {
38    Alpha = Context::ALPHA,
39    Luminance = Context::LUMINANCE,
40    LuminanceAlpha = Context::LUMINANCE_ALPHA,
41    Rgb = Context::RGB,
42    Rgba = Context::RGBA,
43}
44
45impl TextureFormat {
46    pub fn channels(self) -> u32 {
47        match self {
48            TextureFormat::Alpha => 1,
49            TextureFormat::Luminance => 1,
50            TextureFormat::LuminanceAlpha => 2,
51            TextureFormat::Rgb => 3,
52            TextureFormat::Rgba => 4,
53        }
54    }
55}
56
57#[derive(Debug)]
58pub enum TextureContent {
59    None,
60    Image(HtmlImageElement),
61    Bytes(Vec<u8>),
62}
63
64pub const TEXTURES_COUNT: u32 = 16;
65
66#[derive(Debug)]
67struct TextureInfo {
68    gl: Gl,
69    handle: WebGlTexture,
70    width: u32,
71    height: u32,
72    data_type: TextureType,
73    format: TextureFormat,
74    filter: Cell<TextureFilter>,
75}
76
77impl PartialEq<TextureInfo> for TextureInfo {
78    fn eq(&self, other: &TextureInfo) -> bool {
79        self.handle == other.handle
80    }
81}
82
83impl Eq for TextureInfo {}
84
85impl Drop for TextureInfo {
86    fn drop(&mut self) {
87        self.gl.context().delete_texture(Some(&self.handle))
88    }
89}
90
91#[derive(Clone, Debug, PartialEq, Eq)]
92pub struct Texture {
93    data: Rc<TextureInfo>,
94}
95
96impl Texture {
97    pub fn new(
98        gl: Gl,
99        width: u32,
100        height: u32,
101        data_type: TextureType,
102        format: TextureFormat,
103        data: TextureContent,
104    ) -> Result<Texture, GlError> {
105        let handle = gl
106            .context()
107            .create_texture()
108            .ok_or_else(|| GlError::UnknownError(Some("Texture creation failed".into())))?;
109
110        let result = Texture {
111            data: Rc::new(TextureInfo {
112                gl: gl.clone(),
113                handle: handle.clone(),
114                filter: Default::default(),
115                width,
116                height,
117                data_type,
118                format,
119            }),
120        };
121
122        gl.apply(
123            Gl::settings().active_texture(0).texture(0, result.clone()),
124            || {
125                gl.context().tex_parameteri(
126                    Context::TEXTURE_2D,
127                    Context::TEXTURE_WRAP_S,
128                    Context::CLAMP_TO_EDGE as i32,
129                );
130                gl.context().tex_parameteri(
131                    Context::TEXTURE_2D,
132                    Context::TEXTURE_WRAP_T,
133                    Context::CLAMP_TO_EDGE as i32,
134                );
135                gl.context().tex_parameteri(
136                    Context::TEXTURE_2D,
137                    Context::TEXTURE_MAG_FILTER,
138                    TextureFilter::default().into(),
139                );
140                gl.context().tex_parameteri(
141                    Context::TEXTURE_2D,
142                    Context::TEXTURE_MIN_FILTER,
143                    TextureFilter::default().into(),
144                );
145            },
146        );
147
148        match data {
149            TextureContent::None => result.init_buffer()?,
150            TextureContent::Image(image) => result.write_image(&image)?,
151            TextureContent::Bytes(bytes) => result.write_bytes(&bytes)?,
152        }
153
154        Ok(result)
155    }
156
157    pub fn gl(&self) -> Gl {
158        self.data.gl.clone()
159    }
160
161    pub fn width(&self) -> u32 {
162        self.data.width
163    }
164    pub fn height(&self) -> u32 {
165        self.data.height
166    }
167    pub fn data_type(&self) -> TextureType {
168        self.data.data_type
169    }
170    pub fn format(&self) -> TextureFormat {
171        self.data.format
172    }
173
174    pub(crate) fn handle(&self) -> &WebGlTexture {
175        &self.data.handle
176    }
177
178    pub fn size(&self) -> (u32, u32) {
179        (self.width(), self.height())
180    }
181
182    pub fn filter(&self) -> TextureFilter {
183        self.data.filter.get()
184    }
185
186    pub fn set_filter(&self, filter: TextureFilter) {
187        if self.filter() != filter {
188            let ref gl = self.data.gl;
189            let context = gl.context();
190            gl.apply(
191                Gl::settings().texture(0, self.clone()).active_texture(0),
192                || {
193                    context.tex_parameteri(
194                        Context::TEXTURE_2D,
195                        Context::TEXTURE_MAG_FILTER,
196                        filter.into(),
197                    );
198                    context.tex_parameteri(
199                        Context::TEXTURE_2D,
200                        Context::TEXTURE_MIN_FILTER,
201                        filter.into(),
202                    );
203                    self.data.filter.set(filter);
204                },
205            );
206        }
207    }
208
209    pub fn write_image(&self, image: &HtmlImageElement) -> Result<(), GlError> {
210        let gl = self.gl();
211        let format: u32 = self.format().into();
212
213        gl.apply(
214            Gl::settings().active_texture(0).texture(0, self.clone()),
215            || {
216                gl.context()
217                    .tex_image_2d_with_u32_and_u32_and_image(
218                        Context::TEXTURE_2D,
219                        0,
220                        format as i32,
221                        format,
222                        self.data_type().into(),
223                        image,
224                    )
225                    .map_err(|e| GlError::WritePixelsError(Some(JsString::from(e).into())))
226            },
227        )?;
228
229        Ok(())
230    }
231
232    pub fn write_bytes(&self, bytes: &Vec<u8>) -> Result<(), GlError> {
233        let gl = self.gl();
234        let format: u32 = self.format().into();
235
236        gl.apply(
237            Gl::settings().active_texture(0).texture(0, self.clone()),
238            || {
239                gl.context()
240                    .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
241                        Context::TEXTURE_2D,
242                        0,
243                        format as i32,
244                        self.width() as i32,
245                        self.height() as i32,
246                        0,
247                        format,
248                        self.data_type().into(),
249                        Some(bytes),
250                    )
251                    .map_err(|e| GlError::WritePixelsError(Some(JsString::from(e).into())))
252            },
253        )?;
254
255        Ok(())
256    }
257
258    fn init_buffer(&self) -> Result<(), GlError> {
259        let gl = self.gl();
260        let format: u32 = self.format().into();
261
262        gl.apply(
263            Gl::settings().active_texture(0).texture(0, self.clone()),
264            || {
265                gl.context()
266                    .tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
267                        Context::TEXTURE_2D,
268                        0,
269                        format as i32,
270                        self.width() as i32,
271                        self.height() as i32,
272                        0,
273                        format,
274                        self.data_type().into(),
275                        None,
276                    )
277                    .map_err(|e| GlError::InitTextureBufferError(Some(JsString::from(e).into())))
278            },
279        )?;
280
281        Ok(())
282    }
283
284    /// Read RGBA 8-bit data into vector
285    pub fn read_pixels_into_array(&self, array: &mut [u8]) -> Result<(), GlError> {
286        let size = self.width() * self.height() * 4;
287        if array.len() as u32 != size {
288            Err(GlError::InvalidBufferSize {
289                expected: size,
290                received: array.len() as u32,
291            })
292        } else {
293            let gl = self.gl();
294
295            gl.apply(
296                Gl::settings().frame_buffer(gl.frame_buffer_with_color(self.clone())?),
297                || {
298                    gl.context()
299                        .read_pixels_with_opt_u8_array(
300                            0,
301                            0,
302                            self.width() as i32,
303                            self.height() as i32,
304                            TextureFormat::Rgba.into(),
305                            TextureType::Byte.into(),
306                            Some(array),
307                        )
308                        .map_err(|error_object| {
309                            let error: Error = error_object.into();
310                            GlError::ReadPixelsError(Some(error.message().into()))
311                        })
312                },
313            )?;
314            Ok(())
315        }
316    }
317
318    /// Read RGBA 8-bit data into UInt8Array
319    pub fn read_pixels_into_buffer(&self, buffer: &Uint8Array) -> Result<(), GlError> {
320        if self.data_type() != TextureType::Byte {
321            Err(GlError::ReadPixelsError(Some(format!(
322                "Invalid texture data type {:?}",
323                self.data_type()
324            ))))
325        } else if buffer.length() != self.width() * self.height() * self.format().channels() {
326            Err(GlError::InvalidBufferSize {
327                expected: self.width() * self.height() * self.format().channels(),
328                received: buffer.length(),
329            })
330        } else {
331            let gl = self.gl();
332
333            gl.apply(
334                Gl::settings().frame_buffer(gl.frame_buffer_with_color(self.clone())?),
335                || {
336                    gl.context()
337                        .read_pixels_with_opt_array_buffer_view(
338                            0,
339                            0,
340                            self.width() as i32,
341                            self.height() as i32,
342                            TextureFormat::Rgba.into(),
343                            TextureType::Byte.into(),
344                            Some(buffer),
345                        )
346                        .map_err(|error_object| {
347                            let error: Error = error_object.into();
348                            GlError::ReadPixelsError(Some(error.message().into()))
349                        })
350                },
351            )?;
352            Ok(())
353        }
354    }
355
356    pub fn read_pixels_array(&self) -> Result<Vec<u8>, GlError> {
357        let mut result = Vec::with_capacity((self.width() * self.height() * 4) as usize);
358        self.read_pixels_into_array(&mut result)?;
359        return Ok(result);
360    }
361
362    pub fn read_pixels_buffer(&self) -> Result<Uint8Array, GlError> {
363        let result = Uint8Array::new_with_length(self.width() * self.height() * 4);
364        self.read_pixels_into_buffer(&result)?;
365        return Ok(result);
366    }
367
368    pub fn clear(&self, r: f32, g: f32, b: f32, a: f32) -> Result<(), GlError> {
369        let gl = self.gl();
370
371        gl.apply(
372            Gl::settings()
373                .clear_color(r, g, b, a)
374                .viewport(0, 0, self.width() as i32, self.height() as i32)
375                .frame_buffer(gl.frame_buffer_with_color(self.clone())?),
376            || gl.clear_color_buffer(),
377        );
378
379        Ok(())
380    }
381}