diff options
author | Emilio Cobos Álvarez <me@emiliocobos.me> | 2016-06-12 00:16:27 +0200 |
---|---|---|
committer | Emilio Cobos Álvarez <me@emiliocobos.me> | 2016-06-25 00:03:15 +0200 |
commit | 46c14aced2f761c39f0f602962c660e362d98416 (patch) | |
tree | 668c13ee03202b095d352f67b533b8fdac86b889 /components/script/dom/webgl_validations | |
parent | 8d81ee77a877f07e2d4f2779aa252f5f3bb98c7c (diff) | |
download | servo-46c14aced2f761c39f0f602962c660e362d98416.tar.gz servo-46c14aced2f761c39f0f602962c660e362d98416.zip |
webgl: Refactor texture validations to take advantage of rust type system
This commit introduces the `WebGLValidator` trait, and uses it for multiple
validations in the texture-related WebGL code, to move that logic out of the
already bloated `webglrenderingcontext.rs` file.
It also creates a type-safe wrapper for some WebGL types, removing all the
`unreachable!`s there, and introduces a macro for generating them conveniently.
This partially addresses #10693, pending refactor more code to use this
infrastructure, and (possibly?) introducing an `AsGLError` trait for the errors
to make the error handling happen in `WebGLContext`.
Diffstat (limited to 'components/script/dom/webgl_validations')
-rw-r--r-- | components/script/dom/webgl_validations/mod.rs | 13 | ||||
-rw-r--r-- | components/script/dom/webgl_validations/tex_image_2d.rs | 353 | ||||
-rw-r--r-- | components/script/dom/webgl_validations/types.rs | 109 |
3 files changed, 475 insertions, 0 deletions
diff --git a/components/script/dom/webgl_validations/mod.rs b/components/script/dom/webgl_validations/mod.rs new file mode 100644 index 00000000000..2e070c6d6bc --- /dev/null +++ b/components/script/dom/webgl_validations/mod.rs @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub trait WebGLValidator { + type ValidatedOutput; + type Error: ::std::error::Error; + + fn validate(self) -> Result<Self::ValidatedOutput, Self::Error>; +} + +pub mod tex_image_2d; +pub mod types; diff --git a/components/script/dom/webgl_validations/tex_image_2d.rs b/components/script/dom/webgl_validations/tex_image_2d.rs new file mode 100644 index 00000000000..9c4b8fa39fc --- /dev/null +++ b/components/script/dom/webgl_validations/tex_image_2d.rs @@ -0,0 +1,353 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::js::Root; +use dom::webglrenderingcontext::WebGLRenderingContext; +use dom::webgltexture::WebGLTexture; +use std::{self, fmt}; +use super::WebGLValidator; +use super::types::{TexImageTarget, TexDataType, TexFormat}; +use webrender_traits::WebGLError::*; + +/// The errors that the texImage* family of functions can generate. +#[derive(Debug)] +pub enum TexImageValidationError { + /// An invalid texture target was passed, it contains the invalid target. + InvalidTextureTarget(u32), + /// The passed texture target was not bound. + TextureTargetNotBound(u32), + /// Invalid texture dimensions were given. + InvalidCubicTextureDimensions, + /// A negative level was passed. + NegativeLevel, + /// A level too high to be allowed by the implementation was passed. + LevelTooHigh, + /// A negative width and height was passed. + NegativeDimension, + /// A bigger with and height were passed than what the implementation + /// allows. + TextureTooBig, + /// An invalid data type was passed. + InvalidDataType, + /// An invalid texture format was passed. + InvalidTextureFormat, + /// Format did not match internal_format. + TextureFormatMismatch, + /// Invalid data type for the given format. + InvalidTypeForFormat, + /// Invalid border + InvalidBorder, + /// Expected a power of two texture. + NonPotTexture, +} + +impl std::error::Error for TexImageValidationError { + fn description(&self) -> &str { + use self::TexImageValidationError::*; + match *self { + InvalidTextureTarget(_) + => "Invalid texture target", + TextureTargetNotBound(_) + => "Texture was not bound", + InvalidCubicTextureDimensions + => "Invalid dimensions were given for a cubic texture target", + NegativeLevel + => "A negative level was passed", + LevelTooHigh + => "Level too high", + NegativeDimension + => "Negative dimensions were passed", + TextureTooBig + => "Dimensions given are too big", + InvalidDataType + => "Invalid data type", + InvalidTextureFormat + => "Invalid texture format", + TextureFormatMismatch + => "Texture format mismatch", + InvalidTypeForFormat + => "Invalid type for the given format", + InvalidBorder + => "Invalid border", + NonPotTexture + => "Expected a power of two texture", + } + } +} + +impl fmt::Display for TexImageValidationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TexImageValidationError({})", std::error::Error::description(self)) + } +} + +fn log2(n: u32) -> u32 { + 31 - n.leading_zeros() +} + +pub struct CommonTexImage2DValidator<'a> { + context: &'a WebGLRenderingContext, + target: u32, + level: i32, + internal_format: u32, + width: i32, + height: i32, + border: i32, +} + +pub struct CommonTexImage2DValidatorResult { + pub texture: Root<WebGLTexture>, + pub target: TexImageTarget, + pub level: u32, + pub internal_format: TexFormat, + pub width: u32, + pub height: u32, + pub border: u32, +} + +impl<'a> WebGLValidator for CommonTexImage2DValidator<'a> { + type Error = TexImageValidationError; + type ValidatedOutput = CommonTexImage2DValidatorResult; + fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> { + // GL_INVALID_ENUM is generated if target is not GL_TEXTURE_2D, + // GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + // GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + // GL_TEXTURE_CUBE_MAP_POSITIVE_Z, or GL_TEXTURE_CUBE_MAP_NEGATIVE_Z. + let target = match TexImageTarget::from_gl_constant(self.target) { + Some(target) => target, + None => { + self.context.webgl_error(InvalidEnum); + return Err(TexImageValidationError::InvalidTextureTarget(self.target)); + } + }; + + let texture = self.context.bound_texture_for_target(&target); + let limits = self.context.limits(); + + let max_size = if target.is_cubic() { + limits.max_cube_map_tex_size + } else { + limits.max_tex_size + }; + + // If an attempt is made to call this function with no WebGLTexture + // bound, an INVALID_OPERATION error is generated. + let texture = match texture { + Some(texture) => texture, + None => { + self.context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::TextureTargetNotBound(self.target)); + } + }; + + // GL_INVALID_ENUM is generated if internal_format is not an accepted + // format. + let internal_format = match TexFormat::from_gl_constant(self.internal_format) { + Some(format) => format, + None => { + self.context.webgl_error(InvalidEnum); + return Err(TexImageValidationError::InvalidTextureFormat); + } + }; + + // GL_INVALID_VALUE is generated if level is less than 0. + if self.level < 0 { + self.context.webgl_error(InvalidValue); + return Err(TexImageValidationError::NegativeLevel); + } + + // GL_INVALID_VALUE is generated if width or height is less than 0 + if self.width < 0 || self.height < 0 { + self.context.webgl_error(InvalidValue); + return Err(TexImageValidationError::NegativeDimension); + } + + // GL_INVALID_VALUE is generated if width or height is greater than + // GL_MAX_TEXTURE_SIZE when target is GL_TEXTURE_2D or + // GL_MAX_CUBE_MAP_TEXTURE_SIZE when target is not GL_TEXTURE_2D. + if self.width as u32 > max_size || self.height as u32 > max_size { + self.context.webgl_error(InvalidValue); + return Err(TexImageValidationError::TextureTooBig); + } + + let width = self.width as u32; + let height = self.height as u32; + let level = self.level as u32; + + // GL_INVALID_VALUE is generated if level is greater than zero and the + // texture is not power of two. + if level > 0 && (!width.is_power_of_two() || !height.is_power_of_two()) { + self.context.webgl_error(InvalidValue); + return Err(TexImageValidationError::NonPotTexture); + } + + // GL_INVALID_VALUE may be generated if level is greater than + // log_2(max), where max is the returned value of GL_MAX_TEXTURE_SIZE + // when target is GL_TEXTURE_2D or GL_MAX_CUBE_MAP_TEXTURE_SIZE when + // target is not GL_TEXTURE_2D. + if level > log2(max_size) { + self.context.webgl_error(InvalidValue); + return Err(TexImageValidationError::LevelTooHigh); + } + + // GL_INVALID_VALUE is generated if border is not 0. + if self.border != 0 { + self.context.webgl_error(InvalidValue); + return Err(TexImageValidationError::InvalidBorder); + } + + Ok(CommonTexImage2DValidatorResult { + texture: texture, + target: target, + level: level, + internal_format: internal_format, + width: width, + height: height, + border: self.border as u32, + }) + } +} + +impl<'a> CommonTexImage2DValidator<'a> { + pub fn new(context: &'a WebGLRenderingContext, + target: u32, level: i32, + internal_format: u32, + width: i32, height: i32, + border: i32) -> Self { + CommonTexImage2DValidator { + context: context, + target: target, + level: level, + internal_format: internal_format, + width: width, + height: height, + border: border + } + } +} + +pub struct TexImage2DValidator<'a> { + common_validator: CommonTexImage2DValidator<'a>, + format: u32, + data_type: u32, +} + +impl<'a> TexImage2DValidator<'a> { + // TODO: Move data validation logic here. + pub fn new(context: &'a WebGLRenderingContext, + target: u32, + level: i32, + internal_format: u32, + width: i32, + height: i32, + border: i32, + format: u32, + data_type: u32) -> Self { + TexImage2DValidator { + common_validator: CommonTexImage2DValidator::new(context, target, + level, + internal_format, + width, height, + border), + format: format, + data_type: data_type, + } + } +} + +/// The validated result of a TexImage2DValidator-validated call. +pub struct TexImage2DValidatorResult { + /// NB: width, height and level are already unsigned after validation. + pub width: u32, + pub height: u32, + pub level: u32, + pub border: u32, + pub texture: Root<WebGLTexture>, + pub target: TexImageTarget, + pub format: TexFormat, + pub data_type: TexDataType, +} + +/// TexImage2d validator as per +/// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml +impl<'a> WebGLValidator for TexImage2DValidator<'a> { + type ValidatedOutput = TexImage2DValidatorResult; + type Error = TexImageValidationError; + + fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> { + let context = self.common_validator.context; + let CommonTexImage2DValidatorResult { + texture, + target, + level, + internal_format, + width, + height, + border, + } = try!(self.common_validator.validate()); + + // GL_INVALID_VALUE is generated if target is one of the six cube map 2D + // image targets and the width and height parameters are not equal. + if target.is_cubic() && width != height { + context.webgl_error(InvalidValue); + return Err(TexImageValidationError::InvalidCubicTextureDimensions); + } + + // GL_INVALID_ENUM is generated if format or data_type is not an + // accepted value. + let data_type = match TexDataType::from_gl_constant(self.data_type) { + Some(data_type) => data_type, + None => { + context.webgl_error(InvalidEnum); + return Err(TexImageValidationError::InvalidDataType); + }, + }; + + let format = match TexFormat::from_gl_constant(self.format) { + Some(format) => format, + None => { + context.webgl_error(InvalidEnum); + return Err(TexImageValidationError::InvalidTextureFormat); + } + }; + + // GL_INVALID_OPERATION is generated if format does not match + // internal_format. + if format != internal_format { + context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::TextureFormatMismatch); + } + + + // GL_INVALID_OPERATION is generated if type is + // GL_UNSIGNED_SHORT_4_4_4_4 or GL_UNSIGNED_SHORT_5_5_5_1 and format is + // not GL_RGBA. + // + // GL_INVALID_OPERATION is generated if type is GL_UNSIGNED_SHORT_5_6_5 + // and format is not GL_RGB. + match data_type { + TexDataType::UnsignedShort4444 | + TexDataType::UnsignedShort5551 if format != TexFormat::RGBA => { + context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::InvalidTypeForFormat); + }, + TexDataType::UnsignedShort565 if format != TexFormat::RGB => { + context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::InvalidTypeForFormat); + }, + _ => {}, + } + + Ok(TexImage2DValidatorResult { + width: width, + height: height, + level: level, + border: border, + texture: texture, + target: target, + format: format, + data_type: data_type, + }) + } +} diff --git a/components/script/dom/webgl_validations/types.rs b/components/script/dom/webgl_validations/types.rs new file mode 100644 index 00000000000..f42eb548efd --- /dev/null +++ b/components/script/dom/webgl_validations/types.rs @@ -0,0 +1,109 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; + +/// This macro creates type-safe wrappers for WebGL types, associating variants +/// with gl constants. +macro_rules! type_safe_wrapper { + ($name: ident, $($variant:ident => $constant:ident, )+) => { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JSTraceable, HeapSizeOf)] + #[repr(u32)] + pub enum $name { + $( + $variant = constants::$constant, + )+ + } + + impl $name { + pub fn from_gl_constant(constant: u32) -> Option<Self> { + Some(match constant { + $(constants::$constant => $name::$variant, )+ + _ => return None, + }) + } + + #[inline] + pub fn as_gl_constant(&self) -> u32 { + *self as u32 + } + } + } +} + +type_safe_wrapper! { TexImageTarget, + Texture2D => TEXTURE_2D, + CubeMapPositiveX => TEXTURE_CUBE_MAP_POSITIVE_X, + CubeMapNegativeX => TEXTURE_CUBE_MAP_NEGATIVE_X, + CubeMapPositiveY => TEXTURE_CUBE_MAP_POSITIVE_Y, + CubeMapNegativeY => TEXTURE_CUBE_MAP_NEGATIVE_Y, + CubeMapPositiveZ => TEXTURE_CUBE_MAP_POSITIVE_Z, + CubeMapNegativeZ => TEXTURE_CUBE_MAP_NEGATIVE_Z, +} + +impl TexImageTarget { + pub fn is_cubic(&self) -> bool { + match *self { + TexImageTarget::Texture2D => false, + _ => true, + } + } +} + +type_safe_wrapper! { TexDataType, + UnsignedByte => UNSIGNED_BYTE, + UnsignedShort4444 => UNSIGNED_SHORT_4_4_4_4, + UnsignedShort5551 => UNSIGNED_SHORT_5_5_5_1, + UnsignedShort565 => UNSIGNED_SHORT_5_6_5, +} + +impl TexDataType { + /// Returns the size in bytes of each element of data. + pub fn element_size(&self) -> u32 { + use self::TexDataType::*; + match *self { + UnsignedByte => 1, + UnsignedShort4444 | + UnsignedShort5551 | + UnsignedShort565 => 2, + } + } + + /// Returns how many components a single element may hold. For example, a + /// UnsignedShort4444 holds four components, each with 4 bits of data. + pub fn components_per_element(&self) -> u32 { + use self::TexDataType::*; + match *self { + UnsignedByte => 1, + UnsignedShort565 => 3, + UnsignedShort5551 => 4, + UnsignedShort4444 => 4, + } + } +} + +type_safe_wrapper! { TexFormat, + DepthComponent => DEPTH_COMPONENT, + Alpha => ALPHA, + RGB => RGB, + RGBA => RGBA, + Luminance => LUMINANCE, + LuminanceAlpha => LUMINANCE_ALPHA, +} + +impl TexFormat { + /// Returns how many components does this format need. For example, RGBA + /// needs 4 components, while RGB requires 3. + pub fn components(&self) -> u32 { + use self::TexFormat::*; + match *self { + DepthComponent => 1, + Alpha => 1, + Luminance => 1, + LuminanceAlpha => 2, + RGB => 3, + RGBA => 4, + } + } +} |