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