diff options
Diffstat (limited to 'components/script/dom/webglrenderingcontext.rs')
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 716 |
1 files changed, 514 insertions, 202 deletions
diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 7079bbcc76b..34d085d7f6b 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -4,14 +4,15 @@ use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; use canvas_traits::canvas::{byte_swap, multiply_u8_pixel}; -use canvas_traits::webgl::{DOMToTextureCommand, Parameter, ProgramParameter}; -use canvas_traits::webgl::{ShaderParameter, TexParameter, VertexAttrib, WebGLCommand}; +use canvas_traits::webgl::{ActiveAttribInfo, DOMToTextureCommand, Parameter}; +use canvas_traits::webgl::{ShaderParameter, TexParameter, WebGLCommand}; use canvas_traits::webgl::{WebGLContextShareMode, WebGLError}; use canvas_traits::webgl::{WebGLFramebufferBindingRequest, WebGLMsg, WebGLMsgSender}; use canvas_traits::webgl::{WebGLResult, WebGLSLVersion, WebGLVersion}; use canvas_traits::webgl::{WebVRCommand, webgl_channel}; use canvas_traits::webgl::WebGLError::*; use dom::bindings::cell::DomRefCell; +use dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants; use dom::bindings::codegen::Bindings::EXTBlendMinmaxBinding::EXTBlendMinmaxConstants; use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{self, WebGLContextAttributes}; use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; @@ -52,9 +53,10 @@ use euclid::Size2D; use fnv::FnvHashMap; use half::f16; use js::jsapi::{JSContext, JSObject, Type}; -use js::jsval::{BooleanValue, DoubleValue, Int32Value, UInt32Value, JSVal, NullValue, UndefinedValue}; +use js::jsval::{BooleanValue, DoubleValue, Int32Value, UInt32Value, JSVal}; +use js::jsval::{ObjectValue, NullValue, UndefinedValue}; use js::rust::CustomAutoRooterGuard; -use js::typedarray::ArrayBufferView; +use js::typedarray::{ArrayBufferView, CreateWith, Float32Array, Int32Array, Uint32Array}; use net_traits::image::base::PixelFormat; use net_traits::image_cache::ImageResponse; use offscreen_gl_context::{GLContextAttributes, GLLimits}; @@ -63,7 +65,7 @@ use script_layout_interface::HTMLCanvasDataSource; use servo_config::prefs::PREFS; use std::cell::{Cell, Ref}; use std::cmp; -use std::ptr::NonNull; +use std::ptr::{self, NonNull}; use webrender_api; type ImagePixelResult = Result<(Vec<u8>, Size2D<i32>, bool), ()>; @@ -193,7 +195,7 @@ pub struct WebGLRenderingContext { bound_texture_unit: Cell<u32>, bound_buffer_array: MutNullableDom<WebGLBuffer>, bound_buffer_element_array: MutNullableDom<WebGLBuffer>, - bound_attrib_buffers: BoundAttribBuffers, + vertex_attribs: VertexAttribs, current_program: MutNullableDom<WebGLProgram>, #[ignore_malloc_size_of = "Because it's small"] current_vertex_attrib_0: Cell<(f32, f32, f32, f32)>, @@ -234,6 +236,7 @@ impl WebGLRenderingContext { share_mode: ctx_data.share_mode, webgl_version, glsl_version: ctx_data.glsl_version, + vertex_attribs: VertexAttribs::new(ctx_data.limits.max_vertex_attribs), limits: ctx_data.limits, canvas: Dom::from_ref(canvas), last_error: Cell::new(None), @@ -244,13 +247,12 @@ impl WebGLRenderingContext { bound_texture_unit: Cell::new(constants::TEXTURE0), bound_buffer_array: MutNullableDom::new(None), bound_buffer_element_array: MutNullableDom::new(None), - bound_attrib_buffers: Default::default(), bound_renderbuffer: MutNullableDom::new(None), current_program: MutNullableDom::new(None), current_vertex_attrib_0: Cell::new((0f32, 0f32, 0f32, 1f32)), current_scissor: Cell::new((0, 0, size.width, size.height)), current_clear_color: Cell::new((0.0, 0.0, 0.0, 0.0)), - extension_manager: WebGLExtensions::new(webgl_version) + extension_manager: WebGLExtensions::new(webgl_version), } }) } @@ -312,8 +314,8 @@ impl WebGLRenderingContext { }) } - pub fn bound_attrib_buffers(&self) -> &BoundAttribBuffers { - &self.bound_attrib_buffers + pub fn vertex_attribs(&self) -> &VertexAttribs { + &self.vertex_attribs } pub fn bound_buffer_element_array(&self) -> Option<DomRoot<WebGLBuffer>> { @@ -1155,6 +1157,142 @@ impl WebGLRenderingContext { } } + // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ + pub fn draw_arrays_instanced( + &self, + mode: u32, + first: i32, + count: i32, + primcount: i32, + ) { + 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); + } + } + if first < 0 || count < 0 || primcount < 0 { + return self.webgl_error(InvalidValue); + } + + let current_program = handle_potential_webgl_error!( + self, + self.current_program.get().ok_or(InvalidOperation), + return + ); + + let required_len = if count > 0 { + handle_potential_webgl_error!( + self, + first.checked_add(count).map(|len| len as u32).ok_or(InvalidOperation), + return + ) + } else { + 0 + }; + + handle_potential_webgl_error!( + self, + self.vertex_attribs.validate_for_draw(required_len, primcount as u32, ¤t_program.active_attribs()), + return + ); + + if !self.validate_framebuffer_complete() { + return; + } + + self.send_command( + WebGLCommand::DrawArraysInstanced { mode, first, count, primcount }, + ); + self.mark_as_dirty(); + } + + // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ + pub fn draw_elements_instanced( + &self, + mode: u32, + count: i32, + type_: u32, + offset: i64, + primcount: i32, + ) { + 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); + } + } + if count < 0 || offset < 0 || primcount < 0 { + return self.webgl_error(InvalidValue); + } + let type_size = match type_ { + constants::UNSIGNED_BYTE => 1, + constants::UNSIGNED_SHORT => 2, + _ => return self.webgl_error(InvalidEnum), + }; + if offset % type_size != 0 { + return self.webgl_error(InvalidOperation); + } + + let current_program = handle_potential_webgl_error!( + self, + self.current_program.get().ok_or(InvalidOperation), + return + ); + + if count > 0 && primcount > 0 { + if let Some(array_buffer) = self.bound_buffer_element_array.get() { + // WebGL Spec: check buffer overflows, must be a valid multiple of the size. + 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 { + // From the WebGL spec + // + // a non-null WebGLBuffer must be bound to the ELEMENT_ARRAY_BUFFER binding point + // or an INVALID_OPERATION error will be generated. + // + return self.webgl_error(InvalidOperation); + } + } + + // TODO(nox): Pass the correct number of vertices required. + handle_potential_webgl_error!( + self, + self.vertex_attribs.validate_for_draw(0, primcount as u32, ¤t_program.active_attribs()), + return + ); + + if !self.validate_framebuffer_complete() { + return; + } + + self.send_command(WebGLCommand::DrawElementsInstanced { + mode, + count, + type_, + offset: offset as u32, + primcount, + }); + self.mark_as_dirty(); + } + + pub fn vertex_attrib_divisor(&self, index: u32, divisor: u32) { + if index >= self.limits.max_vertex_attribs { + return self.webgl_error(InvalidValue); + } + + self.vertex_attribs.set_divisor(index, divisor); + self.send_command(WebGLCommand::VertexAttribDivisor { index, divisor }); + } + // Used by HTMLCanvasElement.toDataURL // // This emits errors quite liberally, but the spec says that this operation @@ -1331,6 +1469,16 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return Int32Value(constants::UNSIGNED_BYTE as i32); } } + constants::COMPRESSED_TEXTURE_FORMATS => { + // FIXME(nox): https://github.com/servo/servo/issues/20594 + rooted!(in(cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Uint32Array::create( + cx, + CreateWith::Slice(&[]), + rval.handle_mut(), + ).unwrap(); + return ObjectValue(rval.get()); + } constants::VERSION => { rooted!(in(cx) let mut rval = UndefinedValue()); "WebGL 1.0".to_jsval(cx, rval.handle_mut()); @@ -1422,13 +1570,27 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { self.send_command(WebGLCommand::GetParameterInt(param, sender)); Int32Value(receiver.recv().unwrap()) } + Parameter::Int2(param) => { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterInt2(param, sender)); + rooted!(in(cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Int32Array::create( + cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ).unwrap(); + ObjectValue(rval.get()) + } Parameter::Int4(param) => { let (sender, receiver) = webgl_channel().unwrap(); self.send_command(WebGLCommand::GetParameterInt4(param, sender)); - // FIXME(nox): https://github.com/servo/servo/issues/20655 - rooted!(in(cx) let mut rval = UndefinedValue()); - receiver.recv().unwrap().to_jsval(cx, rval.handle_mut()); - rval.get() + rooted!(in(cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Int32Array::create( + cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ).unwrap(); + ObjectValue(rval.get()) } Parameter::Float(param) => { let (sender, receiver) = webgl_channel().unwrap(); @@ -1438,18 +1600,24 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Parameter::Float2(param) => { let (sender, receiver) = webgl_channel().unwrap(); self.send_command(WebGLCommand::GetParameterFloat2(param, sender)); - // FIXME(nox): https://github.com/servo/servo/issues/20655 - rooted!(in(cx) let mut rval = UndefinedValue()); - receiver.recv().unwrap().to_jsval(cx, rval.handle_mut()); - rval.get() + rooted!(in(cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Float32Array::create( + cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ).unwrap(); + ObjectValue(rval.get()) } Parameter::Float4(param) => { let (sender, receiver) = webgl_channel().unwrap(); self.send_command(WebGLCommand::GetParameterFloat4(param, sender)); - // FIXME(nox): https://github.com/servo/servo/issues/20655 - rooted!(in(cx) let mut rval = UndefinedValue()); - receiver.recv().unwrap().to_jsval(cx, rval.handle_mut()); - rval.get() + rooted!(in(cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Float32Array::create( + cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ).unwrap(); + ObjectValue(rval.get()) } } } @@ -2021,7 +2189,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { fn CompileShader(&self, shader: &WebGLShader) { handle_potential_webgl_error!( self, - shader.compile(self.webgl_version, self.glsl_version, &self.extension_manager) + shader.compile( + self.webgl_version, + self.glsl_version, + &self.limits, + &self.extension_manager, + ) ) } @@ -2075,7 +2248,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } // Remove deleted buffer from bound attrib buffers. - self.bound_attrib_buffers.remove_buffer(buffer); + self.vertex_attribs.delete_buffer(buffer); // Delete buffer. handle_object_deletion!(self, self.bound_buffer_array, buffer, @@ -2193,22 +2366,28 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { if first < 0 || count < 0 { return self.webgl_error(InvalidValue); } - if self.current_program.get().is_none() { - return self.webgl_error(InvalidOperation); - } - if let Some(array_buffer) = self.bound_buffer_array.get() { - if count > 0 && (first as u64 + count as u64 > array_buffer.capacity() as u64) { - return self.webgl_error(InvalidOperation); - } - } - { - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.2 - let buffers = self.bound_attrib_buffers.borrow(); - if buffers.iter().any(|(_, &(enabled, ref buffer))| enabled && buffer.is_none()) { - return self.webgl_error(InvalidOperation); - } - } + let current_program = handle_potential_webgl_error!( + self, + self.current_program.get().ok_or(InvalidOperation), + return + ); + + let required_len = if count > 0 { + handle_potential_webgl_error!( + self, + first.checked_add(count).map(|len| len as u32).ok_or(InvalidOperation), + return + ) + } else { + 0 + }; + + handle_potential_webgl_error!( + self, + self.vertex_attribs.validate_for_draw(required_len, 1, ¤t_program.active_attribs()), + return + ); if !self.validate_framebuffer_complete() { return; @@ -2250,15 +2429,11 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return self.webgl_error(InvalidValue); } - if self.current_program.get().is_none() { - // From the WebGL spec - // - // If the CURRENT_PROGRAM is null, an INVALID_OPERATION error will be generated. - // WebGL performs additional error checking beyond that specified - // in OpenGL ES 2.0 during calls to drawArrays and drawElements. - // - return self.webgl_error(InvalidOperation); - } + let current_program = handle_potential_webgl_error!( + self, + self.current_program.get().ok_or(InvalidOperation), + return + ); if count > 0 { if let Some(array_buffer) = self.bound_buffer_element_array.get() { @@ -2277,13 +2452,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } } - { - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.2 - let buffers = self.bound_attrib_buffers.borrow(); - if buffers.iter().any(|(_, &(enabled, ref buffer))| enabled && buffer.is_none()) { - return self.webgl_error(InvalidOperation); - } - } + // TODO(nox): Pass the correct number of vertices required. + handle_potential_webgl_error!( + self, + self.vertex_attribs.validate_for_draw(0, 1, ¤t_program.active_attribs()), + return + ); if !self.validate_framebuffer_complete() { return; @@ -2299,7 +2473,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return self.webgl_error(InvalidValue); } - self.bound_attrib_buffers.enabled(attrib_id, true); + self.vertex_attribs.enabled_as_array(attrib_id, true); self.send_command(WebGLCommand::EnableVertexAttribArray(attrib_id)); } @@ -2309,7 +2483,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return self.webgl_error(InvalidValue); } - self.bound_attrib_buffers.enabled(attrib_id, false); + self.vertex_attribs.enabled_as_array(attrib_id, false); self.send_command(WebGLCommand::DisableVertexAttribArray(attrib_id)); } @@ -2326,18 +2500,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 fn GetActiveAttrib(&self, program: &WebGLProgram, index: u32) -> Option<DomRoot<WebGLActiveInfo>> { - match program.get_active_attrib(index) { - Ok(ret) => Some(ret), - Err(e) => { - self.webgl_error(e); - return None; - } - } + handle_potential_webgl_error!(self, program.get_active_attrib(index).map(Some), None) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 fn GetAttribLocation(&self, program: &WebGLProgram, name: DOMString) -> i32 { - handle_potential_webgl_error!(self, program.get_attrib_location(name), None).unwrap_or(-1) + handle_potential_webgl_error!(self, program.get_attrib_location(name), -1) } #[allow(unsafe_code)] @@ -2509,23 +2677,39 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 unsafe fn GetProgramParameter(&self, _: *mut JSContext, program: &WebGLProgram, param: u32) -> JSVal { - match handle_potential_webgl_error!(self, ProgramParameter::from_u32(param), return NullValue()) { - ProgramParameter::Bool(param) => { + // FIXME(nox): INVALID_OPERATION if program comes from a different context. + match param { + constants::DELETE_STATUS => BooleanValue(program.is_deleted()), + constants::LINK_STATUS => BooleanValue(program.is_linked()), + constants::VALIDATE_STATUS => { + // FIXME(nox): This could be cached on the DOM side when we call validateProgram + // but I'm not sure when the value should be reset. let (sender, receiver) = webgl_channel().unwrap(); - self.send_command(WebGLCommand::GetProgramParameterBool(program.id(), param, sender)); + self.send_command(WebGLCommand::GetProgramValidateStatus(program.id(), sender)); BooleanValue(receiver.recv().unwrap()) } - ProgramParameter::Int(param) => { + constants::ATTACHED_SHADERS => { + // FIXME(nox): This allocates a vector and roots a couple of shaders for nothing. + Int32Value(program.attached_shaders().map(|shaders| shaders.len() as i32).unwrap_or(0)) + } + constants::ACTIVE_ATTRIBUTES => Int32Value(program.active_attribs().len() as i32), + constants::ACTIVE_UNIFORMS => { + // FIXME(nox): We'll need to cache that on the DOM side at some point. let (sender, receiver) = webgl_channel().unwrap(); - self.send_command(WebGLCommand::GetProgramParameterInt(program.id(), param, sender)); + self.send_command(WebGLCommand::GetProgramActiveUniforms(program.id(), sender)); Int32Value(receiver.recv().unwrap()) } + _ => { + self.webgl_error(InvalidEnum); + NullValue() + } } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn GetShaderInfoLog(&self, shader: &WebGLShader) -> Option<DOMString> { - shader.info_log().map(DOMString::from) + // TODO(nox): https://github.com/servo/servo/issues/21133 + Some(shader.info_log()) } #[allow(unsafe_code)] @@ -2591,43 +2775,55 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 unsafe fn GetVertexAttrib(&self, cx: *mut JSContext, index: u32, param: u32) -> JSVal { - if index == 0 && param == constants::CURRENT_VERTEX_ATTRIB { - rooted!(in(cx) let mut result = UndefinedValue()); - let (x, y, z, w) = self.current_vertex_attrib_0.get(); - let attrib = vec![x, y, z, w]; - attrib.to_jsval(cx, result.handle_mut()); - return result.get() + let data = handle_potential_webgl_error!( + self, + self.vertex_attribs.get(index).ok_or(InvalidValue), + return NullValue() + ); + if param == constants::CURRENT_VERTEX_ATTRIB { + let value = if index == 0 { + let (x, y, z, w) = self.current_vertex_attrib_0.get(); + [x, y, z, w] + } else { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetCurrentVertexAttrib(index, sender)); + receiver.recv().unwrap() + }; + rooted!(in(cx) let mut result = ptr::null_mut::<JSObject>()); + let _ = Float32Array::create( + cx, + CreateWith::Slice(&value), + result.handle_mut(), + ).unwrap(); + return ObjectValue(result.get()); } - if param == constants::VERTEX_ATTRIB_ARRAY_BUFFER_BINDING { - rooted!(in(cx) let mut jsval = NullValue()); - if let Some(buffer) = self.bound_attrib_buffers.get(index) { - buffer.to_jsval(cx, jsval.handle_mut()); - } - return jsval.get(); + if !self.extension_manager.is_get_vertex_attrib_name_enabled(param) { + self.webgl_error(WebGLError::InvalidEnum); + return NullValue(); } - match handle_potential_webgl_error!(self, VertexAttrib::from_u32(param), return NullValue()) { - VertexAttrib::Bool(param) => { - let (sender, receiver) = webgl_channel().unwrap(); - self.send_command(WebGLCommand::GetVertexAttribBool(index, param, sender)); - let value = handle_potential_webgl_error!(self, receiver.recv().unwrap(), return NullValue()); - BooleanValue(value) + match param { + constants::VERTEX_ATTRIB_ARRAY_ENABLED => BooleanValue(data.enabled_as_array), + constants::VERTEX_ATTRIB_ARRAY_SIZE => Int32Value(data.size as i32), + constants::VERTEX_ATTRIB_ARRAY_TYPE => Int32Value(data.type_ as i32), + constants::VERTEX_ATTRIB_ARRAY_NORMALIZED => BooleanValue(data.normalized), + constants::VERTEX_ATTRIB_ARRAY_STRIDE => Int32Value(data.stride as i32), + constants::VERTEX_ATTRIB_ARRAY_BUFFER_BINDING => { + rooted!(in(cx) let mut jsval = NullValue()); + if let Some(data) = self.vertex_attribs.get(index) { + if let Some(buffer) = data.buffer() { + buffer.to_jsval(cx, jsval.handle_mut()); + } + } + jsval.get() } - VertexAttrib::Int(param) => { - let (sender, receiver) = webgl_channel().unwrap(); - self.send_command(WebGLCommand::GetVertexAttribInt(index, param, sender)); - let value = handle_potential_webgl_error!(self, receiver.recv().unwrap(), return NullValue()); - Int32Value(value) + ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE => { + Int32Value(data.divisor as i32) } - VertexAttrib::Float4(param) => { - let (sender, receiver) = webgl_channel().unwrap(); - self.send_command(WebGLCommand::GetVertexAttribFloat4(index, param, sender)); - let value = handle_potential_webgl_error!(self, receiver.recv().unwrap(), return NullValue()); - // FIXME(nox): https://github.com/servo/servo/issues/20655 - rooted!(in(cx) let mut result = UndefinedValue()); - value.to_jsval(cx, result.handle_mut()); - result.get() + _ => { + self.webgl_error(InvalidEnum); + NullValue() } } } @@ -2638,10 +2834,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { self.webgl_error(InvalidEnum); return 0; } - let (sender, receiver) = webgl_channel().unwrap(); - self.send_command(WebGLCommand::GetVertexAttribOffset(index, pname, sender)); - - receiver.recv().unwrap() as i64 + let data = handle_potential_webgl_error!( + self, + self.vertex_attribs.get(index).ok_or(InvalidValue), + return 0 + ); + data.offset as i64 } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 @@ -2956,12 +3154,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn LinkProgram(&self, program: Option<&WebGLProgram>) { - if let Some(program) = program { - if let Err(e) = program.link() { - self.webgl_error(e); - } - } + fn LinkProgram(&self, program: &WebGLProgram) { + // FIXME(nox): INVALID_OPERATION if program comes from a different context. + handle_potential_webgl_error!(self, program.link()); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 @@ -2971,7 +3166,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn GetShaderSource(&self, shader: &WebGLShader) -> Option<DOMString> { - shader.source() + // TODO(nox): https://github.com/servo/servo/issues/21133 + Some(shader.source()) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 @@ -3245,11 +3441,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn UseProgram(&self, program: Option<&WebGLProgram>) { if let Some(program) = program { - match program.use_program() { - Ok(()) => self.current_program.set(Some(program)), - Err(e) => self.webgl_error(e), + if program.is_deleted() || !program.is_linked() { + return self.webgl_error(InvalidOperation); } } + self.send_command(WebGLCommand::UseProgram(program.map(|p| p.id()))); + self.current_program.set(program); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 @@ -3328,49 +3525,30 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn VertexAttribPointer(&self, attrib_id: u32, size: i32, data_type: u32, - normalized: bool, stride: i32, offset: i64) { - if attrib_id >= self.limits.max_vertex_attribs { - return self.webgl_error(InvalidValue); - } - - // GLES spec: If offset or stride is negative, an INVALID_VALUE error will be generated - // WebGL spec: the maximum supported stride is 255 - if stride < 0 || stride > 255 || offset < 0 { - return self.webgl_error(InvalidValue); - } - if size < 1 || size > 4 { - return self.webgl_error(InvalidValue); - } - - let buffer_array = match self.bound_buffer_array.get() { - Some(buffer) => buffer, - None => { - return self.webgl_error(InvalidOperation); - } - }; - - // stride and offset must be multiple of data_type - match data_type { - constants::BYTE | constants::UNSIGNED_BYTE => {}, - constants::SHORT | constants::UNSIGNED_SHORT => { - if offset % 2 > 0 || stride % 2 > 0 { - return self.webgl_error(InvalidOperation); - } - }, - constants::FLOAT => { - if offset % 4 > 0 || stride % 4 > 0 { - return self.webgl_error(InvalidOperation); - } - }, - _ => return self.webgl_error(InvalidEnum), - - } - - self.bound_attrib_buffers.bind_buffer(attrib_id, &buffer_array); + fn VertexAttribPointer( + &self, + index: u32, + size: i32, + type_: u32, + normalized: bool, + stride: i32, + offset: i64, + ) { + handle_potential_webgl_error!( + self, + self.vertex_attribs.set_pointer( + index, + size, + type_, + normalized, + stride, + offset, + self.bound_buffer_array.get().as_ref().map(|buffer| &**buffer), + ), + return + ); - let msg = WebGLCommand::VertexAttribPointer(attrib_id, size, data_type, normalized, stride, offset as u32); - self.send_command(msg); + self.send_command(WebGLCommand::VertexAttribPointer(index, size, type_, normalized, stride, offset as u32)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4 @@ -3685,27 +3863,14 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 - fn RenderbufferStorage(&self, target: u32, internal_format: u32, - width: i32, height: i32) { - // From the GLES 2.0.25 spec: - // - // "target must be RENDERBUFFER." + fn RenderbufferStorage(&self, target: u32, internal_format: u32, width: i32, height: i32) { if target != constants::RENDERBUFFER { return self.webgl_error(InvalidEnum); } - // From the GLES 2.0.25 spec: - // - // "If either width or height is greater than the value of - // MAX_RENDERBUFFER_SIZE , the error INVALID_VALUE is - // generated." - // - // and we have to throw out negative-size values as well just - // like for TexImage. - // - // FIXME: Handle max_renderbuffer_size, which doesn't seem to - // be in limits. - if width < 0 || height < 0 { + let max = self.limits.max_renderbuffer_size; + + if width < 0 || width as u32 > max || height < 0 || height as u32 > max { return self.webgl_error(InvalidValue); } @@ -3805,48 +3970,195 @@ impl UniformSetterType { } } -#[derive(Default, JSTraceable, MallocSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] #[must_root] -pub struct BoundAttribBuffers { - elements: DomRefCell<FnvHashMap<u32, (bool, Option<Dom<WebGLBuffer>>)>>, +pub struct VertexAttribs { + attribs: DomRefCell<Box<[VertexAttribData]>>, } -impl BoundAttribBuffers { +impl VertexAttribs { + pub fn new(max: u32) -> Self { + // High-end GPUs have 16 of those, let's just use a boxed slice. + Self { attribs: DomRefCell::new(vec![Default::default(); max as usize].into()) } + } + pub fn clear(&self) { - self.elements.borrow_mut().clear() + for attrib in &mut **self.attribs.borrow_mut() { + *attrib = Default::default(); + } } - pub fn set_from(&self, other: &BoundAttribBuffers) { - *self.elements.borrow_mut() = other.elements.borrow().clone(); + pub fn clone_from(&self, other: &Self) { + self.attribs.borrow_mut().clone_from_slice(&other.attribs.borrow()); } - pub fn borrow(&self) -> Ref<FnvHashMap<u32, (bool, Option<Dom<WebGLBuffer>>)>> { - self.elements.borrow() + pub fn set_pointer( + &self, + index: u32, + size: i32, + type_: u32, + normalized: bool, + stride: i32, + offset: i64, + buffer: Option<&WebGLBuffer>, + ) -> WebGLResult<()> { + let mut attribs = self.attribs.borrow_mut(); + let data = attribs.get_mut(index as usize).ok_or(InvalidValue)?; + + if size < 1 || size > 4 { + return Err(InvalidValue); + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#BUFFER_OFFSET_AND_STRIDE + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#VERTEX_STRIDE + if stride < 0 || stride > 255 || offset < 0 { + return Err(InvalidValue); + } + let bytes_per_component: i32 = match type_ { + constants::BYTE | constants::UNSIGNED_BYTE => 1, + constants::SHORT | constants::UNSIGNED_SHORT => 2, + constants::FLOAT => 4, + _ => return Err(InvalidEnum), + }; + if offset % bytes_per_component as i64 > 0 || stride % bytes_per_component > 0 { + return Err(InvalidOperation); + } + + let buffer = buffer.ok_or(InvalidOperation)?; + + *data = VertexAttribData { + enabled_as_array: data.enabled_as_array, + size: size as u8, + type_, + bytes_per_vertex: size as u8 * bytes_per_component as u8, + normalized, + stride: stride as u8, + offset: offset as u32, + buffer: Some(Dom::from_ref(buffer)), + divisor: data.divisor, + }; + Ok(()) } - fn remove_buffer(&self, buffer: &WebGLBuffer) { - self.elements.borrow_mut().retain(|_, v| { - v.1.as_ref().map_or(true, |b| b.id() != buffer.id()) - }) + pub fn borrow(&self) -> Ref<[VertexAttribData]> { + Ref::map(self.attribs.borrow(), |attribs| &**attribs) } - fn get(&self, index: u32) -> Option<Ref<WebGLBuffer>> { - ref_filter_map(self.elements.borrow(), |elements| { - elements.get(&index).and_then(|&(_, ref buffer)| { - buffer.as_ref().map(|b| &**b) - }) - }) + fn delete_buffer(&self, buffer: &WebGLBuffer) { + for attrib in &mut **self.attribs.borrow_mut() { + if attrib.buffer().map_or(false, |b| b.id() == buffer.id()) { + attrib.buffer = None; + } + } + } + + fn get(&self, index: u32) -> Option<Ref<VertexAttribData>> { + ref_filter_map(self.attribs.borrow(), |attribs| attribs.get(index as usize)) + } + + fn enabled_as_array(&self, index: u32, value: bool) { + self.attribs.borrow_mut()[index as usize].enabled_as_array = value; + } + + fn set_divisor(&self, index: u32, value: u32) { + self.attribs.borrow_mut()[index as usize].divisor = value; + } + + fn validate_for_draw( + &self, + required_len: u32, + instance_count: u32, + active_attribs: &[ActiveAttribInfo], + ) -> WebGLResult<()> { + // TODO(nox): Cache limits per VAO. + let attribs = self.attribs.borrow(); + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.2 + if attribs.iter().any(|data| data.enabled_as_array && data.buffer.is_none()) { + return Err(InvalidOperation); + } + let mut has_active_attrib = false; + let mut has_divisor_0 = false; + for active_info in active_attribs { + if active_info.location < 0 { + continue; + } + has_active_attrib = true; + let attrib = &attribs[active_info.location as usize]; + if attrib.divisor == 0 { + has_divisor_0 = true; + } + if !attrib.enabled_as_array { + continue; + } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.6 + if required_len > 0 && instance_count > 0 { + let max_vertices = attrib.max_vertices(); + if attrib.divisor == 0 { + if max_vertices < required_len { + return Err(InvalidOperation); + } + } else if max_vertices.checked_mul(attrib.divisor).map_or(false, |v| v < instance_count) { + return Err(InvalidOperation); + } + } + } + if has_active_attrib && !has_divisor_0 { + return Err(InvalidOperation); + } + Ok(()) + } +} + +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[must_root] +pub struct VertexAttribData { + enabled_as_array: bool, + size: u8, + type_: u32, + bytes_per_vertex: u8, + normalized: bool, + stride: u8, + offset: u32, + buffer: Option<Dom<WebGLBuffer>>, + divisor: u32, +} + +impl Default for VertexAttribData { + #[allow(unrooted_must_root)] + fn default() -> Self { + Self { + enabled_as_array: false, + size: 4, + type_: constants::FLOAT, + bytes_per_vertex: 16, + normalized: false, + stride: 0, + offset: 0, + buffer: None, + divisor: 0, + } } +} - fn enabled(&self, index: u32, value: bool) { - let mut elements = self.elements.borrow_mut(); - let pair = elements.entry(index).or_insert((false, None)); - pair.0 = value; +impl VertexAttribData { + pub fn buffer(&self) -> Option<&WebGLBuffer> { + self.buffer.as_ref().map(|b| &**b) } - fn bind_buffer(&self, index: u32, buffer: &WebGLBuffer) { - let mut elements = self.elements.borrow_mut(); - let pair = elements.entry(index).or_insert((false, None)); - pair.1 = Some(Dom::from_ref(buffer)); + fn max_vertices(&self) -> u32 { + let capacity = (self.buffer().unwrap().capacity() as u32).saturating_sub(self.offset); + if capacity < self.bytes_per_vertex as u32 { + 0 + } else if self.stride == 0 { + capacity / self.bytes_per_vertex as u32 + } else if self.stride < self.bytes_per_vertex { + (capacity - (self.bytes_per_vertex - self.stride) as u32) / self.stride as u32 + } else { + let mut max = capacity / self.stride as u32; + if capacity % self.stride as u32 >= self.bytes_per_vertex as u32 { + max += 1; + } + max + } } } |