Bug 766366 - Add GLContext::BlitTextureToTexture - r=bjacob
authorJeff Gilbert <jgilbert@mozilla.com>
Tue, 21 Aug 2012 20:30:20 -0700
changeset 102991 d6a5f82cc3fc7cc8e9cdee1e9d0e575c26869fe0
parent 102990 1ce3324a8bad9ac6ea55c9dad9760dae0f60897e
child 102992 cc1f0a7f68a476a1b71d9f0ece0535c24545fb37
push id13761
push userjgilbert@mozilla.com
push dateWed, 22 Aug 2012 03:30:39 +0000
treeherdermozilla-inbound@d6a5f82cc3fc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbjacob
bugs766366
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 766366 - Add GLContext::BlitTextureToTexture - r=bjacob
gfx/gl/GLContext.cpp
gfx/gl/GLContext.h
gfx/gl/GLContextProviderCGL.mm
gfx/gl/GLContextProviderEGL.cpp
gfx/gl/GLContextProviderGLX.cpp
gfx/gl/GLContextProviderOSMesa.cpp
gfx/gl/GLContextProviderWGL.cpp
gfx/gl/GLContextSymbols.h
gfx/gl/GLContextUtils.cpp
gfx/gl/Makefile.in
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -149,16 +149,17 @@ GLContext::InitWithPrefix(const char *pr
         { (PRFuncPtr*) &mSymbols.fGetString, { "GetString", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetTexParameterfv, { "GetTexParameterfv", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetTexParameteriv, { "GetTexParameteriv", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetUniformfv, { "GetUniformfv", "GetUniformfvARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetUniformiv, { "GetUniformiv", "GetUniformivARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetUniformLocation, { "GetUniformLocation", "GetUniformLocationARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetVertexAttribfv, { "GetVertexAttribfv", "GetVertexAttribfvARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fGetVertexAttribiv, { "GetVertexAttribiv", "GetVertexAttribivARB", NULL } },
+        { (PRFuncPtr*) &mSymbols.fGetVertexAttribPointerv, { "GetVertexAttribPointerv", NULL } },
         { (PRFuncPtr*) &mSymbols.fHint, { "Hint", NULL } },
         { (PRFuncPtr*) &mSymbols.fIsBuffer, { "IsBuffer", "IsBufferARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fIsEnabled, { "IsEnabled", NULL } },
         { (PRFuncPtr*) &mSymbols.fIsProgram, { "IsProgram", "IsProgramARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fIsShader, { "IsShader", "IsShaderARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fIsTexture, { "IsTexture", "IsTextureARB", NULL } },
         { (PRFuncPtr*) &mSymbols.fLineWidth, { "LineWidth", NULL } },
         { (PRFuncPtr*) &mSymbols.fLinkProgram, { "LinkProgram", "LinkProgramARB", NULL } },
@@ -1752,16 +1753,17 @@ GLContext::UpdateActualFormat()
 void
 GLContext::MarkDestroyed()
 {
     if (IsDestroyed())
         return;
 
     if (MakeCurrent()) {
         DeleteOffscreenFBOs();
+        DeleteTexBlitProgram();
 
         fDeleteProgram(mBlitProgram);
         mBlitProgram = 0;
         fDeleteFramebuffers(1, &mBlitFramebuffer);
         mBlitFramebuffer = 0;
     } else {
         NS_WARNING("MakeCurrent() failed during MarkDestroyed! Skipping GL object teardown.");
     }
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -33,16 +33,17 @@
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 
 typedef char realGLboolean;
 
 #include "GLContextSymbols.h"
 
 #include "mozilla/mozalloc.h"
+#include "mozilla/Preferences.h"
 
 namespace android {
 class GraphicBuffer;
 }
 
 namespace mozilla {
   namespace layers {
     class LayerManagerOGL;
@@ -505,17 +506,22 @@ struct THEBES_API ContextFormat
 class GLContext
     : public GLLibraryLoader
 {
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GLContext)
 public:
     GLContext(const ContextFormat& aFormat,
               bool aIsOffscreen = false,
               GLContext *aSharedContext = nullptr)
-      : mUserBoundDrawFBO(0),
+      : mTexBlit_Buffer(0),
+        mTexBlit_VertShader(0),
+        mTexBlit_FragShader(0),
+        mTexBlit_Program(0),
+        mTexBlit_UseDrawNotCopy(false),
+        mUserBoundDrawFBO(0),
         mUserBoundReadFBO(0),
         mInternalBoundDrawFBO(0),
         mInternalBoundReadFBO(0),
 #ifdef DEBUG
         mInInternalBindingMode_DrawFBO(true),
         mInInternalBindingMode_ReadFBO(true),
 #endif
         mOffscreenFBOsDirty(false),
@@ -544,16 +550,18 @@ public:
         mMaxRenderbufferSize(0),
         mWorkAroundDriverBugs(true)
 #ifdef DEBUG
         , mGLError(LOCAL_GL_NO_ERROR)
 #endif
     {
         mUserData.Init();
         mOwningThread = NS_GetCurrentThread();
+
+        mTexBlit_UseDrawNotCopy = Preferences::GetBool("gl.blit-draw-not-copy", false);
     }
 
     virtual ~GLContext() {
         NS_ASSERTION(IsDestroyed(), "GLContext implementation must call MarkDestroyed in destructor!");
 #ifdef DEBUG
         if (mSharedContext) {
             GLContext *tip = mSharedContext;
             while (tip->mSharedContext)
@@ -604,16 +612,18 @@ public:
         // support contexts used on multiple threads.
         NS_ASSERTION(IsOwningThreadCurrent(),
                      "MakeCurrent() called on different thread than this context was created on!");
 #endif
 #endif
         return MakeCurrentImpl(aForce);
     }
 
+    virtual bool IsCurrent() = 0;
+
     bool IsContextLost() { return mContextLost; }
 
     virtual bool SetupLookupFunction() = 0;
 
     virtual void WindowDestroyed() {}
 
     virtual void ReleaseSurface() {}
 
@@ -817,16 +827,41 @@ public:
     }
     
     // Before reads from offscreen texture
     void GuaranteeResolve() {
         BlitDirtyFBOs();
         fFinish();
     }
 
+protected:
+    GLuint mTexBlit_Buffer;
+    GLuint mTexBlit_VertShader;
+    GLuint mTexBlit_FragShader;
+    GLuint mTexBlit_Program;
+
+    bool mTexBlit_UseDrawNotCopy;
+
+    bool UseTexQuadProgram();
+    void DeleteTexBlitProgram();
+
+public:
+    void BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
+                                      const gfxIntSize& srcSize,
+                                      const gfxIntSize& destSize);
+    void BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
+                                  const gfxIntSize& srcSize,
+                                  const gfxIntSize& destSize);
+    void BlitFramebufferToTexture(GLuint srcFB, GLuint destTex,
+                                  const gfxIntSize& srcSize,
+                                  const gfxIntSize& destSize);
+    void BlitTextureToTexture(GLuint srcTex, GLuint destTex,
+                              const gfxIntSize& srcSize,
+                              const gfxIntSize& destSize);
+
     /*
      * Resize the current offscreen buffer.  Returns true on success.
      * If it returns false, the context should be treated as unusable
      * and should be recreated.  After the resize, the viewport is not
      * changed; glViewport should be called as appropriate.
      *
      * Only valid if IsOffscreen() returns true.
      */
@@ -1097,36 +1132,45 @@ public:
             fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER_EXT, name);
         else
             fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, name);
 #ifdef DEBUG
         mInInternalBindingMode_ReadFBO = false;
 #endif
     }
 
+    GLuint GetUserBoundFBO() {
+        MOZ_ASSERT(GetUserBoundDrawFBO() == GetUserBoundReadFBO());
+        return GetUserBoundReadFBO();
+    }
+
+    void BindUserFBO(GLuint name) {
+        fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, name);
+    }
+
     // BindInternalDraw/ReadFBO() switch us over into 'internal binding mode'
     //   for the corresponding Draw or Read binding.
     // To exit internal binding mode, use BindUserDraw/ReadFBO().
     // While in internal binding mode for Draw/Read, the corresponding
     //   GetBoundUserDraw/ReadFBO() is undefined, and will trigger ABORT in DEBUG builds.
     void BindInternalDrawFBO(GLuint name) {
 #ifdef DEBUG
-      mInInternalBindingMode_DrawFBO = true;
+        mInInternalBindingMode_DrawFBO = true;
 #endif
         if (SupportsOffscreenSplit())
             raw_fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER_EXT, name);
         else
             raw_fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, name);
 
         mInternalBoundDrawFBO = name;
     }
 
     void BindInternalReadFBO(GLuint name) {
 #ifdef DEBUG
-      mInInternalBindingMode_ReadFBO = true;
+        mInInternalBindingMode_ReadFBO = true;
 #endif
         if (SupportsOffscreenSplit())
             raw_fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER_EXT, name);
         else
             raw_fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, name);
 
         mInternalBoundReadFBO = name;
     }
@@ -2364,16 +2408,20 @@ public:
                 break;
 
             default:
                 raw_fGetIntegerv(pname, params);
                 break;
         }
     }
 
+    void GetUIntegerv(GLenum pname, GLuint *params) {
+        fGetIntegerv(pname, reinterpret_cast<GLint*>(params));
+    }
+
     void fGetFloatv(GLenum pname, GLfloat *params) {
         BEFORE_GL_CALL;
         mSymbols.fGetFloatv(pname, params);
         AFTER_GL_CALL;
     }
 
     void fGetBooleanv(GLenum pname, realGLboolean *params) {
         BEFORE_GL_CALL;
@@ -2482,16 +2530,22 @@ public:
     }
 
     void fGetVertexAttribiv(GLuint index, GLenum pname, GLint* retval) {
         BEFORE_GL_CALL;
         mSymbols.fGetVertexAttribiv(index, pname, retval);
         AFTER_GL_CALL;
     }
 
+    void fGetVertexAttribPointerv(GLuint index, GLenum pname, GLvoid** retval) {
+        BEFORE_GL_CALL;
+        mSymbols.fGetVertexAttribPointerv(index, pname, retval);
+        AFTER_GL_CALL;
+    }
+
     void fHint(GLenum target, GLenum mode) {
         BEFORE_GL_CALL;
         mSymbols.fHint(target, mode);
         AFTER_GL_CALL;
     }
 
     realGLboolean fIsBuffer(GLuint buffer) {
         BEFORE_GL_CALL;
@@ -3226,12 +3280,180 @@ DoesStringMatch(const char* aString, con
     // aWantedVendor followed by alpha character
     const char *afterOccurrence = occurrence + strlen(aWantedString);
     if (isalpha(*afterOccurrence))
         return false;
 
     return true;
 }
 
+//RAII via CRTP!
+template <class Derived>
+struct ScopedGLWrapper
+{
+private:
+    bool mIsUnwrapped;
+
+protected:
+    GLContext* const mGL;
+
+    ScopedGLWrapper(GLContext* gl)
+        : mIsUnwrapped(false)
+        , mGL(gl)
+    {
+        MOZ_ASSERT(&ScopedGLWrapper<Derived>::Unwrap == &Derived::Unwrap);
+        MOZ_ASSERT(&Derived::UnwrapImpl);
+        MOZ_ASSERT(mGL->IsCurrent());
+    }
+
+    virtual ~ScopedGLWrapper() {
+        if (!mIsUnwrapped)
+            Unwrap();
+    }
+
+public:
+    void Unwrap() {
+        MOZ_ASSERT(!mIsUnwrapped);
+
+        Derived* derived = static_cast<Derived*>(this);
+        derived->UnwrapImpl();
+
+        mIsUnwrapped = true;
+    }
+};
+
+struct ScopedFramebufferTexture
+    : public ScopedGLWrapper<ScopedFramebufferTexture>
+{
+    friend class ScopedGLWrapper<ScopedFramebufferTexture>;
+
+protected:
+    bool mComplete; // True if the framebuffer we create is complete.
+    GLuint mFB;
+
+public:
+    ScopedFramebufferTexture(GLContext* gl, GLuint texture)
+        : ScopedGLWrapper<ScopedFramebufferTexture>(gl)
+        , mComplete(false)
+        , mFB(0)
+    {
+        MOZ_ASSERT(mGL->IsCurrent());
+        GLuint boundFB = mGL->GetUserBoundFBO();
+
+        mGL->fGenFramebuffers(1, &mFB);
+        mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mFB);
+        mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
+                                   LOCAL_GL_COLOR_ATTACHMENT0,
+                                   LOCAL_GL_TEXTURE_2D,
+                                   texture,
+                                   0);
+
+        GLenum status = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+        if (status == LOCAL_GL_FRAMEBUFFER_COMPLETE) {
+            mComplete = true;
+        } else {
+            mGL->fDeleteFramebuffers(1, &mFB);
+            mFB = 0;
+        }
+
+        mGL->BindUserFBO(boundFB);
+    }
+
+protected:
+    void UnwrapImpl() {
+        if (!mFB)
+            return;
+
+        MOZ_ASSERT(mGL->IsCurrent());
+        mGL->fDeleteFramebuffers(1, &mFB);
+        mFB = 0;
+    }
+
+public:
+    GLuint FB() const {
+        return mFB;
+    }
+
+    bool IsComplete() const {
+        return mComplete;
+    }
+};
+
+// Wraps glEnable/Disable.
+struct ScopedGLState
+    : public ScopedGLWrapper<ScopedGLState>
+{
+    friend class ScopedGLWrapper<ScopedGLState>;
+
+protected:
+    const GLenum mCapability;
+    bool mOldState;
+
+public:
+    // Use |newState = true| to enable, |false| to disable.
+    ScopedGLState(GLContext* gl, GLenum capability, bool newState)
+        : ScopedGLWrapper<ScopedGLState>(gl)
+        , mCapability(capability)
+    {
+        MOZ_ASSERT(mGL->IsCurrent());
+        mOldState = mGL->fIsEnabled(mCapability);
+
+        // Early out if we're already in the right state.
+        if (newState == mOldState)
+            return;
+
+        if (newState)
+            mGL->fEnable(mCapability);
+        else
+            mGL->fDisable(mCapability);
+    }
+
+protected:
+    void UnwrapImpl() {
+        MOZ_ASSERT(mGL->IsCurrent());
+
+        if (mOldState)
+            mGL->fEnable(mCapability);
+        else
+            mGL->fDisable(mCapability);
+    }
+};
+
+// Saves and restores with GetUserBoundFBO and BindUserFBO.
+struct ScopedFramebufferBinding
+    : public ScopedGLWrapper<ScopedFramebufferBinding>
+{
+    friend class ScopedGLWrapper<ScopedFramebufferBinding>;
+
+protected:
+    GLuint mOldState;
+
+private:
+    void Init() {
+        MOZ_ASSERT(mGL->IsCurrent());
+        mOldState = mGL->GetUserBoundFBO();
+    }
+
+public:
+    ScopedFramebufferBinding(GLContext* gl)
+        : ScopedGLWrapper<ScopedFramebufferBinding>(gl)
+    {
+        Init();
+    }
+
+    ScopedFramebufferBinding(GLContext* gl, GLuint newFB)
+        : ScopedGLWrapper<ScopedFramebufferBinding>(gl)
+    {
+        Init();
+        mGL->BindUserFBO(newFB);
+    }
+
+protected:
+    void UnwrapImpl() {
+        MOZ_ASSERT(mGL->IsCurrent());
+        mGL->BindUserFBO(mOldState);
+    }
+};
+
 } /* namespace gl */
 } /* namespace mozilla */
 
 #endif /* GLCONTEXT_H_ */
--- a/gfx/gl/GLContextProviderCGL.mm
+++ b/gfx/gl/GLContextProviderCGL.mm
@@ -149,16 +149,20 @@ public:
         if (mContext) {
             [mContext makeCurrentContext];
             GLint swapInt = 1;
             [mContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
         }
         return true;
     }
 
+    virtual bool IsCurrent() {
+        return [NSOpenGLContext currentContext] == mContext;
+    }
+
     bool SetupLookupFunction()
     {
         return false;
     }
 
     bool IsDoubleBuffered() 
     { 
       return gUseDoubleBufferedWindows; 
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -502,16 +502,20 @@ public:
 #endif
                 }
             }
         }
 
         return succeeded;
     }
 
+    virtual bool IsCurrent() {
+        return sEGLLibrary.fGetCurrentContext() == mContext;
+    }
+
 #ifdef MOZ_WIDGET_QT
     virtual bool
     RenewSurface() {
         /* We don't support renewing on QT because we don't create the surface ourselves */
         return false;
     }
 #else
     virtual bool
--- a/gfx/gl/GLContextProviderGLX.cpp
+++ b/gfx/gl/GLContextProviderGLX.cpp
@@ -846,16 +846,20 @@ TRY_AGAIN_NO_SHARING:
         if (aForce || sGLXLibrary.xGetCurrentContext() != mContext) {
             succeeded = sGLXLibrary.xMakeCurrent(mDisplay, mDrawable, mContext);
             NS_ASSERTION(succeeded, "Failed to make GL context current!");
         }
 
         return succeeded;
     }
 
+    virtual bool IsCurrent() {
+        return sGLXLibrary.xGetCurrentContext() == mContext;
+    }
+
     bool SetupLookupFunction()
     {
         mLookupFunc = (PlatformLookupFunction)&GLXLibrary::xGetProcAddress;
         return true;
     }
 
     void *GetNativeData(NativeDataType aType)
     {
--- a/gfx/gl/GLContextProviderOSMesa.cpp
+++ b/gfx/gl/GLContextProviderOSMesa.cpp
@@ -188,16 +188,20 @@ public:
                                         LOCAL_GL_UNSIGNED_BYTE,
                                         mThebesSurface->Width(),
                                         mThebesSurface->Height());
         NS_ASSERTION(succeeded, "Failed to make OSMesa context current!");
 
         return succeeded;
     }
 
+    virtual bool IsCurrent() {
+        return sOSMesaLibrary.fGetCurrentContext() == mContext;
+    }
+
     bool SetupLookupFunction()
     {
         mLookupFunc = (PlatformLookupFunction)sOSMesaLibrary.fGetProcAddress;
         return true;
     }
 
     void *GetNativeData(NativeDataType aType)
     {
--- a/gfx/gl/GLContextProviderWGL.cpp
+++ b/gfx/gl/GLContextProviderWGL.cpp
@@ -329,16 +329,20 @@ public:
         if (aForce || sWGLLib[mLibType].fGetCurrentContext() != mContext) {
             succeeded = sWGLLib[mLibType].fMakeCurrent(mDC, mContext);
             NS_ASSERTION(succeeded, "Failed to make GL context current!");
         }
 
         return succeeded;
     }
 
+    virtual bool IsCurrent() {
+        return sWGLLib[mLibType].fGetCurrentContext() == mContext;
+    }
+
     void SetIsDoubleBuffered(bool aIsDB) {
         mIsDoubleBuffered = aIsDB;
     }
 
     virtual bool IsDoubleBuffered() {
         return mIsDoubleBuffered;
     }
 
--- a/gfx/gl/GLContextSymbols.h
+++ b/gfx/gl/GLContextSymbols.h
@@ -140,16 +140,18 @@ struct GLContextSymbols
     typedef void (GLAPIENTRY * PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint* params);
     PFNGLGETUNIFORMIVPROC fGetUniformiv;
     typedef GLint (GLAPIENTRY * PFNGLGETUNIFORMLOCATIONPROC) (GLint programObj, const GLchar* name);
     PFNGLGETUNIFORMLOCATIONPROC fGetUniformLocation;
     typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBFVPROC) (GLuint, GLenum, GLfloat*);
     PFNGLGETVERTEXATTRIBFVPROC fGetVertexAttribfv;
     typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIVPROC) (GLuint, GLenum, GLint*);
     PFNGLGETVERTEXATTRIBIVPROC fGetVertexAttribiv;
+    typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint, GLenum, GLvoid**);
+    PFNGLGETVERTEXATTRIBPOINTERVPROC fGetVertexAttribPointerv;
     typedef void (GLAPIENTRY * PFNGLHINTPROC) (GLenum target, GLenum mode);
     PFNGLHINTPROC fHint;
     typedef realGLboolean (GLAPIENTRY * PFNGLISBUFFERPROC) (GLuint buffer);
     PFNGLISBUFFERPROC fIsBuffer;
     typedef realGLboolean (GLAPIENTRY * PFNGLISENABLEDPROC) (GLenum cap);
     PFNGLISENABLEDPROC fIsEnabled;
     typedef realGLboolean (GLAPIENTRY * PFNGLISPROGRAMPROC) (GLuint program);
     PFNGLISPROGRAMPROC fIsProgram;
new file mode 100644
--- /dev/null
+++ b/gfx/gl/GLContextUtils.cpp
@@ -0,0 +1,424 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#include "GLContext.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/StandardInteger.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace gl {
+
+static const char kTexBlit_VertShaderSource[] = "\
+attribute vec2 aPosition;                   \n\
+                                            \n\
+varying vec2 vTexCoord;                     \n\
+                                            \n\
+void main(void) {                           \n\
+    vTexCoord = aPosition;                  \n\
+    vec2 vertPos = aPosition * 2.0 - 1.0;   \n\
+    gl_Position = vec4(vertPos, 0.0, 1.0);  \n\
+}                                           \n\
+";
+
+static const char kTexBlit_FragShaderSource[] = "\
+uniform sampler2D uTexUnit;                         \n\
+                                                    \n\
+varying vec2 vTexCoord;                             \n\
+                                                    \n\
+void main(void) {                                   \n\
+    gl_FragColor = texture2D(uTexUnit, vTexCoord);  \n\
+}                                                   \n\
+";
+
+// Allowed to be destructive of state we restore in functions below.
+bool
+GLContext::UseTexQuadProgram()
+{
+    bool success = false;
+
+    // Use do-while(false) to let us break on failure
+    do {
+        if (mTexBlit_Program) {
+            // Already have it...
+            success = true;
+            break;
+        }
+
+        /* CCW tri-strip:
+         * 2---3
+         * | \ |
+         * 0---1
+         */
+        GLfloat verts[] = {
+            0.0f, 0.0f,
+            1.0f, 0.0f,
+            0.0f, 1.0f,
+            1.0f, 1.0f
+        };
+
+        MOZ_ASSERT(!mTexBlit_Buffer);
+        fGenBuffers(1, &mTexBlit_Buffer);
+        fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTexBlit_Buffer);
+
+        const size_t vertsSize = sizeof(verts);
+        MOZ_ASSERT(vertsSize >= 3 * sizeof(GLfloat)); // Make sure we have a sane size.
+        fBufferData(LOCAL_GL_ARRAY_BUFFER, vertsSize, verts, LOCAL_GL_STATIC_DRAW);
+
+        fEnableVertexAttribArray(0);
+        fVertexAttribPointer(0,
+                             2,
+                             LOCAL_GL_FLOAT,
+                             false,
+                             0,
+                             nullptr);
+
+        MOZ_ASSERT(!mTexBlit_VertShader);
+        MOZ_ASSERT(!mTexBlit_FragShader);
+
+        const char* vertShaderSource = kTexBlit_VertShaderSource;
+        const char* fragShaderSource = kTexBlit_FragShaderSource;
+
+        mTexBlit_VertShader = fCreateShader(LOCAL_GL_VERTEX_SHADER);
+        fShaderSource(mTexBlit_VertShader, 1, &vertShaderSource, nullptr);
+        fCompileShader(mTexBlit_VertShader);
+
+        mTexBlit_FragShader = fCreateShader(LOCAL_GL_FRAGMENT_SHADER);
+        fShaderSource(mTexBlit_FragShader, 1, &fragShaderSource, nullptr);
+        fCompileShader(mTexBlit_FragShader);
+
+        mTexBlit_Program = fCreateProgram();
+        fAttachShader(mTexBlit_Program, mTexBlit_VertShader);
+        fAttachShader(mTexBlit_Program, mTexBlit_FragShader);
+        fBindAttribLocation(mTexBlit_Program, 0, "aPosition");
+        fLinkProgram(mTexBlit_Program);
+
+        if (DebugMode()) {
+            GLint status = 0;
+            fGetShaderiv(mTexBlit_VertShader, LOCAL_GL_COMPILE_STATUS, &status);
+            if (status != LOCAL_GL_TRUE) {
+                NS_ERROR("Vert shader compilation failed.");
+
+                GLint length = 0;
+                fGetShaderiv(mTexBlit_VertShader, LOCAL_GL_INFO_LOG_LENGTH, &length);
+                if (!length) {
+                    printf_stderr("No shader info log available.\n");
+                    break;
+                }
+
+                nsAutoArrayPtr<char> buffer(new char[length]);
+                fGetShaderInfoLog(mTexBlit_VertShader, length, nullptr, buffer);
+
+                printf_stderr("Shader info log (%d bytes): %s\n", length, buffer.get());
+                break;
+            }
+
+            status = 0;
+            fGetShaderiv(mTexBlit_FragShader, LOCAL_GL_COMPILE_STATUS, &status);
+            if (status != LOCAL_GL_TRUE) {
+                NS_ERROR("Frag shader compilation failed.");
+
+                GLint length = 0;
+                fGetShaderiv(mTexBlit_FragShader, LOCAL_GL_INFO_LOG_LENGTH, &length);
+                if (!length) {
+                    printf_stderr("No shader info log available.\n");
+                    break;
+                }
+
+                nsAutoArrayPtr<char> buffer(new char[length]);
+                fGetShaderInfoLog(mTexBlit_FragShader, length, nullptr, buffer);
+
+                printf_stderr("Shader info log (%d bytes): %s\n", length, buffer.get());
+                break;
+            }
+        }
+
+        GLint status = 0;
+        fGetProgramiv(mTexBlit_Program, LOCAL_GL_LINK_STATUS, &status);
+        if (status != LOCAL_GL_TRUE) {
+            if (DebugMode()) {
+                NS_ERROR("Linking blit program failed.");
+                GLint length = 0;
+                fGetProgramiv(mTexBlit_Program, LOCAL_GL_INFO_LOG_LENGTH, &length);
+                if (!length) {
+                    printf_stderr("No program info log available.\n");
+                    break;
+                }
+
+                nsAutoArrayPtr<char> buffer(new char[length]);
+                fGetProgramInfoLog(mTexBlit_Program, length, nullptr, buffer);
+
+                printf_stderr("Program info log (%d bytes): %s\n", length, buffer.get());
+            }
+            break;
+        }
+
+        MOZ_ASSERT(fGetAttribLocation(mTexBlit_Program, "aPosition") == 0);
+        GLuint texUnitLoc = fGetUniformLocation(mTexBlit_Program, "uTexUnit");
+
+        // Set uniforms here:
+        fUseProgram(mTexBlit_Program);
+        fUniform1i(texUnitLoc, 0);
+
+        success = true;
+    } while (false);
+
+    if (!success) {
+        NS_ERROR("Creating program for texture blit failed!");
+
+        // Clean up:
+        DeleteTexBlitProgram();
+        return false;
+    }
+
+    fUseProgram(mTexBlit_Program);
+    fEnableVertexAttribArray(0);
+    fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTexBlit_Buffer);
+    fVertexAttribPointer(0,
+                         2,
+                         LOCAL_GL_FLOAT,
+                         false,
+                         0,
+                         nullptr);
+    return true;
+}
+
+void
+GLContext::DeleteTexBlitProgram()
+{
+    if (mTexBlit_Buffer) {
+        fDeleteBuffers(1, &mTexBlit_Buffer);
+        mTexBlit_Buffer = 0;
+    }
+    if (mTexBlit_VertShader) {
+        fDeleteShader(mTexBlit_VertShader);
+        mTexBlit_VertShader = 0;
+    }
+    if (mTexBlit_FragShader) {
+        fDeleteShader(mTexBlit_FragShader);
+        mTexBlit_FragShader = 0;
+    }
+    if (mTexBlit_Program) {
+        fDeleteProgram(mTexBlit_Program);
+        mTexBlit_Program = 0;
+    }
+}
+
+void
+GLContext::BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
+                                        const gfxIntSize& srcSize,
+                                        const gfxIntSize& destSize)
+{
+    MOZ_ASSERT(!srcFB || fIsFramebuffer(srcFB));
+    MOZ_ASSERT(!destFB || fIsFramebuffer(destFB));
+
+    MOZ_ASSERT(IsExtensionSupported(EXT_framebuffer_blit) ||
+               IsExtensionSupported(ANGLE_framebuffer_blit));
+
+    ScopedFramebufferBinding boundFB(this);
+    ScopedGLState scissor(this, LOCAL_GL_SCISSOR_TEST, false);
+
+    BindUserReadFBO(srcFB);
+    BindUserDrawFBO(destFB);
+
+    fBlitFramebuffer(0, 0,  srcSize.width,  srcSize.height,
+                     0, 0, destSize.width, destSize.height,
+                     LOCAL_GL_COLOR_BUFFER_BIT,
+                     LOCAL_GL_NEAREST);
+}
+
+void
+GLContext::BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
+                                    const gfxIntSize& srcSize,
+                                    const gfxIntSize& destSize)
+{
+    MOZ_ASSERT(fIsTexture(srcTex));
+    MOZ_ASSERT(!destFB || fIsFramebuffer(destFB));
+
+    if (IsExtensionSupported(EXT_framebuffer_blit) ||
+        IsExtensionSupported(ANGLE_framebuffer_blit))
+    {
+        ScopedFramebufferTexture srcWrapper(this, srcTex);
+        MOZ_ASSERT(srcWrapper.IsComplete());
+
+        BlitFramebufferToFramebuffer(srcWrapper.FB(), destFB,
+                                     srcSize, destSize);
+        return;
+    }
+
+
+    ScopedFramebufferBinding boundFB(this, destFB);
+
+    GLuint boundTexUnit = 0;
+    GetUIntegerv(LOCAL_GL_ACTIVE_TEXTURE, &boundTexUnit);
+    fActiveTexture(LOCAL_GL_TEXTURE0);
+
+    GLuint boundTex = 0;
+    GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &boundTex);
+    fBindTexture(LOCAL_GL_TEXTURE_2D, srcTex);
+
+    GLuint boundProgram = 0;
+    GetUIntegerv(LOCAL_GL_CURRENT_PROGRAM, &boundProgram);
+
+    GLuint boundBuffer = 0;
+    GetUIntegerv(LOCAL_GL_ARRAY_BUFFER_BINDING, &boundBuffer);
+
+    /*
+     * fGetVertexAttribiv takes:
+     *  VERTEX_ATTRIB_ARRAY_ENABLED
+     *  VERTEX_ATTRIB_ARRAY_SIZE,
+     *  VERTEX_ATTRIB_ARRAY_STRIDE,
+     *  VERTEX_ATTRIB_ARRAY_TYPE,
+     *  VERTEX_ATTRIB_ARRAY_NORMALIZED,
+     *  VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
+     *  CURRENT_VERTEX_ATTRIB
+     *
+     * CURRENT_VERTEX_ATTRIB is vertex shader state. \o/
+     * Others appear to be vertex array state,
+     * or alternatively in the internal vertex array state
+     * for a buffer object.
+    */
+
+    GLint attrib0_enabled = 0;
+    GLint attrib0_size = 0;
+    GLint attrib0_stride = 0;
+    GLint attrib0_type = 0;
+    GLint attrib0_normalized = 0;
+    GLint attrib0_bufferBinding = 0;
+    void* attrib0_pointer = nullptr;
+
+    fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED, &attrib0_enabled);
+    fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE, &attrib0_size);
+    fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_STRIDE, &attrib0_stride);
+    fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE, &attrib0_type);
+    fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &attrib0_normalized);
+    fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &attrib0_bufferBinding);
+    fGetVertexAttribPointerv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER, &attrib0_pointer);
+    // Note that uniform values are program state, so we don't need to rebind those.
+
+    ScopedGLState blend       (this, LOCAL_GL_BLEND,      false);
+    ScopedGLState cullFace    (this, LOCAL_GL_CULL_FACE,  false);
+    ScopedGLState depthTest   (this, LOCAL_GL_DEPTH_TEST, false);
+    ScopedGLState dither      (this, LOCAL_GL_DITHER,     false);
+    ScopedGLState polyOffsFill(this, LOCAL_GL_POLYGON_OFFSET_FILL,      false);
+    ScopedGLState sampleAToC  (this, LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, false);
+    ScopedGLState sampleCover (this, LOCAL_GL_SAMPLE_COVERAGE, false);
+    ScopedGLState scissor     (this, LOCAL_GL_SCISSOR_TEST,    false);
+    ScopedGLState stencil     (this, LOCAL_GL_STENCIL_TEST,    false);
+
+    realGLboolean colorMask[4];
+    fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorMask);
+    fColorMask(LOCAL_GL_TRUE,
+               LOCAL_GL_TRUE,
+               LOCAL_GL_TRUE,
+               LOCAL_GL_TRUE);
+
+    GLint viewport[4];
+    fGetIntegerv(LOCAL_GL_VIEWPORT, viewport);
+    fViewport(0, 0, destSize.width, destSize.height);
+
+    // Does destructive things to (only!) what we just saved above.
+    bool good = UseTexQuadProgram();
+    if (!good) {
+        // We're up against the wall, so bail.
+        // This should really be MOZ_CRASH(why) or MOZ_RUNTIME_ASSERT(good).
+        printf_stderr("[%s:%d] Fatal Error: Failed to prepare to blit texture->framebuffer.\n",
+                      __FILE__, __LINE__);
+        MOZ_CRASH();
+    }
+    fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
+
+    fViewport(viewport[0], viewport[1],
+              viewport[2], viewport[3]);
+
+    fColorMask(colorMask[0],
+               colorMask[1],
+               colorMask[2],
+               colorMask[3]);
+
+    if (attrib0_enabled)
+        fEnableVertexAttribArray(0);
+
+    fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0_bufferBinding);
+    fVertexAttribPointer(0,
+                         attrib0_size,
+                         attrib0_type,
+                         attrib0_normalized,
+                         attrib0_stride,
+                         attrib0_pointer);
+
+    fBindBuffer(LOCAL_GL_ARRAY_BUFFER, boundBuffer);
+
+    fUseProgram(boundProgram);
+    fBindTexture(LOCAL_GL_TEXTURE_2D, boundTex);
+    fActiveTexture(boundTexUnit);
+}
+
+void
+GLContext::BlitFramebufferToTexture(GLuint srcFB, GLuint destTex,
+                                    const gfxIntSize& srcSize,
+                                    const gfxIntSize& destSize)
+{
+    MOZ_ASSERT(!srcFB || fIsFramebuffer(srcFB));
+    MOZ_ASSERT(fIsTexture(destTex));
+
+    if (IsExtensionSupported(EXT_framebuffer_blit) ||
+        IsExtensionSupported(ANGLE_framebuffer_blit))
+    {
+        ScopedFramebufferTexture destWrapper(this, destTex);
+        MOZ_ASSERT(destWrapper.IsComplete());
+
+        BlitFramebufferToFramebuffer(srcFB, destWrapper.FB(),
+                                     srcSize, destSize);
+        return;
+    }
+
+    GLuint boundTex = 0;
+    GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &boundTex);
+    fBindTexture(LOCAL_GL_TEXTURE_2D, destTex);
+
+    ScopedFramebufferBinding boundFB(this, srcFB);
+    ScopedGLState scissor(this, LOCAL_GL_SCISSOR_TEST, false);
+
+    fCopyTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0,
+                       0, 0,
+                       0, 0,
+                       srcSize.width, srcSize.height);
+
+    fBindTexture(LOCAL_GL_TEXTURE_2D, boundTex);
+}
+
+void
+GLContext::BlitTextureToTexture(GLuint srcTex, GLuint destTex,
+                                const gfxIntSize& srcSize,
+                                const gfxIntSize& destSize)
+{
+    MOZ_ASSERT(fIsTexture(srcTex));
+    MOZ_ASSERT(fIsTexture(destTex));
+
+    if (mTexBlit_UseDrawNotCopy) {
+        // Draw is texture->framebuffer
+        ScopedFramebufferTexture destWrapper(this, destTex);
+        MOZ_ASSERT(destWrapper.IsComplete());
+
+        BlitTextureToFramebuffer(srcTex, destWrapper.FB(),
+                                 srcSize, destSize);
+        return;
+    }
+
+    // Generally, just use the CopyTexSubImage path
+    ScopedFramebufferTexture srcWrapper(this, srcTex);
+    MOZ_ASSERT(srcWrapper.IsComplete());
+
+    BlitFramebufferToTexture(srcWrapper.FB(), destTex,
+                             srcSize, destSize);
+}
+
+
+} /* namespace gl */
+} /* namespace mozilla */
--- a/gfx/gl/Makefile.in
+++ b/gfx/gl/Makefile.in
@@ -40,16 +40,17 @@ ifdef MOZ_WEBGL
 DEFINES += -DMOZ_WEBGL
 DEFINES += -DMOZ_D3DX9_DLL=$(MOZ_D3DX9_DLL)
 DEFINES += -DMOZ_D3DCOMPILER_DLL=$(MOZ_D3DCOMPILER_DLL)
 endif
 endif
 
 CPPSRCS	= \
 	GLContext.cpp \
+	GLContextUtils.cpp \
 	GLLibraryLoader.cpp \
 	GLContextProviderOSMesa.cpp \
 	$(NULL)
 
 GL_PROVIDER = Null
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 GL_PROVIDER = WGL