Skip to main content

script/dom/webgl/validations/
tex_image_2d.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
5use std::{self, fmt};
6
7use servo_canvas_traits::webgl::WebGLError::*;
8use servo_canvas_traits::webgl::{TexDataType, TexFormat};
9
10use super::WebGLValidator;
11use super::types::TexImageTarget;
12use crate::dom::bindings::root::DomRoot;
13use crate::dom::webgl::webglrenderingcontext::WebGLRenderingContext;
14use crate::dom::webgl::webgltexture::{
15    ImageInfo, TexCompression, TexCompressionValidation, WebGLTexture,
16};
17
18/// The errors that the texImage* family of functions can generate.
19#[derive(Debug)]
20pub(crate) enum TexImageValidationError {
21    /// An invalid texture target was passed, it contains the invalid target.
22    InvalidTextureTarget(u32),
23    /// The passed texture target was not bound.
24    TextureTargetNotBound(u32),
25    /// Invalid texture dimensions were given.
26    InvalidCubicTextureDimensions,
27    /// A negative level was passed.
28    NegativeLevel,
29    /// A level too high to be allowed by the implementation was passed.
30    LevelTooHigh,
31    /// A level less than an allowed minimal value was passed.
32    LevelTooLow,
33    /// A depth less than an allowed minimal value was passed.
34    DepthTooLow,
35    /// A negative width and height was passed.
36    NegativeDimension,
37    /// A bigger with and height were passed than what the implementation
38    /// allows.
39    TextureTooBig,
40    /// An invalid data type was passed.
41    InvalidDataType,
42    /// An invalid texture format was passed.
43    InvalidTextureFormat,
44    /// Format did not match internal_format.
45    TextureFormatMismatch,
46    /// Invalid data type for the given format.
47    InvalidTypeForFormat,
48    /// Invalid border
49    InvalidBorder,
50    /// Expected a power of two texture.
51    NonPotTexture,
52    /// Unrecognized texture compression format.
53    InvalidCompressionFormat,
54    /// Invalid X/Y texture offset parameters.
55    InvalidOffsets,
56    /// No base image has been defined for this texture target and level.
57    MissingBaseTexture,
58    /// The texture already has immutable storage.
59    ImmutableTexture,
60}
61
62impl std::error::Error for TexImageValidationError {}
63
64impl fmt::Display for TexImageValidationError {
65    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66        use self::TexImageValidationError::*;
67        let description = match *self {
68            InvalidTextureTarget(texture_id) => &format!("Invalid texture target ({texture_id})"),
69            TextureTargetNotBound(texture_id) => &format!("Texture was not bound {texture_id}"),
70            InvalidCubicTextureDimensions => {
71                "Invalid dimensions were given for a cubic texture target"
72            },
73            NegativeLevel => "A negative level was passed",
74            LevelTooHigh => "Level too high",
75            LevelTooLow => "Level too low",
76            DepthTooLow => "Depth too low",
77            NegativeDimension => "Negative dimensions were passed",
78            TextureTooBig => "Dimensions given are too big",
79            InvalidDataType => "Invalid data type",
80            InvalidTextureFormat => "Invalid texture format",
81            TextureFormatMismatch => "Texture format mismatch",
82            InvalidTypeForFormat => "Invalid type for the given format",
83            InvalidBorder => "Invalid border",
84            NonPotTexture => "Expected a power of two texture",
85            InvalidCompressionFormat => "Unrecognized texture compression format",
86            InvalidOffsets => "Invalid X/Y texture offset parameters",
87            MissingBaseTexture => "No base image defined for this texture target and level",
88            ImmutableTexture => "Texture storage is immutable",
89        };
90        write!(f, "TexImageValidationError({})", description)
91    }
92}
93
94/// Returns the texture bound to `target`. Generates `INVALID_OPERATION` if
95/// none is bound.
96pub(crate) fn bound_texture(
97    context: &WebGLRenderingContext,
98    target: TexImageTarget,
99) -> Result<DomRoot<WebGLTexture>, TexImageValidationError> {
100    context
101        .textures()
102        .active_texture_for_image_target(target)
103        .ok_or_else(|| {
104            context.webgl_error(InvalidOperation);
105            TexImageValidationError::TextureTargetNotBound(target.as_gl_constant())
106        })
107}
108
109/// Returns `internal_format` as a texture format this context accepts. Generates
110/// `INVALID_ENUM` otherwise.
111pub(crate) fn validate_internal_format(
112    context: &WebGLRenderingContext,
113    internal_format: u32,
114) -> Result<TexFormat, TexImageValidationError> {
115    match TexFormat::from_gl_constant(internal_format) {
116        Some(format)
117            if format.required_webgl_version() <= context.webgl_version() &&
118                format.usable_as_internal() =>
119        {
120            Ok(format)
121        },
122        _ => {
123            context.webgl_error(InvalidEnum);
124            Err(TexImageValidationError::InvalidTextureFormat)
125        },
126    }
127}
128
129pub(crate) struct CommonTexImage2DValidator<'a> {
130    context: &'a WebGLRenderingContext,
131    target: u32,
132    level: i32,
133    internal_format: u32,
134    width: i32,
135    height: i32,
136    border: i32,
137}
138
139pub(crate) struct CommonTexImage2DValidatorResult {
140    pub(crate) texture: DomRoot<WebGLTexture>,
141    pub(crate) target: TexImageTarget,
142    pub(crate) level: u32,
143    pub(crate) internal_format: TexFormat,
144    pub(crate) width: u32,
145    pub(crate) height: u32,
146    pub(crate) border: u32,
147}
148
149impl WebGLValidator for CommonTexImage2DValidator<'_> {
150    type Error = TexImageValidationError;
151    type ValidatedOutput = CommonTexImage2DValidatorResult;
152    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
153        // GL_INVALID_ENUM is generated if target is not GL_TEXTURE_2D,
154        // GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
155        // GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
156        // GL_TEXTURE_CUBE_MAP_POSITIVE_Z, or GL_TEXTURE_CUBE_MAP_NEGATIVE_Z.
157        let target = match TexImageTarget::from_gl_constant(self.target) {
158            Some(target) if target.dimensions() == 2 => target,
159            _ => {
160                self.context.webgl_error(InvalidEnum);
161                return Err(TexImageValidationError::InvalidTextureTarget(self.target));
162            },
163        };
164
165        let limits = self.context.limits();
166
167        let max_size = if target.is_cubic() {
168            limits.max_cube_map_tex_size
169        } else {
170            limits.max_tex_size
171        };
172
173        let texture = bound_texture(self.context, target)?;
174        let internal_format = validate_internal_format(self.context, self.internal_format)?;
175
176        // GL_INVALID_VALUE is generated if target is one of the six cube map 2D
177        // image targets and the width and height parameters are not equal.
178        if target.is_cubic() && self.width != self.height {
179            self.context.webgl_error(InvalidValue);
180            return Err(TexImageValidationError::InvalidCubicTextureDimensions);
181        }
182
183        // GL_INVALID_VALUE is generated if level is less than 0.
184        if self.level < 0 {
185            self.context.webgl_error(InvalidValue);
186            return Err(TexImageValidationError::NegativeLevel);
187        }
188
189        // GL_INVALID_VALUE is generated if width or height is less than 0
190        if self.width < 0 || self.height < 0 {
191            self.context.webgl_error(InvalidValue);
192            return Err(TexImageValidationError::NegativeDimension);
193        }
194
195        let width = self.width as u32;
196        let height = self.height as u32;
197        let level = self.level as u32;
198
199        // GL_INVALID_VALUE is generated if width or height is greater than
200        // GL_MAX_TEXTURE_SIZE when target is GL_TEXTURE_2D or
201        // GL_MAX_CUBE_MAP_TEXTURE_SIZE when target is not GL_TEXTURE_2D.
202        if width > max_size >> level || height > max_size >> level {
203            self.context.webgl_error(InvalidValue);
204            return Err(TexImageValidationError::TextureTooBig);
205        }
206
207        // GL_INVALID_VALUE is generated if level is greater than zero and the
208        // texture is not power of two.
209        if level > 0 && (!width.is_power_of_two() || !height.is_power_of_two()) {
210            self.context.webgl_error(InvalidValue);
211            return Err(TexImageValidationError::NonPotTexture);
212        }
213
214        // GL_INVALID_VALUE may be generated if level is greater than
215        // log_2(max), where max is the returned value of GL_MAX_TEXTURE_SIZE
216        // when target is GL_TEXTURE_2D or GL_MAX_CUBE_MAP_TEXTURE_SIZE when
217        // target is not GL_TEXTURE_2D.
218        if level > max_size.ilog2() {
219            self.context.webgl_error(InvalidValue);
220            return Err(TexImageValidationError::LevelTooHigh);
221        }
222
223        // GL_INVALID_VALUE is generated if border is not 0.
224        if self.border != 0 {
225            self.context.webgl_error(InvalidValue);
226            return Err(TexImageValidationError::InvalidBorder);
227        }
228
229        Ok(CommonTexImage2DValidatorResult {
230            texture,
231            target,
232            level,
233            internal_format,
234            width,
235            height,
236            border: self.border as u32,
237        })
238    }
239}
240
241impl<'a> CommonTexImage2DValidator<'a> {
242    pub(crate) fn new(
243        context: &'a WebGLRenderingContext,
244        target: u32,
245        level: i32,
246        internal_format: u32,
247        width: i32,
248        height: i32,
249        border: i32,
250    ) -> Self {
251        CommonTexImage2DValidator {
252            context,
253            target,
254            level,
255            internal_format,
256            width,
257            height,
258            border,
259        }
260    }
261}
262
263pub(crate) struct TexImage2DValidator<'a> {
264    common_validator: CommonTexImage2DValidator<'a>,
265    format: u32,
266    data_type: u32,
267}
268
269impl<'a> TexImage2DValidator<'a> {
270    /// TODO: Move data validation logic here.
271    #[expect(clippy::too_many_arguments)]
272    pub(crate) fn new(
273        context: &'a WebGLRenderingContext,
274        target: u32,
275        level: i32,
276        internal_format: u32,
277        width: i32,
278        height: i32,
279        border: i32,
280        format: u32,
281        data_type: u32,
282    ) -> Self {
283        TexImage2DValidator {
284            common_validator: CommonTexImage2DValidator::new(
285                context,
286                target,
287                level,
288                internal_format,
289                width,
290                height,
291                border,
292            ),
293            format,
294            data_type,
295        }
296    }
297}
298
299/// The validated result of a TexImage2DValidator-validated call.
300pub(crate) struct TexImage2DValidatorResult {
301    /// NB: width, height and level are already unsigned after validation.
302    pub(crate) width: u32,
303    pub(crate) height: u32,
304    pub(crate) level: u32,
305    pub(crate) border: u32,
306    pub(crate) texture: DomRoot<WebGLTexture>,
307    pub(crate) target: TexImageTarget,
308    pub(crate) internal_format: TexFormat,
309    pub(crate) format: TexFormat,
310    pub(crate) data_type: TexDataType,
311}
312
313/// TexImage2d validator as per
314/// <https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml>
315impl WebGLValidator for TexImage2DValidator<'_> {
316    type ValidatedOutput = TexImage2DValidatorResult;
317    type Error = TexImageValidationError;
318
319    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
320        let context = self.common_validator.context;
321        let CommonTexImage2DValidatorResult {
322            texture,
323            target,
324            level,
325            internal_format,
326            width,
327            height,
328            border,
329        } = self.common_validator.validate()?;
330
331        // GL_INVALID_ENUM is generated if format or data_type is not an
332        // accepted value.
333        let version = context.webgl_version();
334        let data_type = match TexDataType::from_webgl_constant(self.data_type, version) {
335            Some(data_type) if data_type.required_webgl_version() <= version => data_type,
336            _ => {
337                context.webgl_error(InvalidEnum);
338                return Err(TexImageValidationError::InvalidDataType);
339            },
340        };
341
342        let format = match TexFormat::from_gl_constant(self.format) {
343            Some(format) if format.required_webgl_version() <= context.webgl_version() => format,
344            _ => {
345                context.webgl_error(InvalidEnum);
346                return Err(TexImageValidationError::InvalidTextureFormat);
347            },
348        };
349
350        // GL_INVALID_OPERATION is generated if format does not match
351        // internal_format.
352        if format != internal_format.to_unsized() {
353            context.webgl_error(InvalidOperation);
354            return Err(TexImageValidationError::TextureFormatMismatch);
355        }
356
357        // NOTE: In WebGL2 data type check should be done based on the internal
358        // format, but in some functions this validator is called with the
359        // regular unsized format as parameter (eg. TexSubImage2D). For now
360        // it's left here to avoid duplication.
361        //
362        // GL_INVALID_OPERATION is generated if type is
363        // GL_UNSIGNED_SHORT_4_4_4_4 or GL_UNSIGNED_SHORT_5_5_5_1 and format is
364        // not GL_RGBA.
365        //
366        // GL_INVALID_OPERATION is generated if type is GL_UNSIGNED_SHORT_5_6_5
367        // and format is not GL_RGB.
368        match data_type {
369            TexDataType::UnsignedShort4444 | TexDataType::UnsignedShort5551
370                if format != TexFormat::RGBA =>
371            {
372                context.webgl_error(InvalidOperation);
373                return Err(TexImageValidationError::InvalidTypeForFormat);
374            },
375            TexDataType::UnsignedShort565 if format != TexFormat::RGB => {
376                context.webgl_error(InvalidOperation);
377                return Err(TexImageValidationError::InvalidTypeForFormat);
378            },
379            _ => {},
380        }
381
382        Ok(TexImage2DValidatorResult {
383            width,
384            height,
385            level,
386            border,
387            texture,
388            target,
389            internal_format,
390            format,
391            data_type,
392        })
393    }
394}
395
396pub(crate) struct CommonCompressedTexImage2DValidator<'a> {
397    common_validator: CommonTexImage2DValidator<'a>,
398    data_len: usize,
399}
400
401impl<'a> CommonCompressedTexImage2DValidator<'a> {
402    #[expect(clippy::too_many_arguments)]
403    pub(crate) fn new(
404        context: &'a WebGLRenderingContext,
405        target: u32,
406        level: i32,
407        width: i32,
408        height: i32,
409        border: i32,
410        compression_format: u32,
411        data_len: usize,
412    ) -> Self {
413        CommonCompressedTexImage2DValidator {
414            common_validator: CommonTexImage2DValidator::new(
415                context,
416                target,
417                level,
418                compression_format,
419                width,
420                height,
421                border,
422            ),
423            data_len,
424        }
425    }
426}
427
428pub(crate) struct CommonCompressedTexImage2DValidatorResult {
429    pub(crate) texture: DomRoot<WebGLTexture>,
430    pub(crate) target: TexImageTarget,
431    pub(crate) level: u32,
432    pub(crate) width: u32,
433    pub(crate) height: u32,
434    pub(crate) compression: TexCompression,
435}
436
437fn valid_s3tc_dimension(level: u32, side_length: u32, block_size: u32) -> bool {
438    side_length.is_multiple_of(block_size) || (level > 0 && [0, 1, 2].contains(&side_length))
439}
440
441fn valid_compressed_data_len(
442    data_len: usize,
443    width: u32,
444    height: u32,
445    compression: &TexCompression,
446) -> bool {
447    let block_width = compression.block_width as u32;
448    let block_height = compression.block_height as u32;
449
450    let required_blocks_hor = width.div_ceil(block_width);
451    let required_blocks_ver = height.div_ceil(block_height);
452    let required_blocks = required_blocks_hor * required_blocks_ver;
453
454    let required_bytes = required_blocks * compression.bytes_per_block as u32;
455    data_len == required_bytes as usize
456}
457
458fn is_subimage_blockaligned(
459    xoffset: u32,
460    yoffset: u32,
461    width: u32,
462    height: u32,
463    compression: &TexCompression,
464    tex_info: &ImageInfo,
465) -> bool {
466    let block_width = compression.block_width as u32;
467    let block_height = compression.block_height as u32;
468
469    (xoffset.is_multiple_of(block_width) && yoffset.is_multiple_of(block_height)) &&
470        (width.is_multiple_of(block_width) || xoffset + width == tex_info.width()) &&
471        (height.is_multiple_of(block_height) || yoffset + height == tex_info.height())
472}
473
474impl WebGLValidator for CommonCompressedTexImage2DValidator<'_> {
475    type Error = TexImageValidationError;
476    type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
477
478    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
479        let context = self.common_validator.context;
480        let CommonTexImage2DValidatorResult {
481            texture,
482            target,
483            level,
484            internal_format,
485            width,
486            height,
487            border: _,
488        } = self.common_validator.validate()?;
489
490        // GL_INVALID_ENUM is generated if internalformat is not a supported
491        // format returned in GL_COMPRESSED_TEXTURE_FORMATS.
492        let compression = context
493            .extension_manager()
494            .get_tex_compression_format(internal_format.as_gl_constant());
495        let compression = match compression {
496            Some(compression) => compression,
497            None => {
498                context.webgl_error(InvalidEnum);
499                return Err(TexImageValidationError::InvalidCompressionFormat);
500            },
501        };
502
503        // GL_INVALID_VALUE is generated if imageSize is not consistent with the
504        // format, dimensions, and contents of the specified compressed image data.
505        if !valid_compressed_data_len(self.data_len, width, height, &compression) {
506            context.webgl_error(InvalidValue);
507            return Err(TexImageValidationError::TextureFormatMismatch);
508        }
509
510        Ok(CommonCompressedTexImage2DValidatorResult {
511            texture,
512            target,
513            level,
514            width,
515            height,
516            compression,
517        })
518    }
519}
520
521pub(crate) struct CompressedTexImage2DValidator<'a> {
522    compression_validator: CommonCompressedTexImage2DValidator<'a>,
523}
524
525impl<'a> CompressedTexImage2DValidator<'a> {
526    #[expect(clippy::too_many_arguments)]
527    pub(crate) fn new(
528        context: &'a WebGLRenderingContext,
529        target: u32,
530        level: i32,
531        width: i32,
532        height: i32,
533        border: i32,
534        compression_format: u32,
535        data_len: usize,
536    ) -> Self {
537        CompressedTexImage2DValidator {
538            compression_validator: CommonCompressedTexImage2DValidator::new(
539                context,
540                target,
541                level,
542                width,
543                height,
544                border,
545                compression_format,
546                data_len,
547            ),
548        }
549    }
550}
551
552impl WebGLValidator for CompressedTexImage2DValidator<'_> {
553    type Error = TexImageValidationError;
554    type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
555
556    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
557        let context = self.compression_validator.common_validator.context;
558        let CommonCompressedTexImage2DValidatorResult {
559            texture,
560            target,
561            level,
562            width,
563            height,
564            compression,
565        } = self.compression_validator.validate()?;
566
567        // GL_INVALID_OPERATION is generated if parameter combinations are not
568        // supported by the specific compressed internal format as specified
569        // in the specific texture compression extension.
570        let compression_valid = match compression.validation {
571            TexCompressionValidation::S3TC => {
572                let valid_width =
573                    valid_s3tc_dimension(level, width, compression.block_width as u32);
574                let valid_height =
575                    valid_s3tc_dimension(level, height, compression.block_height as u32);
576                valid_width && valid_height
577            },
578            TexCompressionValidation::None => true,
579        };
580        if !compression_valid {
581            context.webgl_error(InvalidOperation);
582            return Err(TexImageValidationError::TextureFormatMismatch);
583        }
584
585        Ok(CommonCompressedTexImage2DValidatorResult {
586            texture,
587            target,
588            level,
589            width,
590            height,
591            compression,
592        })
593    }
594}
595
596pub(crate) struct CompressedTexSubImage2DValidator<'a> {
597    compression_validator: CommonCompressedTexImage2DValidator<'a>,
598    xoffset: i32,
599    yoffset: i32,
600}
601
602impl<'a> CompressedTexSubImage2DValidator<'a> {
603    #[expect(clippy::too_many_arguments)]
604    pub(crate) fn new(
605        context: &'a WebGLRenderingContext,
606        target: u32,
607        level: i32,
608        xoffset: i32,
609        yoffset: i32,
610        width: i32,
611        height: i32,
612        compression_format: u32,
613        data_len: usize,
614    ) -> Self {
615        CompressedTexSubImage2DValidator {
616            compression_validator: CommonCompressedTexImage2DValidator::new(
617                context,
618                target,
619                level,
620                width,
621                height,
622                0,
623                compression_format,
624                data_len,
625            ),
626            xoffset,
627            yoffset,
628        }
629    }
630}
631
632impl WebGLValidator for CompressedTexSubImage2DValidator<'_> {
633    type Error = TexImageValidationError;
634    type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
635
636    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
637        let context = self.compression_validator.common_validator.context;
638        let CommonCompressedTexImage2DValidatorResult {
639            texture,
640            target,
641            level,
642            width,
643            height,
644            compression,
645        } = self.compression_validator.validate()?;
646
647        // GL_INVALID_OPERATION is generated if no base image has been defined
648        // for this texture target and level via compressedTexImage2D.
649        let Some(tex_info) = texture.image_info_for_target(&target, level) else {
650            context.webgl_error(InvalidOperation);
651            return Err(TexImageValidationError::MissingBaseTexture);
652        };
653
654        // GL_INVALID_VALUE is generated if:
655        //   - xoffset or yoffset is less than 0
656        //   - x offset plus the width is greater than the texture width
657        //   - y offset plus the height is greater than the texture height
658        if self.xoffset < 0 ||
659            (self.xoffset as u32 + width) > tex_info.width() ||
660            self.yoffset < 0 ||
661            (self.yoffset as u32 + height) > tex_info.height()
662        {
663            context.webgl_error(InvalidValue);
664            return Err(TexImageValidationError::InvalidOffsets);
665        }
666
667        // GL_INVALID_OPERATION is generated if format does not match
668        // internal_format.
669        if compression.format != tex_info.internal_format() {
670            context.webgl_error(InvalidOperation);
671            return Err(TexImageValidationError::TextureFormatMismatch);
672        }
673
674        // GL_INVALID_OPERATION is generated if parameter combinations are not
675        // supported by the specific compressed internal format as specified
676        // in the specific texture compression extension.
677        let compression_valid = match compression.validation {
678            TexCompressionValidation::S3TC => is_subimage_blockaligned(
679                self.xoffset as u32,
680                self.yoffset as u32,
681                width,
682                height,
683                &compression,
684                &tex_info,
685            ),
686            TexCompressionValidation::None => true,
687        };
688        if !compression_valid {
689            context.webgl_error(InvalidOperation);
690            return Err(TexImageValidationError::TextureFormatMismatch);
691        }
692
693        Ok(CommonCompressedTexImage2DValidatorResult {
694            texture,
695            target,
696            level,
697            width,
698            height,
699            compression,
700        })
701    }
702}
703
704pub(crate) struct TexStorageValidator<'a> {
705    context: &'a WebGLRenderingContext,
706    dimensions: u8,
707    target: u32,
708    levels: i32,
709    internal_format: u32,
710    width: i32,
711    height: i32,
712    depth: i32,
713}
714
715pub(crate) struct TexStorageValidatorResult {
716    pub(crate) texture: DomRoot<WebGLTexture>,
717    pub(crate) target: TexImageTarget,
718    pub(crate) levels: u32,
719    pub(crate) internal_format: TexFormat,
720    pub(crate) width: u32,
721    pub(crate) height: u32,
722    pub(crate) depth: u32,
723}
724
725impl<'a> TexStorageValidator<'a> {
726    #[expect(clippy::too_many_arguments)]
727    pub(crate) fn new(
728        context: &'a WebGLRenderingContext,
729        dimensions: u8,
730        target: u32,
731        levels: i32,
732        internal_format: u32,
733        width: i32,
734        height: i32,
735        depth: i32,
736    ) -> Self {
737        TexStorageValidator {
738            context,
739            dimensions,
740            target,
741            levels,
742            internal_format,
743            width,
744            height,
745            depth,
746        }
747    }
748}
749
750impl WebGLValidator for TexStorageValidator<'_> {
751    type Error = TexImageValidationError;
752    type ValidatedOutput = TexStorageValidatorResult;
753
754    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
755        let context = self.context;
756
757        // Cube map faces are image targets, not storage targets.
758        let target = match TexImageTarget::from_gl_constant(self.target) {
759            Some(
760                target @ (TexImageTarget::Texture2D |
761                TexImageTarget::CubeMap |
762                TexImageTarget::Texture3D |
763                TexImageTarget::Texture2DArray),
764            ) if target.dimensions() == self.dimensions => target,
765            _ => {
766                context.webgl_error(InvalidEnum);
767                return Err(TexImageValidationError::InvalidTextureTarget(self.target));
768            },
769        };
770
771        let texture = bound_texture(context, target)?;
772        if texture.target().is_none() {
773            context.webgl_error(InvalidOperation);
774            return Err(TexImageValidationError::TextureTargetNotBound(self.target));
775        }
776        if texture.is_immutable() {
777            context.webgl_error(InvalidOperation);
778            return Err(TexImageValidationError::ImmutableTexture);
779        }
780
781        let internal_format = validate_internal_format(context, self.internal_format)?;
782        if !internal_format.is_sized() {
783            context.webgl_error(InvalidEnum);
784            return Err(TexImageValidationError::InvalidTextureFormat);
785        }
786        // Compressed formats have no 3D layout, only 2D arrays of compressed images.
787        if target == TexImageTarget::Texture3D && internal_format.is_compressed() {
788            context.webgl_error(InvalidOperation);
789            return Err(TexImageValidationError::InvalidTextureFormat);
790        }
791
792        if self.levels < 1 {
793            context.webgl_error(InvalidValue);
794            return Err(TexImageValidationError::LevelTooLow);
795        }
796        if self.width < 1 || self.height < 1 {
797            context.webgl_error(InvalidValue);
798            return Err(TexImageValidationError::NegativeDimension);
799        }
800        if self.depth < 1 {
801            context.webgl_error(InvalidValue);
802            return Err(TexImageValidationError::DepthTooLow);
803        }
804        let levels = self.levels as u32;
805        let width = self.width as u32;
806        let height = self.height as u32;
807        let depth = self.depth as u32;
808
809        if target.is_cubic() && width != height {
810            context.webgl_error(InvalidValue);
811            return Err(TexImageValidationError::InvalidCubicTextureDimensions);
812        }
813
814        // Largest width and height, and largest depth, of the base level.
815        let limits = context.limits();
816        let (max_size, max_depth) = match target {
817            TexImageTarget::Texture3D => (limits.max_3d_tex_size, limits.max_3d_tex_size),
818            TexImageTarget::Texture2DArray => {
819                (limits.max_tex_size, limits.max_array_texture_layers)
820            },
821            TexImageTarget::CubeMap => (limits.max_cube_map_tex_size, 1),
822            _ => (limits.max_tex_size, 1),
823        };
824        if width > max_size || height > max_size || depth > max_depth {
825            context.webgl_error(InvalidValue);
826            return Err(TexImageValidationError::TextureTooBig);
827        }
828
829        // Array layers are not mipmapped, so depth bounds the chain of a 3D texture only.
830        let max_extent = match target {
831            TexImageTarget::Texture3D => width.max(height).max(depth),
832            _ => width.max(height),
833        };
834        if levels > max_extent.ilog2() + 1 {
835            context.webgl_error(InvalidOperation);
836            return Err(TexImageValidationError::LevelTooHigh);
837        }
838
839        Ok(TexStorageValidatorResult {
840            texture,
841            target,
842            levels,
843            internal_format,
844            width,
845            height,
846            depth,
847        })
848    }
849}