maia_wasm/render/engine/
texture.rs

1use super::RenderEngine;
2use crate::array_view::ArrayView;
3use std::rc::Rc;
4use wasm_bindgen::prelude::*;
5use web_sys::{WebGl2RenderingContext, WebGlTexture};
6
7/// WebGL2 texture.
8///
9/// An opaque object describing a WebGL2 texture. It pairs a WebGL2 texture
10/// object (`WebGlTexture`) together with the identifier of its corresponding
11/// sampler.
12#[derive(Debug)]
13pub struct Texture {
14    texture: Rc<WebGlTexture>,
15    sampler: String,
16}
17
18impl Texture {
19    /// Creates a new texture.
20    pub fn new(sampler: String, texture: Rc<WebGlTexture>) -> Texture {
21        Texture { sampler, texture }
22    }
23
24    /// Returns the texture object.
25    pub fn texture(&self) -> &Rc<WebGlTexture> {
26        &self.texture
27    }
28
29    /// Returns the sampler identifier.
30    pub fn sampler(&self) -> &str {
31        &self.sampler
32    }
33}
34
35/// WebGL2 texture builder.
36///
37/// This builder object is used to create a new WebGL2 texture with a given set
38/// of parameters.
39pub struct TextureBuilder<'a> {
40    engine: &'a mut RenderEngine,
41    texture: Rc<WebGlTexture>,
42}
43
44impl TextureBuilder<'_> {
45    pub(super) fn new(engine: &mut RenderEngine) -> Result<TextureBuilder<'_>, JsValue> {
46        let texture = Rc::new(
47            engine
48                .gl
49                .create_texture()
50                .ok_or("failed to create texture")?,
51        );
52        engine.bind_texture(&texture);
53        Ok(TextureBuilder { engine, texture })
54    }
55
56    /// Applies a parameter to the texture.
57    pub fn set_parameter(self, parameter: TextureParameter) -> Self {
58        self.engine.gl.tex_parameteri(
59            WebGl2RenderingContext::TEXTURE_2D,
60            parameter.parameter_code(),
61            parameter.value_code(),
62        );
63        self
64    }
65
66    /// Builds and returns the new texture.
67    pub fn build(self) -> Rc<WebGlTexture> {
68        self.texture
69    }
70}
71
72/// Texture parameter.
73///
74/// This enum describes all the parameters that can be applied to a WebGL2 texture.
75#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
76pub enum TextureParameter {
77    /// Magnification filter.
78    MagFilter(TextureMagFilter),
79    /// Minification filter.
80    MinFilter(TextureMinFilter),
81    /// Wrap-S.
82    WrapS(TextureWrap),
83    /// Wrap-T.
84    WrapT(TextureWrap),
85    /// Wrap-R.
86    WrapR(TextureWrap),
87}
88
89impl TextureParameter {
90    fn parameter_code(&self) -> u32 {
91        match self {
92            TextureParameter::MagFilter(_) => WebGl2RenderingContext::TEXTURE_MAG_FILTER,
93            TextureParameter::MinFilter(_) => WebGl2RenderingContext::TEXTURE_MIN_FILTER,
94            TextureParameter::WrapS(_) => WebGl2RenderingContext::TEXTURE_WRAP_S,
95            TextureParameter::WrapT(_) => WebGl2RenderingContext::TEXTURE_WRAP_T,
96            TextureParameter::WrapR(_) => WebGl2RenderingContext::TEXTURE_WRAP_R,
97        }
98    }
99
100    fn value_code(&self) -> i32 {
101        match *self {
102            TextureParameter::MagFilter(a) => a as i32,
103            TextureParameter::MinFilter(a) => a as i32,
104            TextureParameter::WrapS(a) => a as i32,
105            TextureParameter::WrapT(a) => a as i32,
106            TextureParameter::WrapR(a) => a as i32,
107        }
108    }
109}
110
111/// Magnification filter.
112///
113/// This enum lists the magnification filters supported by WebGL2.
114#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
115#[repr(u32)]
116pub enum TextureMagFilter {
117    /// Linear filtering.
118    #[default]
119    Linear = WebGl2RenderingContext::LINEAR,
120    /// Nearest-neighbor filtering.
121    Nearest = WebGl2RenderingContext::NEAREST,
122}
123
124/// Minification filter.
125///
126/// This enum lists the minification filters supported by WebGL2.
127#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
128#[repr(u32)]
129pub enum TextureMinFilter {
130    /// Linear filtering.
131    Linear = WebGl2RenderingContext::LINEAR,
132    /// Nearest-neighbor filtering.
133    Nearest = WebGl2RenderingContext::NEAREST,
134    /// Nearest-neighbor filtering using the nearest mipmap.
135    NearestMipmapNearest = WebGl2RenderingContext::NEAREST_MIPMAP_NEAREST,
136    /// Linear filtering using the nearest mipmap.
137    LinearMipmapNearest = WebGl2RenderingContext::LINEAR_MIPMAP_NEAREST,
138    /// Nearest-neighbor filtering using linear interpolation between mipmaps.
139    #[default]
140    NearestMipmapLinear = WebGl2RenderingContext::NEAREST_MIPMAP_LINEAR,
141    /// Linear filtering using linear interpolation between mipmaps.
142    LinearMipmapLinear = WebGl2RenderingContext::LINEAR_MIPMAP_LINEAR,
143}
144
145/// Texture wrapping.
146///
147/// This enum lists the texture wrapping settings supported by WebGL2.
148#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
149#[repr(u32)]
150pub enum TextureWrap {
151    /// Repeat.
152    #[default]
153    Repeat = WebGl2RenderingContext::REPEAT,
154    /// Clamp to edge.
155    ClampToEdge = WebGl2RenderingContext::CLAMP_TO_EDGE,
156    /// Mirrored repeat.
157    MirroredRepeat = WebGl2RenderingContext::MIRRORED_REPEAT,
158}
159
160/// WebGL2 texture internal format.
161///
162/// This trait gives idiomatic Rust usage of WebGL2 texture formats. The trait
163/// gives the WebGL2 constants that list the corresponding internal format and
164/// format, and a Rust type that can be converted to a JS type, using the
165/// [`ArrayView`] trait.
166pub trait TextureInternalFormat {
167    /// WebGL2 constant that indicates the internal format.
168    const INTERNAL_FORMAT: i32;
169    /// WebGL2 constant that indicates the format.
170    const FORMAT: u32;
171    /// Native Rust type corresponding to this format.
172    type T: ArrayView;
173}
174
175macro_rules! new_format {
176    ($doc:expr, $type:ident, $int:expr, $fmt:expr, $t:ty) => {
177        #[doc = $doc]
178        #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
179        pub struct $type {}
180
181        impl TextureInternalFormat for $type {
182            const INTERNAL_FORMAT: i32 = ($int) as i32;
183            const FORMAT: u32 = $fmt;
184            type T = $t;
185        }
186    };
187}
188
189new_format!(
190    r#"RGB texture internal format.
191
192This uses `u8` as the native Rust type and the `RGB` WebGL2 format as
193internal format and format."#,
194    Rgb,
195    WebGl2RenderingContext::RGB,
196    WebGl2RenderingContext::RGB,
197    u8
198);
199new_format!(
200    r#"RGBA texture internal format.
201
202This uses `u8` as the native Rust type and the `RGBA` WebGL2 format as
203internal format and format."#,
204    Rgba,
205    WebGl2RenderingContext::RGBA,
206    WebGl2RenderingContext::RGBA,
207    u8
208);
209new_format!(
210    r#"Luminance alpha texture internal format.
211
212This uses `u8` as the native Rust type and the `LUMINANCE_ALPHA` WebGl2
213format as internal format and format."#,
214    LuminanceAlpha,
215    WebGl2RenderingContext::LUMINANCE_ALPHA,
216    WebGl2RenderingContext::LUMINANCE_ALPHA,
217    u8
218);
219new_format!(
220    r#"R16F texture internal format.
221
222This uses `f32` as the native Rust type, the `R16F` WebGL2 format as
223internal format, and the `RED` WebGL2 format as format."#,
224    R16f,
225    WebGl2RenderingContext::R16F,
226    WebGl2RenderingContext::RED,
227    f32
228);
229
230impl RenderEngine {
231    /// Loads a texture with an image.
232    ///
233    /// The `image` is described by a slice of elements whose type is the native
234    /// Rust type corresponding to the WebGL2 texture internal format `F`. Such
235    /// internal format, and its corresponding format are used to load the image
236    /// into the texture.
237    ///
238    /// The `width` and `height` parameters indicate the dimensions of the image
239    /// in pixels.
240    pub fn texture_image<F: TextureInternalFormat>(
241        &mut self,
242        texture: &Rc<WebGlTexture>,
243        image: &[F::T],
244        width: usize,
245        height: usize,
246    ) -> Result<(), JsValue> {
247        self.bind_texture(texture);
248        unsafe {
249            let view = F::T::view(image);
250            let level = 0;
251            let border = 0;
252            self.gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_array_buffer_view(
253		WebGl2RenderingContext::TEXTURE_2D,
254		level,
255		F::INTERNAL_FORMAT,
256		width as i32,
257		height as i32,
258		border,
259		F::FORMAT,
260		F::T::GL_TYPE,
261		Some(&view)
262		    )?;
263        };
264        Ok(())
265    }
266
267    /// Loads a portion of a texture with an image.
268    ///
269    /// The `image` is described in the same way as in
270    /// [`texture_image`](RenderEngine::texture_image), together with its
271    /// corresponding `width` and `height` dimensions.
272    ///
273    /// The `xoffset` and `yoffset` give the offsets in pixels that indicate in
274    /// which portion of the texture the image data should be loaded.
275    pub fn texture_subimage<F: TextureInternalFormat>(
276        &mut self,
277        texture: &Rc<WebGlTexture>,
278        image: &[F::T],
279        xoffset: usize,
280        yoffset: usize,
281        width: usize,
282        height: usize,
283    ) -> Result<(), JsValue> {
284        self.bind_texture(texture);
285        unsafe {
286            let view = F::T::view(image);
287            let level = 0;
288            self.gl
289                .tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_array_buffer_view(
290                    WebGl2RenderingContext::TEXTURE_2D,
291                    level,
292                    xoffset as i32,
293                    yoffset as i32,
294                    width as i32,
295                    height as i32,
296                    F::FORMAT,
297                    F::T::GL_TYPE,
298                    Some(&view),
299                )?;
300        };
301        Ok(())
302    }
303
304    pub(super) fn texture_from_text_render<F: TextureInternalFormat>(
305        &mut self,
306        texture: &Rc<WebGlTexture>,
307    ) -> Result<(), JsValue> {
308        self.bind_texture(texture);
309        let level = 0;
310
311        // See https://registry.khronos.org/webgl/specs/1.0/index.html#PIXEL_STORAGE_PARAMETERS
312        //
313        // UNPACK_PREMULTIPLY_ALPHA_WEBGL of type boolean
314        //
315        // If set, then during any subsequent calls to texImage2D or
316        // texSubImage2D, the alpha channel of the source data, if present, is
317        // multiplied into the color channels during the data transfer. The
318        // initial value is false. Any non-zero value is interpreted as true.
319        //
320        // We do this here and un-do it afterwards because according to Firefox
321        // "Alpha-premult and y-flip are deprecated for non-DOM Element uploads"
322        // (which affects the textures loaded from a JS Array.
323        self.gl
324            .pixel_storei(WebGl2RenderingContext::UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
325
326        self.gl
327            .tex_image_2d_with_u32_and_u32_and_html_canvas_element(
328                WebGl2RenderingContext::TEXTURE_2D,
329                level,
330                F::INTERNAL_FORMAT,
331                F::FORMAT,
332                F::T::GL_TYPE,
333                self.text_render.canvas(),
334            )?;
335
336        self.gl
337            .pixel_storei(WebGl2RenderingContext::UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
338
339        Ok(())
340    }
341
342    /// Generate mipmap.
343    ///
344    /// This function generates the mipmap from a given texture.
345    pub fn generate_mipmap(&mut self, texture: &Rc<WebGlTexture>) {
346        self.bind_texture(texture);
347        self.gl.generate_mipmap(WebGl2RenderingContext::TEXTURE_2D);
348    }
349}