diff options
author | bors-servo <metajack+bors@gmail.com> | 2015-08-25 09:23:00 -0600 |
---|---|---|
committer | bors-servo <metajack+bors@gmail.com> | 2015-08-25 09:23:00 -0600 |
commit | a109a333f1f95d4fc677b29e3613b2615514c080 (patch) | |
tree | 3509f4dc996823641e384972baf7a22aa5ad1ad9 /components/script/dom | |
parent | 4d0b4a7b8cda681d1cd6b6cd8e690c0793532d0b (diff) | |
parent | 6341c77700fa5f914c32c6153e9c532bc69474fd (diff) | |
download | servo-a109a333f1f95d4fc677b29e3613b2615514c080.tar.gz servo-a109a333f1f95d4fc677b29e3613b2615514c080.zip |
Auto merge of #6770 - ecoal95:webgl-again, r=jdm
Add multiple WebGL calls and improve error detection
Since it probably won't merge until multiprocess lands, I plan to use this PR to keep improving WebGL support until it can land.
Main TODOs are integration of tests, since it seems https://github.com/KhronosGroup/WebGL/issues/1105 is going nowhere, adding missing calls and proper painting via native surfaces instead of readback.
I can't resolve conflicts right now because of time but I will do it soon.
<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/6770)
<!-- Reviewable:end -->
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/canvasrenderingcontext2d.rs | 24 | ||||
-rw-r--r-- | components/script/dom/htmlcanvaselement.rs | 16 | ||||
-rw-r--r-- | components/script/dom/webglbuffer.rs | 19 | ||||
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 412 | ||||
-rw-r--r-- | components/script/dom/webgltexture.rs | 93 | ||||
-rw-r--r-- | components/script/dom/webidls/WebGLRenderingContext.webidl | 45 |
6 files changed, 554 insertions, 55 deletions
diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 9e2ec6d1446..03f4014ee7a 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -17,11 +17,16 @@ use dom::bindings::num::Finite; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle}; use dom::canvaspattern::CanvasPattern; +use dom::htmlcanvaselement::utils as canvas_utils; use dom::htmlcanvaselement::{HTMLCanvasElement, HTMLCanvasElementHelpers}; use dom::htmlimageelement::{HTMLImageElement, HTMLImageElementHelpers}; use dom::imagedata::{ImageData, ImageDataHelpers}; use dom::node::{window_from_node, NodeHelpers, NodeDamage}; +use msg::constellation_msg::Msg as ConstellationMsg; +use net_traits::image::base::PixelFormat; +use net_traits::image_cache_task::ImageResponse; + use cssparser::Color as CSSColor; use cssparser::{Parser, RGBA}; use euclid::matrix2d::Matrix2D; @@ -34,10 +39,6 @@ use canvas_traits::{CanvasMsg, Canvas2dMsg, CanvasCommonMsg}; use canvas_traits::{FillOrStrokeStyle, LinearGradientStyle, RadialGradientStyle, RepetitionStyle}; use canvas_traits::{LineCapStyle, LineJoinStyle, CompositionOrBlending}; -use msg::constellation_msg::Msg as ConstellationMsg; -use net_traits::image::base::PixelFormat; -use net_traits::image_cache_task::{ImageCacheChan, ImageResponse}; - use ipc_channel::ipc::{self, IpcSender}; use num::{Float, ToPrimitive}; use std::borrow::ToOwned; @@ -201,7 +202,7 @@ impl CanvasRenderingContext2D { Size2D::new(source_rect_clipped.size.width, source_rect_clipped.size.height)); - return (source_rect, dest_rect) + (source_rect, dest_rect) } // @@ -364,9 +365,10 @@ impl CanvasRenderingContext2D { PixelFormat::KA8 => panic!("KA8 color type not supported"), }; - return Some((image_data, image_size)); + Some((image_data, image_size)) } + // TODO(ecoal95): Move this to `HTMLCanvasElement`, and support WebGL contexts fn fetch_canvas_data(&self, canvas_element: &HTMLCanvasElement, source_rect: Rect<f64>) @@ -385,18 +387,14 @@ impl CanvasRenderingContext2D { renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::GetImageData(source_rect.to_i32(), image_size, sender))).unwrap(); - return Some((receiver.recv().unwrap(), image_size)); + Some((receiver.recv().unwrap(), image_size)) } + #[inline] fn request_image_from_cache(&self, url: Url) -> ImageResponse { let canvas = self.canvas.root(); let window = window_from_node(canvas.r()); - let window = window.r(); - let image_cache = window.image_cache_task(); - let (response_chan, response_port) = ipc::channel().unwrap(); - image_cache.request_image(url, ImageCacheChan(response_chan), None); - let result = response_port.recv().unwrap(); - result.image_response + canvas_utils::request_image_from_cache(window.r(), url) } fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> { diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 4823f881be7..83e8397d859 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -159,6 +159,7 @@ pub trait HTMLCanvasElementHelpers { fn get_or_init_webgl_context(self, cx: *mut JSContext, attrs: Option<HandleValue>) -> Option<Root<WebGLRenderingContext>>; + fn is_valid(self) -> bool; } @@ -325,3 +326,18 @@ impl<'a> From<&'a WebGLContextAttributes> for GLContextAttributes { } } +pub mod utils { + use dom::window::Window; + use ipc_channel::ipc; + use net_traits::image_cache_task::{ImageCacheChan, ImageResponse}; + use url::Url; + + pub fn request_image_from_cache(window: &Window, url: Url) -> ImageResponse { + let image_cache = window.image_cache_task(); + let (response_chan, response_port) = ipc::channel().unwrap(); + image_cache.request_image(url, ImageCacheChan(response_chan), None); + let result = response_port.recv().unwrap(); + result.image_response + } +} + diff --git a/components/script/dom/webglbuffer.rs b/components/script/dom/webglbuffer.rs index 5ad078a38e5..6f958d62597 100644 --- a/components/script/dom/webglbuffer.rs +++ b/components/script/dom/webglbuffer.rs @@ -9,7 +9,7 @@ use dom::bindings::js::Root; use dom::bindings::utils::reflect_dom_object; use dom::webglobject::WebGLObject; -use canvas_traits::{CanvasMsg, CanvasWebGLMsg}; +use canvas_traits::{CanvasMsg, CanvasWebGLMsg, WebGLError, WebGLResult}; use ipc_channel::ipc::{self, IpcSender}; use std::cell::Cell; @@ -18,6 +18,8 @@ use std::cell::Cell; pub struct WebGLBuffer { webgl_object: WebGLObject, id: u32, + /// The target to which this buffer was bound the first time + target: Cell<Option<u32>>, is_deleted: Cell<bool>, #[ignore_heap_size_of = "Defined in ipc-channel"] renderer: IpcSender<CanvasMsg>, @@ -28,6 +30,7 @@ impl WebGLBuffer { WebGLBuffer { webgl_object: WebGLObject::new_inherited(), id: id, + target: Cell::new(None), is_deleted: Cell::new(false), renderer: renderer, } @@ -49,7 +52,7 @@ impl WebGLBuffer { pub trait WebGLBufferHelpers { fn id(self) -> u32; - fn bind(self, target: u32); + fn bind(self, target: u32) -> WebGLResult<()>; fn delete(self); } @@ -58,8 +61,18 @@ impl<'a> WebGLBufferHelpers for &'a WebGLBuffer { self.id } - fn bind(self, target: u32) { + // NB: Only valid buffer targets come here + fn bind(self, target: u32) -> WebGLResult<()> { + if let Some(previous_target) = self.target.get() { + if target != previous_target { + return Err(WebGLError::InvalidOperation); + } + } else { + self.target.set(Some(target)); + } self.renderer.send(CanvasMsg::WebGL(CanvasWebGLMsg::BindBuffer(target, self.id))).unwrap(); + + Ok(()) } fn delete(self) { 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 { diff --git a/components/script/dom/webgltexture.rs b/components/script/dom/webgltexture.rs index 579c196e90a..7fa44178155 100644 --- a/components/script/dom/webgltexture.rs +++ b/components/script/dom/webgltexture.rs @@ -3,21 +3,29 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl +use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; use dom::bindings::codegen::Bindings::WebGLTextureBinding; use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; use dom::bindings::utils::reflect_dom_object; use dom::webglobject::WebGLObject; -use canvas_traits::{CanvasMsg, CanvasWebGLMsg}; +use canvas_traits::{CanvasMsg, CanvasWebGLMsg, WebGLError, WebGLResult}; use ipc_channel::ipc::{self, IpcSender}; use std::cell::Cell; +pub enum TexParameterValue { + Float(f32), + Int(i32), +} + #[dom_struct] #[derive(HeapSizeOf)] pub struct WebGLTexture { webgl_object: WebGLObject, id: u32, + /// The target to which this texture was bound the first time + target: Cell<Option<u32>>, is_deleted: Cell<bool>, #[ignore_heap_size_of = "Defined in ipc-channel"] renderer: IpcSender<CanvasMsg>, @@ -28,6 +36,7 @@ impl WebGLTexture { WebGLTexture { webgl_object: WebGLObject::new_inherited(), id: id, + target: Cell::new(None), is_deleted: Cell::new(false), renderer: renderer, } @@ -49,8 +58,12 @@ impl WebGLTexture { pub trait WebGLTextureHelpers { fn id(self) -> u32; - fn bind(self, target: u32); + fn bind(self, target: u32) -> WebGLResult<()>; fn delete(self); + fn tex_parameter(self, + target: u32, + name: u32, + value: TexParameterValue) -> WebGLResult<()>; } impl<'a> WebGLTextureHelpers for &'a WebGLTexture { @@ -58,8 +71,19 @@ impl<'a> WebGLTextureHelpers for &'a WebGLTexture { self.id } - fn bind(self, target: u32) { + // NB: Only valid texture targets come here + fn bind(self, target: u32) -> WebGLResult<()> { + if let Some(previous_target) = self.target.get() { + if target != previous_target { + return Err(WebGLError::InvalidOperation); + } + } else { + self.target.set(Some(target)); + } + self.renderer.send(CanvasMsg::WebGL(CanvasWebGLMsg::BindTexture(self.id, target))).unwrap(); + + Ok(()) } fn delete(self) { @@ -68,4 +92,67 @@ impl<'a> WebGLTextureHelpers for &'a WebGLTexture { self.renderer.send(CanvasMsg::WebGL(CanvasWebGLMsg::DeleteTexture(self.id))).unwrap(); } } + + /// We have to follow the conversion rules for GLES 2.0. See: + /// https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html + /// + fn tex_parameter(self, + target: u32, + name: u32, + value: TexParameterValue) -> WebGLResult<()> { + let (int_value, _float_value) = match value { + TexParameterValue::Int(int_value) => (int_value, int_value as f32), + TexParameterValue::Float(float_value) => (float_value as i32, float_value), + }; + + match name { + constants::TEXTURE_MIN_FILTER => { + match int_value as u32 { + constants::NEAREST | + constants::LINEAR | + constants::NEAREST_MIPMAP_NEAREST | + constants::LINEAR_MIPMAP_NEAREST | + constants::NEAREST_MIPMAP_LINEAR | + constants::LINEAR_MIPMAP_LINEAR => { + self.renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::TexParameteri(target, name, int_value))) + .unwrap(); + return Ok(()); + }, + + _ => return Err(WebGLError::InvalidEnum), + } + }, + constants::TEXTURE_MAG_FILTER => { + match int_value as u32 { + constants::NEAREST | + constants::LINEAR => { + self.renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::TexParameteri(target, name, int_value))) + .unwrap(); + return Ok(()); + }, + + _ => return Err(WebGLError::InvalidEnum), + } + }, + constants::TEXTURE_WRAP_S | + constants::TEXTURE_WRAP_T => { + match int_value as u32 { + constants::CLAMP_TO_EDGE | + constants::MIRRORED_REPEAT | + constants::REPEAT => { + self.renderer + .send(CanvasMsg::WebGL(CanvasWebGLMsg::TexParameteri(target, name, int_value))) + .unwrap(); + return Ok(()); + }, + + _ => return Err(WebGLError::InvalidEnum), + } + }, + + _ => return Err(WebGLError::InvalidEnum), + } + } } diff --git a/components/script/dom/webidls/WebGLRenderingContext.webidl b/components/script/dom/webidls/WebGLRenderingContext.webidl index a888f97bac7..957220efa54 100644 --- a/components/script/dom/webidls/WebGLRenderingContext.webidl +++ b/components/script/dom/webidls/WebGLRenderingContext.webidl @@ -24,6 +24,11 @@ typedef unsigned long GLuint; typedef unrestricted float GLfloat; typedef unrestricted float GLclampf; +typedef (ImageData or + HTMLImageElement or + HTMLCanvasElement or + HTMLVideoElement) TexImageSource; + dictionary WebGLContextAttributes { GLboolean alpha = true; @@ -495,9 +500,9 @@ interface WebGLRenderingContextBase //[WebGLHandlesContextLoss] GLenum checkFramebufferStatus(GLenum target); void clear(GLbitfield mask); void clearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); - //void clearDepth(GLclampf depth); - //void clearStencil(GLint s); - //void colorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); + void clearDepth(GLclampf depth); + void clearStencil(GLint s); + void colorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); void compileShader(WebGLShader? shader); //void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, @@ -521,7 +526,7 @@ interface WebGLRenderingContextBase WebGLShader? createShader(GLenum type); WebGLTexture? createTexture(); - //void cullFace(GLenum mode); + void cullFace(GLenum mode); void deleteBuffer(WebGLBuffer? buffer); void deleteFramebuffer(WebGLFramebuffer? framebuffer); @@ -530,16 +535,16 @@ interface WebGLRenderingContextBase void deleteShader(WebGLShader? shader); void deleteTexture(WebGLTexture? texture); - //void depthFunc(GLenum func); - //void depthMask(GLboolean flag); - //void depthRange(GLclampf zNear, GLclampf zFar); + void depthFunc(GLenum func); + void depthMask(GLboolean flag); + void depthRange(GLclampf zNear, GLclampf zFar); //void detachShader(WebGLProgram? program, WebGLShader? shader); - //void disable(GLenum cap); + void disable(GLenum cap); //void disableVertexAttribArray(GLuint index); void drawArrays(GLenum mode, GLint first, GLsizei count); //void drawElements(GLenum mode, GLsizei count, GLenum type, GLintptr offset); - //void enable(GLenum cap); + void enable(GLenum cap); void enableVertexAttribArray(GLuint index); //void finish(); //void flush(); @@ -548,7 +553,7 @@ interface WebGLRenderingContextBase // WebGLRenderbuffer? renderbuffer); //void framebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, // WebGLTexture? texture, GLint level); - //void frontFace(GLenum mode); + void frontFace(GLenum mode); //void generateMipmap(GLenum target); @@ -584,7 +589,7 @@ interface WebGLRenderingContextBase //[WebGLHandlesContextLoss] GLsizeiptr getVertexAttribOffset(GLuint index, GLenum pname); - //void hint(GLenum target, GLenum mode); + void hint(GLenum target, GLenum mode); //[WebGLHandlesContextLoss] GLboolean isBuffer(WebGLBuffer? buffer); //[WebGLHandlesContextLoss] GLboolean isEnabled(GLenum cap); //[WebGLHandlesContextLoss] GLboolean isFramebuffer(WebGLFramebuffer? framebuffer); @@ -592,10 +597,10 @@ interface WebGLRenderingContextBase //[WebGLHandlesContextLoss] GLboolean isRenderbuffer(WebGLRenderbuffer? renderbuffer); //[WebGLHandlesContextLoss] GLboolean isShader(WebGLShader? shader); //[WebGLHandlesContextLoss] GLboolean isTexture(WebGLTexture? texture); - //void lineWidth(GLfloat width); + void lineWidth(GLfloat width); void linkProgram(WebGLProgram? program); - //void pixelStorei(GLenum pname, GLint param); - //void polygonOffset(GLfloat factor, GLfloat units); + void pixelStorei(GLenum pname, GLint param); + void polygonOffset(GLfloat factor, GLfloat units); //void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, // GLenum format, GLenum type, ArrayBufferView? pixels); @@ -614,18 +619,14 @@ interface WebGLRenderingContextBase //void stencilOp(GLenum fail, GLenum zfail, GLenum zpass); //void stencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass); - //typedef (ImageData or - // HTMLImageElement or - // HTMLCanvasElement or - // HTMLVideoElement) TexImageSource; //void texImage2D(GLenum target, GLint level, GLenum internalformat, // GLsizei width, GLsizei height, GLint border, GLenum format, // GLenum type, ArrayBufferView? pixels); - //void texImage2D(GLenum target, GLint level, GLenum internalformat, - // GLenum format, GLenum type, TexImageSource? source); // May throw DOMException + 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 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, |