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 | |
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')
-rw-r--r-- | components/script/dom/mod.rs | 1 | ||||
-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 | ||||
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 591 | ||||
-rw-r--r-- | components/script/dom/webgltexture.rs | 54 |
6 files changed, 706 insertions, 415 deletions
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index ff1a4638998..d76721ed040 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -394,6 +394,7 @@ pub mod validation; pub mod validitystate; pub mod values; pub mod virtualmethods; +pub mod webgl_validations; pub mod webglactiveinfo; pub mod webglbuffer; pub mod webglcontextevent; 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, + } + } +} diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index b23ec913eca..46d562afa05 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -18,6 +18,10 @@ use dom::event::{Event, EventBubbles, EventCancelable}; use dom::htmlcanvaselement::HTMLCanvasElement; use dom::htmlcanvaselement::utils as canvas_utils; use dom::node::{Node, NodeDamage, window_from_node}; +use dom::webgl_validations::WebGLValidator; +use dom::webgl_validations::tex_image_2d::{CommonTexImage2DValidator, CommonTexImage2DValidatorResult}; +use dom::webgl_validations::tex_image_2d::{TexImage2DValidator, TexImage2DValidatorResult}; +use dom::webgl_validations::types::{TexFormat, TexImageTarget, TexDataType}; use dom::webglactiveinfo::WebGLActiveInfo; use dom::webglbuffer::WebGLBuffer; use dom::webglcontextevent::WebGLContextEvent; @@ -88,10 +92,6 @@ pub struct WebGLRenderingContext { current_vertex_attrib_0: Cell<(f32, f32, f32, f32)>, } -fn log2(n: u32) -> u32 { - 31 - n.leading_zeros() -} - impl WebGLRenderingContext { fn new_inherited(global: GlobalRef, canvas: &HTMLCanvasElement, @@ -141,6 +141,22 @@ impl WebGLRenderingContext { } } + pub fn limits(&self) -> &GLLimits { + &self.limits + } + + pub fn bound_texture_for_target(&self, target: &TexImageTarget) -> Option<Root<WebGLTexture>> { + match *target { + TexImageTarget::Texture2D => self.bound_texture_2d.get(), + TexImageTarget::CubeMapPositiveX | + TexImageTarget::CubeMapNegativeX | + TexImageTarget::CubeMapPositiveY | + TexImageTarget::CubeMapNegativeY | + TexImageTarget::CubeMapPositiveZ | + TexImageTarget::CubeMapNegativeZ => self.bound_texture_cube_map.get(), + } + } + pub fn recreate(&self, size: Size2D<i32>) { self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Recreate(size))).unwrap(); } @@ -239,31 +255,6 @@ impl WebGLRenderingContext { true } - fn texture_for_target(&self, target: u32) -> Option<Root<WebGLTexture>> { - match target { - constants::TEXTURE_2D => self.bound_texture_2d.get(), - constants::TEXTURE_CUBE_MAP_POSITIVE_X | constants::TEXTURE_CUBE_MAP_NEGATIVE_X | - constants::TEXTURE_CUBE_MAP_POSITIVE_Y | constants::TEXTURE_CUBE_MAP_NEGATIVE_Y | - constants::TEXTURE_CUBE_MAP_POSITIVE_Z | constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => { - self.bound_texture_cube_map.get() - }, - _ => None, - } - } - - fn face_index_for_target(&self, target: u32) -> Option<u8> { - match target { - constants::TEXTURE_2D => Some(0), - constants::TEXTURE_CUBE_MAP_POSITIVE_X => Some(0), - constants::TEXTURE_CUBE_MAP_NEGATIVE_X => Some(1), - constants::TEXTURE_CUBE_MAP_POSITIVE_Y => Some(2), - constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => Some(3), - constants::TEXTURE_CUBE_MAP_POSITIVE_Z => Some(4), - constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => Some(5), - _ => None - } - } - fn get_image_pixels(&self, source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) -> ImagePixelResult { @@ -331,68 +322,18 @@ impl WebGLRenderingContext { return Ok((pixels, size)); } - fn validate_tex_internal_format(&self, internal_format: u32) -> bool { - // GL_INVALID_VALUE is generated if internal_format is not an - // accepted format. - match internal_format { - constants::DEPTH_COMPONENT | - constants::ALPHA | - constants::RGB | - constants::RGBA | - constants::LUMINANCE | - constants::LUMINANCE_ALPHA => true, - - _ => { - self.webgl_error(InvalidValue); - false - }, - } - } - - fn validate_tex_format(&self, format: u32) -> bool { - // GL_INVALID_VALUE is generated if internal_format is not an - // accepted format. - match format { - constants::DEPTH_COMPONENT | - constants::ALPHA | - constants::RGB | - constants::RGBA | - constants::LUMINANCE | - constants::LUMINANCE_ALPHA => true, - - _ => { - self.webgl_error(InvalidEnum); - false - }, - } - } - + // TODO(emilio): Move this logic to a validator. #[allow(unsafe_code)] fn validate_tex_image_2d_data(&self, - width: i32, - height: i32, - format: u32, - data_type: u32, + width: u32, + height: u32, + format: TexFormat, + data_type: TexDataType, data: Option<*mut JSObject>) - -> Result<i32, ()> { - // TODO(emilio, #10693): Add type-safe wrappers to validations - let (element_size, components_per_element) = match data_type { - constants::UNSIGNED_BYTE => (1, 1), - constants::UNSIGNED_SHORT_5_6_5 => (2, 3), - constants::UNSIGNED_SHORT_5_5_5_1 | - constants::UNSIGNED_SHORT_4_4_4_4 => (2, 4), - _ => unreachable!(), // previously validated - }; - - let components = match format { - constants::DEPTH_COMPONENT => 1, - constants::ALPHA => 1, - constants::LUMINANCE => 1, - constants::LUMINANCE_ALPHA => 2, - constants::RGB => 3, - constants::RGBA => 4, - _ => unreachable!(), // previously validated - }; + -> Result<u32, ()> { + let element_size = data_type.element_size(); + let components_per_element = data_type.components_per_element(); + let components = format.components(); // If data is non-null, the type of pixels must match the type of the // data to be read. @@ -423,178 +364,18 @@ impl WebGLRenderingContext { return Ok(expected_byte_length); } - fn validate_tex_image_2d_parameters(&self, - target: u32, - level: i32, - internal_format: u32, - width: i32, - height: i32, - border: i32, - format: u32, - data_type: u32) -> bool { - // Validate common tex image parameters - if !self.validate_common_tex_image_parameters(target, level, width, height) { - return false; - } - - // GL_INVALID_ENUM is generated if data_type is not an accepted value. - match data_type { - constants::UNSIGNED_BYTE | - constants::UNSIGNED_SHORT_4_4_4_4 | - constants::UNSIGNED_SHORT_5_5_5_1 | - constants::UNSIGNED_SHORT_5_6_5 => {}, - _ => { - self.webgl_error(InvalidEnum); - return false; - }, - } - // Validate format - if !self.validate_tex_format(format) { - return false; - } - - // Validate internal_format - if !self.validate_tex_internal_format(internal_format) { - return false; - } - - // GL_INVALID_OPERATION is generated if format does not - // match internal_format. - if format != internal_format { - self.webgl_error(InvalidOperation); - return false; - } - - // GL_INVALID_VALUE is generated if border is not 0. - if border != 0 { - self.webgl_error(InvalidValue); - return false; - } - - // 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 { - constants::UNSIGNED_SHORT_4_4_4_4 | - constants::UNSIGNED_SHORT_5_5_5_1 if format != constants::RGBA => { - self.webgl_error(InvalidOperation); - return false; - }, - constants::UNSIGNED_SHORT_5_6_5 if format != constants::RGB => { - self.webgl_error(InvalidOperation); - return false; - }, - _ => {}, - } - - true - } - - fn validate_common_tex_image_parameters(&self, - target: u32, - level: i32, - width: i32, - height: i32) -> bool { - // 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. - // - // max_size is GL_MAX_TEXTURE_SIZE when target is GL_TEXTURE_2D or - // GL_MAX_CUBE_MAP_TEXTURE_SIZE when target is not GL_TEXTURE_2D. - let (texture, max) = match target { - constants::TEXTURE_2D - => (self.bound_texture_2d.get(), self.limits.max_tex_size), - constants::TEXTURE_CUBE_MAP_POSITIVE_X | - constants::TEXTURE_CUBE_MAP_NEGATIVE_X | - constants::TEXTURE_CUBE_MAP_POSITIVE_Y | - constants::TEXTURE_CUBE_MAP_NEGATIVE_Y | - constants::TEXTURE_CUBE_MAP_POSITIVE_Z | - constants::TEXTURE_CUBE_MAP_NEGATIVE_Z - => (self.bound_texture_cube_map.get(), self.limits.max_cube_map_tex_size), - _ => { - self.webgl_error(InvalidEnum); - return false; - }, - }; - - // If an attempt is made to call this function with no - // WebGLTexture bound, an INVALID_OPERATION error is generated. - if texture.is_none() { - self.webgl_error(InvalidOperation); - return false; - } - - let is_cubic = target != constants::TEXTURE_2D; - - // 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 is_cubic && width != height { - self.webgl_error(InvalidValue); - return false; - } - - // GL_INVALID_VALUE is generated if level is less than 0. - // - // GL_INVALID_VALUE is generated if width or height is less than 0 - if width < 0 || height < 0 || level < 0 || - width as u32 > max || height as u32 > max { - self.webgl_error(InvalidValue); - return false; - } - - // 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) as i32 { - self.webgl_error(InvalidValue); - return false; - } - - // GL_INVALID_VALUE is generated if level is greater than zero and the - // texture is not power of two. - if level > 0 && - (!(width as u32).is_power_of_two() || - !(height as u32).is_power_of_two()) { - self.webgl_error(InvalidValue); - return false; - } - - true - } - fn tex_image_2d(&self, - target: u32, - level: i32, - internal_format: u32, - width: i32, - height: i32, - border: i32, - format: u32, - data_type: u32, + texture: Root<WebGLTexture>, + target: TexImageTarget, + data_type: TexDataType, + internal_format: TexFormat, + level: u32, + width: u32, + height: u32, + _border: u32, pixels: Vec<u8>) { // NB: pixels should NOT be premultipied - // This should be validated before reaching this function - debug_assert!(self.validate_tex_image_2d_parameters(target, level, - internal_format, - width, height, - border, format, - data_type)); - - let slot = match target { - constants::TEXTURE_2D - => self.bound_texture_2d.get(), - _ => self.bound_texture_cube_map.get(), - }; - - let texture = slot.as_ref().expect("No bound texture found after validation"); - - if format == constants::RGBA && - data_type == constants::UNSIGNED_BYTE && + if internal_format == TexFormat::RGBA && + data_type == TexDataType::UnsignedByte && self.texture_unpacking_settings.get().contains(PREMULTIPLY_ALPHA) { // TODO(emilio): premultiply here. } @@ -603,15 +384,18 @@ impl WebGLRenderingContext { // TexImage2D depth is always equal to 1 handle_potential_webgl_error!(self, texture.initialize(target, - width as u32, - height as u32, 1, + width, + height, 1, internal_format, - level as u32, + level, Some(data_type))); // TODO(emilio): Invert axis, convert colorspace, premultiply alpha if requested - let msg = WebGLCommand::TexImage2D(target, level, internal_format as i32, - width, height, format, data_type, pixels); + let msg = WebGLCommand::TexImage2D(target.as_gl_constant(), level as i32, + internal_format.as_gl_constant() as i32, + width as i32, height as i32, + internal_format.as_gl_constant(), + data_type.as_gl_constant(), pixels); self.ipc_renderer .send(CanvasMsg::WebGL(msg)) @@ -619,54 +403,29 @@ impl WebGLRenderingContext { } fn tex_sub_image_2d(&self, - target: u32, - level: i32, + texture: Root<WebGLTexture>, + target: TexImageTarget, + level: u32, xoffset: i32, yoffset: i32, - width: i32, - height: i32, - format: u32, - data_type: u32, + width: u32, + height: u32, + format: TexFormat, + data_type: TexDataType, pixels: Vec<u8>) { // NB: pixels should NOT be premultipied - // This should be validated before reaching this function - debug_assert!(self.validate_tex_image_2d_parameters(target, level, - format, - width, height, - 0, format, - data_type)); - - let slot = match target { - constants::TEXTURE_2D - => self.bound_texture_2d.get(), - constants::TEXTURE_CUBE_MAP - => self.bound_texture_cube_map.get(), - - _ => return self.webgl_error(InvalidEnum), - }; - - let texture = slot.as_ref().expect("No bound texture found after validation"); - - if format == constants::RGBA && - data_type == constants::UNSIGNED_BYTE && - self.texture_unpacking_settings.get().contains(PREMULTIPLY_ALPHA) { - // TODO(emilio): premultiply here. - } - // We have already validated level - let face_index = self.face_index_for_target(target).unwrap(); - let image_info = texture.image_info_at_face(face_index, level as u32); + let image_info = texture.image_info_for_target(&target, level); // GL_INVALID_VALUE is generated if: // - xoffset or yoffset is less than 0 // - x offset plus the width is greater than the texture width // - y offset plus the height is greater than the texture height - if xoffset < 0 || ((xoffset + width) as u32) > image_info.width() || - yoffset < 0 || ((yoffset + height) as u32) > image_info.height() { + if xoffset < 0 || (xoffset as u32 + width) > image_info.width() || + yoffset < 0 || (yoffset as u32 + height) > image_info.height() { return self.webgl_error(InvalidValue); } - // Using internal_format() to do this check - // because we are sure format is as same as internal_format. + // NB: format and internal_format must match. if format != image_info.internal_format().unwrap() || data_type != image_info.data_type().unwrap() { return self.webgl_error(InvalidOperation); @@ -675,8 +434,11 @@ impl WebGLRenderingContext { // TODO(emilio): Flip Y axis if necessary here // TODO(emilio): Invert axis, convert colorspace, premultiply alpha if requested - let msg = WebGLCommand::TexSubImage2D(target, level, xoffset, yoffset, - width, height, format, data_type, pixels); + let msg = WebGLCommand::TexSubImage2D(target.as_gl_constant(), + level as i32, xoffset, yoffset, + width as i32, height as i32, + format.as_gl_constant(), + data_type.as_gl_constant(), pixels); self.ipc_renderer .send(CanvasMsg::WebGL(msg)) @@ -1058,43 +820,39 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 fn CopyTexImage2D(&self, target: u32, level: i32, internal_format: u32, x: i32, y: i32, width: i32, height: i32, border: i32) { - // Validate common tex image parameters - if !self.validate_common_tex_image_parameters(target, level, width, height) || - !self.validate_tex_internal_format(internal_format) { - return; - } - - // GL_INVALID_VALUE is generated if the border is not 0 - if border != 0 { - self.webgl_error(InvalidValue); - return; - } - - let texture = self.texture_for_target(target).unwrap(); - let face_index = self.face_index_for_target(target).unwrap(); - - // We have already validated level - let image_info = texture.image_info_at_face(face_index, level as u32); - - // The color buffer components can be dropped during the conversion to the - // internal_format, but new components cannot be added - let invalid_format = match image_info.internal_format() { - Some(src_format) => match (src_format, internal_format) { - (constants::ALPHA, constants::ALPHA) | (constants::RGB, constants::RGB) | - (constants::RGB, constants::LUMINANCE) | (constants::RGBA, _) => false, - _ => true, - }, - None => false, + let validator = CommonTexImage2DValidator::new(self, target, level, + internal_format, width, + height, border); + let CommonTexImage2DValidatorResult { + texture, + target, + level, + internal_format, + width, + height, + border, + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, }; + let image_info = texture.image_info_for_target(&target, level); + + // The color buffer components can be dropped during the conversion to + // the internal_format, but new components cannot be added. + // + // Note that this only applies if we're copying to an already + // initialized texture. + // // GL_INVALID_OPERATION is generated if the color buffer cannot be - // converted to the internal_format - if invalid_format { - self.webgl_error(InvalidOperation); - return; + // converted to the internal_format. + if let Some(old_internal_format) = image_info.internal_format() { + if old_internal_format.components() > internal_format.components() { + return self.webgl_error(InvalidOperation); + } } - // TexImage2D depth is always equal to 1 + // NB: TexImage2D depth is always equal to 1 handle_potential_webgl_error!(self, texture.initialize(target, width as u32, height as u32, 1, @@ -1102,8 +860,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { level as u32, None)); - let msg = WebGLCommand::CopyTexImage2D(target, level, internal_format, x, y, - width, height, border); + let msg = WebGLCommand::CopyTexImage2D(target.as_gl_constant(), + level as i32, + internal_format.as_gl_constant(), + x, y, + width as i32, height as i32, + border as i32); self.ipc_renderer.send(CanvasMsg::WebGL(msg)).unwrap() } @@ -1111,29 +873,39 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 fn CopyTexSubImage2D(&self, target: u32, level: i32, xoffset: i32, yoffset: i32, x: i32, y: i32, width: i32, height: i32) { - // Validate common tex image parameters - if !self.validate_common_tex_image_parameters(target, level, width, height) { - return; - } - - let texture = self.texture_for_target(target).unwrap(); - let face_index = self.face_index_for_target(target).unwrap(); + // NB: We use a dummy (valid) format and border in order to reuse the + // common validations, but this should have its own validator. + let validator = CommonTexImage2DValidator::new(self, target, level, + TexFormat::RGBA.as_gl_constant(), + width, height, 0); + let CommonTexImage2DValidatorResult { + texture, + target, + level, + width, + height, + .. + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, + }; - // We have already validated level - let image_info = texture.image_info_at_face(face_index, level as u32); + let image_info = texture.image_info_for_target(&target, level); // GL_INVALID_VALUE is generated if: // - xoffset or yoffset is less than 0 // - x offset plus the width is greater than the texture width // - y offset plus the height is greater than the texture height - if xoffset < 0 || ((xoffset + width) as u32) > image_info.width() || - yoffset < 0 || ((yoffset + height) as u32) > image_info.height() { + if xoffset < 0 || (xoffset as u32 + width) > image_info.width() || + yoffset < 0 || (yoffset as u32 + height) > image_info.height() { self.webgl_error(InvalidValue); return; } - let msg = WebGLCommand::CopyTexSubImage2D(target, level, xoffset, yoffset, - x, y, width, height); + let msg = WebGLCommand::CopyTexSubImage2D(target.as_gl_constant(), + level as i32, xoffset, yoffset, + x, y, + width as i32, height as i32); self.ipc_renderer.send(CanvasMsg::WebGL(msg)).unwrap(); } @@ -2104,15 +1876,23 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { format: u32, data_type: u32, data: Option<*mut JSObject>) { - if !self.validate_tex_image_2d_parameters(target, - level, - internal_format, - width, height, - border, - format, - data_type) { - return; // Error handled in validate() - } + let validator = TexImage2DValidator::new(self, target, level, + internal_format, width, height, + border, format, data_type); + + let TexImage2DValidatorResult { + texture, + target, + width, + height, + level, + border, + format, + data_type, + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, // NB: The validator sets the correct error for us. + }; let expected_byte_length = match self.validate_tex_image_2d_data(width, height, @@ -2136,10 +1916,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return self.webgl_error(InvalidOperation); } - self.tex_image_2d(target, level, - internal_format, - width, height, border, - format, data_type, buff) + self.tex_image_2d(texture, target, data_type, format, + level, width, height, border, buff) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 @@ -2156,17 +1934,28 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Err(_) => return, }; - // NB: Border must be zero - if !self.validate_tex_image_2d_parameters(target, level, internal_format, - size.width, size.height, 0, - format, data_type) { - return; // Error handled in validate() - } - self.tex_image_2d(target, level, - internal_format, - size.width, size.height, 0, - format, data_type, pixels); + let validator = TexImage2DValidator::new(self, + target, level, internal_format, + size.width, size.height, + 0, format, data_type); + + let TexImage2DValidatorResult { + texture, + target, + width, + height, + level, + border, + format, + data_type, + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, // NB: The validator sets the correct error for us. + }; + + self.tex_image_2d(texture, target, data_type, format, + level, width, height, border, pixels); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 @@ -2181,15 +1970,22 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { format: u32, data_type: u32, data: Option<*mut JSObject>) { - if !self.validate_tex_image_2d_parameters(target, - level, - format, - width, height, - 0, - format, - data_type) { - return; // Error handled in validate() - } + let validator = TexImage2DValidator::new(self, target, level, + format, width, height, + 0, format, data_type); + let TexImage2DValidatorResult { + texture, + target, + width, + height, + level, + format, + data_type, + .. + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, // NB: The validator sets the correct error for us. + }; let expected_byte_length = match self.validate_tex_image_2d_data(width, height, @@ -2214,10 +2010,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return self.webgl_error(InvalidOperation); } - self.tex_sub_image_2d(target, level, - xoffset, yoffset, - width, height, - format, data_type, buff); + self.tex_sub_image_2d(texture, target, level, xoffset, yoffset, + width, height, format, data_type, buff); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 @@ -2229,23 +2023,30 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { format: u32, data_type: u32, source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) { - // Get pixels from image source let (pixels, size) = match self.get_image_pixels(source) { Ok((pixels, size)) => (pixels, size), Err(_) => return, }; - // NB: Border must be zero - if !self.validate_tex_image_2d_parameters(target, level, format, - size.width, size.height, 0, - format, data_type) { - return; // Error handled in validate() - } + let validator = TexImage2DValidator::new(self, target, level, format, + size.width, size.height, + 0, format, data_type); + let TexImage2DValidatorResult { + texture, + target, + width, + height, + level, + format, + data_type, + .. + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, // NB: The validator sets the correct error for us. + }; - self.tex_sub_image_2d(target, level, - xoffset, yoffset, - size.width, size.height, - format, data_type, pixels); + self.tex_sub_image_2d(texture, target, level, xoffset, yoffset, + width, height, format, data_type, pixels); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 diff --git a/components/script/dom/webgltexture.rs b/components/script/dom/webgltexture.rs index bd9f34515ba..647b36ddfe7 100644 --- a/components/script/dom/webgltexture.rs +++ b/components/script/dom/webgltexture.rs @@ -10,6 +10,7 @@ use dom::bindings::codegen::Bindings::WebGLTextureBinding; use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; use dom::bindings::reflector::reflect_dom_object; +use dom::webgl_validations::types::{TexImageTarget, TexFormat, TexDataType}; use dom::webglobject::WebGLObject; use ipc_channel::ipc::{self, IpcSender}; use std::cell::Cell; @@ -104,13 +105,13 @@ impl WebGLTexture { } pub fn initialize(&self, - target: u32, + target: TexImageTarget, width: u32, height: u32, depth: u32, - internal_format: u32, + internal_format: TexFormat, level: u32, - data_type: Option<u32>) -> WebGLResult<()> { + data_type: Option<TexDataType>) -> WebGLResult<()> { let image_info = ImageInfo { width: width, height: height, @@ -120,17 +121,8 @@ impl WebGLTexture { data_type: data_type, }; - let face = match target { - constants::TEXTURE_2D | constants::TEXTURE_CUBE_MAP_POSITIVE_X => 0, - constants::TEXTURE_CUBE_MAP_NEGATIVE_X => 1, - constants::TEXTURE_CUBE_MAP_POSITIVE_Y => 2, - constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => 3, - constants::TEXTURE_CUBE_MAP_POSITIVE_Z => 4, - constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => 5, - _ => unreachable!(), - }; - - self.set_image_infos_at_level_and_face(level, face, image_info); + let face_index = self.face_index_for_target(&target); + self.set_image_infos_at_level_and_face(level, face_index, image_info); Ok(()) } @@ -312,7 +304,27 @@ impl WebGLTexture { true } - pub fn image_info_at_face(&self, face: u8, level: u32) -> ImageInfo { + fn face_index_for_target(&self, + target: &TexImageTarget) -> u8 { + match *target { + TexImageTarget::Texture2D => 0, + TexImageTarget::CubeMapPositiveX => 0, + TexImageTarget::CubeMapNegativeX => 1, + TexImageTarget::CubeMapPositiveY => 2, + TexImageTarget::CubeMapNegativeY => 3, + TexImageTarget::CubeMapPositiveZ => 4, + TexImageTarget::CubeMapNegativeZ => 5, + } + } + + pub fn image_info_for_target(&self, + target: &TexImageTarget, + level: u32) -> ImageInfo { + let face_index = self.face_index_for_target(&target); + self.image_info_at_face(face_index, level) + } + + fn image_info_at_face(&self, face: u8, level: u32) -> ImageInfo { let pos = (level * self.face_count.get() as u32) + face as u32; self.image_info_array.borrow()[pos as usize] } @@ -347,9 +359,9 @@ pub struct ImageInfo { width: u32, height: u32, depth: u32, - internal_format: Option<u32>, + internal_format: Option<TexFormat>, is_initialized: bool, - data_type: Option<u32>, + data_type: Option<TexDataType>, } impl ImageInfo { @@ -372,16 +384,18 @@ impl ImageInfo { self.height } - pub fn internal_format(&self) -> Option<u32> { + pub fn internal_format(&self) -> Option<TexFormat> { self.internal_format } - pub fn data_type(&self) -> Option<u32> { + pub fn data_type(&self) -> Option<TexDataType> { self.data_type } fn is_power_of_two(&self) -> bool { - self.width.is_power_of_two() && self.height.is_power_of_two() && self.depth.is_power_of_two() + self.width.is_power_of_two() && + self.height.is_power_of_two() && + self.depth.is_power_of_two() } fn is_initialized(&self) -> bool { |