WebGL2: Implement invalidateFramebuffer and invalidateSubFramebuffer (bug 1076456, r=jgilbert).
authorDavid Anderson <danderson@mozilla.com>
Tue, 14 Oct 2014 14:49:49 -0700
changeset 210464 33aa799d4fe25af5d7af729d4b9ecffc40b1735a
parent 210463 1fa7422822db39db49394def22aa1e889845dea7
child 210465 7ac94f68aa7569f93e469c6824a11ada87ab643d
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjgilbert
bugs1076456
milestone36.0a1
WebGL2: Implement invalidateFramebuffer and invalidateSubFramebuffer (bug 1076456, r=jgilbert).
dom/canvas/WebGL2Context.cpp
dom/canvas/WebGL2ContextFramebuffers.cpp
dom/canvas/WebGLContextValidate.cpp
dom/canvas/test/webgl-mochitest.ini
dom/canvas/test/webgl-mochitest/test_webgl2_invalidate_framebuffer.html
dom/canvas/test/webgl-mochitest/webgl-util.js
gfx/gl/GLContext.cpp
gfx/gl/GLContext.h
gfx/gl/GLContextFeatures.cpp
gfx/gl/GLContextSymbols.h
gfx/gl/GLScreenBuffer.cpp
gfx/gl/GLScreenBuffer.h
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -73,17 +73,18 @@ WebGLContext::InitWebGL2()
         WebGLExtensionID::OES_texture_half_float,
         WebGLExtensionID::OES_texture_half_float_linear,
         WebGLExtensionID::OES_vertex_array_object,
         WebGLExtensionID::WEBGL_depth_texture,
         WebGLExtensionID::WEBGL_draw_buffers
     };
     const GLFeature sFeatureRequiredArr[] = {
         GLFeature::instanced_non_arrays,
-        GLFeature::transform_feedback2
+        GLFeature::transform_feedback2,
+        GLFeature::invalidate_framebuffer
     };
 
     // check WebGL extensions that are supposed to be natively supported
     for (size_t i = 0; i < size_t(MOZ_ARRAY_LENGTH(sExtensionNativelySupportedArr)); i++)
     {
         WebGLExtensionID extension = sExtensionNativelySupportedArr[i];
 
         if (!IsExtensionSupported(extension)) {
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -27,27 +27,83 @@ WebGL2Context::FramebufferTextureLayer(G
 }
 
 void
 WebGL2Context::GetInternalformatParameter(JSContext*, GLenum target, GLenum internalformat, GLenum pname, JS::MutableHandleValue retval)
 {
     MOZ_CRASH("Not Implemented.");
 }
 
+// Map attachments intended for the default buffer, to attachments for a non-
+// default buffer.
+static void
+TranslateDefaultAttachments(const dom::Sequence<GLenum>& in, dom::Sequence<GLenum>* out)
+{
+    for (size_t i = 0; i < in.Length(); i++) {
+        switch (in[i]) {
+            case LOCAL_GL_COLOR:
+                out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0);
+                break;
+            case LOCAL_GL_DEPTH:
+                out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT);
+                break;
+            case LOCAL_GL_STENCIL:
+                out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT);
+                break;
+        }
+    }
+}
+
 void
 WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+    MakeContextCurrent();
+
+    if (target != LOCAL_GL_FRAMEBUFFER)
+        return ErrorInvalidEnumInfo("invalidateFramebuffer: target", target);
+    for (size_t i = 0; i < attachments.Length(); i++) {
+        if (!ValidateFramebufferAttachment(attachments[i], "invalidateFramebuffer"))
+            return;
+    }
+
+    if (!mBoundFramebuffer && !gl->IsDrawingToDefaultFramebuffer()) {
+        dom::Sequence<GLenum> tmpAttachments;
+        TranslateDefaultAttachments(attachments, &tmpAttachments);
+        gl->fInvalidateFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements());
+    } else {
+        gl->fInvalidateFramebuffer(target, attachments.Length(), attachments.Elements());
+    }
 }
 
 void
-WebGL2Context::InvalidateSubFramebuffer (GLenum target, const dom::Sequence<GLenum>& attachments,
-                                         GLint x, GLint y, GLsizei width, GLsizei height)
+WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments,
+                                        GLint x, GLint y, GLsizei width, GLsizei height)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+    MakeContextCurrent();
+
+    if (target != LOCAL_GL_FRAMEBUFFER)
+        return ErrorInvalidEnumInfo("invalidateFramebuffer: target", target);
+    for (size_t i = 0; i < attachments.Length(); i++) {
+        if (!ValidateFramebufferAttachment(attachments[i], "invalidateSubFramebuffer"))
+            return;
+    }
+
+    if (!mBoundFramebuffer && !gl->IsDrawingToDefaultFramebuffer()) {
+        dom::Sequence<GLenum> tmpAttachments;
+        TranslateDefaultAttachments(attachments, &tmpAttachments);
+        gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(),
+                                      x, y, width, height);
+    } else {
+        gl->fInvalidateSubFramebuffer(target, attachments.Length(), attachments.Elements(),
+                                      x, y, width, height);
+    }
 }
 
 void
 WebGL2Context::ReadBuffer(GLenum mode)
 {
     MOZ_CRASH("Not Implemented.");
 }
 
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -334,16 +334,28 @@ bool WebGLContext::ValidateGLSLString(co
 
 /**
  * Return true if the framebuffer attachment is valid. Attachment must
  * be one of depth/stencil/depth_stencil/color attachment.
  */
 bool
 WebGLContext::ValidateFramebufferAttachment(GLenum attachment, const char* funcName)
 {
+    if (!mBoundFramebuffer) {
+        switch (attachment) {
+            case LOCAL_GL_COLOR:
+            case LOCAL_GL_DEPTH:
+            case LOCAL_GL_STENCIL:
+                return true;
+            default:
+                ErrorInvalidEnum("%s: attachment: invalid enum value 0x%x.", funcName, attachment);
+                return false;
+        }
+    }
+
     if (attachment == LOCAL_GL_DEPTH_ATTACHMENT ||
         attachment == LOCAL_GL_STENCIL_ATTACHMENT ||
         attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
     {
         return true;
     }
 
     GLenum colorAttachCount = 1;
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -19,8 +19,10 @@ skip-if = toolkit == 'android' #bug 8654
 [webgl-mochitest/test_webgl_conformance.html]
 skip-if = buildapp == 'mulet' || toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [webgl-mochitest/test_webgl_request_context.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [webgl-mochitest/test_webgl_request_mismatch.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [webgl-mochitest/test_webgl2_not_exposed.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
+[webgl-mochitest/test_webgl2_invalidate_framebuffer.html]
+skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_webgl2_invalidate_framebuffer.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+
+<title>WebGL2 test: Framebuffers</title>
+
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script src="driver-info.js"></script>
+<script src="webgl-util.js"></script>
+<body>
+<canvas id="c" width="64" height="64"></canvas>
+<script>
+
+WebGLUtil.withWebGL2('c', function (gl) {
+  gl.invalidateFramebuffer(gl.FRAMEBUFFER, [gl.COLOR]);
+  ok(gl.getError() == 0, 'invalidateFramebuffer');
+  gl.invalidateSubFramebuffer(gl.FRAMEBUFFER, [gl.COLOR], 0, 0, 64, 64);
+  ok(gl.getError() == 0, 'invalidateSubFramebuffer');
+  gl.invalidateFramebuffer(gl.FRAMEBUFFER, [gl.GL_COLOR_ATTACHMENT0]);
+  ok(gl.getError() == gl.INVALID_ENUM, 'invalidateFrameBuffer should fail with GL_COLOR_ATTACHMENT on the default framebuffer');
+}, function () {
+  SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
--- a/dom/canvas/test/webgl-mochitest/webgl-util.js
+++ b/dom/canvas/test/webgl-mochitest/webgl-util.js
@@ -53,16 +53,54 @@ WebGLUtil = (function() {
     if (!gl) {
       error('WebGL context could not be retrieved from \'' + canvasId + '\'.');
       return null;
     }
 
     return gl;
   }
 
+  function withWebGL2(canvasId, callback, onFinished) {
+    var prefArrArr = [
+      ['webgl.force-enabled', true],
+      ['webgl.disable-angle', true],
+      ['webgl.enable-prototype-webgl2', true],
+    ];
+    var prefEnv = {'set': prefArrArr};
+    SpecialPowers.pushPrefEnv(prefEnv, function() {
+      var canvas = document.getElementById(canvasId);
+
+      var gl = null;
+      try {
+        gl = canvas.getContext('webgl2');
+      } catch(e) {}
+
+      if (!gl) {
+        try {
+          gl = canvas.getContext('experimental-webgl2');
+        } catch(e) {}
+      }
+
+      if (!gl) {
+        todo(false, 'WebGL2 is not supported');
+        onFinished();
+        return;
+      }
+
+      function errorFunc(str) {
+        ok(false, 'Error: ' + str);
+      }
+      setErrorFunc(errorFunc);
+      setWarningFunc(errorFunc);
+
+      callback(gl);
+      onFinished();
+    });
+  }
+
   function getContentFromElem(elem) {
     var str = "";
     var k = elem.firstChild;
     while (k) {
       if (k.nodeType == 3)
         str += k.textContent;
 
       k = k.nextSibling;
@@ -120,12 +158,13 @@ WebGLUtil = (function() {
     return prog;
   }
 
   return {
     setErrorFunc: setErrorFunc,
     setWarningFunc: setWarningFunc,
 
     getWebGL: getWebGL,
+    withWebGL2: withWebGL2,
     createShaderById: createShaderById,
     createProgramByIds: createProgramByIds,
   };
 })();
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -79,16 +79,17 @@ static const char *sExtensionNames[] = {
     "GL_ARB_copy_buffer",
     "GL_ARB_depth_texture",
     "GL_ARB_draw_buffers",
     "GL_ARB_draw_instanced",
     "GL_ARB_framebuffer_object",
     "GL_ARB_framebuffer_sRGB",
     "GL_ARB_half_float_pixel",
     "GL_ARB_instanced_arrays",
+    "GL_ARB_invalidate_subdata",
     "GL_ARB_map_buffer_range",
     "GL_ARB_occlusion_query2",
     "GL_ARB_pixel_buffer_object",
     "GL_ARB_robustness",
     "GL_ARB_sampler_objects",
     "GL_ARB_sync",
     "GL_ARB_texture_compression",
     "GL_ARB_texture_float",
@@ -1351,16 +1352,31 @@ GLContext::InitWithPrefix(const char *pr
             if (!LoadSymbols(&umnSymbols[0], trygl, prefix)) {
                 NS_ERROR("GL supports uniform matrix with non-square dim without supplying its functions.");
 
                 MarkUnsupported(GLFeature::uniform_matrix_nonsquare);
                 ClearSymbols(umnSymbols);
             }
         }
 
+        if (IsSupported(GLFeature::invalidate_framebuffer)) {
+            SymLoadStruct invSymbols[] = {
+                { (PRFuncPtr *) &mSymbols.fInvalidateFramebuffer,    { "InvalidateFramebuffer", nullptr } },
+                { (PRFuncPtr *) &mSymbols.fInvalidateSubFramebuffer, { "InvalidateSubFramebuffer", nullptr } },
+                END_SYMBOLS
+            };
+
+            if (!LoadSymbols(&invSymbols[0], trygl, prefix)) {
+                NS_ERROR("GL supports framebuffer invalidation without supplying its functions.");
+
+                MarkUnsupported(GLFeature::invalidate_framebuffer);
+                ClearSymbols(invSymbols);
+            }
+        }
+
         if (IsExtensionSupported(KHR_debug)) {
             SymLoadStruct extSymbols[] = {
                 { (PRFuncPtr*) &mSymbols.fDebugMessageControl,  { "DebugMessageControl",  "DebugMessageControlKHR",  nullptr } },
                 { (PRFuncPtr*) &mSymbols.fDebugMessageInsert,   { "DebugMessageInsert",   "DebugMessageInsertKHR",   nullptr } },
                 { (PRFuncPtr*) &mSymbols.fDebugMessageCallback, { "DebugMessageCallback", "DebugMessageCallbackKHR", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fGetDebugMessageLog,   { "GetDebugMessageLog",   "GetDebugMessageLogKHR",   nullptr } },
                 { (PRFuncPtr*) &mSymbols.fGetPointerv,          { "GetPointerv",          "GetPointervKHR",          nullptr } },
                 { (PRFuncPtr*) &mSymbols.fPushDebugGroup,       { "PushDebugGroup",       "PushDebugGroupKHR",       nullptr } },
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -95,16 +95,17 @@ MOZ_BEGIN_ENUM_CLASS(GLFeature)
     framebuffer_multisample,
     framebuffer_object,
     get_integer_indexed,
     get_integer64_indexed,
     get_query_object_iv,
     gpu_shader4,
     instanced_arrays,
     instanced_non_arrays,
+    invalidate_framebuffer,
     map_buffer_range,
     occlusion_query,
     occlusion_query_boolean,
     occlusion_query2,
     packed_depth_stencil,
     query_objects,
     renderbuffer_color_float,
     renderbuffer_color_half_float,
@@ -362,16 +363,17 @@ public:
         ARB_copy_buffer,
         ARB_depth_texture,
         ARB_draw_buffers,
         ARB_draw_instanced,
         ARB_framebuffer_object,
         ARB_framebuffer_sRGB,
         ARB_half_float_pixel,
         ARB_instanced_arrays,
+        ARB_invalidate_subdata,
         ARB_map_buffer_range,
         ARB_occlusion_query2,
         ARB_pixel_buffer_object,
         ARB_robustness,
         ARB_sampler_objects,
         ARB_sync,
         ARB_texture_compression,
         ARB_texture_float,
@@ -885,16 +887,30 @@ public:
             default:
                 // Nothing we care about, likely an error.
                 break;
         }
 
         raw_fBindFramebuffer(target, framebuffer);
     }
 
+    void fInvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments) {
+        BEFORE_GL_CALL;
+        ASSERT_SYMBOL_PRESENT(fInvalidateFramebuffer);
+        mSymbols.fInvalidateFramebuffer(target, numAttachments, attachments);
+        AFTER_GL_CALL;
+    }
+
+    void fInvalidateSubFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) {
+        BEFORE_GL_CALL;
+        ASSERT_SYMBOL_PRESENT(fInvalidateSubFramebuffer);
+        mSymbols.fInvalidateSubFramebuffer(target, numAttachments, attachments, x, y, width, height);
+        AFTER_GL_CALL;
+    }
+
     void fBindTexture(GLenum target, GLuint texture) {
         BEFORE_GL_CALL;
         mSymbols.fBindTexture(target, texture);
         AFTER_GL_CALL;
     }
 
     void fBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) {
         BEFORE_GL_CALL;
@@ -3532,16 +3548,20 @@ public:
     /* Clear to transparent black, with 0 depth and stencil,
      * while preserving current ClearColor etc. values.
      * Useful for resizing offscreen buffers.
      */
     void ClearSafely();
 
     bool WorkAroundDriverBugs() const { return mWorkAroundDriverBugs; }
 
+    bool IsDrawingToDefaultFramebuffer() {
+        return Screen()->IsDrawFramebufferDefault();
+    }
+
 protected:
     nsRefPtr<TextureGarbageBin> mTexGarbageBin;
 
 public:
     TextureGarbageBin* TexGarbageBin() {
         MOZ_ASSERT(mTexGarbageBin);
         return mTexGarbageBin;
     }
--- a/gfx/gl/GLContextFeatures.cpp
+++ b/gfx/gl/GLContextFeatures.cpp
@@ -284,16 +284,25 @@ static const FeatureInfo sFeatureInfoArr
         }
         /* This is an expanded version of `instanced_arrays` that allows for all
          * enabled active attrib arrays to have non-zero divisors.
          * ANGLE_instanced_arrays and NV_instanced_arrays forbid this, but GLES3
          * has no such restriction.
          */
     },
     {
+        "invalidate_framebuffer",
+        430, // OpenGL version
+        300, // OpenGL ES version
+        GLContext::ARB_invalidate_subdata,
+        {
+            GLContext::Extensions_End
+        }
+    },
+    {
         "map_buffer_range",
         300, // OpenGL version
         300, // OpenGL ES version
         GLContext::ARB_map_buffer_range,
         {
             GLContext::Extensions_End
         }
     },
--- a/gfx/gl/GLContextSymbols.h
+++ b/gfx/gl/GLContextSymbols.h
@@ -329,16 +329,21 @@ struct GLContextSymbols
     PFNGLISFRAMEBUFFER fIsFramebuffer;
     typedef realGLboolean (GLAPIENTRY * PFNGLISRENDERBUFFER) (GLuint renderbuffer);
     PFNGLISRENDERBUFFER fIsRenderbuffer;
     typedef realGLboolean (GLAPIENTRY * PFNGLISVERTEXARRAY) (GLuint array);
     PFNGLISVERTEXARRAY fIsVertexArray;
     typedef void (GLAPIENTRY * PFNGLRENDERBUFFERSTORAGE) (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height);
     PFNGLRENDERBUFFERSTORAGE fRenderbufferStorage;
 
+    typedef void (GLAPIENTRY * PFNINVALIDATEFRAMEBUFFER) (GLenum target, GLsizei numAttachments, const GLenum* attachments);
+    PFNINVALIDATEFRAMEBUFFER fInvalidateFramebuffer;
+    typedef void (GLAPIENTRY * PFNINVALIDATESUBFRAMEBUFFER) (GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height);
+    PFNINVALIDATESUBFRAMEBUFFER fInvalidateSubFramebuffer;
+
         // These functions are only used by Skia/GL in desktop mode.
         // Other parts of Gecko should avoid using these
         typedef void (GLAPIENTRY * PFNGLCLIENTACTIVETEXTURE) (GLenum texture);
         PFNGLCLIENTACTIVETEXTURE fClientActiveTexture;
         typedef void (GLAPIENTRY * PFNDISABLECLIENTSTATE) (GLenum capability);
         PFNDISABLECLIENTSTATE fDisableClientState;
         typedef void (GLAPIENTRY * PFNENABLECLIENTSTATE) (GLenum capability);
         PFNENABLECLIENTSTATE fEnableClientState;
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -524,16 +524,30 @@ GLScreenBuffer::Readback(SharedSurface* 
   }
 
   if (needsSwap) {
       src->UnlockProd();
       SharedSurf()->LockProd();
   }
 }
 
+bool
+GLScreenBuffer::IsDrawFramebufferDefault() const
+{
+    if (!mDraw)
+        return IsReadFramebufferDefault();
+    return mDraw->mFB == 0;
+}
+
+bool
+GLScreenBuffer::IsReadFramebufferDefault() const
+{
+    return SharedSurf()->mAttachType == AttachmentType::Screen;
+}
+
 ////////////////////////////////////////////////////////////////////////
 // DrawBuffer
 
 bool
 DrawBuffer::Create(GLContext* const gl,
                    const SurfaceCaps& caps,
                    const GLFormats& formats,
                    const gfx::IntSize& size,
--- a/gfx/gl/GLScreenBuffer.h
+++ b/gfx/gl/GLScreenBuffer.h
@@ -256,14 +256,17 @@ public:
     GLuint GetDrawFB() const;
     GLuint GetReadFB() const;
 
     // Here `fb` is the actual framebuffer you want bound. Binding 0 will
     // bind the (generally useless) default framebuffer.
     void BindFB_Internal(GLuint fb);
     void BindDrawFB_Internal(GLuint fb);
     void BindReadFB_Internal(GLuint fb);
+
+    bool IsDrawFramebufferDefault() const;
+    bool IsReadFramebufferDefault() const;
 };
 
 }   // namespace gl
 }   // namespace mozilla
 
 #endif  // SCREEN_BUFFER_H_