servo: Merge #13872 - webgl: Add basic support for framebuffer attachments (from anholt:webgl-fbo); r=emilio
authorEric Anholt <eric@anholt.net>
Thu, 27 Oct 2016 21:39:15 -0500
changeset 339999 b57e3657949c211e6c840f58a997d501f89d393a
parent 339998 0134fc513d67e35bef8b99dd97515d42de518807
child 340000 5715901eb610b1886795e567c9320bb8e36a415c
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
servo: Merge #13872 - webgl: Add basic support for framebuffer attachments (from anholt:webgl-fbo); r=emilio This is by no means a complete implementation, but I've slowed down on working on it, so I think we should look at what it takes to merge the current code. There are some major features missing, like initializing renderbuffers to 0 (uninitialized memory leak), tracking the attachments' attributes (width/height/format) for parameter requests, and lots of missing glCheckFramebufferStatus() validation. On the other hand, this is enough to run some demos using FBOs. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #13639 (github issue number if applicable). <!-- Either: --> - [X] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: fbec79e920c0b0ddeaeeb6c0cc97b20ad85729e0
servo/components/script/dom/webglframebuffer.rs
servo/components/script/dom/webglrenderbuffer.rs
servo/components/script/dom/webglrenderingcontext.rs
servo/components/script/dom/webidls/WebGLRenderingContext.webidl
--- a/servo/components/script/dom/webglframebuffer.rs
+++ b/servo/components/script/dom/webglframebuffer.rs
@@ -1,45 +1,70 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
 use canvas_traits::CanvasMsg;
+use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::WebGLFramebufferBinding;
 use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
-use dom::bindings::js::Root;
+use dom::bindings::js::{HeapGCValue, JS, Root};
 use dom::bindings::reflector::reflect_dom_object;
 use dom::globalscope::GlobalScope;
 use dom::webglobject::WebGLObject;
+use dom::webglrenderbuffer::WebGLRenderbuffer;
+use dom::webgltexture::WebGLTexture;
 use ipc_channel::ipc::{self, IpcSender};
 use std::cell::Cell;
-use webrender_traits::{WebGLCommand, WebGLFramebufferBindingRequest, WebGLFramebufferId};
+use webrender_traits::{WebGLCommand, WebGLFramebufferBindingRequest, WebGLFramebufferId, WebGLResult, WebGLError};
+
+#[must_root]
+#[derive(JSTraceable, Clone, HeapSizeOf)]
+enum WebGLFramebufferAttachment {
+    Renderbuffer(JS<WebGLRenderbuffer>),
+    Texture(JS<WebGLTexture>),
+}
+
+impl HeapGCValue for WebGLFramebufferAttachment {}
 
 #[dom_struct]
 pub struct WebGLFramebuffer {
     webgl_object: WebGLObject,
     id: WebGLFramebufferId,
     /// target can only be gl::FRAMEBUFFER at the moment
     target: Cell<Option<u32>>,
     is_deleted: Cell<bool>,
+    status: Cell<u32>,
     #[ignore_heap_size_of = "Defined in ipc-channel"]
     renderer: IpcSender<CanvasMsg>,
+
+    // The attachment points for textures and renderbuffers on this
+    // FBO.
+    color: DOMRefCell<Option<WebGLFramebufferAttachment>>,
+    depth: DOMRefCell<Option<WebGLFramebufferAttachment>>,
+    stencil: DOMRefCell<Option<WebGLFramebufferAttachment>>,
+    depthstencil: DOMRefCell<Option<WebGLFramebufferAttachment>>,
 }
 
 impl WebGLFramebuffer {
     fn new_inherited(renderer: IpcSender<CanvasMsg>,
                      id: WebGLFramebufferId)
                      -> WebGLFramebuffer {
         WebGLFramebuffer {
             webgl_object: WebGLObject::new_inherited(),
             id: id,
             target: Cell::new(None),
             is_deleted: Cell::new(false),
             renderer: renderer,
+            status: Cell::new(constants::FRAMEBUFFER_UNSUPPORTED),
+            color: DOMRefCell::new(None),
+            depth: DOMRefCell::new(None),
+            stencil: DOMRefCell::new(None),
+            depthstencil: DOMRefCell::new(None),
         }
     }
 
     pub fn maybe_new(global: &GlobalScope, renderer: IpcSender<CanvasMsg>)
                      -> Option<Root<WebGLFramebuffer>> {
         let (sender, receiver) = ipc::channel().unwrap();
         renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateFramebuffer(sender))).unwrap();
 
@@ -59,36 +84,219 @@ impl WebGLFramebuffer {
 
 
 impl WebGLFramebuffer {
     pub fn id(&self) -> WebGLFramebufferId {
         self.id
     }
 
     pub fn bind(&self, target: u32) {
+        // Update the framebuffer status on binding.  It may have
+        // changed if its attachments were resized or deleted while
+        // we've been unbound.
+        self.update_status();
+
         self.target.set(Some(target));
         let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Explicit(self.id));
         self.renderer.send(CanvasMsg::WebGL(cmd)).unwrap();
     }
 
     pub fn delete(&self) {
         if !self.is_deleted.get() {
             self.is_deleted.set(true);
             let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteFramebuffer(self.id)));
         }
     }
 
     pub fn is_deleted(&self) -> bool {
         self.is_deleted.get()
     }
 
+    fn update_status(&self) {
+        let has_c = self.color.borrow().is_some();
+        let has_z = self.depth.borrow().is_some();
+        let has_s = self.stencil.borrow().is_some();
+        let has_zs = self.depthstencil.borrow().is_some();
+
+        // 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;
+        }
+
+        if has_c || has_z || has_zs || has_s {
+            self.status.set(constants::FRAMEBUFFER_COMPLETE);
+        } else {
+            self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
+        }
+    }
+
     pub fn check_status(&self) -> u32 {
-        // Until we build support for attaching renderbuffers or
-        // textures, all user FBOs are incomplete.
-        return constants::FRAMEBUFFER_UNSUPPORTED;
+        return self.status.get();
+    }
+
+    pub fn renderbuffer(&self, attachment: u32, rb: Option<&WebGLRenderbuffer>) -> WebGLResult<()> {
+        let binding = match attachment {
+            constants::COLOR_ATTACHMENT0 => &self.color,
+            constants::DEPTH_ATTACHMENT => &self.depth,
+            constants::STENCIL_ATTACHMENT => &self.stencil,
+            constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil,
+            _ => return Err(WebGLError::InvalidEnum),
+        };
+
+        let rb_id = match rb {
+            Some(rb) => {
+                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Renderbuffer(JS::from_ref(rb)));
+                Some(rb.id())
+            }
+
+            _ => {
+                *binding.borrow_mut() = None;
+                None
+            }
+        };
+
+        self.renderer.send(CanvasMsg::WebGL(WebGLCommand::FramebufferRenderbuffer(constants::FRAMEBUFFER,
+                                                                                  attachment,
+                                                                                  constants::RENDERBUFFER,
+                                                                                  rb_id))).unwrap();
+
+        self.update_status();
+        Ok(())
+    }
+
+    pub fn texture2d(&self, attachment: u32, textarget: u32, texture: Option<&WebGLTexture>,
+                     level: i32) -> WebGLResult<()> {
+        let binding = match attachment {
+            constants::COLOR_ATTACHMENT0 => &self.color,
+            constants::DEPTH_ATTACHMENT => &self.depth,
+            constants::STENCIL_ATTACHMENT => &self.stencil,
+            constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil,
+            _ => return Err(WebGLError::InvalidEnum),
+        };
+
+        let tex_id = match texture {
+            // Note, from the GLES 2.0.25 spec, page 113:
+            //      "If texture is zero, then textarget and level are ignored."
+            Some(texture) => {
+                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture(JS::from_ref(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
+                //      map texture and textarget must be one of:
+                //      TEXTURE_CUBE_MAP_POSITIVE_X,
+                //      TEXTURE_CUBE_MAP_POSITIVE_Y,
+                //      TEXTURE_CUBE_MAP_POSITIVE_Z,
+                //      TEXTURE_CUBE_MAP_NEGATIVE_X,
+                //      TEXTURE_CUBE_MAP_NEGATIVE_Y, or
+                //      TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise,
+                //      INVALID_OPERATION is generated."
+                let is_cube = match textarget {
+                    constants::TEXTURE_2D => false,
+
+                    constants::TEXTURE_CUBE_MAP_POSITIVE_X => true,
+                    constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true,
+                    constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true,
+                    constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true,
+                    constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true,
+                    constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true,
+
+                    _ => return Err(WebGLError::InvalidEnum),
+                };
+
+                match texture.target() {
+                    Some(constants::TEXTURE_CUBE_MAP) if is_cube => {}
+                    Some(_) if !is_cube => {}
+                    _ => return Err(WebGLError::InvalidOperation),
+                }
+
+                Some(texture.id())
+            }
+
+            _ => {
+                *binding.borrow_mut() = None;
+                self.update_status();
+                None
+            }
+        };
+
+        self.renderer.send(CanvasMsg::WebGL(WebGLCommand::FramebufferTexture2D(constants::FRAMEBUFFER,
+                                                                               attachment,
+                                                                               textarget,
+                                                                               tex_id,
+                                                                               level))).unwrap();
+
+        self.update_status();
+        Ok(())
+    }
+
+    pub fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) {
+        let attachments = [&self.color,
+                           &self.depth,
+                           &self.stencil,
+                           &self.depthstencil];
+
+        for attachment in &attachments {
+            let matched = {
+                match *attachment.borrow() {
+                    Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb))
+                        if rb.id() == att_rb.id() => true,
+                    _ => false,
+                }
+            };
+
+            if matched {
+                *attachment.borrow_mut() = None;
+                self.update_status();
+            }
+        }
+    }
+
+    pub fn detach_texture(&self, texture: &WebGLTexture) {
+        let attachments = [&self.color,
+                           &self.depth,
+                           &self.stencil,
+                           &self.depthstencil];
+
+        for attachment in &attachments {
+            let matched = {
+                match *attachment.borrow() {
+                    Some(WebGLFramebufferAttachment::Texture(ref att_texture))
+                        if texture.id() == att_texture.id() => true,
+                    _ => false,
+                }
+            };
+
+            if matched {
+                *attachment.borrow_mut() = None;
+            }
+        }
     }
 
     pub fn target(&self) -> Option<u32> {
         self.target.get()
     }
 }
 
 impl Drop for WebGLFramebuffer {
--- a/servo/components/script/dom/webglrenderbuffer.rs
+++ b/servo/components/script/dom/webglrenderbuffer.rs
@@ -1,43 +1,46 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
 use canvas_traits::CanvasMsg;
 use dom::bindings::codegen::Bindings::WebGLRenderbufferBinding;
+use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
 use dom::bindings::js::Root;
 use dom::bindings::reflector::reflect_dom_object;
 use dom::globalscope::GlobalScope;
 use dom::webglobject::WebGLObject;
 use ipc_channel::ipc::{self, IpcSender};
 use std::cell::Cell;
-use webrender_traits::{WebGLCommand, WebGLRenderbufferId};
+use webrender_traits::{WebGLCommand, WebGLRenderbufferId, WebGLResult, WebGLError};
 
 #[dom_struct]
 pub struct WebGLRenderbuffer {
     webgl_object: WebGLObject,
     id: WebGLRenderbufferId,
     ever_bound: Cell<bool>,
     is_deleted: Cell<bool>,
+    internal_format: Cell<Option<u32>>,
     #[ignore_heap_size_of = "Defined in ipc-channel"]
     renderer: IpcSender<CanvasMsg>,
 }
 
 impl WebGLRenderbuffer {
     fn new_inherited(renderer: IpcSender<CanvasMsg>,
                      id: WebGLRenderbufferId)
                      -> WebGLRenderbuffer {
         WebGLRenderbuffer {
             webgl_object: WebGLObject::new_inherited(),
             id: id,
             ever_bound: Cell::new(false),
             is_deleted: Cell::new(false),
             renderer: renderer,
+            internal_format: Cell::new(None),
         }
     }
 
     pub fn maybe_new(global: &GlobalScope, renderer: IpcSender<CanvasMsg>)
                      -> Option<Root<WebGLRenderbuffer>> {
         let (sender, receiver) = ipc::channel().unwrap();
         renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateRenderbuffer(sender))).unwrap();
 
@@ -76,9 +79,33 @@ impl WebGLRenderbuffer {
 
     pub fn is_deleted(&self) -> bool {
         self.is_deleted.get()
     }
 
     pub fn ever_bound(&self) -> bool {
         self.ever_bound.get()
     }
+
+    pub fn storage(&self, internal_format: u32, width: i32, height: i32) -> WebGLResult<()> {
+        // Validate the internal_format, and save it for completeness
+        // validation.
+        match internal_format {
+            constants::RGBA4 |
+            constants::DEPTH_STENCIL |
+            constants::DEPTH_COMPONENT16 |
+            constants::STENCIL_INDEX8 =>
+                self.internal_format.set(Some(internal_format)),
+
+            _ => return Err(WebGLError::InvalidEnum),
+        };
+
+        // FIXME: Check that w/h are < MAX_RENDERBUFFER_SIZE
+
+        // FIXME: Invalidate completeness after the call
+
+        let msg = CanvasMsg::WebGL(WebGLCommand::RenderbufferStorage(constants::RENDERBUFFER,
+                                                                     internal_format, width, height));
+        self.renderer.send(msg).unwrap();
+
+        Ok(())
+    }
 }
--- a/servo/components/script/dom/webglrenderingcontext.rs
+++ b/servo/components/script/dom/webglrenderingcontext.rs
@@ -69,21 +69,27 @@ macro_rules! handle_potential_webgl_erro
 //
 //     "If a texture that is currently bound to one of the targets
 //      TEXTURE_2D, or TEXTURE_CUBE_MAP is deleted, it is as though
 //      BindTexture had been executed with the same target and texture
 //      zero."
 //
 // and similar text occurs for other object types.
 macro_rules! handle_object_deletion {
-    ($binding:expr, $object:ident) => {
+    ($self_:expr, $binding:expr, $object:ident, $unbind_command:expr) => {
         if let Some(bound_object) = $binding.get() {
             if bound_object.id() == $object.id() {
                 $binding.set(None);
             }
+
+            if let Some(command) = $unbind_command {
+                $self_.ipc_renderer
+                    .send(CanvasMsg::WebGL(command))
+                    .unwrap();
+            }
         }
     };
 }
 
 macro_rules! object_binding_to_js_or_null {
     ($cx: expr, $binding:expr) => {
         {
             rooted!(in($cx) let mut rval = NullValue());
@@ -799,23 +805,33 @@ impl WebGLRenderingContextMethods for We
     }
 
     // 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);
         }
 
-        self.bound_framebuffer.set(framebuffer);
         if let Some(framebuffer) = framebuffer {
-            framebuffer.bind(target)
+            if framebuffer.is_deleted() {
+                // From the WebGL spec:
+                //
+                //     "An attempt to bind a deleted framebuffer will
+                //      generate an INVALID_OPERATION error, and the
+                //      current binding will remain untouched."
+                return self.webgl_error(InvalidOperation);
+            } else {
+                framebuffer.bind(target);
+                self.bound_framebuffer.set(Some(framebuffer));
+            }
         } else {
             // Bind the default framebuffer
             let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Default);
             self.ipc_renderer.send(CanvasMsg::WebGL(cmd)).unwrap();
+            self.bound_framebuffer.set(framebuffer);
         }
     }
 
     // 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);
         }
@@ -1236,51 +1252,87 @@ impl WebGLRenderingContextMethods for We
             }
         }
         WebGLShader::maybe_new(&self.global(), self.ipc_renderer.clone(), shader_type)
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
     fn DeleteBuffer(&self, buffer: Option<&WebGLBuffer>) {
         if let Some(buffer) = buffer {
-            handle_object_deletion!(self.bound_buffer_array, buffer);
-            handle_object_deletion!(self.bound_buffer_element_array, buffer);
+            handle_object_deletion!(self, self.bound_buffer_array, buffer,
+                                    Some(WebGLCommand::BindBuffer(constants::ARRAY_BUFFER, None)));
+            handle_object_deletion!(self, self.bound_buffer_element_array, buffer,
+                                    Some(WebGLCommand::BindBuffer(constants::ELEMENT_ARRAY_BUFFER, None)));
             buffer.delete()
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
     fn DeleteFramebuffer(&self, framebuffer: Option<&WebGLFramebuffer>) {
         if let Some(framebuffer) = framebuffer {
-            handle_object_deletion!(self.bound_framebuffer, framebuffer);
+            handle_object_deletion!(self, self.bound_framebuffer, framebuffer,
+                                    Some(WebGLCommand::BindFramebuffer(constants::FRAMEBUFFER,
+                                                                       WebGLFramebufferBindingRequest::Default)));
             framebuffer.delete()
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
     fn DeleteRenderbuffer(&self, renderbuffer: Option<&WebGLRenderbuffer>) {
         if let Some(renderbuffer) = renderbuffer {
-            handle_object_deletion!(self.bound_renderbuffer, renderbuffer);
+            handle_object_deletion!(self, self.bound_renderbuffer, renderbuffer,
+                                    Some(WebGLCommand::BindRenderbuffer(constants::RENDERBUFFER, None)));
+            // From the GLES 2.0.25 spec, page 113:
+            //
+            //     "If a renderbuffer object is deleted while its
+            //     image is attached to the currently bound
+            //     framebuffer, then it is as if
+            //     FramebufferRenderbuffer had been called, with a
+            //     renderbuffer of 0, for each attachment point to
+            //     which this image was attached in the currently
+            //     bound framebuffer."
+            //
+            if let Some(fb) = self.bound_framebuffer.get() {
+                fb.detach_renderbuffer(renderbuffer);
+            }
+
             renderbuffer.delete()
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
     fn DeleteTexture(&self, texture: Option<&WebGLTexture>) {
         if let Some(texture) = texture {
-            handle_object_deletion!(self.bound_texture_2d, texture);
-            handle_object_deletion!(self.bound_texture_cube_map, texture);
+            handle_object_deletion!(self, self.bound_texture_2d, texture,
+                                    Some(WebGLCommand::BindTexture(constants::TEXTURE_2D, None)));
+            handle_object_deletion!(self, self.bound_texture_cube_map, texture,
+                                    Some(WebGLCommand::BindTexture(constants::TEXTURE_CUBE_MAP, None)));
+
+            // From the GLES 2.0.25 spec, page 113:
+            //
+            //     "If a texture object is deleted while its image is
+            //      attached to the currently bound framebuffer, then
+            //      it is as if FramebufferTexture2D had been called,
+            //      with a texture of 0, for each attachment point to
+            //      which this image was attached in the currently
+            //      bound framebuffer."
+            if let Some(fb) = self.bound_framebuffer.get() {
+                fb.detach_texture(texture);
+            }
             texture.delete()
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
     fn DeleteProgram(&self, program: Option<&WebGLProgram>) {
         if let Some(program) = program {
-            handle_object_deletion!(self.current_program, program);
+            // FIXME: We should call glUseProgram(0), but
+            // WebGLCommand::UseProgram() doesn't take an Option
+            // currently.  This is also a problem for useProgram(null)
+            handle_object_deletion!(self, self.current_program, program, None);
             program.delete()
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
     fn DeleteShader(&self, shader: Option<&WebGLShader>) {
         if let Some(shader) = shader {
             shader.delete()
@@ -2521,16 +2573,92 @@ impl WebGLRenderingContextMethods for We
     fn TexParameterf(&self, target: u32, name: u32, value: f32) {
         self.tex_parameter(target, name, TexParameterValue::Float(value))
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
     fn TexParameteri(&self, target: u32, name: u32, value: i32) {
         self.tex_parameter(target, name, TexParameterValue::Int(value))
     }
+
+    fn CheckFramebufferStatus(&self, target: u32) -> u32 {
+        // From the GLES 2.0.25 spec, 4.4 ("Framebuffer Objects"):
+        //
+        //    "If target is not FRAMEBUFFER, INVALID_ENUM is
+        //     generated. If CheckFramebufferStatus generates an
+        //     error, 0 is returned."
+        if target != constants::FRAMEBUFFER {
+            self.webgl_error(InvalidEnum);
+            return 0;
+        }
+
+        match self.bound_framebuffer.get() {
+            Some(fb) => return fb.check_status(),
+            None => return constants::FRAMEBUFFER_COMPLETE,
+        }
+    }
+
+    fn RenderbufferStorage(&self, target: u32, internal_format: u32,
+                           width: i32, height: i32) {
+        // From the GLES 2.0.25 spec:
+        //
+        //    "target must be RENDERBUFFER."
+        if target != constants::RENDERBUFFER {
+            return self.webgl_error(InvalidOperation)
+        }
+
+        // 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 {
+            return self.webgl_error(InvalidValue);
+        }
+
+        match self.bound_renderbuffer.get() {
+            Some(rb) => handle_potential_webgl_error!(self, rb.storage(internal_format, width, height)),
+            None => self.webgl_error(InvalidOperation),
+        };
+
+        // FIXME: We need to clear the renderbuffer before it can be
+        // accessed.  See https://github.com/servo/servo/issues/13710
+    }
+
+    fn FramebufferRenderbuffer(&self, target: u32, attachment: u32,
+                               renderbuffertarget: u32,
+                               rb: Option<&WebGLRenderbuffer>) {
+        if target != constants::FRAMEBUFFER || renderbuffertarget != constants::RENDERBUFFER {
+            return self.webgl_error(InvalidEnum);
+        }
+
+        match self.bound_framebuffer.get() {
+            Some(fb) => handle_potential_webgl_error!(self, fb.renderbuffer(attachment, rb)),
+            None => self.webgl_error(InvalidOperation),
+        };
+    }
+
+    fn FramebufferTexture2D(&self, target: u32, attachment: u32,
+                            textarget: u32, texture: Option<&WebGLTexture>,
+                            level: i32) {
+        if target != constants::FRAMEBUFFER {
+            return self.webgl_error(InvalidEnum);
+        }
+
+        match self.bound_framebuffer.get() {
+            Some(fb) => handle_potential_webgl_error!(self, fb.texture2d(attachment, textarget, texture, level)),
+            None => self.webgl_error(InvalidOperation),
+        };
+    }
 }
 
 pub trait LayoutCanvasWebGLRenderingContextHelpers {
     #[allow(unsafe_code)]
     unsafe fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg>;
 }
 
 impl LayoutCanvasWebGLRenderingContextHelpers for LayoutJS<WebGLRenderingContext> {
--- a/servo/components/script/dom/webidls/WebGLRenderingContext.webidl
+++ b/servo/components/script/dom/webidls/WebGLRenderingContext.webidl
@@ -496,17 +496,17 @@ interface WebGLRenderingContextBase
     // in the meantime, and marking the function as [Throws], so we can handle
     // the type error from inside.
     [Throws]
     void bufferData(GLenum target, object? data, GLenum usage);
     //void bufferSubData(GLenum target, GLintptr offset, BufferDataSource? data);
     [Throws]
     void bufferSubData(GLenum target, GLintptr offset, object? data);
 
-    //[WebGLHandlesContextLoss] GLenum checkFramebufferStatus(GLenum target);
+    [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 compileShader(WebGLShader? shader);
 
     // FIXME(simartin) The Code generator doesn't handle ArrayBufferView so we're
@@ -561,21 +561,21 @@ interface WebGLRenderingContextBase
     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 enableVertexAttribArray(GLuint index);
     void finish();
     void flush();
-    //void framebufferRenderbuffer(GLenum target, GLenum attachment,
-    //                             GLenum renderbuffertarget,
-    //                             WebGLRenderbuffer? renderbuffer);
-    //void framebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget,
-    //                          WebGLTexture? texture, GLint level);
+    void framebufferRenderbuffer(GLenum target, GLenum attachment,
+                                 GLenum renderbuffertarget,
+                                 WebGLRenderbuffer? renderbuffer);
+    void framebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget,
+                              WebGLTexture? texture, GLint level);
     void frontFace(GLenum mode);
 
     void generateMipmap(GLenum target);
 
     WebGLActiveInfo? getActiveAttrib(WebGLProgram? program, GLuint index);
     WebGLActiveInfo? getActiveUniform(WebGLProgram? program, GLuint index);
     //sequence<WebGLShader>? getAttachedShaders(WebGLProgram? program);
 
@@ -621,18 +621,18 @@ interface WebGLRenderingContextBase
     void polygonOffset(GLfloat factor, GLfloat units);
 
     //void readPixels(GLint x, GLint y, GLsizei width, GLsizei height,
     //                GLenum format, GLenum type, ArrayBufferView? pixels);
     [Throws]
     void readPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                     GLenum format, GLenum type, object? pixels);
 
-    //void renderbufferStorage(GLenum target, GLenum internalformat,
-    //                         GLsizei width, GLsizei height);
+    void renderbufferStorage(GLenum target, GLenum internalformat,
+                             GLsizei width, GLsizei height);
     void sampleCoverage(GLclampf value, GLboolean invert);
     void scissor(GLint x, GLint y, GLsizei width, GLsizei height);
 
     void shaderSource(WebGLShader? shader, DOMString source);
 
     void stencilFunc(GLenum func, GLint ref, GLuint mask);
     void stencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask);
     void stencilMask(GLuint mask);