diff options
Diffstat (limited to 'components/script/dom/webglrenderingcontext.rs')
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 791 |
1 files changed, 357 insertions, 434 deletions
diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 4aa6c047a1b..ab7178ffa16 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -2,7 +2,7 @@ * 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 byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{ByteOrder, NativeEndian, WriteBytesExt}; use canvas_traits::canvas::{byte_swap, multiply_u8_pixel}; use canvas_traits::webgl::{DOMToTextureCommand, Parameter}; use canvas_traits::webgl::{TexParameter, WebGLCommand, WebGLContextShareMode, WebGLError}; @@ -52,6 +52,7 @@ use dom::window::Window; use dom_struct::dom_struct; use euclid::Size2D; use half::f16; +use ipc_channel::ipc; use js::jsapi::{JSContext, JSObject, Type}; use js::jsval::{BooleanValue, DoubleValue, Int32Value, UInt32Value, JSVal}; use js::jsval::{ObjectValue, NullValue, UndefinedValue}; @@ -336,17 +337,12 @@ impl WebGLRenderingContext { // // The WebGL spec mentions a couple more operations that trigger // this: clear() and getParameter(IMPLEMENTATION_COLOR_READ_*). - fn validate_framebuffer_complete(&self) -> bool { + fn validate_framebuffer(&self) -> WebGLResult<()> { match self.bound_framebuffer.get() { - Some(fb) => match fb.check_status() { - constants::FRAMEBUFFER_COMPLETE => return true, - _ => { - self.webgl_error(InvalidFramebufferOperation); - return false; - } + Some(ref fb) if fb.check_status() != constants::FRAMEBUFFER_COMPLETE => { + Err(InvalidFramebufferOperation) }, - // The default framebuffer is always complete. - None => return true, + _ => Ok(()), } } @@ -485,206 +481,6 @@ impl WebGLRenderingContext { } } - // https://en.wikipedia.org/wiki/Relative_luminance - #[inline] - fn luminance(r: u8, g: u8, b: u8) -> u8 { - (0.2126 * (r as f32) + - 0.7152 * (g as f32) + - 0.0722 * (b as f32)) as u8 - } - - /// Translates an image in rgba8 (red in the first byte) format to - /// the format that was requested of TexImage. - /// - /// From the WebGL 1.0 spec, 5.14.8: - /// - /// "The source image data is conceptually first converted to - /// the data type and format specified by the format and type - /// arguments, and then transferred to the WebGL - /// implementation. If a packed pixel format is specified - /// which would imply loss of bits of precision from the image - /// data, this loss of precision must occur." - fn rgba8_image_to_tex_image_data(&self, - format: TexFormat, - data_type: TexDataType, - pixels: Vec<u8>) -> Vec<u8> { - // hint for vector allocation sizing. - let pixel_count = pixels.len() / 4; - - match (format, data_type) { - (TexFormat::RGBA, TexDataType::UnsignedByte) => pixels, - (TexFormat::RGB, TexDataType::UnsignedByte) => { - // Remove alpha channel - let mut rgb8 = Vec::<u8>::with_capacity(pixel_count * 3); - for rgba8 in pixels.chunks(4) { - rgb8.push(rgba8[0]); - rgb8.push(rgba8[1]); - rgb8.push(rgba8[2]); - } - rgb8 - }, - - (TexFormat::Alpha, TexDataType::UnsignedByte) => { - let mut alpha = Vec::<u8>::with_capacity(pixel_count); - for rgba8 in pixels.chunks(4) { - alpha.push(rgba8[3]); - } - alpha - }, - - (TexFormat::Luminance, TexDataType::UnsignedByte) => { - let mut luminance = Vec::<u8>::with_capacity(pixel_count); - for rgba8 in pixels.chunks(4) { - luminance.push(Self::luminance(rgba8[0], rgba8[1], rgba8[2])); - } - luminance - }, - - (TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => { - let mut data = Vec::<u8>::with_capacity(pixel_count * 2); - for rgba8 in pixels.chunks(4) { - data.push(Self::luminance(rgba8[0], rgba8[1], rgba8[2])); - data.push(rgba8[3]); - } - data - }, - - (TexFormat::RGBA, TexDataType::UnsignedShort4444) => { - let mut rgba4 = Vec::<u8>::with_capacity(pixel_count * 2); - for rgba8 in pixels.chunks(4) { - rgba4.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf0) << 8 | - (rgba8[1] as u16 & 0xf0) << 4 | - (rgba8[2] as u16 & 0xf0) | - (rgba8[3] as u16 & 0xf0) >> 4).unwrap(); - } - rgba4 - } - - (TexFormat::RGBA, TexDataType::UnsignedShort5551) => { - let mut rgba5551 = Vec::<u8>::with_capacity(pixel_count * 2); - for rgba8 in pixels.chunks(4) { - rgba5551.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf8) << 8 | - (rgba8[1] as u16 & 0xf8) << 3 | - (rgba8[2] as u16 & 0xf8) >> 2 | - (rgba8[3] as u16) >> 7).unwrap(); - } - rgba5551 - } - - (TexFormat::RGB, TexDataType::UnsignedShort565) => { - let mut rgb565 = Vec::<u8>::with_capacity(pixel_count * 2); - for rgba8 in pixels.chunks(4) { - rgb565.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf8) << 8 | - (rgba8[1] as u16 & 0xfc) << 3 | - (rgba8[2] as u16 & 0xf8) >> 3).unwrap(); - } - rgb565 - } - - - (TexFormat::RGBA, TexDataType::Float) => { - let mut rgbaf32 = Vec::<u8>::with_capacity(pixel_count * 16); - for rgba8 in pixels.chunks(4) { - rgbaf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap(); - rgbaf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap(); - rgbaf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap(); - rgbaf32.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap(); - } - rgbaf32 - } - - (TexFormat::RGB, TexDataType::Float) => { - let mut rgbf32 = Vec::<u8>::with_capacity(pixel_count * 12); - for rgba8 in pixels.chunks(4) { - rgbf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap(); - rgbf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap(); - rgbf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap(); - } - rgbf32 - } - - (TexFormat::Alpha, TexDataType::Float) => { - let mut alpha = Vec::<u8>::with_capacity(pixel_count * 4); - for rgba8 in pixels.chunks(4) { - alpha.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap(); - } - alpha - }, - - (TexFormat::Luminance, TexDataType::Float) => { - let mut luminance = Vec::<u8>::with_capacity(pixel_count * 4); - for rgba8 in pixels.chunks(4) { - let p = Self::luminance(rgba8[0], rgba8[1], rgba8[2]); - luminance.write_f32::<NativeEndian>(p as f32).unwrap(); - } - luminance - }, - - (TexFormat::LuminanceAlpha, TexDataType::Float) => { - let mut data = Vec::<u8>::with_capacity(pixel_count * 8); - for rgba8 in pixels.chunks(4) { - let p = Self::luminance(rgba8[0], rgba8[1], rgba8[2]); - data.write_f32::<NativeEndian>(p as f32).unwrap(); - data.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap(); - } - data - }, - - (TexFormat::RGBA, TexDataType::HalfFloat) => { - let mut rgbaf16 = Vec::<u8>::with_capacity(pixel_count * 8); - for rgba8 in pixels.chunks(4) { - rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits()).unwrap(); - rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits()).unwrap(); - rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits()).unwrap(); - rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap(); - } - rgbaf16 - }, - - (TexFormat::RGB, TexDataType::HalfFloat) => { - let mut rgbf16 = Vec::<u8>::with_capacity(pixel_count * 6); - for rgba8 in pixels.chunks(4) { - rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits()).unwrap(); - rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits()).unwrap(); - rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits()).unwrap(); - } - rgbf16 - }, - - (TexFormat::Alpha, TexDataType::HalfFloat) => { - let mut alpha = Vec::<u8>::with_capacity(pixel_count * 2); - for rgba8 in pixels.chunks(4) { - alpha.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap(); - } - alpha - }, - - (TexFormat::Luminance, TexDataType::HalfFloat) => { - let mut luminance = Vec::<u8>::with_capacity(pixel_count * 4); - for rgba8 in pixels.chunks(4) { - let p = Self::luminance(rgba8[0], rgba8[1], rgba8[2]); - luminance.write_u16::<NativeEndian>(f16::from_f32(p as f32).as_bits()).unwrap(); - } - luminance - }, - - (TexFormat::LuminanceAlpha, TexDataType::HalfFloat) => { - let mut data = Vec::<u8>::with_capacity(pixel_count * 8); - for rgba8 in pixels.chunks(4) { - let p = Self::luminance(rgba8[0], rgba8[1], rgba8[2]); - data.write_u16::<NativeEndian>(f16::from_f32(p as f32).as_bits()).unwrap(); - data.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap(); - } - data - }, - - // Validation should have ensured that we only hit the - // above cases, but we haven't turned the (format, type) - // into an enum yet so there's a default case here. - _ => unreachable!("Unsupported formats {:?} {:?}", format, data_type) - } - } - fn get_image_pixels( &self, source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement, @@ -829,83 +625,51 @@ impl WebGLRenderingContext { /// Performs premultiplication of the pixels if /// UNPACK_PREMULTIPLY_ALPHA_WEBGL is currently enabled. - fn premultiply_pixels(&self, - format: TexFormat, - data_type: TexDataType, - pixels: Vec<u8>) -> Vec<u8> { + fn premultiply_pixels(&self, format: TexFormat, data_type: TexDataType, pixels: &mut [u8]) { if !self.texture_unpacking_settings.get().contains(TextureUnpacking::PREMULTIPLY_ALPHA) { - return pixels; + return; } match (format, data_type) { (TexFormat::RGBA, TexDataType::UnsignedByte) => { - let mut premul = Vec::<u8>::with_capacity(pixels.len()); - for rgba in pixels.chunks(4) { - premul.push(multiply_u8_pixel(rgba[0], rgba[3])); - premul.push(multiply_u8_pixel(rgba[1], rgba[3])); - premul.push(multiply_u8_pixel(rgba[2], rgba[3])); - premul.push(rgba[3]); + for rgba in pixels.chunks_mut(4) { + rgba[0] = multiply_u8_pixel(rgba[0], rgba[3]); + rgba[1] = multiply_u8_pixel(rgba[1], rgba[3]); + rgba[2] = multiply_u8_pixel(rgba[2], rgba[3]); } - premul - } + }, (TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => { - let mut premul = Vec::<u8>::with_capacity(pixels.len()); - for la in pixels.chunks(2) { - premul.push(multiply_u8_pixel(la[0], la[1])); - premul.push(la[1]); + for la in pixels.chunks_mut(2) { + la[0] = multiply_u8_pixel(la[0], la[1]); } - premul - } - + }, (TexFormat::RGBA, TexDataType::UnsignedShort5551) => { - let mut premul = Vec::<u8>::with_capacity(pixels.len()); - for mut rgba in pixels.chunks(2) { - let pix = rgba.read_u16::<NativeEndian>().unwrap(); - if pix & (1 << 15) != 0 { - premul.write_u16::<NativeEndian>(pix).unwrap(); - } else { - premul.write_u16::<NativeEndian>(0).unwrap(); + for rgba in pixels.chunks_mut(2) { + if NativeEndian::read_u16(rgba) & 1 == 0 { + NativeEndian::write_u16(rgba, 0); } } - premul - } - + }, (TexFormat::RGBA, TexDataType::UnsignedShort4444) => { - let mut premul = Vec::<u8>::with_capacity(pixels.len()); - for mut rgba in pixels.chunks(2) { - let pix = rgba.read_u16::<NativeEndian>().unwrap(); - let extend_to_8_bits = |val| { (val | val << 4) as u8 }; - let r = extend_to_8_bits(pix & 0x000f); - let g = extend_to_8_bits((pix & 0x00f0) >> 4); - let b = extend_to_8_bits((pix & 0x0f00) >> 8); - let a = extend_to_8_bits((pix & 0xf000) >> 12); - - premul.write_u16::<NativeEndian>((multiply_u8_pixel(r, a) & 0xf0) as u16 >> 4 | - (multiply_u8_pixel(g, a) & 0xf0) as u16 | - ((multiply_u8_pixel(b, a) & 0xf0) as u16) << 4 | - pix & 0xf000).unwrap(); + for rgba in pixels.chunks_mut(2) { + let pix = NativeEndian::read_u16(rgba); + let extend_to_8_bits = |val| (val | val << 4) as u8; + let r = extend_to_8_bits(pix >> 12 & 0x0f); + let g = extend_to_8_bits(pix >> 8 & 0x0f); + let b = extend_to_8_bits(pix >> 4 & 0x0f); + let a = extend_to_8_bits(pix & 0x0f); + NativeEndian::write_u16( + rgba, + ((multiply_u8_pixel(r, a) & 0xf0) as u16) << 8 | + ((multiply_u8_pixel(g, a) & 0xf0) as u16) << 4 | + ((multiply_u8_pixel(b, a) & 0xf0) as u16) | + ((a & 0x0f) as u16), + ); } - premul - } - + }, // Other formats don't have alpha, so return their data untouched. - _ => pixels - } - } - - // Remove premultiplied alpha. - // This is only called when texImage2D is called using a canvas2d source and - // UNPACK_PREMULTIPLY_ALPHA_WEBGL is disabled. Pixels got from a canvas2D source - // are always RGBA8 with premultiplied alpha, so we don't have to worry about - // additional formats as happens in the premultiply_pixels method. - fn remove_premultiplied_alpha(&self, mut pixels: Vec<u8>) -> Vec<u8> { - for rgba in pixels.chunks_mut(4) { - let a = (rgba[3] as f32) / 255.0; - rgba[0] = (rgba[0] as f32 / a) as u8; - rgba[1] = (rgba[1] as f32 / a) as u8; - rgba[2] = (rgba[2] as f32 / a) as u8; + _ => {}, } - pixels } fn prepare_pixels(&self, @@ -921,16 +685,16 @@ impl WebGLRenderingContext { if !source_premultiplied && dest_premultiply { if source_from_image_or_canvas { // When the pixels come from image or canvas or imagedata, use RGBA8 format - pixels = self.premultiply_pixels(TexFormat::RGBA, TexDataType::UnsignedByte, pixels); + self.premultiply_pixels(TexFormat::RGBA, TexDataType::UnsignedByte, &mut pixels); } else { - pixels = self.premultiply_pixels(internal_format, data_type, pixels); + self.premultiply_pixels(internal_format, data_type, &mut pixels); } } else if source_premultiplied && !dest_premultiply { - pixels = self.remove_premultiplied_alpha(pixels); + remove_premultiplied_alpha(&mut pixels); } if source_from_image_or_canvas { - pixels = self.rgba8_image_to_tex_image_data(internal_format, data_type, pixels); + pixels = rgba8_image_to_tex_image_data(internal_format, data_type, pixels); } // FINISHME: Consider doing premultiply and flip in a single mutable Vec. @@ -970,17 +734,17 @@ impl WebGLRenderingContext { let internal_format = self.extension_manager.get_effective_tex_internal_format(format, data_type); // TODO(emilio): convert colorspace if requested - let msg = WebGLCommand::TexImage2D( + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.send_command(WebGLCommand::TexImage2D( target.as_gl_constant(), level as i32, internal_format as i32, width as i32, height as i32, format, data_type, - pixels.into(), - ); - - self.send_command(msg); + receiver, + )); + sender.send(&pixels).unwrap(); if let Some(fb) = self.bound_framebuffer.get() { fb.invalidate_texture(&*texture); @@ -1025,7 +789,8 @@ impl WebGLRenderingContext { self.send_command(WebGLCommand::PixelStorei(constants::UNPACK_ALIGNMENT, unpacking_alignment as i32)); // TODO(emilio): convert colorspace if requested - let msg = WebGLCommand::TexSubImage2D( + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.send_command(WebGLCommand::TexSubImage2D( target.as_gl_constant(), level as i32, xoffset, @@ -1034,10 +799,9 @@ impl WebGLRenderingContext { height as i32, format.as_gl_constant(), data_type.as_gl_constant(), - pixels.into(), - ); - - self.send_command(msg); + receiver, + )); + sender.send(&pixels).unwrap(); } fn get_gl_extensions(&self) -> String { @@ -1076,48 +840,38 @@ impl WebGLRenderingContext { first: i32, count: i32, primcount: i32, - ) { + ) -> WebGLResult<()> { match mode { constants::POINTS | constants::LINE_STRIP | constants::LINE_LOOP | constants::LINES | constants::TRIANGLE_STRIP | constants::TRIANGLE_FAN | constants::TRIANGLES => {}, _ => { - return self.webgl_error(InvalidEnum); + return Err(InvalidEnum); } } if first < 0 || count < 0 || primcount < 0 { - return self.webgl_error(InvalidValue); + return Err(InvalidValue); } - let current_program = handle_potential_webgl_error!( - self, - self.current_program.get().ok_or(InvalidOperation), - return - ); + let current_program = self.current_program.get().ok_or(InvalidOperation)?; let required_len = if count > 0 { - handle_potential_webgl_error!( - self, - first.checked_add(count).map(|len| len as u32).ok_or(InvalidOperation), - return - ) + first.checked_add(count).map(|len| len as u32).ok_or(InvalidOperation)? } else { 0 }; - handle_potential_webgl_error!( - self, - self.current_vao().validate_for_draw(required_len, primcount as u32, ¤t_program.active_attribs()), - return - ); + self.current_vao().validate_for_draw( + required_len, + primcount as u32, + ¤t_program.active_attribs(), + )?; - if !self.validate_framebuffer_complete() { - return; - } + self.validate_framebuffer()?; if count == 0 || primcount == 0 { - return; + return Ok(()); } self.send_command(if primcount == 1 { @@ -1126,6 +880,7 @@ impl WebGLRenderingContext { WebGLCommand::DrawArraysInstanced { mode, first, count, primcount } }); self.mark_as_dirty(); + Ok(()) } // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ @@ -1136,60 +891,47 @@ impl WebGLRenderingContext { type_: u32, offset: i64, primcount: i32, - ) { + ) -> WebGLResult<()> { match mode { constants::POINTS | constants::LINE_STRIP | constants::LINE_LOOP | constants::LINES | constants::TRIANGLE_STRIP | constants::TRIANGLE_FAN | constants::TRIANGLES => {}, _ => { - return self.webgl_error(InvalidEnum); + return Err(InvalidEnum); } } if count < 0 || offset < 0 || primcount < 0 { - return self.webgl_error(InvalidValue); + return Err(InvalidValue); } let type_size = match type_ { constants::UNSIGNED_BYTE => 1, constants::UNSIGNED_SHORT => 2, constants::UNSIGNED_INT if self.extension_manager.is_element_index_uint_enabled() => 4, - _ => return self.webgl_error(InvalidEnum), + _ => return Err(InvalidEnum), }; if offset % type_size != 0 { - return self.webgl_error(InvalidOperation); + return Err(InvalidOperation); } - let current_program = handle_potential_webgl_error!( - self, - self.current_program.get().ok_or(InvalidOperation), - return - ); + let current_program = self.current_program.get().ok_or(InvalidOperation)?; + let array_buffer = self.current_vao().element_array_buffer().get().ok_or(InvalidOperation)?; if count > 0 && primcount > 0 { - if let Some(array_buffer) = self.current_vao().element_array_buffer().get() { - // This operation cannot overflow in u64 and we know all those values are nonnegative. - let val = offset as u64 + (count as u64 * type_size as u64); - if val > array_buffer.capacity() as u64 { - return self.webgl_error(InvalidOperation); - } - } else { - return self.webgl_error(InvalidOperation); + // This operation cannot overflow in u64 and we know all those values are nonnegative. + let val = offset as u64 + (count as u64 * type_size as u64); + if val > array_buffer.capacity() as u64 { + return Err(InvalidOperation); } } // TODO(nox): Pass the correct number of vertices required. - handle_potential_webgl_error!( - self, - self.current_vao().validate_for_draw(0, primcount as u32, ¤t_program.active_attribs()), - return - ); + self.current_vao().validate_for_draw(0, primcount as u32, ¤t_program.active_attribs())?; - if !self.validate_framebuffer_complete() { - return; - } + self.validate_framebuffer()?; if count == 0 || primcount == 0 { - return; + return Ok(()); } let offset = offset as u32; @@ -1199,6 +941,7 @@ impl WebGLRenderingContext { WebGLCommand::DrawElementsInstanced { mode, count, type_, offset, primcount } }); self.mark_as_dirty(); + Ok(()) } pub fn vertex_attrib_divisor(&self, index: u32, divisor: u32) { @@ -1216,20 +959,18 @@ impl WebGLRenderingContext { // can fail and that it is UB what happens in that case. // // https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2 - pub fn get_image_data(&self, mut width: u32, mut height: u32) -> Option<Vec<u8>> { - if !self.validate_framebuffer_complete() { - return None; - } + pub fn get_image_data(&self, width: u32, height: u32) -> Option<Vec<u8>> { + handle_potential_webgl_error!(self, self.validate_framebuffer(), return None); - if let Some((fb_width, fb_height)) = self.get_current_framebuffer_size() { - width = cmp::min(width, fb_width as u32); - height = cmp::min(height, fb_height as u32); - } else { - self.webgl_error(InvalidOperation); - return None; - } + let (fb_width, fb_height) = handle_potential_webgl_error!( + self, + self.get_current_framebuffer_size().ok_or(InvalidOperation), + return None + ); + let width = cmp::min(width, fb_width as u32); + let height = cmp::min(height, fb_height as u32); - let (sender, receiver) = webgl_channel().unwrap(); + let (sender, receiver) = ipc::bytes_channel().unwrap(); self.send_command(WebGLCommand::ReadPixels( 0, 0, @@ -1239,7 +980,7 @@ impl WebGLRenderingContext { constants::UNSIGNED_BYTE, sender, )); - Some(receiver.recv().unwrap().into()) + Some(receiver.recv().unwrap()) } pub fn array_buffer(&self) -> Option<DomRoot<WebGLBuffer>> { @@ -1324,19 +1065,6 @@ impl Drop for WebGLRenderingContext { } } -#[allow(unsafe_code)] -unsafe fn fallible_array_buffer_view_to_vec( - cx: *mut JSContext, - abv: *mut JSObject, -) -> Result<Vec<u8>, Error> { - assert!(!abv.is_null()); - typedarray!(in(cx) let array_buffer_view: ArrayBufferView = abv); - match array_buffer_view { - Ok(v) => Ok(v.to_vec()), - Err(_) => Err(Error::Type("Not an ArrayBufferView".to_owned())), - } -} - impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1 fn Canvas(&self) -> DomRoot<HTMLCanvasElement> { @@ -1436,18 +1164,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // GL_OES_read_format support (assuming an underlying GLES // driver. Desktop is happy to format convert for us). constants::IMPLEMENTATION_COLOR_READ_FORMAT => { - if !self.validate_framebuffer_complete() { - return NullValue(); - } else { - return Int32Value(constants::RGBA as i32); - } + handle_potential_webgl_error!(self, self.validate_framebuffer(), return NullValue()); + return Int32Value(constants::RGBA as i32); } constants::IMPLEMENTATION_COLOR_READ_TYPE => { - if !self.validate_framebuffer_complete() { - return NullValue(); - } else { - return Int32Value(constants::UNSIGNED_BYTE as i32); - } + handle_potential_webgl_error!(self, self.validate_framebuffer(), return NullValue()); + return Int32Value(constants::UNSIGNED_BYTE as i32); } constants::COMPRESSED_TEXTURE_FORMATS => { // FIXME(nox): https://github.com/servo/servo/issues/20594 @@ -1892,80 +1614,71 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { handle_potential_webgl_error!(self, texture.generate_mipmap()); } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 - unsafe fn BufferData( + #[allow(unsafe_code)] + fn BufferData( &self, - cx: *mut JSContext, target: u32, - data: *mut JSObject, + data: Option<ArrayBufferViewOrArrayBuffer>, usage: u32, - ) -> ErrorResult { - if data.is_null() { - return Ok(self.webgl_error(InvalidValue)); - } + ) { + let data = handle_potential_webgl_error!(self, data.ok_or(InvalidValue), return); - typedarray!(in(cx) let array_buffer: ArrayBuffer = data); - let data_vec = match array_buffer { - Ok(data) => data.to_vec(), - Err(_) => fallible_array_buffer_view_to_vec(cx, data)?, - }; + let bound_buffer = handle_potential_webgl_error!(self, self.bound_buffer(target), return); + let bound_buffer = handle_potential_webgl_error!(self, bound_buffer.ok_or(InvalidOperation), return); - let bound_buffer = handle_potential_webgl_error!(self, self.bound_buffer(target), return Ok(())); - let bound_buffer = match bound_buffer { - Some(bound_buffer) => bound_buffer, - None => return Ok(self.webgl_error(InvalidOperation)), + let data = unsafe { + // Safe because we don't do anything with JS until the end of the method. + match data { + ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref data) => data.as_slice(), + ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref data) => data.as_slice(), + } }; - - handle_potential_webgl_error!(self, bound_buffer.buffer_data(target, data_vec, usage)); - Ok(()) + handle_potential_webgl_error!(self, bound_buffer.buffer_data(data, usage)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 - fn BufferData_(&self, target: u32, size: i64, usage: u32) -> ErrorResult { - let bound_buffer = handle_potential_webgl_error!(self, self.bound_buffer(target), return Ok(())); - let bound_buffer = match bound_buffer { - Some(bound_buffer) => bound_buffer, - None => return Ok(self.webgl_error(InvalidOperation)), - }; + fn BufferData_(&self, target: u32, size: i64, usage: u32) { + let bound_buffer = handle_potential_webgl_error!(self, self.bound_buffer(target), return); + let bound_buffer = handle_potential_webgl_error!(self, bound_buffer.ok_or(InvalidOperation), return); if size < 0 { - return Ok(self.webgl_error(InvalidValue)); + return self.webgl_error(InvalidValue); } // FIXME: Allocating a buffer based on user-requested size is // not great, but we don't have a fallible allocation to try. let data = vec![0u8; size as usize]; - handle_potential_webgl_error!(self, bound_buffer.buffer_data(target, data, usage)); - Ok(()) + handle_potential_webgl_error!(self, bound_buffer.buffer_data(&data, usage)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 + #[allow(unsafe_code)] fn BufferSubData(&self, target: u32, offset: i64, data: ArrayBufferViewOrArrayBuffer) { - let data_vec = match data { - // Typed array is rooted, so we can safely temporarily retrieve its slice - ArrayBufferViewOrArrayBuffer::ArrayBuffer(inner) => inner.to_vec(), - ArrayBufferViewOrArrayBuffer::ArrayBufferView(inner) => inner.to_vec(), - }; - let bound_buffer = handle_potential_webgl_error!(self, self.bound_buffer(target), return); - let bound_buffer = match bound_buffer { - Some(bound_buffer) => bound_buffer, - None => return self.webgl_error(InvalidOperation), - }; + let bound_buffer = handle_potential_webgl_error!(self, bound_buffer.ok_or(InvalidOperation), return); if offset < 0 { return self.webgl_error(InvalidValue); } - if (offset as usize) + data_vec.len() > bound_buffer.capacity() { + let data = unsafe { + // Safe because we don't do anything with JS until the end of the method. + match data { + ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref data) => data.as_slice(), + ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref data) => data.as_slice(), + } + }; + if (offset as u64) + data.len() as u64 > bound_buffer.capacity() as u64 { return self.webgl_error(InvalidValue); } + let (sender, receiver) = ipc::bytes_channel().unwrap(); self.send_command(WebGLCommand::BufferSubData( target, offset as isize, - data_vec.into(), + receiver, )); + sender.send(data).unwrap(); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 @@ -1989,9 +1702,7 @@ 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) { - if !self.validate_framebuffer_complete() { - return; - } + handle_potential_webgl_error!(self, self.validate_framebuffer(), return); let validator = CommonTexImage2DValidator::new(self, target, level, internal_format, width, @@ -2046,9 +1757,7 @@ 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) { - if !self.validate_framebuffer_complete() { - return; - } + handle_potential_webgl_error!(self, self.validate_framebuffer(), return); // NB: We use a dummy (valid) format and border in order to reuse the // common validations, but this should have its own validator. @@ -2089,9 +1798,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn Clear(&self, mask: u32) { - if !self.validate_framebuffer_complete() { - return; - } + handle_potential_webgl_error!(self, self.validate_framebuffer(), return); if mask & !(constants::DEPTH_BUFFER_BIT | constants::STENCIL_BUFFER_BIT | constants::COLOR_BUFFER_BIT) != 0 { return self.webgl_error(InvalidValue); } @@ -2347,12 +2054,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn DrawArrays(&self, mode: u32, first: i32, count: i32) { - self.draw_arrays_instanced(mode, first, count, 1); + handle_potential_webgl_error!(self, self.draw_arrays_instanced(mode, first, count, 1)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn DrawElements(&self, mode: u32, count: i32, type_: u32, offset: i64) { - self.draw_elements_instanced(mode, count, type_, offset, 1); + handle_potential_webgl_error!(self, self.draw_elements_instanced(mode, count, type_, offset, 1)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 @@ -2757,7 +2464,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 fn IsBuffer(&self, buffer: Option<&WebGLBuffer>) -> bool { buffer.map_or(false, |buf| { - self.validate_ownership(buf).is_ok() && buf.target().is_some() && !buf.is_marked_for_deletion() + self.validate_ownership(buf).is_ok() && buf.target().is_some() && !buf.is_deleted() }) } @@ -2874,9 +2581,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Some(ref mut data) => (data.get_array_type(), unsafe { data.as_mut_slice() }), }; - if !self.validate_framebuffer_complete() { - return; - } + handle_potential_webgl_error!(self, self.validate_framebuffer(), return); match array_type { Type::Uint8 => (), @@ -2957,7 +2662,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { _ => return self.webgl_error(InvalidOperation), }; - let (sender, receiver) = webgl_channel().unwrap(); + let (sender, receiver) = ipc::bytes_channel().unwrap(); self.send_command(WebGLCommand::ReadPixels(x, y, width, height, format, pixel_type, sender)); let result = receiver.recv().unwrap(); @@ -3643,7 +3348,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, }; if values.len() < 1 { - return self.webgl_error(InvalidOperation); + // https://github.com/KhronosGroup/WebGL/issues/2700 + return self.webgl_error(InvalidValue); } self.vertex_attrib(indx, values[0], 0f32, 0f32, 1f32); } @@ -3660,7 +3366,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, }; if values.len() < 2 { - return self.webgl_error(InvalidOperation); + // https://github.com/KhronosGroup/WebGL/issues/2700 + return self.webgl_error(InvalidValue); } self.vertex_attrib(indx, values[0], values[1], 0f32, 1f32); } @@ -3677,7 +3384,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, }; if values.len() < 3 { - return self.webgl_error(InvalidOperation); + // https://github.com/KhronosGroup/WebGL/issues/2700 + return self.webgl_error(InvalidValue); } self.vertex_attrib(indx, values[0], values[1], values[2], 1f32); } @@ -3694,7 +3402,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, }; if values.len() < 4 { - return self.webgl_error(InvalidOperation); + // https://github.com/KhronosGroup/WebGL/issues/2700 + return self.webgl_error(InvalidValue); } self.vertex_attrib(indx, values[0], values[1], values[2], values[3]); } @@ -3742,7 +3451,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { border: i32, format: u32, data_type: u32, - mut pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>, + pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>, ) -> ErrorResult { if !self.extension_manager.is_tex_type_enabled(data_type) { return Ok(self.webgl_error(InvalidEnum)); @@ -3780,7 +3489,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // initialized to 0 is passed. let buff = match *pixels { None => vec![0u8; expected_byte_length as usize], - Some(ref mut data) => data.to_vec(), + Some(ref data) => data.to_vec(), }; // From the WebGL spec: @@ -4271,3 +3980,217 @@ impl TextureUnit { None } } + +// Remove premultiplied alpha. +// This is only called when texImage2D is called using a canvas2d source and +// UNPACK_PREMULTIPLY_ALPHA_WEBGL is disabled. Pixels got from a canvas2D source +// are always RGBA8 with premultiplied alpha, so we don't have to worry about +// additional formats as happens in the premultiply_pixels method. +fn remove_premultiplied_alpha(pixels: &mut [u8]) { + for rgba in pixels.chunks_mut(4) { + let a = (rgba[3] as f32) / 255.0; + rgba[0] = (rgba[0] as f32 / a) as u8; + rgba[1] = (rgba[1] as f32 / a) as u8; + rgba[2] = (rgba[2] as f32 / a) as u8; + } +} + +/// Translates an image in rgba8 (red in the first byte) format to +/// the format that was requested of TexImage. +/// +/// From the WebGL 1.0 spec, 5.14.8: +/// +/// "The source image data is conceptually first converted to +/// the data type and format specified by the format and type +/// arguments, and then transferred to the WebGL +/// implementation. If a packed pixel format is specified +/// which would imply loss of bits of precision from the image +/// data, this loss of precision must occur." +fn rgba8_image_to_tex_image_data( + format: TexFormat, + data_type: TexDataType, + mut pixels: Vec<u8>, +) -> Vec<u8> { + // hint for vector allocation sizing. + let pixel_count = pixels.len() / 4; + + match (format, data_type) { + (TexFormat::RGBA, TexDataType::UnsignedByte) => pixels, + (TexFormat::RGB, TexDataType::UnsignedByte) => { + for i in 0..pixel_count { + let rgb = { + let rgb = &pixels[i * 4..i * 4 + 3]; + [rgb[0], rgb[1], rgb[2]] + }; + pixels[i * 3..i * 3 + 3].copy_from_slice(&rgb); + } + pixels.truncate(pixel_count * 3); + pixels + }, + (TexFormat::Alpha, TexDataType::UnsignedByte) => { + for i in 0..pixel_count { + let p = pixels[i * 4 + 3]; + pixels[i] = p; + } + pixels.truncate(pixel_count); + pixels + }, + (TexFormat::Luminance, TexDataType::UnsignedByte) => { + for i in 0..pixel_count { + let p = pixels[i * 4]; + pixels[i] = p; + } + pixels.truncate(pixel_count); + pixels + }, + (TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => { + for i in 0..pixel_count { + let (lum, a) = { + let rgba = &pixels[i * 4..i * 4 + 4]; + (rgba[0], rgba[3]) + }; + pixels[i * 2] = lum; + pixels[i * 2 + 1] = a; + } + pixels.truncate(pixel_count * 2); + pixels + }, + (TexFormat::RGBA, TexDataType::UnsignedShort4444) => { + for i in 0..pixel_count { + let p = { + let rgba = &pixels[i * 4..i * 4 + 4]; + (rgba[0] as u16 & 0xf0) << 8 | + (rgba[1] as u16 & 0xf0) << 4 | + (rgba[2] as u16 & 0xf0) | + (rgba[3] as u16 & 0xf0) >> 4 + }; + NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p); + } + pixels.truncate(pixel_count * 2); + pixels + }, + (TexFormat::RGBA, TexDataType::UnsignedShort5551) => { + for i in 0..pixel_count { + let p = { + let rgba = &pixels[i * 4..i * 4 + 4]; + (rgba[0] as u16 & 0xf8) << 8 | + (rgba[1] as u16 & 0xf8) << 3 | + (rgba[2] as u16 & 0xf8) >> 2 | + (rgba[3] as u16) >> 7 + }; + NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p); + } + pixels.truncate(pixel_count * 2); + pixels + }, + (TexFormat::RGB, TexDataType::UnsignedShort565) => { + for i in 0..pixel_count { + let p = { + let rgb = &pixels[i * 4..i * 4 + 3]; + (rgb[0] as u16 & 0xf8) << 8 | + (rgb[1] as u16 & 0xfc) << 3 | + (rgb[2] as u16 & 0xf8) >> 3 + }; + NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p); + } + pixels.truncate(pixel_count * 2); + pixels + }, + (TexFormat::RGBA, TexDataType::Float) => { + let mut rgbaf32 = Vec::<u8>::with_capacity(pixel_count * 16); + for rgba8 in pixels.chunks(4) { + rgbaf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap(); + rgbaf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap(); + rgbaf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap(); + rgbaf32.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap(); + } + rgbaf32 + } + + (TexFormat::RGB, TexDataType::Float) => { + let mut rgbf32 = Vec::<u8>::with_capacity(pixel_count * 12); + for rgba8 in pixels.chunks(4) { + rgbf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap(); + rgbf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap(); + rgbf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap(); + } + rgbf32 + } + + (TexFormat::Alpha, TexDataType::Float) => { + for rgba8 in pixels.chunks_mut(4) { + let p = rgba8[3] as f32; + NativeEndian::write_f32(rgba8, p); + } + pixels + }, + + (TexFormat::Luminance, TexDataType::Float) => { + for rgba8 in pixels.chunks_mut(4) { + let p = rgba8[0] as f32; + NativeEndian::write_f32(rgba8, p); + } + pixels + }, + + (TexFormat::LuminanceAlpha, TexDataType::Float) => { + let mut data = Vec::<u8>::with_capacity(pixel_count * 8); + for rgba8 in pixels.chunks(4) { + data.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap(); + data.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap(); + } + data + }, + + (TexFormat::RGBA, TexDataType::HalfFloat) => { + let mut rgbaf16 = Vec::<u8>::with_capacity(pixel_count * 8); + for rgba8 in pixels.chunks(4) { + rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits()).unwrap(); + rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits()).unwrap(); + rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits()).unwrap(); + rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap(); + } + rgbaf16 + }, + + (TexFormat::RGB, TexDataType::HalfFloat) => { + let mut rgbf16 = Vec::<u8>::with_capacity(pixel_count * 6); + for rgba8 in pixels.chunks(4) { + rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits()).unwrap(); + rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits()).unwrap(); + rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits()).unwrap(); + } + rgbf16 + }, + (TexFormat::Alpha, TexDataType::HalfFloat) => { + for i in 0..pixel_count { + let p = f16::from_f32(pixels[i * 4 + 3] as f32).as_bits(); + NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p); + } + pixels.truncate(pixel_count * 2); + pixels + }, + (TexFormat::Luminance, TexDataType::HalfFloat) => { + for i in 0..pixel_count { + let p = f16::from_f32(pixels[i * 4] as f32).as_bits(); + NativeEndian::write_u16(&mut pixels[i * 2..i * 2 + 2], p); + } + pixels.truncate(pixel_count * 2); + pixels + }, + (TexFormat::LuminanceAlpha, TexDataType::HalfFloat) => { + for rgba8 in pixels.chunks_mut(4) { + let lum = f16::from_f32(rgba8[0] as f32).as_bits(); + let a = f16::from_f32(rgba8[3] as f32).as_bits(); + NativeEndian::write_u16(&mut rgba8[0..2], lum); + NativeEndian::write_u16(&mut rgba8[2..4], a); + } + pixels + }, + + // Validation should have ensured that we only hit the + // above cases, but we haven't turned the (format, type) + // into an enum yet so there's a default case here. + _ => unreachable!("Unsupported formats {:?} {:?}", format, data_type) + } +} |