1use 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#[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}
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 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 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 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 if target.is_cubic() && self.width != self.height {
167 self.context.webgl_error(InvalidValue);
168 return Err(TexImageValidationError::InvalidCubicTextureDimensions);
169 }
170
171 if self.level < 0 {
173 self.context.webgl_error(InvalidValue);
174 return Err(TexImageValidationError::NegativeLevel);
175 }
176
177 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 if width > max_size >> level || height > max_size >> level {
191 self.context.webgl_error(InvalidValue);
192 return Err(TexImageValidationError::TextureTooBig);
193 }
194
195 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 if level > max_size.ilog2() {
207 self.context.webgl_error(InvalidValue);
208 return Err(TexImageValidationError::LevelTooHigh);
209 }
210
211 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 #[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
287pub(crate) struct TexImage2DValidatorResult {
289 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
301impl 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 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 if format != internal_format.to_unsized() {
341 context.webgl_error(InvalidOperation);
342 return Err(TexImageValidationError::TextureFormatMismatch);
343 }
344
345 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 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 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 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 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 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 if compression.format != tex_info.internal_format() {
658 context.webgl_error(InvalidOperation);
659 return Err(TexImageValidationError::TextureFormatMismatch);
660 }
661
662 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}