diff options
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 403 | ||||
-rw-r--r-- | components/script/dom/webgltexture.rs | 11 |
2 files changed, 310 insertions, 104 deletions
diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 70379e772d2..b23ec913eca 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -40,6 +40,7 @@ use util::vec::byte_swap; use webrender_traits::WebGLError::*; use webrender_traits::{WebGLCommand, WebGLError, WebGLFramebufferBindingRequest, WebGLParameter}; +type ImagePixelResult = Result<(Vec<u8>, Size2D<i32>), ()>; pub const MAX_UNIFORM_AND_ATTRIBUTE_LEN: usize = 256; macro_rules! handle_potential_webgl_error { @@ -263,6 +264,73 @@ impl WebGLRenderingContext { } } + fn get_image_pixels(&self, + source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) + -> ImagePixelResult { + let source = match source { + Some(s) => s, + None => return Err(()), + }; + + // NOTE: Getting the pixels probably can be short-circuited if some + // parameter is invalid. + // + // Nontheless, since it's the error case, I'm not totally sure the + // complexity is worth it. + let (pixels, size) = match source { + ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::ImageData(image_data) => { + let global = self.global(); + (image_data.get_data_array(&global.r()), image_data.get_size()) + }, + ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLImageElement(image) => { + let img_url = match image.get_url() { + Some(url) => url, + None => return Err(()), + }; + + let window = window_from_node(&*self.canvas); + + let img = match canvas_utils::request_image_from_cache(window.r(), img_url) { + ImageResponse::Loaded(img) => img, + ImageResponse::PlaceholderLoaded(_) | ImageResponse::None | + ImageResponse::MetadataLoaded(_) + => return Err(()), + }; + + let size = Size2D::new(img.width as i32, img.height as i32); + + // TODO(emilio): Validate that the format argument + // is coherent with the image. + // + // RGB8 should be easy to support too + let mut data = match img.format { + PixelFormat::RGBA8 => img.bytes.to_vec(), + _ => unimplemented!(), + }; + + byte_swap(&mut data); + + (data, size) + }, + // TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D, + // but we need to refactor it moving it to `HTMLCanvasElement` and support + // WebGLContext (probably via GetPixels()). + ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLCanvasElement(canvas) => { + let canvas = canvas.r(); + if let Some((mut data, size)) = canvas.fetch_all_data() { + byte_swap(&mut data); + (data, size) + } else { + return Err(()); + } + }, + ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLVideoElement(_rooted_video) + => unimplemented!(), + }; + + 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. @@ -281,6 +349,79 @@ impl WebGLRenderingContext { } } + 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 + }, + } + } + + #[allow(unsafe_code)] + fn validate_tex_image_2d_data(&self, + width: i32, + height: i32, + format: u32, + data_type: u32, + 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 + }; + + // If data is non-null, the type of pixels must match the type of the + // data to be read. + // If it is UNSIGNED_BYTE, a Uint8Array must be supplied; + // if it is UNSIGNED_SHORT_5_6_5, UNSIGNED_SHORT_4_4_4_4, + // or UNSIGNED_SHORT_5_5_5_1, a Uint16Array must be supplied. + // If the types do not match, an INVALID_OPERATION error is generated. + let received_size = if let Some(data) = data { + if unsafe { array_buffer_view_data_checked::<u16>(data).is_some() } { + 2 + } else if unsafe { array_buffer_view_data_checked::<u8>(data).is_some() } { + 1 + } else { + self.webgl_error(InvalidOperation); + return Err(()); + } + } else { + element_size + }; + + if received_size != element_size { + self.webgl_error(InvalidOperation); + return Err(()); + } + + // NOTE: width and height are positive or zero due to validate() + let expected_byte_length = width * height * element_size * components / components_per_element; + return Ok(expected_byte_length); + } fn validate_tex_image_2d_parameters(&self, target: u32, @@ -307,6 +448,10 @@ impl WebGLRenderingContext { return false; }, } + // Validate format + if !self.validate_tex_format(format) { + return false; + } // Validate internal_format if !self.validate_tex_internal_format(internal_format) { @@ -461,8 +606,8 @@ impl WebGLRenderingContext { width as u32, height as u32, 1, internal_format, - level as u32)); - + level as u32, + Some(data_type))); // TODO(emilio): Invert axis, convert colorspace, premultiply alpha if requested let msg = WebGLCommand::TexImage2D(target, level, internal_format as i32, @@ -472,6 +617,71 @@ impl WebGLRenderingContext { .send(CanvasMsg::WebGL(msg)) .unwrap() } + + fn tex_sub_image_2d(&self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + width: i32, + height: i32, + format: u32, + data_type: 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, + 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); + + // 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() { + return self.webgl_error(InvalidValue); + } + + // Using internal_format() to do this check + // because we are sure format is as same as internal_format. + if format != image_info.internal_format().unwrap() || + data_type != image_info.data_type().unwrap() { + return self.webgl_error(InvalidOperation); + } + + // 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); + + self.ipc_renderer + .send(CanvasMsg::WebGL(msg)) + .unwrap() + } } impl Drop for WebGLRenderingContext { @@ -889,7 +1099,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { width as u32, height as u32, 1, internal_format, - level as u32)); + level as u32, + None)); let msg = WebGLCommand::CopyTexImage2D(target, level, internal_format, x, y, width, height, border); @@ -1881,7 +2092,6 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { .unwrap() } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 fn TexImage2D(&self, _cx: *mut JSContext, @@ -1904,51 +2114,15 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return; // Error handled in validate() } - // 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 - }; - - // If data is non-null, the type of pixels must match the type of the - // data to be read. - // If it is UNSIGNED_BYTE, a Uint8Array must be supplied; - // if it is UNSIGNED_SHORT_5_6_5, UNSIGNED_SHORT_4_4_4_4, - // or UNSIGNED_SHORT_5_5_5_1, a Uint16Array must be supplied. - // If the types do not match, an INVALID_OPERATION error is generated. - let received_size = if let Some(data) = data { - if unsafe { array_buffer_view_data_checked::<u16>(data).is_some() } { - 2 - } else if unsafe { array_buffer_view_data_checked::<u8>(data).is_some() } { - 1 - } else { - return self.webgl_error(InvalidOperation); - } - } else { - element_size + let expected_byte_length = match self.validate_tex_image_2d_data(width, + height, + format, + data_type, + data) { + Ok(byte_length) => byte_length, + Err(_) => return, }; - if received_size != element_size { - return self.webgl_error(InvalidOperation); - } - - // NOTE: width and height are positive or zero due to validate() - let expected_byte_length = width * height * element_size * components / components_per_element; - - // If data is null, a buffer of sufficient size // initialized to 0 is passed. let buff = if let Some(data) = data { @@ -1976,79 +2150,102 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { format: u32, data_type: u32, source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) { - let source = match source { - Some(s) => s, - None => return, + // 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, internal_format, + size.width, size.height, 0, + format, data_type) { + return; // Error handled in validate() + } - // NOTE: Getting the pixels probably can be short-circuited if some - // parameter is invalid. - // - // Nontheless, since it's the error case, I'm not totally sure the - // complexity is worth it. - let (pixels, size) = match source { - ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::ImageData(image_data) => { - let global = self.global(); - (image_data.get_data_array(&global.r()), image_data.get_size()) - }, - ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLImageElement(image) => { - let img_url = match image.get_url() { - Some(url) => url, - None => return, - }; + self.tex_image_2d(target, level, + internal_format, + size.width, size.height, 0, + format, data_type, pixels); + } - let window = window_from_node(&*self.canvas); + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexSubImage2D(&self, + _cx: *mut JSContext, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + width: i32, + height: i32, + 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 img = match canvas_utils::request_image_from_cache(window.r(), img_url) { - ImageResponse::Loaded(img) => img, - ImageResponse::PlaceholderLoaded(_) | ImageResponse::None | - ImageResponse::MetadataLoaded(_) - => return, - }; + let expected_byte_length = match self.validate_tex_image_2d_data(width, + height, + format, + data_type, + data) { + Ok(byte_length) => byte_length, + Err(()) => return, + }; - let size = Size2D::new(img.width as i32, img.height as i32); + // If data is null, a buffer of sufficient size + // initialized to 0 is passed. + let buff = if let Some(data) = data { + array_buffer_view_to_vec::<u8>(data) + .expect("Can't reach here without being an ArrayBufferView!") + } else { + vec![0u8; expected_byte_length as usize] + }; - // TODO(emilio): Validate that the format argument - // is coherent with the image. - // - // RGB8 should be easy to support too - let mut data = match img.format { - PixelFormat::RGBA8 => img.bytes.to_vec(), - _ => unimplemented!(), - }; + if expected_byte_length != 0 && + buff.len() != expected_byte_length as usize { + return self.webgl_error(InvalidOperation); + } - byte_swap(&mut data); + self.tex_sub_image_2d(target, level, + xoffset, yoffset, + width, height, + format, data_type, buff); + } - (data, size) - }, - // TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D, - // but we need to refactor it moving it to `HTMLCanvasElement` and support - // WebGLContext (probably via GetPixels()). - ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLCanvasElement(canvas) => { - let canvas = canvas.r(); - if let Some((mut data, size)) = canvas.fetch_all_data() { - byte_swap(&mut data); - (data, size) - } else { - return - } - }, - ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLVideoElement(_rooted_video) - => unimplemented!(), + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexSubImage2D_(&self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + 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, internal_format, + if !self.validate_tex_image_2d_parameters(target, level, 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) + self.tex_sub_image_2d(target, level, + xoffset, yoffset, + size.width, size.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 ccd05182968..bd9f34515ba 100644 --- a/components/script/dom/webgltexture.rs +++ b/components/script/dom/webgltexture.rs @@ -109,13 +109,15 @@ impl WebGLTexture { height: u32, depth: u32, internal_format: u32, - level: u32) -> WebGLResult<()> { + level: u32, + data_type: Option<u32>) -> WebGLResult<()> { let image_info = ImageInfo { width: width, height: height, depth: depth, internal_format: Some(internal_format), is_initialized: true, + data_type: data_type, }; let face = match target { @@ -274,6 +276,7 @@ impl WebGLTexture { depth: 0, internal_format: base_image_info.internal_format, is_initialized: base_image_info.is_initialized(), + data_type: base_image_info.data_type, }; self.set_image_infos_at_level(level, image_info); @@ -346,6 +349,7 @@ pub struct ImageInfo { depth: u32, internal_format: Option<u32>, is_initialized: bool, + data_type: Option<u32>, } impl ImageInfo { @@ -356,6 +360,7 @@ impl ImageInfo { depth: 0, internal_format: None, is_initialized: false, + data_type: None, } } @@ -371,6 +376,10 @@ impl ImageInfo { self.internal_format } + pub fn data_type(&self) -> Option<u32> { + 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() } |