diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2016-10-27 21:39:15 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-10-27 21:39:15 -0500 |
commit | fbec79e920c0b0ddeaeeb6c0cc97b20ad85729e0 (patch) | |
tree | c3721f70f05a9d6fa9d86292d9b532b0d5c9856f /components/script | |
parent | b4a882f81ab9d8128166a49f0514a398ad7a3b7d (diff) | |
parent | 71d266052b09c031707139466163a7087f0acc19 (diff) | |
download | servo-fbec79e920c0b0ddeaeeb6c0cc97b20ad85729e0.tar.gz servo-fbec79e920c0b0ddeaeeb6c0cc97b20ad85729e0.zip |
Auto merge of #13872 - anholt:webgl-fbo, r=emilio
webgl: Add basic support for framebuffer attachments
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. -->
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/13872)
<!-- Reviewable:end -->
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/webglframebuffer.rs | 218 | ||||
-rw-r--r-- | components/script/dom/webglrenderbuffer.rs | 29 | ||||
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 148 | ||||
-rw-r--r-- | components/script/dom/webidls/WebGLRenderingContext.webidl | 16 |
4 files changed, 387 insertions, 24 deletions
diff --git a/components/script/dom/webglframebuffer.rs b/components/script/dom/webglframebuffer.rs index 22749832389..8e22f17d079 100644 --- a/components/script/dom/webglframebuffer.rs +++ b/components/script/dom/webglframebuffer.rs @@ -4,15 +4,27 @@ // 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 { @@ -21,8 +33,16 @@ pub struct WebGLFramebuffer { /// 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 { @@ -35,6 +55,11 @@ impl WebGLFramebuffer { 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), } } @@ -64,6 +89,11 @@ impl WebGLFramebuffer { } 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(); @@ -80,10 +110,188 @@ impl WebGLFramebuffer { 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> { diff --git a/components/script/dom/webglrenderbuffer.rs b/components/script/dom/webglrenderbuffer.rs index de4eaa2d2c8..9e3c516cfbd 100644 --- a/components/script/dom/webglrenderbuffer.rs +++ b/components/script/dom/webglrenderbuffer.rs @@ -5,13 +5,14 @@ // 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 { @@ -19,6 +20,7 @@ pub struct WebGLRenderbuffer { 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>, } @@ -33,6 +35,7 @@ impl WebGLRenderbuffer { ever_bound: Cell::new(false), is_deleted: Cell::new(false), renderer: renderer, + internal_format: Cell::new(None), } } @@ -81,4 +84,28 @@ impl WebGLRenderbuffer { 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(()) + } } diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index abfa10983b0..b67f524825f 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -74,11 +74,17 @@ macro_rules! handle_potential_webgl_error { // // 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(); + } } }; } @@ -804,13 +810,23 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { 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); } } @@ -1241,8 +1257,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // 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() } } @@ -1250,7 +1268,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // 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() } } @@ -1258,7 +1278,22 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // 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() } } @@ -1266,8 +1301,22 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // 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() } } @@ -1275,7 +1324,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // 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() } } @@ -2526,6 +2578,82 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { 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 { diff --git a/components/script/dom/webidls/WebGLRenderingContext.webidl b/components/script/dom/webidls/WebGLRenderingContext.webidl index 1eb85fc236d..3f20d89ce96 100644 --- a/components/script/dom/webidls/WebGLRenderingContext.webidl +++ b/components/script/dom/webidls/WebGLRenderingContext.webidl @@ -501,7 +501,7 @@ interface WebGLRenderingContextBase [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); @@ -566,11 +566,11 @@ interface WebGLRenderingContextBase 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); @@ -626,8 +626,8 @@ interface WebGLRenderingContextBase 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); |