Skip to main content

script/dom/webgl/
webgltexture.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
6
7use std::cell::Cell;
8use std::cmp;
9
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use script_bindings::cell::DomRefCell;
13use script_bindings::reflector::{DomObject as _, reflect_dom_object_with_cx};
14use script_bindings::weakref::WeakRef;
15use servo_canvas_traits::webgl::{
16    TexDataType, TexFormat, TexParameter, TexParameterBool, TexParameterInt, WebGLCommand,
17    WebGLError, WebGLResult, WebGLTextureId, WebGLVersion, webgl_channel,
18};
19
20use crate::dom::bindings::codegen::Bindings::EXTTextureFilterAnisotropicBinding::EXTTextureFilterAnisotropicConstants;
21use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
22use crate::dom::bindings::inheritance::Castable;
23use crate::dom::bindings::reflector::DomGlobal;
24use crate::dom::bindings::root::{DomRoot, MutNullableDom};
25use crate::dom::webgl::validations::types::TexImageTarget;
26use crate::dom::webgl::webglframebuffer::WebGLFramebuffer;
27use crate::dom::webgl::webglobject::WebGLObject;
28use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
29use crate::dom::webglrenderingcontext::capture_webgl_backtrace;
30#[cfg(feature = "webxr")]
31use crate::dom::xrsession::XRSession;
32
33pub(crate) enum TexParameterValue {
34    Float(f32),
35    Int(i32),
36    Bool(bool),
37}
38
39// Textures generated for WebXR are owned by the WebXR device, not by the WebGL thread
40// so the GL texture should not be deleted when the texture is garbage collected.
41#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
42#[derive(JSTraceable, MallocSizeOf)]
43enum WebGLTextureOwner {
44    WebGL,
45    #[cfg(feature = "webxr")]
46    WebXR(WeakRef<XRSession>),
47}
48
49const MAX_LEVEL_COUNT: usize = 31;
50const MAX_FACE_COUNT: usize = 6;
51
52#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
53#[derive(JSTraceable, MallocSizeOf)]
54struct DroppableWebGLTexture {
55    context: WeakRef<WebGLRenderingContext>,
56    #[no_trace]
57    id: WebGLTextureId,
58    is_deleted: Cell<bool>,
59    owner: WebGLTextureOwner,
60}
61
62impl DroppableWebGLTexture {
63    fn send_with_fallibility(&self, command: WebGLCommand, fallibility: Operation) {
64        if let Some(root) = self.context.root() {
65            let result = root.sender().send(command, capture_webgl_backtrace());
66            if matches!(fallibility, Operation::Infallible) {
67                result.expect("Operation failed");
68            }
69        }
70    }
71
72    fn delete(&self, operation_fallibility: Operation) {
73        if !self.is_deleted.get() {
74            self.is_deleted.set(true);
75
76            /*
77            If a texture object is deleted while its image is attached to one or more attachment
78            points in a currently bound framebuffer, then it is as if FramebufferTexture had been
79            called, with a texture of zero, for each attachment point to which this im-age was
80            attached in that framebuffer. In other words, this texture image is firstdetached from
81            all attachment points in a currently bound framebuffer.
82            - GLES 3.0, 4.4.2.3, "Attaching Texture Images to a Framebuffer"
83            */
84            if let Some(context) = self.context.root() {
85                if let Some(fb) = context.get_draw_framebuffer_slot().get() {
86                    let _ = fb.detach_texture(self.id);
87                }
88                if let Some(fb) = context.get_read_framebuffer_slot().get() {
89                    let _ = fb.detach_texture(self.id);
90                }
91            }
92
93            // We don't delete textures owned by WebXR
94            #[cfg(feature = "webxr")]
95            if let WebGLTextureOwner::WebXR(_) = self.owner {
96                return;
97            }
98
99            self.send_with_fallibility(WebGLCommand::DeleteTexture(self.id), operation_fallibility);
100        }
101    }
102}
103
104impl Drop for DroppableWebGLTexture {
105    fn drop(&mut self) {
106        self.delete(Operation::Fallible);
107    }
108}
109
110#[dom_struct(associated_memory)]
111pub(crate) struct WebGLTexture {
112    webgl_object: WebGLObject,
113    /// The target to which this texture was bound the first time
114    target: Cell<Option<u32>>,
115    /// Stores information about mipmap levels and cubemap faces.
116    #[ignore_malloc_size_of = "Arrays are cumbersome"]
117    image_info_array: DomRefCell<[Option<ImageInfo>; MAX_LEVEL_COUNT * MAX_FACE_COUNT]>,
118    /// Face count can only be 1 or 6
119    face_count: Cell<u8>,
120    base_mipmap_level: u32,
121    // Store information for min and mag filters
122    min_filter: Cell<u32>,
123    mag_filter: Cell<u32>,
124    /// Framebuffer that this texture is attached to.
125    attached_framebuffer: MutNullableDom<WebGLFramebuffer>,
126    /// Number of immutable levels.
127    immutable_levels: Cell<Option<u32>>,
128    droppable: DroppableWebGLTexture,
129}
130
131impl WebGLTexture {
132    fn new_inherited(
133        context: &WebGLRenderingContext,
134        id: WebGLTextureId,
135        #[cfg(feature = "webxr")] owner: Option<&XRSession>,
136    ) -> Self {
137        Self {
138            webgl_object: WebGLObject::new_inherited(context),
139            target: Cell::new(None),
140            immutable_levels: Cell::new(None),
141            face_count: Cell::new(0),
142            base_mipmap_level: 0,
143            min_filter: Cell::new(constants::NEAREST_MIPMAP_LINEAR),
144            mag_filter: Cell::new(constants::LINEAR),
145            image_info_array: DomRefCell::new([None; MAX_LEVEL_COUNT * MAX_FACE_COUNT]),
146            attached_framebuffer: Default::default(),
147            droppable: DroppableWebGLTexture {
148                context: WeakRef::new(context),
149                id,
150                is_deleted: Cell::new(false),
151                #[cfg(feature = "webxr")]
152                owner: owner
153                    .map(|session| WebGLTextureOwner::WebXR(WeakRef::new(session)))
154                    .unwrap_or(WebGLTextureOwner::WebGL),
155                #[cfg(not(feature = "webxr"))]
156                owner: WebGLTextureOwner::WebGL,
157            },
158        }
159    }
160
161    pub(crate) fn maybe_new(
162        cx: &mut JSContext,
163        context: &WebGLRenderingContext,
164    ) -> Option<DomRoot<Self>> {
165        let (sender, receiver) = webgl_channel().unwrap();
166        context.send_command(WebGLCommand::CreateTexture(sender));
167        receiver
168            .recv()
169            .unwrap()
170            .map(|id| WebGLTexture::new(cx, context, id))
171    }
172
173    pub(crate) fn new(
174        cx: &mut JSContext,
175        context: &WebGLRenderingContext,
176        id: WebGLTextureId,
177    ) -> DomRoot<Self> {
178        reflect_dom_object_with_cx(
179            Box::new(WebGLTexture::new_inherited(
180                context,
181                id,
182                #[cfg(feature = "webxr")]
183                None,
184            )),
185            &*context.global(),
186            cx,
187        )
188    }
189
190    #[cfg(feature = "webxr")]
191    pub(crate) fn new_webxr(
192        cx: &mut JSContext,
193        context: &WebGLRenderingContext,
194        id: WebGLTextureId,
195        session: &XRSession,
196    ) -> DomRoot<Self> {
197        reflect_dom_object_with_cx(
198            Box::new(WebGLTexture::new_inherited(context, id, Some(session))),
199            &*context.global(),
200            cx,
201        )
202    }
203}
204
205impl WebGLTexture {
206    pub(crate) fn id(&self) -> WebGLTextureId {
207        self.droppable.id
208    }
209
210    // NB: Only valid texture targets come here
211    pub(crate) fn bind(&self, target: u32) -> WebGLResult<()> {
212        if self.is_invalid() {
213            return Err(WebGLError::InvalidOperation);
214        }
215
216        if let Some(previous_target) = self.target.get() {
217            if target != previous_target {
218                return Err(WebGLError::InvalidOperation);
219            }
220        } else {
221            // This is the first time binding
222            let face_count = match target {
223                constants::TEXTURE_2D | constants::TEXTURE_2D_ARRAY | constants::TEXTURE_3D => 1,
224                constants::TEXTURE_CUBE_MAP => 6,
225                _ => return Err(WebGLError::InvalidEnum),
226            };
227            self.face_count.set(face_count);
228            self.target.set(Some(target));
229        }
230
231        self.upcast()
232            .send_command(WebGLCommand::BindTexture(target, Some(self.id())));
233
234        Ok(())
235    }
236
237    #[expect(clippy::too_many_arguments)]
238    pub(crate) fn initialize(
239        &self,
240        target: TexImageTarget,
241        width: u32,
242        height: u32,
243        depth: u32,
244        internal_format: TexFormat,
245        level: u32,
246        data_type: Option<TexDataType>,
247    ) -> WebGLResult<()> {
248        let image_info = ImageInfo {
249            width,
250            height,
251            depth,
252            internal_format,
253            data_type,
254        };
255
256        let face_index = self.face_index_for_target(&target);
257        self.set_image_infos_at_level_and_face(level, face_index, image_info);
258
259        if let Some(fb) = self.attached_framebuffer.get() {
260            fb.update_status();
261        }
262
263        self.update_size();
264
265        Ok(())
266    }
267
268    pub(crate) fn generate_mipmap(&self) -> WebGLResult<()> {
269        let target = match self.target.get() {
270            Some(target) => target,
271            None => {
272                error!("Cannot generate mipmap on texture that has no target!");
273                return Err(WebGLError::InvalidOperation);
274            },
275        };
276
277        let base_image_info = self.base_image_info().ok_or(WebGLError::InvalidOperation)?;
278
279        let is_cubic = target == constants::TEXTURE_CUBE_MAP;
280        if is_cubic && !self.is_cube_complete() {
281            return Err(WebGLError::InvalidOperation);
282        }
283
284        if !base_image_info.is_power_of_two() {
285            return Err(WebGLError::InvalidOperation);
286        }
287
288        if base_image_info.is_compressed_format() {
289            return Err(WebGLError::InvalidOperation);
290        }
291
292        self.upcast()
293            .send_command(WebGLCommand::GenerateMipmap(target));
294
295        if self.base_mipmap_level + base_image_info.get_max_mimap_levels() == 0 {
296            return Err(WebGLError::InvalidOperation);
297        }
298
299        let last_level = self.base_mipmap_level + base_image_info.get_max_mimap_levels() - 1;
300        self.populate_mip_chain(self.base_mipmap_level, last_level)
301    }
302
303    pub(crate) fn delete(&self, operation_fallibility: Operation) {
304        self.droppable.delete(operation_fallibility);
305    }
306
307    pub(crate) fn is_invalid(&self) -> bool {
308        // https://immersive-web.github.io/layers/#xrwebglsubimagetype
309        #[cfg(feature = "webxr")]
310        if let WebGLTextureOwner::WebXR(ref session) = self.droppable.owner &&
311            let Some(xr) = session.root() &&
312            xr.is_outside_raf()
313        {
314            return true;
315        }
316        self.droppable.is_deleted.get()
317    }
318
319    pub(crate) fn is_immutable(&self) -> bool {
320        self.immutable_levels.get().is_some()
321    }
322
323    pub(crate) fn target(&self) -> Option<u32> {
324        self.target.get()
325    }
326
327    pub(crate) fn maybe_get_tex_parameter(&self, param: TexParameter) -> Option<TexParameterValue> {
328        match param {
329            TexParameter::Int(TexParameterInt::TextureImmutableLevels) => Some(
330                TexParameterValue::Int(self.immutable_levels.get().unwrap_or(0) as i32),
331            ),
332            TexParameter::Bool(TexParameterBool::TextureImmutableFormat) => {
333                Some(TexParameterValue::Bool(self.is_immutable()))
334            },
335            _ => None,
336        }
337    }
338
339    /// We have to follow the conversion rules for GLES 2.0. See:
340    /// <https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html>
341    pub(crate) fn tex_parameter(&self, param: u32, value: TexParameterValue) -> WebGLResult<()> {
342        let target = self.target().unwrap();
343
344        let (int_value, float_value) = match value {
345            TexParameterValue::Int(int_value) => (int_value, int_value as f32),
346            TexParameterValue::Float(float_value) => (float_value as i32, float_value),
347            TexParameterValue::Bool(_) => unreachable!("no settable tex params should be booleans"),
348        };
349
350        let Some(context) = self.upcast().context() else {
351            return Err(WebGLError::ContextLost);
352        };
353        let is_webgl2 = context.webgl_version() == WebGLVersion::WebGL2;
354
355        let update_filter = |filter: &Cell<u32>| {
356            if filter.get() == int_value as u32 {
357                return Ok(());
358            }
359            filter.set(int_value as u32);
360            context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
361            Ok(())
362        };
363        if is_webgl2 {
364            match param {
365                constants::TEXTURE_BASE_LEVEL | constants::TEXTURE_MAX_LEVEL => {
366                    context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
367                    return Ok(());
368                },
369                constants::TEXTURE_COMPARE_FUNC => match int_value as u32 {
370                    constants::LEQUAL |
371                    constants::GEQUAL |
372                    constants::LESS |
373                    constants::GREATER |
374                    constants::EQUAL |
375                    constants::NOTEQUAL |
376                    constants::ALWAYS |
377                    constants::NEVER => {
378                        context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
379                        return Ok(());
380                    },
381                    _ => return Err(WebGLError::InvalidEnum),
382                },
383                constants::TEXTURE_COMPARE_MODE => match int_value as u32 {
384                    constants::COMPARE_REF_TO_TEXTURE | constants::NONE => {
385                        context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
386                        return Ok(());
387                    },
388                    _ => return Err(WebGLError::InvalidEnum),
389                },
390                constants::TEXTURE_MAX_LOD | constants::TEXTURE_MIN_LOD => {
391                    context.send_command(WebGLCommand::TexParameterf(target, param, float_value));
392                    return Ok(());
393                },
394                constants::TEXTURE_WRAP_R => match int_value as u32 {
395                    constants::CLAMP_TO_EDGE | constants::MIRRORED_REPEAT | constants::REPEAT => {
396                        self.upcast()
397                            .send_command(WebGLCommand::TexParameteri(target, param, int_value));
398                        return Ok(());
399                    },
400                    _ => return Err(WebGLError::InvalidEnum),
401                },
402                _ => {},
403            }
404        }
405        match param {
406            constants::TEXTURE_MIN_FILTER => match int_value as u32 {
407                constants::NEAREST |
408                constants::LINEAR |
409                constants::NEAREST_MIPMAP_NEAREST |
410                constants::LINEAR_MIPMAP_NEAREST |
411                constants::NEAREST_MIPMAP_LINEAR |
412                constants::LINEAR_MIPMAP_LINEAR => update_filter(&self.min_filter),
413                _ => Err(WebGLError::InvalidEnum),
414            },
415            constants::TEXTURE_MAG_FILTER => match int_value as u32 {
416                constants::NEAREST | constants::LINEAR => update_filter(&self.mag_filter),
417                _ => Err(WebGLError::InvalidEnum),
418            },
419            constants::TEXTURE_WRAP_S | constants::TEXTURE_WRAP_T => match int_value as u32 {
420                constants::CLAMP_TO_EDGE | constants::MIRRORED_REPEAT | constants::REPEAT => {
421                    context.send_command(WebGLCommand::TexParameteri(target, param, int_value));
422                    Ok(())
423                },
424                _ => Err(WebGLError::InvalidEnum),
425            },
426            EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT => {
427                // NaN is not less than 1., what a time to be alive.
428                if float_value < 1. || !float_value.is_normal() {
429                    return Err(WebGLError::InvalidValue);
430                }
431                context.send_command(WebGLCommand::TexParameterf(target, param, float_value));
432                Ok(())
433            },
434            _ => Err(WebGLError::InvalidEnum),
435        }
436    }
437
438    pub(crate) fn min_filter(&self) -> u32 {
439        self.min_filter.get()
440    }
441
442    pub(crate) fn mag_filter(&self) -> u32 {
443        self.mag_filter.get()
444    }
445
446    pub(crate) fn is_using_linear_filtering(&self) -> bool {
447        let filters = [self.min_filter.get(), self.mag_filter.get()];
448        filters.iter().any(|filter| {
449            matches!(
450                *filter,
451                constants::LINEAR |
452                    constants::NEAREST_MIPMAP_LINEAR |
453                    constants::LINEAR_MIPMAP_NEAREST |
454                    constants::LINEAR_MIPMAP_LINEAR
455            )
456        })
457    }
458
459    pub(crate) fn populate_mip_chain(&self, first_level: u32, last_level: u32) -> WebGLResult<()> {
460        let base_image_info = self
461            .image_info_at_face(0, first_level)
462            .ok_or(WebGLError::InvalidOperation)?;
463
464        let mut ref_width = base_image_info.width;
465        let mut ref_height = base_image_info.height;
466
467        if ref_width == 0 || ref_height == 0 {
468            return Err(WebGLError::InvalidOperation);
469        }
470
471        for level in (first_level + 1)..last_level {
472            if ref_width == 1 && ref_height == 1 {
473                break;
474            }
475
476            ref_width = cmp::max(1, ref_width / 2);
477            ref_height = cmp::max(1, ref_height / 2);
478
479            let image_info = ImageInfo {
480                width: ref_width,
481                height: ref_height,
482                depth: 0,
483                internal_format: base_image_info.internal_format,
484                data_type: base_image_info.data_type,
485            };
486
487            self.set_image_infos_at_level(level, image_info);
488        }
489
490        self.update_size();
491        Ok(())
492    }
493
494    fn is_cube_complete(&self) -> bool {
495        debug_assert_eq!(self.face_count.get(), 6);
496
497        let image_info = match self.base_image_info() {
498            Some(info) => info,
499            None => return false,
500        };
501
502        let ref_width = image_info.width;
503        let ref_format = image_info.internal_format;
504
505        for face in 0..self.face_count.get() {
506            let current_image_info = match self.image_info_at_face(face, self.base_mipmap_level) {
507                Some(info) => info,
508                None => return false,
509            };
510
511            // Compares height with width to enforce square dimensions
512            if current_image_info.internal_format != ref_format ||
513                current_image_info.width != ref_width ||
514                current_image_info.height != ref_width
515            {
516                return false;
517            }
518        }
519
520        true
521    }
522
523    fn face_index_for_target(&self, target: &TexImageTarget) -> u8 {
524        match *target {
525            TexImageTarget::CubeMapPositiveX => 0,
526            TexImageTarget::CubeMapNegativeX => 1,
527            TexImageTarget::CubeMapPositiveY => 2,
528            TexImageTarget::CubeMapNegativeY => 3,
529            TexImageTarget::CubeMapPositiveZ => 4,
530            TexImageTarget::CubeMapNegativeZ => 5,
531            _ => 0,
532        }
533    }
534
535    pub(crate) fn image_info_for_target(
536        &self,
537        target: &TexImageTarget,
538        level: u32,
539    ) -> Option<ImageInfo> {
540        let face_index = self.face_index_for_target(target);
541        self.image_info_at_face(face_index, level)
542    }
543
544    pub(crate) fn image_info_at_face(&self, face: u8, level: u32) -> Option<ImageInfo> {
545        let pos = (level * self.face_count.get() as u32) + face as u32;
546        self.image_info_array.borrow()[pos as usize]
547    }
548
549    fn set_image_infos_at_level(&self, level: u32, image_info: ImageInfo) {
550        for face in 0..self.face_count.get() {
551            self.set_image_infos_at_level_and_face(level, face, image_info);
552        }
553    }
554
555    fn set_image_infos_at_level_and_face(&self, level: u32, face: u8, image_info: ImageInfo) {
556        debug_assert!(face < self.face_count.get());
557        let pos = (level * self.face_count.get() as u32) + face as u32;
558        self.image_info_array.borrow_mut()[pos as usize] = Some(image_info);
559    }
560
561    fn update_size(&self) {
562        let size = self
563            .image_info_array
564            .borrow()
565            .iter()
566            .filter_map(|info| *info)
567            .map(|info| info.physical_size())
568            .sum();
569        self.reflector().update_memory_size(self, size);
570    }
571
572    fn base_image_info(&self) -> Option<ImageInfo> {
573        assert!((self.base_mipmap_level as usize) < MAX_LEVEL_COUNT);
574
575        self.image_info_at_face(0, self.base_mipmap_level)
576    }
577
578    pub(crate) fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) {
579        self.attached_framebuffer.set(Some(fb));
580    }
581
582    pub(crate) fn detach_from_framebuffer(&self) {
583        self.attached_framebuffer.set(None);
584    }
585
586    pub(crate) fn storage(
587        &self,
588        target: TexImageTarget,
589        levels: u32,
590        internal_format: TexFormat,
591        width: u32,
592        height: u32,
593        depth: u32,
594    ) -> WebGLResult<()> {
595        // Handled by the caller
596        assert!(!self.is_immutable());
597        assert!(self.target().is_some());
598
599        let target_id = target.as_gl_constant();
600        let command = match target {
601            TexImageTarget::Texture2D | TexImageTarget::CubeMap => {
602                WebGLCommand::TexStorage2D(target_id, levels, internal_format, width, height)
603            },
604            TexImageTarget::Texture3D | TexImageTarget::Texture2DArray => {
605                WebGLCommand::TexStorage3D(target_id, levels, internal_format, width, height, depth)
606            },
607            _ => unreachable!(), // handled by the caller
608        };
609        self.upcast().send_command(command);
610
611        let mut width = width;
612        let mut height = height;
613        let mut depth = depth;
614        for level in 0..levels {
615            let image_info = ImageInfo {
616                width,
617                height,
618                depth,
619                internal_format,
620                data_type: None,
621            };
622            self.set_image_infos_at_level(level, image_info);
623
624            width = cmp::max(1, width / 2);
625            height = cmp::max(1, height / 2);
626            depth = cmp::max(1, depth / 2);
627        }
628
629        self.immutable_levels.set(Some(levels));
630
631        if let Some(fb) = self.attached_framebuffer.get() {
632            fb.update_status();
633        }
634
635        self.update_size();
636
637        Ok(())
638    }
639}
640
641#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
642pub(crate) struct ImageInfo {
643    width: u32,
644    height: u32,
645    depth: u32,
646    #[no_trace]
647    internal_format: TexFormat,
648    #[no_trace]
649    data_type: Option<TexDataType>,
650}
651
652impl ImageInfo {
653    pub(crate) fn width(&self) -> u32 {
654        self.width
655    }
656
657    pub(crate) fn height(&self) -> u32 {
658        self.height
659    }
660
661    pub(crate) fn internal_format(&self) -> TexFormat {
662        self.internal_format
663    }
664
665    pub(crate) fn data_type(&self) -> Option<TexDataType> {
666        self.data_type
667    }
668
669    fn is_power_of_two(&self) -> bool {
670        self.width.is_power_of_two() &&
671            self.height.is_power_of_two() &&
672            self.depth.is_power_of_two()
673    }
674
675    fn get_max_mimap_levels(&self) -> u32 {
676        let largest = cmp::max(cmp::max(self.width, self.height), self.depth);
677        if largest == 0 {
678            return 0;
679        }
680        // FloorLog2(largest) + 1
681        (largest as f64).log2() as u32 + 1
682    }
683
684    fn is_compressed_format(&self) -> bool {
685        self.internal_format.is_compressed()
686    }
687
688    /// Returns approximate physical size
689    pub(crate) fn physical_size(&self) -> usize {
690        self.width as usize *
691            self.height as usize *
692            self.depth as usize *
693            self.internal_format.components() as usize
694    }
695}
696
697#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
698pub(crate) enum TexCompressionValidation {
699    None,
700    S3TC,
701}
702
703#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
704pub(crate) struct TexCompression {
705    #[no_trace]
706    pub(crate) format: TexFormat,
707    pub(crate) bytes_per_block: u8,
708    pub(crate) block_width: u8,
709    pub(crate) block_height: u8,
710    pub(crate) validation: TexCompressionValidation,
711}