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