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 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 if format != internal_format.to_unsized() {
342 context.webgl_error(InvalidOperation);
343 return Err(TexImageValidationError::TextureFormatMismatch);
344 }
345
346 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 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 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 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 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 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 if compression.format != tex_info.internal_format() {
659 context.webgl_error(InvalidOperation);
660 return Err(TexImageValidationError::TextureFormatMismatch);
661 }
662
663 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}