diff options
author | Mátyás Mustoha <matyas.mustoha@h-lab.eu> | 2020-02-20 13:32:38 +0100 |
---|---|---|
committer | Mátyás Mustoha <matyas.mustoha@h-lab.eu> | 2020-02-21 11:04:30 +0100 |
commit | b41805a9206f940737c2644f73d975ae35e38f99 (patch) | |
tree | 119787334ac1d38cb73cf3a1f7cae51aedf57c29 /components/script/dom/webglframebuffer.rs | |
parent | f5ff38b87559cd25db44fa7bc6f2a9c4cb2264e0 (diff) | |
download | servo-b41805a9206f940737c2644f73d975ae35e38f99.tar.gz servo-b41805a9206f940737c2644f73d975ae35e38f99.zip |
Add support for WebGL2 framebuffer attachments
Adds an initial implementation for the framebuffer attachments
introduced with WebGL2 and the related enums and constrains checks.
Diffstat (limited to 'components/script/dom/webglframebuffer.rs')
-rw-r--r-- | components/script/dom/webglframebuffer.rs | 302 |
1 files changed, 192 insertions, 110 deletions
diff --git a/components/script/dom/webglframebuffer.rs b/components/script/dom/webglframebuffer.rs index def5b36a27f..c2eb10fcd60 100644 --- a/components/script/dom/webglframebuffer.rs +++ b/components/script/dom/webglframebuffer.rs @@ -4,8 +4,8 @@ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; use crate::dom::bindings::codegen::Bindings::WebGLFramebufferBinding; -use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; @@ -14,9 +14,10 @@ use crate::dom::webglrenderbuffer::WebGLRenderbuffer; use crate::dom::webglrenderingcontext::WebGLRenderingContext; use crate::dom::webgltexture::WebGLTexture; use crate::dom::xrsession::XRSession; -use canvas_traits::webgl::{webgl_channel, WebGLError, WebGLResult}; +use canvas_traits::webgl::{webgl_channel, WebGLError, WebGLResult, WebGLVersion}; use canvas_traits::webgl::{WebGLCommand, WebGLFramebufferBindingRequest}; use canvas_traits::webgl::{WebGLFramebufferId, WebGLOpaqueFramebufferId}; +use canvas_traits::webgl::{WebGLRenderbufferId, WebGLTextureId}; use dom_struct::dom_struct; use euclid::Size2D; use std::cell::Cell; @@ -29,6 +30,10 @@ pub enum CompleteForRendering { MissingColorAttachment, } +fn log2(n: u32) -> u32 { + 31 - n.leading_zeros() +} + #[unrooted_must_root_lint::must_root] #[derive(Clone, JSTraceable, MallocSizeOf)] enum WebGLFramebufferAttachment { @@ -84,15 +89,15 @@ pub enum WebGLFramebufferAttachmentRoot { #[dom_struct] pub struct WebGLFramebuffer { webgl_object: WebGLObject, + webgl_version: WebGLVersion, id: WebGLFramebufferId, - /// target can only be gl::FRAMEBUFFER at the moment target: Cell<Option<u32>>, is_deleted: Cell<bool>, size: Cell<Option<(i32, i32)>>, status: Cell<u32>, // The attachment points for textures and renderbuffers on this // FBO. - color: DomRefCell<Option<WebGLFramebufferAttachment>>, + colors: Vec<DomRefCell<Option<WebGLFramebufferAttachment>>>, depth: DomRefCell<Option<WebGLFramebufferAttachment>>, stencil: DomRefCell<Option<WebGLFramebufferAttachment>>, depthstencil: DomRefCell<Option<WebGLFramebufferAttachment>>, @@ -106,12 +111,13 @@ impl WebGLFramebuffer { fn new_inherited(context: &WebGLRenderingContext, id: WebGLFramebufferId) -> Self { Self { webgl_object: WebGLObject::new_inherited(context), + webgl_version: context.webgl_version(), id: id, target: Cell::new(None), is_deleted: Cell::new(false), size: Cell::new(None), status: Cell::new(constants::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT), - color: DomRefCell::new(None), + colors: vec![DomRefCell::new(None); context.limits().max_color_attachments as usize], depth: DomRefCell::new(None), stencil: DomRefCell::new(None), depthstencil: DomRefCell::new(None), @@ -213,91 +219,138 @@ impl WebGLFramebuffer { self.size.get() } + fn check_attachment_constraints( + &self, + attachment: &Option<WebGLFramebufferAttachment>, + constraints: &[u32], + fb_size: &mut Option<(i32, i32)>, + ) -> Result<(), u32> { + // Get the size of this attachment. + let (format, size) = match attachment { + Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => { + (Some(att_rb.internal_format()), att_rb.size()) + }, + Some(WebGLFramebufferAttachment::Texture { + texture: ref att_tex, + level, + }) => match att_tex.image_info_at_face(0, *level as u32) { + Some(info) => ( + Some(info.internal_format().as_gl_constant()), + Some((info.width() as i32, info.height() as i32)), + ), + None => return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT), + }, + None => (None, None), + }; + + // Make sure that, if we've found any other attachment, + // that the size matches. + if size.is_some() { + if fb_size.is_some() && size != *fb_size { + return Err(constants::FRAMEBUFFER_INCOMPLETE_DIMENSIONS); + } else { + *fb_size = size; + } + } + + if let Some(format) = format { + if constraints.iter().all(|c| *c != format) { + return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT); + } + } + + Ok(()) + } + pub fn update_status(&self) { - let c = self.color.borrow(); let z = self.depth.borrow(); let s = self.stencil.borrow(); let zs = self.depthstencil.borrow(); - let has_c = c.is_some(); let has_z = z.is_some(); let has_s = s.is_some(); let has_zs = zs.is_some(); - let attachments = [&*c, &*z, &*s, &*zs]; + let attachments = [&*z, &*s, &*zs]; let attachment_constraints = [ &[ - constants::RGBA4, - constants::RGB5_A1, - constants::RGB565, - constants::RGBA, - constants::RGB, + constants::DEPTH_COMPONENT16, + constants::DEPTH_COMPONENT24, + constants::DEPTH_COMPONENT32F, + constants::DEPTH24_STENCIL8, + constants::DEPTH32F_STENCIL8, + ][..], + &[ + constants::STENCIL_INDEX8, + constants::DEPTH24_STENCIL8, + constants::DEPTH32F_STENCIL8, ][..], - &[constants::DEPTH_COMPONENT16][..], - &[constants::STENCIL_INDEX8][..], &[constants::DEPTH_STENCIL][..], ]; - // From the WebGL spec, 6.6 ("Framebuffer Object Attachments"): - // - // "In the WebGL API, it is an error to concurrently attach - // renderbuffers to the following combinations of - // attachment points: - // - // DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT - // STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT - // DEPTH_ATTACHMENT + STENCIL_ATTACHMENT - // - // If any of the constraints above are violated, then: - // - // checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED." - if (has_zs && (has_z || has_s)) || (has_z && has_s) { - self.status.set(constants::FRAMEBUFFER_UNSUPPORTED); - return; + let is_supported = match self.webgl_version { + // From the WebGL 1.0 spec, 6.6 ("Framebuffer Object Attachments"): + // + // "In the WebGL API, it is an error to concurrently attach + // renderbuffers to the following combinations of + // attachment points: + // + // DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT + // STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT + // DEPTH_ATTACHMENT + STENCIL_ATTACHMENT + // + // If any of the constraints above are violated, then: + // + // checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED." + WebGLVersion::WebGL1 => !(has_zs && (has_z || has_s)) && !(has_z && has_s), + + // In WebGL 2.0, DEPTH_STENCIL_ATTACHMENT is considered an alias for + // DEPTH_ATTACHMENT + STENCIL_ATTACHMENT, i.e., the same image is attached to both DEPTH_ATTACHMENT + // and STENCIL_ATTACHMENT, overwriting the original images attached to the two attachment points. + // If different images are bound to the depth and stencil attachment points, checkFramebufferStatus + // returns FRAMEBUFFER_UNSUPPORTED, and getFramebufferAttachmentParameter with attachment of + // DEPTH_STENCIL_ATTACHMENT generates an INVALID_OPERATION error. + // -- WebGL 2.0 spec, 4.1.5 Framebuffer Object Attachments + WebGLVersion::WebGL2 => { + use WebGLFramebufferAttachment::{Renderbuffer, Texture}; + match (&*z, &*s) { + (Some(Renderbuffer(a)), Some(Renderbuffer(b))) => a.id() == b.id(), + (Some(Texture { texture: a, .. }), Some(Texture { texture: b, .. })) => { + a.id() == b.id() + }, + _ => !has_z || !has_s, + } + }, + }; + if !is_supported { + return self.status.set(constants::FRAMEBUFFER_UNSUPPORTED); } let mut fb_size = None; + for (attachment, constraints) in attachments.iter().zip(&attachment_constraints) { - // Get the size of this attachment. - let (format, size) = match **attachment { - Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => { - (Some(att_rb.internal_format()), att_rb.size()) - }, - Some(WebGLFramebufferAttachment::Texture { - texture: ref att_tex, - level, - }) => match att_tex.image_info_at_face(0, level as u32) { - Some(info) => ( - Some(info.internal_format().as_gl_constant()), - Some((info.width() as i32, info.height() as i32)), - ), - None => { - self.status - .set(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT); - return; - }, - }, - None => (None, None), - }; - - // Make sure that, if we've found any other attachment, - // that the size matches. - if size.is_some() { - if fb_size.is_some() && size != fb_size { - self.status - .set(constants::FRAMEBUFFER_INCOMPLETE_DIMENSIONS); - return; - } else { - fb_size = size; - } + if let Err(errnum) = + self.check_attachment_constraints(attachment, constraints, &mut fb_size) + { + return self.status.set(errnum); } + } - if let Some(format) = format { - if constraints.iter().all(|c| *c != format) { - self.status - .set(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT); - return; - } + let color_constraints = &[ + constants::RGBA4, + constants::RGB5_A1, + constants::RGB565, + constants::RGBA, + constants::RGB, + ][..]; + let has_c = self.colors.iter().any(|att| att.borrow().is_some()); + for attachment in self.colors.iter() { + let attachment = attachment.borrow(); + if let Err(errnum) = + self.check_attachment_constraints(&*attachment, color_constraints, &mut fb_size) + { + return self.status.set(errnum); } } + self.size.set(fb_size); if has_c || has_z || has_zs || has_s { @@ -339,13 +392,12 @@ impl WebGLFramebuffer { return CompleteForRendering::Complete; } - if self.color.borrow().is_none() { + if self.colors.iter().any(|att| att.borrow().is_none()) { return CompleteForRendering::MissingColorAttachment; } if !self.is_initialized.get() { let attachments = [ - (&self.color, constants::COLOR_BUFFER_BIT), (&self.depth, constants::DEPTH_BUFFER_BIT), (&self.stencil, constants::STENCIL_BUFFER_BIT), ( @@ -362,6 +414,14 @@ impl WebGLFramebuffer { } } } + for attachment in self.colors.iter() { + if let Some(ref att) = *attachment.borrow() { + if att.needs_initialization() { + att.mark_initialized(); + clear_bits |= constants::COLOR_BUFFER_BIT; + } + } + } self.upcast::<WebGLObject>() .context() .initialize_framebuffer(clear_bits); @@ -397,7 +457,7 @@ impl WebGLFramebuffer { self.upcast::<WebGLObject>() .context() .send_command(WebGLCommand::FramebufferRenderbuffer( - constants::FRAMEBUFFER, + self.target.get().unwrap(), attachment, constants::RENDERBUFFER, rb_id, @@ -436,7 +496,10 @@ impl WebGLFramebuffer { attachment: u32, ) -> Option<&DomRefCell<Option<WebGLFramebufferAttachment>>> { match attachment { - constants::COLOR_ATTACHMENT0 => Some(&self.color), + constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => { + let idx = attachment - constants::COLOR_ATTACHMENT0; + self.colors.get(idx as usize) + }, constants::DEPTH_ATTACHMENT => Some(&self.depth), constants::STENCIL_ATTACHMENT => Some(&self.stencil), constants::DEPTH_STENCIL_ATTACHMENT => Some(&self.depthstencil), @@ -455,7 +518,7 @@ impl WebGLFramebuffer { WebGLFramebufferAttachment::Renderbuffer(ref rb) => { rb.attach_to_framebuffer(self); context.send_command(WebGLCommand::FramebufferRenderbuffer( - constants::FRAMEBUFFER, + self.target.get().unwrap(), attachment_point, constants::RENDERBUFFER, Some(rb.id()), @@ -464,7 +527,7 @@ impl WebGLFramebuffer { WebGLFramebufferAttachment::Texture { ref texture, level } => { texture.attach_to_framebuffer(self); context.send_command(WebGLCommand::FramebufferTexture2D( - constants::FRAMEBUFFER, + self.target.get().unwrap(), attachment_point, texture.target().expect("missing texture target"), Some(texture.id()), @@ -517,15 +580,6 @@ impl WebGLFramebuffer { // Note, from the GLES 2.0.25 spec, page 113: // "If texture is zero, then textarget and level are ignored." Some(texture) => { - // From the GLES 2.0.25 spec, page 113: - // - // "level specifies the mipmap level of the texture image - // to be attached to the framebuffer and must be - // 0. Otherwise, INVALID_VALUE is generated." - if level != 0 { - return Err(WebGLError::InvalidValue); - } - // "If texture is not zero, then texture must either // name an existing texture object with an target of // textarget, or texture must name an existing cube @@ -556,6 +610,16 @@ impl WebGLFramebuffer { _ => return Err(WebGLError::InvalidOperation), } + let context = self.upcast::<WebGLObject>().context(); + let max_tex_size = if is_cube { + context.limits().max_cube_map_tex_size + } else { + context.limits().max_tex_size + }; + if level < 0 || level as u32 > log2(max_tex_size) { + return Err(WebGLError::InvalidValue); + } + *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture { texture: Dom::from_ref(texture), level: level, @@ -571,7 +635,7 @@ impl WebGLFramebuffer { self.upcast::<WebGLObject>() .context() .send_command(WebGLCommand::FramebufferTexture2D( - constants::FRAMEBUFFER, + self.target.get().unwrap(), attachment, textarget, tex_id, @@ -591,57 +655,75 @@ impl WebGLFramebuffer { where F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32), { + let rb_id = rb.id(); let attachments = [ - (&self.color, constants::COLOR_ATTACHMENT0), (&self.depth, constants::DEPTH_ATTACHMENT), (&self.stencil, constants::STENCIL_ATTACHMENT), (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT), ]; - for (attachment, name) in &attachments { - let matched = { - match *attachment.borrow() { - Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) - if rb.id() == att_rb.id() => - { - true - }, - _ => false, - } - }; + fn has_matching_id( + attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>, + target: &WebGLRenderbufferId, + ) -> bool { + match *attachment.borrow() { + Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => { + att_rb.id() == *target + }, + _ => false, + } + } - if matched { + for (attachment, name) in &attachments { + if has_matching_id(attachment, &rb_id) { closure(attachment, *name); } } + + for (idx, attachment) in self.colors.iter().enumerate() { + if has_matching_id(attachment, &rb_id) { + let name = constants::COLOR_ATTACHMENT0 + idx as u32; + closure(attachment, name); + } + } } fn with_matching_textures<F>(&self, texture: &WebGLTexture, mut closure: F) where F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32), { + let tex_id = texture.id(); let attachments = [ - (&self.color, constants::COLOR_ATTACHMENT0), (&self.depth, constants::DEPTH_ATTACHMENT), (&self.stencil, constants::STENCIL_ATTACHMENT), (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT), ]; - for (attachment, name) in &attachments { - let matched = { - match *attachment.borrow() { - Some(WebGLFramebufferAttachment::Texture { - texture: ref att_texture, - .. - }) if texture.id() == att_texture.id() => true, - _ => false, - } - }; + fn has_matching_id( + attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>, + target: &WebGLTextureId, + ) -> bool { + match *attachment.borrow() { + Some(WebGLFramebufferAttachment::Texture { + texture: ref att_texture, + .. + }) if att_texture.id() == *target => true, + _ => false, + } + } - if matched { + for (attachment, name) in &attachments { + if has_matching_id(attachment, &tex_id) { closure(attachment, *name); } } + + for (idx, attachment) in self.colors.iter().enumerate() { + if has_matching_id(attachment, &tex_id) { + let name = constants::COLOR_ATTACHMENT0 + idx as u32; + closure(attachment, name); + } + } } pub fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) -> WebGLResult<()> { |