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, 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 let w = D::width(size);
134 let h = D::height(size);
135
136 let skip_bytes = (pf.format.bytes_len() * w as usize) % 8;
138 set_pack_alignment(&mut gfx_state, skip_bytes);
139
140 match gfx_state.create_or_get_readback_framebuffer() {
144 Some(ref readback_fb) => {
145 let texels_nb = (w * h) as usize * pf.channels_len();
147 let mut texels = vec![Default::default(); texels_nb];
148
149 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 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 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
279pub(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 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 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 Dim::Cubemap => {
428 create_cubemap_storage(state, iformat, D::width(size), levels)?;
429 Ok(())
430 }
431
432 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
502fn 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
516fn 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}
529fn 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 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 let input_bytes = base_level_texels.len() * mem::size_of::<T>();
550
551 if input_bytes < expected_bytes {
552 return Err(TextureError::not_enough_pixels(expected_bytes, input_bytes));
554 }
555 }
556
557 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
587fn 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 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}