diff options
author | ecoal95 <ecoal95@gmail.com> | 2015-07-25 21:01:44 +0200 |
---|---|---|
committer | ecoal95 <ecoal95@gmail.com> | 2015-08-25 17:16:46 +0200 |
commit | 6341c77700fa5f914c32c6153e9c532bc69474fd (patch) | |
tree | 3509f4dc996823641e384972baf7a22aa5ad1ad9 /components/script/dom/webglrenderingcontext.rs | |
parent | af3310f1490e93b2b1a77c1c1e9aab13cc46c9d5 (diff) | |
download | servo-6341c77700fa5f914c32c6153e9c532bc69474fd.tar.gz servo-6341c77700fa5f914c32c6153e9c532bc69474fd.zip |
webgl: Implement multiple calls and improve error detection
This commit implements WebGL's:
* cullFace
* frontFace
* enable
* disable
* depthMask
* colorMask
* clearDepth
* clearStencil
* depthFunc
* depthRange
* hint
* lineWidth
* pixelStorei
* polygonOffset
* texParameteri
* texParameterf
* texImage2D (partially)
It inlines a lot of OpenGL calls to keep the file
`components/canvas/webgl_paint_task.rs` as small as possible while
keeping readability.
It also improves error detection on previous calls, and sets node damage
on the canvas in the drawing calls.
It adds a `TexImage2D` reftest, even though it's not enabled because:
* WebGL paints the image when it loads (asynchronously), so the reftest doesn't wait for it and it finishes early
* If we change the source for the base64 src of the image it works as expected in non-headless mode, but the test harness locks
Diffstat (limited to 'components/script/dom/webglrenderingcontext.rs')
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 412 |
1 files changed, 398 insertions, 14 deletions
diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 356e556eaa6..2effacd8c59 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -5,35 +5,48 @@ use canvas_traits:: {CanvasMsg, CanvasWebGLMsg, CanvasCommonMsg, WebGLError, WebGLShaderParameter, WebGLFramebufferBindingRequest}; +use canvas_traits::WebGLError::*; use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding:: {self, WebGLContextAttributes, WebGLRenderingContextMethods}; use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; +use dom::bindings::codegen::InheritTypes::NodeCast; +use dom::bindings::codegen::UnionTypes::ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement; use dom::bindings::conversions::ToJSValConvertible; use dom::bindings::global::{GlobalRef, GlobalField}; use dom::bindings::js::{JS, LayoutJS, Root}; use dom::bindings::utils::{Reflector, reflect_dom_object}; -use dom::htmlcanvaselement::{HTMLCanvasElement}; +use dom::htmlcanvaselement::HTMLCanvasElement; +use dom::htmlcanvaselement::utils as canvas_utils; +use dom::htmlimageelement::HTMLImageElementHelpers; +use dom::imagedata::ImageDataHelpers; +use dom::node::{window_from_node, NodeHelpers, NodeDamage}; use dom::webglbuffer::{WebGLBuffer, WebGLBufferHelpers}; use dom::webglframebuffer::{WebGLFramebuffer, WebGLFramebufferHelpers}; use dom::webglprogram::{WebGLProgram, WebGLProgramHelpers}; use dom::webglrenderbuffer::{WebGLRenderbuffer, WebGLRenderbufferHelpers}; use dom::webglshader::{WebGLShader, WebGLShaderHelpers}; -use dom::webgltexture::{WebGLTexture, WebGLTextureHelpers}; +use dom::webgltexture::{TexParameterValue, WebGLTexture, WebGLTextureHelpers}; use dom::webgluniformlocation::{WebGLUniformLocation, WebGLUniformLocationHelpers}; use euclid::size::Size2D; use ipc_channel::ipc::{self, IpcSender}; use js::jsapi::{JSContext, JSObject, RootedValue}; use js::jsapi::{JS_GetFloat32ArrayData, JS_GetObjectAsArrayBufferView}; use js::jsval::{JSVal, UndefinedValue, NullValue, Int32Value, BooleanValue}; + use msg::constellation_msg::Msg as ConstellationMsg; -use offscreen_gl_context::GLContextAttributes; +use net_traits::image::base::PixelFormat; +use net_traits::image_cache_task::ImageResponse; + use std::cell::Cell; use std::mem; use std::ptr; use std::slice; use std::sync::mpsc::channel; use util::str::DOMString; +use util::vec::byte_swap; + +use offscreen_gl_context::GLContextAttributes; pub const MAX_UNIFORM_AND_ATTRIBUTE_LEN: usize = 256; @@ -42,13 +55,23 @@ macro_rules! handle_potential_webgl_error { match $call { Ok(ret) => ret, Err(error) => { - $context.handle_webgl_error(error); + $context.webgl_error(error); $return_on_error } } } } +/// Set of bitflags for texture unpacking (texImage2d, etc...) +bitflags! { + #[derive(HeapSizeOf, JSTraceable)] + flags TextureUnpacking: u8 { + const FLIP_Y_AXIS = 0x01, + const PREMULTIPLY_ALPHA = 0x02, + const CONVERT_COLORSPACE = 0x04, + } +} + #[dom_struct] #[derive(HeapSizeOf)] pub struct WebGLRenderingContext { @@ -59,6 +82,9 @@ pub struct WebGLRenderingContext { ipc_renderer: IpcSender<CanvasMsg>, canvas: JS<HTMLCanvasElement>, last_error: Cell<Option<WebGLError>>, + texture_unpacking_settings: Cell<TextureUnpacking>, + bound_texture_2d: Cell<Option<JS<WebGLTexture>>>, + bound_texture_cube_map: Cell<Option<JS<WebGLTexture>>>, } impl WebGLRenderingContext { @@ -80,8 +106,11 @@ impl WebGLRenderingContext { global: GlobalField::from_rooted(&global), renderer_id: renderer_id, ipc_renderer: ipc_renderer, - last_error: Cell::new(None), canvas: JS::from_ref(canvas), + last_error: Cell::new(None), + texture_unpacking_settings: Cell::new(CONVERT_COLORSPACE), + bound_texture_2d: Cell::new(None), + bound_texture_cube_map: Cell::new(None), } }) } @@ -101,6 +130,12 @@ impl WebGLRenderingContext { pub fn recreate(&self, size: Size2D<i32>) { self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Recreate(size))).unwrap(); } + + fn mark_as_dirty(&self) { + let canvas = self.canvas.root(); + let node = NodeCast::from_ref(canvas.r()); + node.dirty(NodeDamage::OtherNodeDamage); + } } impl Drop for WebGLRenderingContext { @@ -241,8 +276,15 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 fn BindBuffer(self, target: u32, buffer: Option<&WebGLBuffer>) { + match target { + constants::ARRAY_BUFFER | + constants::ELEMENT_ARRAY_BUFFER => (), + + _ => return self.webgl_error(InvalidEnum), + } + if let Some(buffer) = buffer { - buffer.bind(target) + handle_potential_webgl_error!(self, buffer.bind(target), ()) } else { // Unbind the current buffer self.ipc_renderer @@ -253,6 +295,10 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 fn BindFramebuffer(self, target: u32, framebuffer: Option<&WebGLFramebuffer>) { + if target != constants::FRAMEBUFFER { + return self.webgl_error(InvalidOperation); + } + if let Some(framebuffer) = framebuffer { framebuffer.bind(target) } else { @@ -264,6 +310,10 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 fn BindRenderbuffer(self, target: u32, renderbuffer: Option<&WebGLRenderbuffer>) { + if target != constants::RENDERBUFFER { + return self.webgl_error(InvalidEnum); + } + if let Some(renderbuffer) = renderbuffer { renderbuffer.bind(target) } else { @@ -276,8 +326,23 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 fn BindTexture(self, target: u32, texture: Option<&WebGLTexture>) { + let slot = match target { + constants::TEXTURE_2D => &self.bound_texture_2d, + constants::TEXTURE_CUBE_MAP => &self.bound_texture_cube_map, + + _ => return self.webgl_error(InvalidEnum), + }; + if let Some(texture) = texture { - texture.bind(target) + match texture.bind(target) { + Ok(_) => slot.set(Some(JS::from_ref(texture))), + Err(err) => return self.webgl_error(err), + } + } else { + // Unbind the currently bound texture + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::BindTexture(target, 0))) + .unwrap() } } @@ -306,7 +371,8 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn Clear(self, mask: u32) { - self.ipc_renderer.send(CanvasMsg::WebGL(CanvasWebGLMsg::Clear(mask))).unwrap() + self.ipc_renderer.send(CanvasMsg::WebGL(CanvasWebGLMsg::Clear(mask))).unwrap(); + self.mark_as_dirty(); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 @@ -316,6 +382,102 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { .unwrap() } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn ClearDepth(self, depth: f32) { + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::ClearDepth(depth as f64))) + .unwrap() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn ClearStencil(self, stencil: i32) { + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::ClearStencil(stencil))) + .unwrap() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn ColorMask(self, r: bool, g: bool, b: bool, a: bool) { + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::ColorMask(r, g, b, a))) + .unwrap() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn CullFace(self, mode: u32) { + match mode { + constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::CullFace(mode))) + .unwrap(), + _ => self.webgl_error(InvalidEnum), + } + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn FrontFace(self, mode: u32) { + match mode { + constants::CW | constants::CCW => + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::FrontFace(mode))) + .unwrap(), + _ => self.webgl_error(InvalidEnum), + } + } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn DepthFunc(self, func: u32) { + match func { + constants::NEVER | constants::LESS | + constants::EQUAL | constants::LEQUAL | + constants::GREATER | constants::NOTEQUAL | + constants::GEQUAL | constants::ALWAYS => + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::DepthFunc(func))) + .unwrap(), + _ => self.webgl_error(InvalidEnum), + } + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn DepthMask(self, flag: bool) { + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::DepthMask(flag))) + .unwrap() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn DepthRange(self, near: f32, far: f32) { + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::DepthRange(near as f64, far as f64))) + .unwrap() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn Enable(self, cap: u32) { + match cap { + constants::BLEND | constants::CULL_FACE | constants::DEPTH_TEST | constants::DITHER | + constants::POLYGON_OFFSET_FILL | constants::SAMPLE_ALPHA_TO_COVERAGE | constants::SAMPLE_COVERAGE | + constants::SAMPLE_COVERAGE_INVERT | constants::SCISSOR_TEST => + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::Enable(cap))) + .unwrap(), + _ => self.webgl_error(InvalidEnum), + } + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn Disable(self, cap: u32) { + match cap { + constants::BLEND | constants::CULL_FACE | constants::DEPTH_TEST | constants::DITHER | + constants::POLYGON_OFFSET_FILL | constants::SAMPLE_ALPHA_TO_COVERAGE | constants::SAMPLE_COVERAGE | + constants::SAMPLE_COVERAGE_INVERT | constants::SCISSOR_TEST => + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::Disable(cap))) + .unwrap(), + _ => self.webgl_error(InvalidEnum), + } + } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn CompileShader(self, shader: Option<&WebGLShader>) { if let Some(shader) = shader { @@ -401,9 +563,24 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn DrawArrays(self, mode: u32, first: i32, count: i32) { - self.ipc_renderer - .send(CanvasMsg::WebGL(CanvasWebGLMsg::DrawArrays(mode, first, count))) - .unwrap() + match mode { + constants::POINTS | constants::LINE_STRIP | + constants::LINE_LOOP | constants::LINES | + constants::TRIANGLE_STRIP | constants::TRIANGLE_FAN | + constants::TRIANGLES => { + // TODO(ecoal95): Check the CURRENT_PROGRAM when we keep track of it, and if it's + // null generate an InvalidOperation error + if first < 0 || count < 0 { + self.webgl_error(InvalidValue); + } else { + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::DrawArrays(mode, first, count))) + .unwrap(); + self.mark_as_dirty(); + } + }, + _ => self.webgl_error(InvalidEnum), + } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 @@ -456,6 +633,96 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { } } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn Hint(self, target: u32, mode: u32) { + if target != constants::GENERATE_MIPMAP_HINT { + return self.webgl_error(InvalidEnum); + } + + match mode { + constants::FASTEST | + constants::NICEST | + constants::DONT_CARE => (), + + _ => return self.webgl_error(InvalidEnum), + } + + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::Hint(target, mode))) + .unwrap() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn LineWidth(self, width: f32) { + if width.is_nan() || width <= 0f32 { + return self.webgl_error(InvalidValue); + } + + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::LineWidth(width))) + .unwrap() + } + + // NOTE: Usage of this function could affect rendering while we keep using + // readback to render to the page. + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn PixelStorei(self, param_name: u32, param_value: i32) { + let mut texture_settings = self.texture_unpacking_settings.get(); + match param_name { + constants::UNPACK_FLIP_Y_WEBGL => { + if param_value != 0 { + texture_settings.insert(FLIP_Y_AXIS) + } else { + texture_settings.remove(FLIP_Y_AXIS) + } + + self.texture_unpacking_settings.set(texture_settings); + return; + }, + constants::UNPACK_PREMULTIPLY_ALPHA_WEBGL => { + if param_value != 0 { + texture_settings.insert(PREMULTIPLY_ALPHA) + } else { + texture_settings.remove(PREMULTIPLY_ALPHA) + } + + self.texture_unpacking_settings.set(texture_settings); + return; + }, + constants::UNPACK_COLORSPACE_CONVERSION_WEBGL => { + match param_value as u32 { + constants::BROWSER_DEFAULT_WEBGL + => texture_settings.insert(CONVERT_COLORSPACE), + constants::NONE + => texture_settings.remove(CONVERT_COLORSPACE), + _ => return self.webgl_error(InvalidEnum), + } + + self.texture_unpacking_settings.set(texture_settings); + return; + }, + constants::UNPACK_ALIGNMENT | + constants::PACK_ALIGNMENT => { + match param_value { + 1 | 2 | 4 | 8 => (), + _ => return self.webgl_error(InvalidValue), + } + }, + _ => return self.webgl_error(InvalidEnum), + } + + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::PixelStorei(param_name, param_value))) + .unwrap() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn PolygonOffset(self, factor: f32, units: f32) { + self.ipc_renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::PolygonOffset(factor, units))) + .unwrap() + } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn LinkProgram(self, program: Option<&WebGLProgram>) { if let Some(program) = program { @@ -516,7 +783,7 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { normalized: bool, stride: i32, offset: i64) { if let constants::FLOAT = data_type { let msg = CanvasMsg::WebGL( - CanvasWebGLMsg::VertexAttribPointer2f(attrib_id, size, normalized, stride, offset)); + CanvasWebGLMsg::VertexAttribPointer2f(attrib_id, size, normalized, stride, offset as u32)); self.ipc_renderer.send(msg).unwrap() } else { panic!("VertexAttribPointer: Data Type not supported") @@ -529,20 +796,137 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { .send(CanvasMsg::WebGL(CanvasWebGLMsg::Viewport(x, y, width, height))) .unwrap() } + + // 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 >) { + // TODO(ecoal95): Check for bound WebGLTexture, and validate more parameters + match target { + constants::TEXTURE_2D | + constants::TEXTURE_CUBE_MAP => (), + + _ => return self.webgl_error(InvalidEnum), + } + + let source = match source { + Some(s) => s, + None => return, + }; + + let (pixels, size) = match source { + ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::eImageData(image_data) => { + let global = self.global.root(); + (image_data.get_data_array(&global.r()), image_data.get_size()) + }, + ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::eHTMLImageElement(image) => { + let img_url = match image.r().get_url() { + Some(url) => url, + None => return, + }; + + let canvas = self.canvas.root(); + let window = window_from_node(canvas.r()); + + let img = match canvas_utils::request_image_from_cache(window.r(), img_url) { + ImageResponse::Loaded(img) => img, + ImageResponse::PlaceholderLoaded(_) | ImageResponse::None + => return, + }; + + let size = Size2D::new(img.width as i32, img.height as i32); + // TODO(ecoal95): 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(ecoal95): Getting canvas data is implemented in CanvasRenderingContext2D, but + // we need to refactor it moving it to `HTMLCanvasElement` and supporting WebGLContext + ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::eHTMLCanvasElement(_rooted_canvas) + => unimplemented!(), + ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::eHTMLVideoElement(_rooted_video) + => unimplemented!(), + }; + + // TODO(ecoal95): Invert axis, convert colorspace, premultiply alpha if requested + let msg = CanvasWebGLMsg::TexImage2D(target, level, internal_format as i32, + size.width, size.height, + format, data_type, pixels); + + self.ipc_renderer + .send(CanvasMsg::WebGL(msg)) + .unwrap() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexParameterf(self, target: u32, name: u32, value: f32) { + match target { + constants::TEXTURE_2D | + constants::TEXTURE_CUBE_MAP => { + if let Some(texture) = self.bound_texture_for(target) { + let texture = texture.root(); + let result = texture.r().tex_parameter(target, name, TexParameterValue::Float(value)); + handle_potential_webgl_error!(self, result, ()); + } else { + return self.webgl_error(InvalidOperation); + } + }, + + _ => return self.webgl_error(InvalidEnum), + } + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexParameteri(self, target: u32, name: u32, value: i32) { + match target { + constants::TEXTURE_2D | + constants::TEXTURE_CUBE_MAP => { + if let Some(texture) = self.bound_texture_for(target) { + let texture = texture.root(); + let result = texture.r().tex_parameter(target, name, TexParameterValue::Int(value)); + handle_potential_webgl_error!(self, result, ()); + } else { + return self.webgl_error(InvalidOperation); + } + }, + + _ => return self.webgl_error(InvalidEnum), + } + } } pub trait WebGLRenderingContextHelpers { - fn handle_webgl_error(&self, err: WebGLError); + fn webgl_error(&self, err: WebGLError); + fn bound_texture_for(&self, target: u32) -> Option<JS<WebGLTexture>>; } impl<'a> WebGLRenderingContextHelpers for &'a WebGLRenderingContext { - fn handle_webgl_error(&self, err: WebGLError) { + fn webgl_error(&self, err: WebGLError) { // If an error has been detected no further errors must be // recorded until `getError` has been called if self.last_error.get().is_none() { self.last_error.set(Some(err)); } } + + fn bound_texture_for(&self, target: u32) -> Option<JS<WebGLTexture>> { + match target { + constants::TEXTURE_2D => self.bound_texture_2d.get(), + constants::TEXTURE_CUBE_MAP => self.bound_texture_cube_map.get(), + + _ => unreachable!(), + } + } } pub trait LayoutCanvasWebGLRenderingContextHelpers { |