Skip to main content

luminance_webgl/webgl2/
texture.rs

1use crate::webgl2::{
2  array_buffer::IntoArrayBuffer,
3  pixel::webgl_pixel_format,
4  state::{comparison_to_glenum, WebGL2State},
5  WebGL2,
6};
7use luminance::{
8  backend::texture::{Texture as TextureBackend, TextureBase},
9  pixel::{Pixel, PixelFormat},
10  texture::{Dim, Dimensionable, MagFilter, MinFilter, Sampler, TexelUpload, TextureError, Wrap},
11};
12use std::{cell::RefCell, mem, rc::Rc, slice};
13use web_sys::{WebGl2RenderingContext, WebGlTexture};
14
15pub struct Texture {
16  pub(crate) handle: WebGlTexture,
17  pub(crate) target: u32, // “type” of the texture; used for bindings
18  mipmaps: usize,
19  state: Rc<RefCell<WebGL2State>>,
20}
21
22impl Texture {
23  pub(crate) fn handle(&self) -> &WebGlTexture {
24    &self.handle
25  }
26}
27
28impl Drop for Texture {
29  fn drop(&mut self) {
30    self
31      .state
32      .borrow_mut()
33      .ctx
34      .delete_texture(Some(&self.handle));
35  }
36}
37
38unsafe impl TextureBase for WebGL2 {
39  type TextureRepr = Texture;
40}
41
42unsafe impl<D, P> TextureBackend<D, P> for WebGL2
43where
44  D: Dimensionable,
45  P: Pixel,
46  P::Encoding: IntoArrayBuffer,
47  P::RawEncoding: IntoArrayBuffer,
48{
49  unsafe fn new_texture(
50    &mut self,
51    size: D::Size,
52    sampler: Sampler,
53    texels: TexelUpload<[P::Encoding]>,
54  ) -> Result<Self::TextureRepr, TextureError> {
55    generic_new_texture::<D, P, P::Encoding>(self, size, sampler, texels)
56  }
57
58  unsafe fn new_texture_raw(
59    &mut self,
60    size: D::Size,
61    sampler: Sampler,
62    texels: TexelUpload<[P::RawEncoding]>,
63  ) -> Result<Self::TextureRepr, TextureError> {
64    generic_new_texture::<D, P, P::RawEncoding>(self, size, sampler, texels)
65  }
66
67  unsafe fn mipmaps(texture: &Self::TextureRepr) -> usize {
68    texture.mipmaps
69  }
70
71  unsafe fn upload_part(
72    texture: &mut Self::TextureRepr,
73    offset: D::Offset,
74    size: D::Size,
75    texels: TexelUpload<[P::Encoding]>,
76  ) -> Result<(), TextureError> {
77    let mut gfx_state = texture.state.borrow_mut();
78
79    gfx_state.bind_texture(texture.target, Some(&texture.handle));
80
81    upload_texels::<D, P, P::Encoding>(&mut gfx_state, texture.target, offset, size, texels)?;
82
83    Ok(())
84  }
85
86  unsafe fn upload(
87    texture: &mut Self::TextureRepr,
88    size: D::Size,
89    texels: TexelUpload<[P::Encoding]>,
90  ) -> Result<(), TextureError> {
91    <Self as TextureBackend<D, P>>::upload_part(texture, D::ZERO_OFFSET, size, texels)
92  }
93
94  unsafe fn upload_part_raw(
95    texture: &mut Self::TextureRepr,
96    offset: D::Offset,
97    size: D::Size,
98    texels: TexelUpload<[P::RawEncoding]>,
99  ) -> Result<(), TextureError> {
100    let mut gfx_state = texture.state.borrow_mut();
101
102    gfx_state.bind_texture(texture.target, Some(&texture.handle));
103
104    upload_texels::<D, P, P::RawEncoding>(&mut gfx_state, texture.target, offset, size, texels)?;
105
106    Ok(())
107  }
108
109  unsafe fn upload_raw(
110    texture: &mut Self::TextureRepr,
111    size: D::Size,
112    texels: TexelUpload<[P::RawEncoding]>,
113  ) -> Result<(), TextureError> {
114    <Self as TextureBackend<D, P>>::upload_part_raw(texture, D::ZERO_OFFSET, size, texels)
115  }
116
117  unsafe fn get_raw_texels(
118    texture: &Self::TextureRepr,
119    size: D::Size,
120  ) -> Result<Vec<P::RawEncoding>, TextureError>
121  where
122    P::RawEncoding: Copy + Default,
123  {
124    let pf = P::pixel_format();
125    let (format, _, ty) = webgl_pixel_format(pf).ok_or(TextureError::UnsupportedPixelFormat(pf))?;
126
127    let mut gfx_state = texture.state.borrow_mut();
128    gfx_state.bind_texture(texture.target, Some(&texture.handle));
129
130    // Retrieve the size of the texture (w and h); WebGL2 doesn’t support the
131    // glGetTexLevelParameteriv function (I know it’s fucking surprising), so we have to implement
132    // a workaround and store that value on the CPU side.
133    let w = D::width(size);
134    let h = D::height(size);
135
136    // set the packing alignment based on the number of bytes to skip
137    let skip_bytes = (pf.format.bytes_len() * w as usize) % 8;
138    set_pack_alignment(&mut gfx_state, skip_bytes);
139
140    // We need a workaround to get the texel data, because WebGL2 doesn’t support the glGetTexImage
141    // function. The idea is that we are using a special read framebuffer that is always around and
142    // on which we can attach the texture we want to read the texels from.
143    match gfx_state.create_or_get_readback_framebuffer() {
144      Some(ref readback_fb) => {
145        // Resize the vec to allocate enough space to host the returned texels.
146        let texels_nb = (w * h) as usize * pf.channels_len();
147        let mut texels = vec![Default::default(); texels_nb];
148
149        // Attach the texture so that we can read from the framebuffer; careful here, since we are
150        // reading from a 2D texture while the attached texture might not be compatible.
151        gfx_state.bind_read_framebuffer(Some(readback_fb));
152        gfx_state.ctx.framebuffer_texture_2d(
153          WebGl2RenderingContext::READ_FRAMEBUFFER,
154          WebGl2RenderingContext::COLOR_ATTACHMENT0,
155          texture.target,
156          Some(&texture.handle),
157          0,
158        );
159
160        // Read from the framebuffer.
161        gfx_state
162          .ctx
163          .read_pixels_with_u8_array_and_dst_offset(
164            0,
165            0,
166            w as i32,
167            h as i32,
168            format,
169            ty,
170            slice::from_raw_parts_mut(
171              texels.as_mut_ptr() as *mut u8,
172              texels_nb * mem::size_of::<P::RawEncoding>(),
173            ),
174            0,
175          )
176          .map_err(|e| TextureError::CannotRetrieveTexels(format!("{:?}", e)))?;
177
178        // Detach the texture from the framebuffer.
179        gfx_state.ctx.framebuffer_texture_2d(
180          WebGl2RenderingContext::READ_FRAMEBUFFER,
181          WebGl2RenderingContext::COLOR_ATTACHMENT0,
182          texture.target,
183          None,
184          0,
185        );
186
187        Ok(texels)
188      }
189
190      None => Err(TextureError::cannot_retrieve_texels(
191        "unavailable readback framebuffer",
192      )),
193    }
194  }
195
196  unsafe fn resize(
197    texture: &mut Self::TextureRepr,
198    size: D::Size,
199    texels: TexelUpload<[P::Encoding]>,
200  ) -> Result<(), TextureError> {
201    let mipmaps = texels.mipmaps();
202    let mut state = texture.state.borrow_mut();
203
204    state.bind_texture(texture.target, Some(&texture.handle));
205    create_texture_storage::<D>(&mut state, size, mipmaps, P::pixel_format())?;
206    upload_texels::<D, P, P::Encoding>(&mut state, texture.target, D::ZERO_OFFSET, size, texels)
207  }
208
209  unsafe fn resize_raw(
210    texture: &mut Self::TextureRepr,
211    size: D::Size,
212    texels: TexelUpload<[P::RawEncoding]>,
213  ) -> Result<(), TextureError> {
214    let mipmaps = texels.mipmaps();
215    let mut state = texture.state.borrow_mut();
216
217    state.bind_texture(texture.target, Some(&texture.handle));
218    create_texture_storage::<D>(&mut state, size, mipmaps, P::pixel_format())?;
219    upload_texels::<D, P, P::RawEncoding>(&mut state, texture.target, D::ZERO_OFFSET, size, texels)
220  }
221}
222
223pub(crate) fn opengl_target(d: Dim) -> Option<u32> {
224  match d {
225    Dim::Dim2 => Some(WebGl2RenderingContext::TEXTURE_2D),
226    Dim::Dim3 => Some(WebGl2RenderingContext::TEXTURE_3D),
227    Dim::Cubemap => Some(WebGl2RenderingContext::TEXTURE_CUBE_MAP),
228    Dim::Dim2Array => Some(WebGl2RenderingContext::TEXTURE_2D_ARRAY),
229    _ => None,
230  }
231}
232
233unsafe fn generic_new_texture<D, P, Px>(
234  webgl2: &mut WebGL2,
235  size: D::Size,
236  sampler: Sampler,
237  texels: TexelUpload<[Px]>,
238) -> Result<Texture, TextureError>
239where
240  D: Dimensionable,
241  P: Pixel,
242  Px: IntoArrayBuffer,
243{
244  let dim = D::dim();
245  let target = opengl_target(dim).ok_or_else(|| {
246    TextureError::TextureStorageCreationFailed(format!("incompatible texture dim: {}", dim))
247  })?;
248
249  let mut state = webgl2.state.borrow_mut();
250
251  let handle = state.create_texture().ok_or_else(|| {
252    TextureError::TextureStorageCreationFailed("cannot create texture".to_owned())
253  })?;
254  state.bind_texture(target, Some(&handle));
255
256  let mipmaps = texels.mipmaps();
257
258  setup_texture::<D>(
259    &mut state,
260    target,
261    size,
262    mipmaps,
263    P::pixel_format(),
264    sampler,
265  )?;
266
267  upload_texels::<D, P, Px>(&mut state, target, D::ZERO_OFFSET, size, texels)?;
268
269  let texture = Texture {
270    handle,
271    target,
272    mipmaps,
273    state: webgl2.state.clone(),
274  };
275
276  Ok(texture)
277}
278
279/// Set all the required internal state required for the texture to be valid.
280pub(crate) unsafe fn setup_texture<D>(
281  state: &mut WebGL2State,
282  target: u32,
283  size: D::Size,
284  mipmaps: usize,
285  pf: PixelFormat,
286  sampler: Sampler,
287) -> Result<(), TextureError>
288where
289  D: Dimensionable,
290{
291  set_texture_levels(state, target, mipmaps);
292  apply_sampler_to_texture(state, target, sampler);
293  create_texture_storage::<D>(state, size, 1 + mipmaps, pf)
294}
295
296fn set_texture_levels(state: &mut WebGL2State, target: u32, mipmaps: usize) {
297  state
298    .ctx
299    .tex_parameteri(target, WebGl2RenderingContext::TEXTURE_BASE_LEVEL, 0);
300
301  state.ctx.tex_parameteri(
302    target,
303    WebGl2RenderingContext::TEXTURE_MAX_LEVEL,
304    mipmaps as i32,
305  );
306}
307
308fn apply_sampler_to_texture(state: &mut WebGL2State, target: u32, sampler: Sampler) {
309  state.ctx.tex_parameteri(
310    target,
311    WebGl2RenderingContext::TEXTURE_WRAP_R,
312    webgl_wrap(sampler.wrap_r) as i32,
313  );
314  state.ctx.tex_parameteri(
315    target,
316    WebGl2RenderingContext::TEXTURE_WRAP_S,
317    webgl_wrap(sampler.wrap_s) as i32,
318  );
319  state.ctx.tex_parameteri(
320    target,
321    WebGl2RenderingContext::TEXTURE_WRAP_T,
322    webgl_wrap(sampler.wrap_t) as i32,
323  );
324  state.ctx.tex_parameteri(
325    target,
326    WebGl2RenderingContext::TEXTURE_MIN_FILTER,
327    webgl_min_filter(sampler.min_filter) as i32,
328  );
329  state.ctx.tex_parameteri(
330    target,
331    WebGl2RenderingContext::TEXTURE_MAG_FILTER,
332    webgl_mag_filter(sampler.mag_filter) as i32,
333  );
334
335  match sampler.depth_comparison {
336    Some(fun) => {
337      state.ctx.tex_parameteri(
338        target,
339        WebGl2RenderingContext::TEXTURE_COMPARE_FUNC,
340        comparison_to_glenum(fun) as i32,
341      );
342      state.ctx.tex_parameteri(
343        target,
344        WebGl2RenderingContext::TEXTURE_COMPARE_MODE,
345        WebGl2RenderingContext::COMPARE_REF_TO_TEXTURE as i32,
346      );
347    }
348
349    None => {
350      state.ctx.tex_parameteri(
351        target,
352        WebGl2RenderingContext::TEXTURE_COMPARE_MODE,
353        WebGl2RenderingContext::NONE as i32,
354      );
355    }
356  }
357}
358
359fn webgl_wrap(wrap: Wrap) -> u32 {
360  match wrap {
361    Wrap::ClampToEdge => WebGl2RenderingContext::CLAMP_TO_EDGE,
362    Wrap::Repeat => WebGl2RenderingContext::REPEAT,
363    Wrap::MirroredRepeat => WebGl2RenderingContext::MIRRORED_REPEAT,
364  }
365}
366
367fn webgl_min_filter(filter: MinFilter) -> u32 {
368  match filter {
369    MinFilter::Nearest => WebGl2RenderingContext::NEAREST,
370    MinFilter::Linear => WebGl2RenderingContext::LINEAR,
371    MinFilter::NearestMipmapNearest => WebGl2RenderingContext::NEAREST_MIPMAP_NEAREST,
372    MinFilter::NearestMipmapLinear => WebGl2RenderingContext::NEAREST_MIPMAP_LINEAR,
373    MinFilter::LinearMipmapNearest => WebGl2RenderingContext::LINEAR_MIPMAP_NEAREST,
374    MinFilter::LinearMipmapLinear => WebGl2RenderingContext::LINEAR_MIPMAP_LINEAR,
375  }
376}
377
378fn webgl_mag_filter(filter: MagFilter) -> u32 {
379  match filter {
380    MagFilter::Nearest => WebGl2RenderingContext::NEAREST,
381    MagFilter::Linear => WebGl2RenderingContext::LINEAR,
382  }
383}
384
385fn create_texture_storage<D>(
386  state: &mut WebGL2State,
387  size: D::Size,
388  levels: usize,
389  pf: PixelFormat,
390) -> Result<(), TextureError>
391where
392  D: Dimensionable,
393{
394  match webgl_pixel_format(pf) {
395    Some(glf) => {
396      let (_, iformat, _) = glf;
397
398      match D::dim() {
399        // 2D texture
400        Dim::Dim2 => {
401          create_texture_2d_storage(
402            state,
403            WebGl2RenderingContext::TEXTURE_2D,
404            iformat,
405            D::width(size),
406            D::height(size),
407            levels,
408          )?;
409          Ok(())
410        }
411
412        // 3D texture
413        Dim::Dim3 => {
414          create_texture_3d_storage(
415            state,
416            WebGl2RenderingContext::TEXTURE_3D,
417            iformat,
418            D::width(size),
419            D::height(size),
420            D::depth(size),
421            levels,
422          )?;
423          Ok(())
424        }
425
426        // cubemap
427        Dim::Cubemap => {
428          create_cubemap_storage(state, iformat, D::width(size), levels)?;
429          Ok(())
430        }
431
432        // 2D array texture
433        Dim::Dim2Array => {
434          create_texture_3d_storage(
435            state,
436            WebGl2RenderingContext::TEXTURE_2D_ARRAY,
437            iformat,
438            D::width(size),
439            D::height(size),
440            D::depth(size),
441            levels,
442          )?;
443          Ok(())
444        }
445
446        _ => Err(TextureError::unsupported_pixel_format(pf)),
447      }
448    }
449
450    None => Err(TextureError::unsupported_pixel_format(pf)),
451  }
452}
453
454fn create_texture_2d_storage(
455  state: &mut WebGL2State,
456  target: u32,
457  iformat: u32,
458  w: u32,
459  h: u32,
460  levels: usize,
461) -> Result<(), TextureError> {
462  state
463    .ctx
464    .tex_storage_2d(target, levels as i32, iformat, w as i32, h as i32);
465
466  Ok(())
467}
468
469fn create_texture_3d_storage(
470  state: &mut WebGL2State,
471  target: u32,
472  iformat: u32,
473  w: u32,
474  h: u32,
475  d: u32,
476  levels: usize,
477) -> Result<(), TextureError> {
478  state
479    .ctx
480    .tex_storage_3d(target, levels as i32, iformat, w as i32, h as i32, d as i32);
481
482  Ok(())
483}
484
485fn create_cubemap_storage(
486  state: &mut WebGL2State,
487  iformat: u32,
488  s: u32,
489  mipmaps: usize,
490) -> Result<(), TextureError> {
491  state.ctx.tex_storage_2d(
492    WebGl2RenderingContext::TEXTURE_CUBE_MAP,
493    mipmaps as i32,
494    iformat,
495    s as i32,
496    s as i32,
497  );
498
499  Ok(())
500}
501
502// set the unpack alignment for uploading aligned texels
503fn set_unpack_alignment(state: &mut WebGL2State, skip_bytes: usize) {
504  let unpack_alignment = match skip_bytes {
505    0 => 8,
506    2 => 2,
507    4 => 4,
508    _ => 1,
509  } as i32;
510
511  state
512    .ctx
513    .pixel_storei(WebGl2RenderingContext::UNPACK_ALIGNMENT, unpack_alignment);
514}
515
516// set the pack alignment for downloading aligned texels
517fn set_pack_alignment(state: &mut WebGL2State, skip_bytes: usize) {
518  let pack_alignment = match skip_bytes {
519    0 => 8,
520    2 => 2,
521    4 => 4,
522    _ => 1,
523  } as i32;
524
525  state
526    .ctx
527    .pixel_storei(WebGl2RenderingContext::PACK_ALIGNMENT, pack_alignment);
528}
529// Upload texels into the texture’s memory. Becareful of the type of texels you send down.
530fn upload_texels<D, P, T>(
531  state: &mut WebGL2State,
532  target: u32,
533  off: D::Offset,
534  size: D::Size,
535  texels: TexelUpload<[T]>,
536) -> Result<(), TextureError>
537where
538  D: Dimensionable,
539  P: Pixel,
540  T: IntoArrayBuffer,
541{
542  // number of bytes in the input texels argument
543  let pf = P::pixel_format();
544  let pf_size = pf.format.bytes_len();
545  let expected_bytes = D::count(size) * pf_size;
546
547  if let Some(base_level_texels) = texels.get_base_level() {
548    // number of bytes in the input texels argument
549    let input_bytes = base_level_texels.len() * mem::size_of::<T>();
550
551    if input_bytes < expected_bytes {
552      // potential segfault / overflow; abort
553      return Err(TextureError::not_enough_pixels(expected_bytes, input_bytes));
554    }
555  }
556
557  // set the pixel row alignment to the required value for uploading data according to the width
558  // of the texture and the size of a single pixel; here, skip_bytes represents the number of bytes
559  // that will be skipped
560  let skip_bytes = (D::width(size) as usize * pf_size) % 8;
561  set_unpack_alignment(state, skip_bytes);
562
563  match texels {
564    TexelUpload::BaseLevel { texels, mipmaps } => {
565      set_texels::<D, _>(state, target, pf, 0, size, off, texels)?;
566
567      if mipmaps > 0 {
568        state.ctx.generate_mipmap(target);
569      }
570    }
571
572    TexelUpload::Levels(levels) => {
573      for (i, &texels) in levels.into_iter().enumerate() {
574        set_texels::<D, _>(state, target, pf, i as _, size, off, texels)?;
575      }
576    }
577
578    TexelUpload::Reserve { mipmaps } => {
579      if mipmaps > 0 {
580        state.ctx.generate_mipmap(target);
581      }
582    }
583  }
584  Ok(())
585}
586
587// Set texels for a texture.
588fn set_texels<D, T>(
589  state: &mut WebGL2State,
590  target: u32,
591  pf: PixelFormat,
592  level: i32,
593  size: D::Size,
594  off: D::Offset,
595  texels: &[T],
596) -> Result<(), TextureError>
597where
598  D: Dimensionable,
599  T: IntoArrayBuffer,
600{
601  // coerce the texels slice into a web-sys array buffer so that we can pass them to the super ugly
602  // method below
603  let array_buffer;
604  unsafe {
605    array_buffer = T::into_array_buffer(texels);
606  }
607
608  match webgl_pixel_format(pf) {
609    Some((format, _, encoding)) => match D::dim() {
610      Dim::Dim2 => {
611        state
612          .ctx
613          .tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_array_buffer_view_and_src_offset(
614            target,
615            level,
616            D::x_offset(off) as i32,
617            D::y_offset(off) as i32,
618            D::width(size) as i32,
619            D::height(size) as i32,
620            format,
621            encoding,
622            &array_buffer,
623            0,
624          )
625          .map_err(|e| TextureError::CannotUploadTexels(format!("{:?}", e)))?;
626      }
627
628      Dim::Dim3 => {
629        state
630          .ctx
631          .tex_sub_image_3d_with_opt_array_buffer_view(
632            target,
633            level,
634            D::x_offset(off) as i32,
635            D::y_offset(off) as i32,
636            D::z_offset(off) as i32,
637            D::width(size) as i32,
638            D::height(size) as i32,
639            D::depth(size) as i32,
640            format,
641            encoding,
642            Some(&array_buffer),
643          )
644          .map_err(|e| TextureError::CannotUploadTexels(format!("{:?}", e)))?;
645      }
646
647      Dim::Cubemap => {
648        state
649          .ctx
650          .tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_array_buffer_view_and_src_offset(
651            WebGl2RenderingContext::TEXTURE_CUBE_MAP_POSITIVE_X + D::z_offset(off),
652            level,
653            D::x_offset(off) as i32,
654            D::y_offset(off) as i32,
655            D::width(size) as i32,
656            D::height(size) as i32,
657            format,
658            encoding,
659            &array_buffer,
660            0,
661          )
662          .map_err(|e| TextureError::CannotUploadTexels(format!("{:?}", e)))?;
663      }
664
665      Dim::Dim2Array => {
666        state
667          .ctx
668          .tex_sub_image_3d_with_opt_array_buffer_view(
669            target,
670            level,
671            D::x_offset(off) as i32,
672            D::y_offset(off) as i32,
673            D::z_offset(off) as i32,
674            D::width(size) as i32,
675            D::height(size) as i32,
676            D::depth(size) as i32,
677            format,
678            encoding,
679            Some(&array_buffer),
680          )
681          .map_err(|e| TextureError::CannotUploadTexels(format!("{:?}", e)))?;
682      }
683
684      _ => return Err(TextureError::unsupported_pixel_format(pf)),
685    },
686
687    None => return Err(TextureError::unsupported_pixel_format(pf)),
688  }
689
690  Ok(())
691}