servo: Merge #11168 - Implement WebGL TexSubImage2D (from daoshengmu:texSubImage2D); r=emilio
authorDaosheng Mu <daoshengmu@gmail.com>
Thu, 09 Jun 2016 19:49:08 -0500
changeset 339055 3f9be465a8dd91484d74e5b8aa73748f51c4c81c
parent 339054 ff6c198258187ebca567112edb201a2f37dd9b57
child 339056 fc43c04d381d0398286f23f8161a4820a4155cdd
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
servo: Merge #11168 - Implement WebGL TexSubImage2D (from daoshengmu:texSubImage2D); r=emilio 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 Source-Repo: https://github.com/servo/servo Source-Revision: 5e8ab6c0ff3eb8504c977f16dfc9507e36853e71
servo/components/script/dom/webglrenderingcontext.rs
servo/components/script/dom/webgltexture.rs
servo/components/script/dom/webidls/WebGLRenderingContext.webidl
--- a/servo/components/script/dom/webglrenderingcontext.rs
+++ b/servo/components/script/dom/webglrenderingcontext.rs
@@ -35,16 +35,17 @@ use net_traits::image::base::PixelFormat
 use net_traits::image_cache_thread::ImageResponse;
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use script_traits::ScriptMsg as ConstellationMsg;
 use std::cell::Cell;
 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 {
     ($context:ident, $call:expr, $return_on_error:expr) => {
         match $call {
             Ok(ret) => ret,
             Err(error) => {
                 $context.webgl_error(error);
@@ -258,16 +259,83 @@ impl WebGLRenderingContext {
             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 {
+        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.
         match internal_format {
             constants::DEPTH_COMPONENT |
             constants::ALPHA |
             constants::RGB |
             constants::RGBA |
@@ -276,16 +344,89 @@ impl WebGLRenderingContext {
 
             _ => {
                 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
+            },
+        }
+    }
+
+    #[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,
                                         level: i32,
                                         internal_format: u32,
                                         width: i32,
                                         height: i32,
                                         border: i32,
@@ -302,16 +443,20 @@ impl WebGLRenderingContext {
             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.
@@ -456,27 +601,92 @@ impl WebGLRenderingContext {
 
         // TODO(emilio): Flip Y axis if necessary here
 
         // TexImage2D depth is always equal to 1
         handle_potential_webgl_error!(self, texture.initialize(target,
                                                                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,
                                            width, height, format, data_type, pixels);
 
         self.ipc_renderer
             .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 {
     fn drop(&mut self) {
         self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Close)).unwrap();
     }
 }
 
@@ -884,17 +1094,18 @@ impl WebGLRenderingContextMethods for We
             return;
         }
 
         // TexImage2D depth is always equal to 1
         handle_potential_webgl_error!(self, texture.initialize(target,
                                                                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);
 
         self.ipc_renderer.send(CanvasMsg::WebGL(msg)).unwrap()
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
@@ -1876,17 +2087,16 @@ impl WebGLRenderingContextMethods for We
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4
     fn Viewport(&self, x: i32, y: i32, width: i32, height: i32) {
         self.ipc_renderer
             .send(CanvasMsg::WebGL(WebGLCommand::Viewport(x, y, width, height)))
             .unwrap()
     }
 
-    #[allow(unsafe_code)]
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
     fn TexImage2D(&self,
                   _cx: *mut JSContext,
                   target: u32,
                   level: i32,
                   internal_format: u32,
                   width: i32,
                   height: i32,
@@ -1899,61 +2109,25 @@ impl WebGLRenderingContextMethods for We
                                                   internal_format,
                                                   width, height,
                                                   border,
                                                   format,
                                                   data_type) {
             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
+        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 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
-        };
-
-        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 {
             array_buffer_view_to_vec::<u8>(data)
                 .expect("Can't reach here without being an ArrayBufferView!")
         } else {
             vec![0u8; expected_byte_length as usize]
         };
@@ -1971,89 +2145,112 @@ impl WebGLRenderingContextMethods for We
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
     fn TexImage2D_(&self,
                    target: u32,
                    level: i32,
                    internal_format: u32,
                    format: u32,
                    data_type: u32,
                    source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) {
-        let source = match source {
-            Some(s) => s,
-            None => return,
-        };
-
-
-        // 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,
-                };
-
-                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,
-                };
-
-                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
-                }
-            },
-            ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLVideoElement(_rooted_video)
-                => unimplemented!(),
+        // 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()
         }
 
         self.tex_image_2d(target, level,
                           internal_format,
                           size.width, size.height, 0,
-                          format, data_type, pixels)
+                          format, data_type, pixels);
+    }
+
+    // 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 expected_byte_length = match self.validate_tex_image_2d_data(width,
+                                                                         height,
+                                                                         format,
+                                                                         data_type,
+                                                                         data) {
+            Ok(byte_length) => byte_length,
+            Err(()) => return,
+        };
+
+        // 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]
+        };
+
+        if expected_byte_length != 0 &&
+            buff.len() != expected_byte_length as usize {
+            return self.webgl_error(InvalidOperation);
+        }
+
+        self.tex_sub_image_2d(target, level,
+                              xoffset, yoffset,
+                              width, height,
+                              format, data_type, buff);
+    }
+
+    // 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, format,
+                                                  size.width, size.height, 0,
+                                                  format, data_type) {
+            return; // Error handled in validate()
+        }
+
+        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
     fn TexParameterf(&self, target: u32, name: u32, value: f32) {
         self.tex_parameter(target, name, TexParameterValue::Float(value))
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
--- a/servo/components/script/dom/webgltexture.rs
+++ b/servo/components/script/dom/webgltexture.rs
@@ -104,23 +104,25 @@ impl WebGLTexture {
     }
 
     pub fn initialize(&self,
                       target: u32,
                       width: u32,
                       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 {
             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,
@@ -269,16 +271,17 @@ impl WebGLTexture {
             ref_height = cmp::max(1, ref_height / 2);
 
             let image_info = ImageInfo {
                 width: ref_width,
                 height: ref_height,
                 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);
         }
         Ok(())
     }
 
     fn is_cube_complete(&self) -> bool {
@@ -341,41 +344,47 @@ impl Drop for WebGLTexture {
 
 #[derive(Clone, Copy, PartialEq, Debug, JSTraceable, HeapSizeOf)]
 pub struct ImageInfo {
     width: u32,
     height: u32,
     depth: u32,
     internal_format: Option<u32>,
     is_initialized: bool,
+    data_type: Option<u32>,
 }
 
 impl ImageInfo {
     fn new() -> ImageInfo {
         ImageInfo {
             width: 0,
             height: 0,
             depth: 0,
             internal_format: None,
             is_initialized: false,
+            data_type: None,
         }
     }
 
     pub fn width(&self) -> u32 {
         self.width
     }
 
     pub fn height(&self) -> u32 {
         self.height
     }
 
     pub fn internal_format(&self) -> Option<u32> {
         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()
     }
 
     fn is_initialized(&self) -> bool {
         self.is_initialized
     }
 
--- a/servo/components/script/dom/webidls/WebGLRenderingContext.webidl
+++ b/servo/components/script/dom/webidls/WebGLRenderingContext.webidl
@@ -641,21 +641,21 @@ interface WebGLRenderingContextBase
                     GLsizei width, GLsizei height, GLint border, GLenum format,
                     GLenum type, optional object data);
     void texImage2D(GLenum target, GLint level, GLenum internalformat,
                     GLenum format, GLenum type, TexImageSource? source); // May throw DOMException
 
     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);
     //void uniform1fv(WebGLUniformLocation? location, sequence<GLfloat> v);
     void uniform1fv(WebGLUniformLocation? location, optional object v);
     void uniform1i(WebGLUniformLocation? location, GLint x);
     //void uniform1iv(WebGLUniformLocation? location, Int32Array v);
     //void uniform1iv(WebGLUniformLocation? location, sequence<long> v);