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