aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/script/dom/webglrenderingcontext.rs403
-rw-r--r--components/script/dom/webgltexture.rs11
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()
}