aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2016-06-09 19:49:08 -0500
committerGitHub <noreply@github.com>2016-06-09 19:49:08 -0500
commit5e8ab6c0ff3eb8504c977f16dfc9507e36853e71 (patch)
treefc58554029ba8d8fe01cf1b9ef6b99f0865b332e /components/script
parent6f9016cf3edce828bf0edb5411b963d7c9f2d209 (diff)
parentfd32bd5a3a2388fa42504c8df6c0064d934e6c40 (diff)
downloadservo-5e8ab6c0ff3eb8504c977f16dfc9507e36853e71.tar.gz
servo-5e8ab6c0ff3eb8504c977f16dfc9507e36853e71.zip
Auto merge of #11168 - daoshengmu:texSubImage2D, r=emilio
Implement WebGL TexSubImage2D Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). Either: - [ ] There are tests for these changes OR - [X] These changes do not require tests because I have run the wpt test of texSubImage2D.html, and it works. Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. I have implemented ```TexSubImage2D``` follow [the spec](https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8). This is my first version of implementation, and I notice I can reuse the code from ```TexImage2D```. Therefore, I would like to discuss make ```validate_tex_image2D_from_buffer``` and ```validate_tex_image2D_from_source``` to remove duplicate code. Part of #10209 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/11168) <!-- Reviewable:end -->
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/webglrenderingcontext.rs403
-rw-r--r--components/script/dom/webgltexture.rs11
-rw-r--r--components/script/dom/webidls/WebGLRenderingContext.webidl10
3 files changed, 315 insertions, 109 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()
}
diff --git a/components/script/dom/webidls/WebGLRenderingContext.webidl b/components/script/dom/webidls/WebGLRenderingContext.webidl
index 3ed1c8cdb31..679fedf354b 100644
--- a/components/script/dom/webidls/WebGLRenderingContext.webidl
+++ b/components/script/dom/webidls/WebGLRenderingContext.webidl
@@ -646,11 +646,11 @@ interface WebGLRenderingContextBase
void texParameterf(GLenum target, GLenum pname, GLfloat param);
void texParameteri(GLenum target, GLenum pname, GLint param);
- //void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
- // GLsizei width, GLsizei height,
- // GLenum format, GLenum type, ArrayBufferView? pixels);
- //void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
- // GLenum format, GLenum type, TexImageSource? source); // May throw DOMException
+ void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
+ GLsizei width, GLsizei height,
+ GLenum format, GLenum type, optional object data);
+ void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
+ GLenum format, GLenum type, TexImageSource? source); // May throw DOMException
void uniform1f(WebGLUniformLocation? location, GLfloat x);
//void uniform1fv(WebGLUniformLocation? location, Float32Array v);