1use 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#[derive(Debug)]
20pub(crate) enum TexImageValidationError {
21 InvalidTextureTarget(u32),
23 TextureTargetNotBound(u32),
25 InvalidCubicTextureDimensions,
27 NegativeLevel,
29 LevelTooHigh,
31 LevelTooLow,
33 DepthTooLow,
35 NegativeDimension,
37 TextureTooBig,
40 InvalidDataType,
42 InvalidTextureFormat,
44 TextureFormatMismatch,
46 InvalidTypeForFormat,
48 InvalidBorder,
50 NonPotTexture,
52 InvalidCompressionFormat,
54 InvalidOffsets,
56 MissingBaseTexture,
58 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
94pub(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
109pub(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 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 if target.is_cubic() && self.width != self.height {
179 self.context.webgl_error(InvalidValue);
180 return Err(TexImageValidationError::InvalidCubicTextureDimensions);
181 }
182
183 if self.level < 0 {
185 self.context.webgl_error(InvalidValue);
186 return Err(TexImageValidationError::NegativeLevel);
187 }
188
189 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 if width > max_size >> level || height > max_size >> level {
203 self.context.webgl_error(InvalidValue);
204 return Err(TexImageValidationError::TextureTooBig);
205 }
206
207 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 if level > max_size.ilog2() {
219 self.context.webgl_error(InvalidValue);
220 return Err(TexImageValidationError::LevelTooHigh);
221 }
222
223 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 #[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
299pub(crate) struct TexImage2DValidatorResult {
301 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
313impl 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 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 if format != internal_format.to_unsized() {
353 context.webgl_error(InvalidOperation);
354 return Err(TexImageValidationError::TextureFormatMismatch);
355 }
356
357 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 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 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 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 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 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 if compression.format != tex_info.internal_format() {
670 context.webgl_error(InvalidOperation);
671 return Err(TexImageValidationError::TextureFormatMismatch);
672 }
673
674 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 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 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 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 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}