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, 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 version = context.webgl_version();
322        let data_type = match TexDataType::from_webgl_constant(self.data_type, version) {
323            Some(data_type) if data_type.required_webgl_version() <= version => data_type,
324            _ => {
325                context.webgl_error(InvalidEnum);
326                return Err(TexImageValidationError::InvalidDataType);
327            },
328        };
329
330        let format = match TexFormat::from_gl_constant(self.format) {
331            Some(format) if format.required_webgl_version() <= context.webgl_version() => format,
332            _ => {
333                context.webgl_error(InvalidEnum);
334                return Err(TexImageValidationError::InvalidTextureFormat);
335            },
336        };
337
338        // GL_INVALID_OPERATION is generated if format does not match
339        // internal_format.
340        if format != internal_format.to_unsized() {
341            context.webgl_error(InvalidOperation);
342            return Err(TexImageValidationError::TextureFormatMismatch);
343        }
344
345        // NOTE: In WebGL2 data type check should be done based on the internal
346        // format, but in some functions this validator is called with the
347        // regular unsized format as parameter (eg. TexSubImage2D). For now
348        // it's left here to avoid duplication.
349        //
350        // GL_INVALID_OPERATION is generated if type is
351        // GL_UNSIGNED_SHORT_4_4_4_4 or GL_UNSIGNED_SHORT_5_5_5_1 and format is
352        // not GL_RGBA.
353        //
354        // GL_INVALID_OPERATION is generated if type is GL_UNSIGNED_SHORT_5_6_5
355        // and format is not GL_RGB.
356        match data_type {
357            TexDataType::UnsignedShort4444 | TexDataType::UnsignedShort5551
358                if format != TexFormat::RGBA =>
359            {
360                context.webgl_error(InvalidOperation);
361                return Err(TexImageValidationError::InvalidTypeForFormat);
362            },
363            TexDataType::UnsignedShort565 if format != TexFormat::RGB => {
364                context.webgl_error(InvalidOperation);
365                return Err(TexImageValidationError::InvalidTypeForFormat);
366            },
367            _ => {},
368        }
369
370        Ok(TexImage2DValidatorResult {
371            width,
372            height,
373            level,
374            border,
375            texture,
376            target,
377            internal_format,
378            format,
379            data_type,
380        })
381    }
382}
383
384pub(crate) struct CommonCompressedTexImage2DValidator<'a> {
385    common_validator: CommonTexImage2DValidator<'a>,
386    data_len: usize,
387}
388
389impl<'a> CommonCompressedTexImage2DValidator<'a> {
390    #[expect(clippy::too_many_arguments)]
391    pub(crate) fn new(
392        context: &'a WebGLRenderingContext,
393        target: u32,
394        level: i32,
395        width: i32,
396        height: i32,
397        border: i32,
398        compression_format: u32,
399        data_len: usize,
400    ) -> Self {
401        CommonCompressedTexImage2DValidator {
402            common_validator: CommonTexImage2DValidator::new(
403                context,
404                target,
405                level,
406                compression_format,
407                width,
408                height,
409                border,
410            ),
411            data_len,
412        }
413    }
414}
415
416pub(crate) struct CommonCompressedTexImage2DValidatorResult {
417    pub(crate) texture: DomRoot<WebGLTexture>,
418    pub(crate) target: TexImageTarget,
419    pub(crate) level: u32,
420    pub(crate) width: u32,
421    pub(crate) height: u32,
422    pub(crate) compression: TexCompression,
423}
424
425fn valid_s3tc_dimension(level: u32, side_length: u32, block_size: u32) -> bool {
426    side_length.is_multiple_of(block_size) || (level > 0 && [0, 1, 2].contains(&side_length))
427}
428
429fn valid_compressed_data_len(
430    data_len: usize,
431    width: u32,
432    height: u32,
433    compression: &TexCompression,
434) -> bool {
435    let block_width = compression.block_width as u32;
436    let block_height = compression.block_height as u32;
437
438    let required_blocks_hor = width.div_ceil(block_width);
439    let required_blocks_ver = height.div_ceil(block_height);
440    let required_blocks = required_blocks_hor * required_blocks_ver;
441
442    let required_bytes = required_blocks * compression.bytes_per_block as u32;
443    data_len == required_bytes as usize
444}
445
446fn is_subimage_blockaligned(
447    xoffset: u32,
448    yoffset: u32,
449    width: u32,
450    height: u32,
451    compression: &TexCompression,
452    tex_info: &ImageInfo,
453) -> bool {
454    let block_width = compression.block_width as u32;
455    let block_height = compression.block_height as u32;
456
457    (xoffset.is_multiple_of(block_width) && yoffset.is_multiple_of(block_height)) &&
458        (width.is_multiple_of(block_width) || xoffset + width == tex_info.width()) &&
459        (height.is_multiple_of(block_height) || yoffset + height == tex_info.height())
460}
461
462impl WebGLValidator for CommonCompressedTexImage2DValidator<'_> {
463    type Error = TexImageValidationError;
464    type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
465
466    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
467        let context = self.common_validator.context;
468        let CommonTexImage2DValidatorResult {
469            texture,
470            target,
471            level,
472            internal_format,
473            width,
474            height,
475            border: _,
476        } = self.common_validator.validate()?;
477
478        // GL_INVALID_ENUM is generated if internalformat is not a supported
479        // format returned in GL_COMPRESSED_TEXTURE_FORMATS.
480        let compression = context
481            .extension_manager()
482            .get_tex_compression_format(internal_format.as_gl_constant());
483        let compression = match compression {
484            Some(compression) => compression,
485            None => {
486                context.webgl_error(InvalidEnum);
487                return Err(TexImageValidationError::InvalidCompressionFormat);
488            },
489        };
490
491        // GL_INVALID_VALUE is generated if imageSize is not consistent with the
492        // format, dimensions, and contents of the specified compressed image data.
493        if !valid_compressed_data_len(self.data_len, width, height, &compression) {
494            context.webgl_error(InvalidValue);
495            return Err(TexImageValidationError::TextureFormatMismatch);
496        }
497
498        Ok(CommonCompressedTexImage2DValidatorResult {
499            texture,
500            target,
501            level,
502            width,
503            height,
504            compression,
505        })
506    }
507}
508
509pub(crate) struct CompressedTexImage2DValidator<'a> {
510    compression_validator: CommonCompressedTexImage2DValidator<'a>,
511}
512
513impl<'a> CompressedTexImage2DValidator<'a> {
514    #[expect(clippy::too_many_arguments)]
515    pub(crate) fn new(
516        context: &'a WebGLRenderingContext,
517        target: u32,
518        level: i32,
519        width: i32,
520        height: i32,
521        border: i32,
522        compression_format: u32,
523        data_len: usize,
524    ) -> Self {
525        CompressedTexImage2DValidator {
526            compression_validator: CommonCompressedTexImage2DValidator::new(
527                context,
528                target,
529                level,
530                width,
531                height,
532                border,
533                compression_format,
534                data_len,
535            ),
536        }
537    }
538}
539
540impl WebGLValidator for CompressedTexImage2DValidator<'_> {
541    type Error = TexImageValidationError;
542    type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
543
544    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
545        let context = self.compression_validator.common_validator.context;
546        let CommonCompressedTexImage2DValidatorResult {
547            texture,
548            target,
549            level,
550            width,
551            height,
552            compression,
553        } = self.compression_validator.validate()?;
554
555        // GL_INVALID_OPERATION is generated if parameter combinations are not
556        // supported by the specific compressed internal format as specified
557        // in the specific texture compression extension.
558        let compression_valid = match compression.validation {
559            TexCompressionValidation::S3TC => {
560                let valid_width =
561                    valid_s3tc_dimension(level, width, compression.block_width as u32);
562                let valid_height =
563                    valid_s3tc_dimension(level, height, compression.block_height as u32);
564                valid_width && valid_height
565            },
566            TexCompressionValidation::None => true,
567        };
568        if !compression_valid {
569            context.webgl_error(InvalidOperation);
570            return Err(TexImageValidationError::TextureFormatMismatch);
571        }
572
573        Ok(CommonCompressedTexImage2DValidatorResult {
574            texture,
575            target,
576            level,
577            width,
578            height,
579            compression,
580        })
581    }
582}
583
584pub(crate) struct CompressedTexSubImage2DValidator<'a> {
585    compression_validator: CommonCompressedTexImage2DValidator<'a>,
586    xoffset: i32,
587    yoffset: i32,
588}
589
590impl<'a> CompressedTexSubImage2DValidator<'a> {
591    #[expect(clippy::too_many_arguments)]
592    pub(crate) fn new(
593        context: &'a WebGLRenderingContext,
594        target: u32,
595        level: i32,
596        xoffset: i32,
597        yoffset: i32,
598        width: i32,
599        height: i32,
600        compression_format: u32,
601        data_len: usize,
602    ) -> Self {
603        CompressedTexSubImage2DValidator {
604            compression_validator: CommonCompressedTexImage2DValidator::new(
605                context,
606                target,
607                level,
608                width,
609                height,
610                0,
611                compression_format,
612                data_len,
613            ),
614            xoffset,
615            yoffset,
616        }
617    }
618}
619
620impl WebGLValidator for CompressedTexSubImage2DValidator<'_> {
621    type Error = TexImageValidationError;
622    type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
623
624    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
625        let context = self.compression_validator.common_validator.context;
626        let CommonCompressedTexImage2DValidatorResult {
627            texture,
628            target,
629            level,
630            width,
631            height,
632            compression,
633        } = self.compression_validator.validate()?;
634
635        // GL_INVALID_OPERATION is generated if no base image has been defined
636        // for this texture target and level via compressedTexImage2D.
637        let Some(tex_info) = texture.image_info_for_target(&target, level) else {
638            context.webgl_error(InvalidOperation);
639            return Err(TexImageValidationError::MissingBaseTexture);
640        };
641
642        // GL_INVALID_VALUE is generated if:
643        //   - xoffset or yoffset is less than 0
644        //   - x offset plus the width is greater than the texture width
645        //   - y offset plus the height is greater than the texture height
646        if self.xoffset < 0 ||
647            (self.xoffset as u32 + width) > tex_info.width() ||
648            self.yoffset < 0 ||
649            (self.yoffset as u32 + height) > tex_info.height()
650        {
651            context.webgl_error(InvalidValue);
652            return Err(TexImageValidationError::InvalidOffsets);
653        }
654
655        // GL_INVALID_OPERATION is generated if format does not match
656        // internal_format.
657        if compression.format != tex_info.internal_format() {
658            context.webgl_error(InvalidOperation);
659            return Err(TexImageValidationError::TextureFormatMismatch);
660        }
661
662        // GL_INVALID_OPERATION is generated if parameter combinations are not
663        // supported by the specific compressed internal format as specified
664        // in the specific texture compression extension.
665        let compression_valid = match compression.validation {
666            TexCompressionValidation::S3TC => is_subimage_blockaligned(
667                self.xoffset as u32,
668                self.yoffset as u32,
669                width,
670                height,
671                &compression,
672                &tex_info,
673            ),
674            TexCompressionValidation::None => true,
675        };
676        if !compression_valid {
677            context.webgl_error(InvalidOperation);
678            return Err(TexImageValidationError::TextureFormatMismatch);
679        }
680
681        Ok(CommonCompressedTexImage2DValidatorResult {
682            texture,
683            target,
684            level,
685            width,
686            height,
687            compression,
688        })
689    }
690}
691
692pub(crate) struct TexStorageValidator<'a> {
693    common_validator: CommonTexImage2DValidator<'a>,
694    dimensions: u8,
695    depth: i32,
696}
697
698pub(crate) struct TexStorageValidatorResult {
699    pub(crate) texture: DomRoot<WebGLTexture>,
700    pub(crate) target: TexImageTarget,
701    pub(crate) levels: u32,
702    pub(crate) internal_format: TexFormat,
703    pub(crate) width: u32,
704    pub(crate) height: u32,
705    pub(crate) depth: u32,
706}
707
708impl<'a> TexStorageValidator<'a> {
709    #[expect(clippy::too_many_arguments)]
710    pub(crate) fn new(
711        context: &'a WebGLRenderingContext,
712        dimensions: u8,
713        target: u32,
714        levels: i32,
715        internal_format: u32,
716        width: i32,
717        height: i32,
718        depth: i32,
719    ) -> Self {
720        TexStorageValidator {
721            common_validator: CommonTexImage2DValidator::new(
722                context,
723                target,
724                levels,
725                internal_format,
726                width,
727                height,
728                0,
729            ),
730            dimensions,
731            depth,
732        }
733    }
734}
735
736impl WebGLValidator for TexStorageValidator<'_> {
737    type Error = TexImageValidationError;
738    type ValidatedOutput = TexStorageValidatorResult;
739
740    fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
741        let context = self.common_validator.context;
742        let CommonTexImage2DValidatorResult {
743            texture,
744            target,
745            level,
746            internal_format,
747            width,
748            height,
749            border: _,
750        } = self.common_validator.validate()?;
751
752        if self.depth < 1 {
753            context.webgl_error(InvalidValue);
754            return Err(TexImageValidationError::DepthTooLow);
755        }
756        if level < 1 {
757            context.webgl_error(InvalidValue);
758            return Err(TexImageValidationError::LevelTooLow);
759        }
760
761        let dimensions_valid = match target {
762            TexImageTarget::Texture2D | TexImageTarget::CubeMap => self.dimensions == 2,
763            TexImageTarget::Texture3D | TexImageTarget::Texture2DArray => self.dimensions == 3,
764            _ => false,
765        };
766        if !dimensions_valid {
767            context.webgl_error(InvalidEnum);
768            return Err(TexImageValidationError::InvalidTextureTarget(
769                target.as_gl_constant(),
770            ));
771        }
772
773        if !internal_format.is_sized() {
774            context.webgl_error(InvalidEnum);
775            return Err(TexImageValidationError::InvalidTextureFormat);
776        }
777
778        let max_level = cmp::max(width, height).ilog2() + 1;
779        if level > max_level {
780            context.webgl_error(InvalidOperation);
781            return Err(TexImageValidationError::LevelTooHigh);
782        }
783
784        if texture.target().is_none() {
785            context.webgl_error(InvalidOperation);
786            return Err(TexImageValidationError::TextureTargetNotBound(
787                target.as_gl_constant(),
788            ));
789        }
790
791        Ok(TexStorageValidatorResult {
792            texture,
793            target,
794            levels: level,
795            internal_format,
796            width,
797            height,
798            depth: self.depth as u32,
799        })
800    }
801}