Bug 1322746 - Support PLANAR_YCBCR, GPU_VIDEO, and D3D11_YCBCR_IMAGE in GLBlitHelper. - r=jerry draft
authorjgilbert@mozilla.com <jgilbert@mozilla.com>
Tue, 18 Jul 2017 02:25:08 +0000
changeset 655208 7b7b58c08ba3d477d88cf26d940fbc6845c29e89
parent 655207 46d2322443599fa99fef37338afe2d89f762ec63
child 655209 751e93aa1aeb9668e3b9e615516e9b4bc85cfd1c
push id76798
push userbmo:dmu@mozilla.com
push dateTue, 29 Aug 2017 18:29:40 +0000
reviewersjerry
bugs1322746
milestone57.0a1
Bug 1322746 - Support PLANAR_YCBCR, GPU_VIDEO, and D3D11_YCBCR_IMAGE in GLBlitHelper. - r=jerry MozReview-Commit-ID: 16FwWYhF7eh
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/TexUnpackBlob.cpp
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLTextureUpload.cpp
gfx/gl/GLBlitHelper.cpp
gfx/gl/GLBlitHelper.h
gfx/gl/GLBlitHelperD3D.cpp
gfx/gl/GLContext.h
gfx/gl/GLContextEGL.h
gfx/gl/GLLibraryEGL.cpp
gfx/gl/GLLibraryEGL.h
gfx/gl/HeapCopyOfStackArray.h
gfx/gl/ScopedGLHelpers.cpp
gfx/gl/ScopedGLHelpers.h
gfx/gl/SharedSurface.cpp
gfx/gl/SharedSurfaceD3D11Interop.cpp
gfx/gl/moz.build
gfx/layers/D3D11YCbCrImage.cpp
gfx/layers/D3D11YCbCrImage.h
gfx/layers/GLImages.cpp
gfx/layers/GPUVideoImage.h
gfx/layers/client/GPUVideoTextureClient.h
gfx/layers/d3d11/TextureD3D11.h
gfx/layers/ipc/LayersSurfaces.ipdlh
gfx/thebes/gfxUtils.cpp
gfx/thebes/gfxUtils.h
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5244,19 +5244,27 @@ CanvasRenderingContext2D::DrawImage(cons
 
       gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
       gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
       gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
       gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
       gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
 
       const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
-      bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, srcImage->GetSize(),
-                                                     videoTexture, LOCAL_GL_TEXTURE_2D,
-                                                     destOrigin);
+      bool ok = false;
+      do {
+        const gl::ScopedFramebufferForTexture autoFBForTex(gl, videoTexture);
+        if (!autoFBForTex.IsComplete()) {
+          MOZ_ASSERT(false, "ScopedFramebufferForTexture not complete.");
+          break;
+        }
+        const gl::ScopedBindFramebuffer bindFB(gl, autoFBForTex.FB());
+        ok = gl->BlitHelper()->BlitImageToFramebuffer(srcImage, srcImage->GetSize(),
+                                                      destOrigin);
+      } while (false);
       if (ok) {
         NativeSurface texSurf;
         texSurf.mType = NativeSurfaceType::OPENGL_TEXTURE;
         texSurf.mFormat = SurfaceFormat::R5G6B5_UINT16;
         texSurf.mSize.width = srcImage->GetSize().width;
         texSurf.mSize.height = srcImage->GetSize().height;
         texSurf.mSurface = (void*)((uintptr_t)videoTexture);
 
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -696,19 +696,17 @@ TexUnpackImage::TexOrSubImage(bool isSub
         if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
             fallbackReason = "bug: failed to confirm FB for blit";
             break;
         }
 
         const gfx::IntSize destSize(mWidth, mHeight);
         const auto dstOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
                                                          : gl::OriginPos::BottomLeft);
-        if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, destSize, scopedFB.FB(),
-                                                      dstOrigin))
-        {
+        if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, destSize, dstOrigin)) {
             fallbackReason = "likely bug: failed to blit";
             break;
         }
 
         // Blitting was successful, so we're done!
         *out_error = 0;
         return true;
     } while (false);
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -716,16 +716,22 @@ WebGLContext::InitAndValidateGL(FailureR
 
     if (IsWebGL2() &&
         !InitWebGL2(out_failReason))
     {
         // Todo: Bug 898404: Only allow WebGL2 on GL>=3.0 on desktop GL.
         return false;
     }
 
+    if (!gl->IsSupported(GLFeature::vertex_array_object)) {
+        *out_failReason = { "FEATURE_FAILURE_WEBGL_VAOS",
+                            "Requires vertex_array_object." };
+        return false;
+    }
+
     mDefaultVertexArray = WebGLVertexArray::Create(this);
     mDefaultVertexArray->mAttribs.SetLength(mGLMaxVertexAttribs);
     mBoundVertexArray = mDefaultVertexArray;
 
     // OpenGL core profiles remove the default VAO object from version
     // 4.0.0. We create a default VAO for all core profiles,
     // regardless of version.
     //
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -1828,18 +1828,21 @@ ScopedCopyTexImageSource::ScopedCopyTexI
         MOZ_CRASH("GFX: Temp framebuffer is not complete.");
     }
 
     // Restore RB binding.
     scopedRB.Unwrap(); // This function should really have a better name.
 
     // Draw-blit rgbaTex into rgbaFB.
     const gfx::IntSize srcSize(srcWidth, srcHeight);
-    gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), rgbaFB,
-                                                   srcSize, srcSize);
+    {
+        const gl::ScopedBindFramebuffer bindFB(gl, rgbaFB);
+        gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), srcSize,
+                                                       srcSize);
+    }
 
     // Restore Tex2D binding and destroy the temp tex.
     scopedBindTex.Unwrap();
     scopedTex.Unwrap();
 
     // Leave RB and FB alive, and FB bound.
     mRB = rgbaRB;
     mFB = rgbaFB;
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -7,18 +7,20 @@
 #include "gfxUtils.h"
 #include "GLBlitHelper.h"
 #include "GLContext.h"
 #include "GLScreenBuffer.h"
 #include "ScopedGLHelpers.h"
 #include "mozilla/Preferences.h"
 #include "ImageContainer.h"
 #include "HeapCopyOfStackArray.h"
+#include "mozilla/gfx/Logging.h"
 #include "mozilla/gfx/Matrix.h"
 #include "mozilla/UniquePtr.h"
+#include "GPUVideoImage.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidSurfaceTexture.h"
 #include "GLImages.h"
 #include "GLLibraryEGL.h"
 #endif
 
 #ifdef XP_MACOSX
@@ -27,992 +29,930 @@
 #endif
 
 using mozilla::layers::PlanarYCbCrImage;
 using mozilla::layers::PlanarYCbCrData;
 
 namespace mozilla {
 namespace gl {
 
-GLBlitHelper::GLBlitHelper(GLContext* gl)
+// --
+
+ScopedSaveMultiTex::ScopedSaveMultiTex(GLContext* const gl, const uint8_t texCount,
+                                       const GLenum texTarget)
+    : mGL(*gl)
+    , mTexCount(texCount)
+    , mTexTarget(texTarget)
+    , mOldTexUnit(mGL.GetIntAs<GLenum>(LOCAL_GL_ACTIVE_TEXTURE))
+{
+    GLenum texBinding;
+    switch (mTexTarget) {
+    case LOCAL_GL_TEXTURE_2D:
+        texBinding = LOCAL_GL_TEXTURE_BINDING_2D;
+        break;
+    case LOCAL_GL_TEXTURE_RECTANGLE:
+        texBinding = LOCAL_GL_TEXTURE_BINDING_RECTANGLE;
+        break;
+    case LOCAL_GL_TEXTURE_EXTERNAL:
+        texBinding = LOCAL_GL_TEXTURE_BINDING_EXTERNAL;
+        break;
+    default:
+        gfxCriticalError() << "Unhandled texTarget: " << texTarget;
+    }
+
+    for (uint8_t i = 0; i < mTexCount; i++) {
+        mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + i);
+        if (mGL.IsSupported(GLFeature::sampler_objects)) {
+            mOldTexSampler[i] = mGL.GetIntAs<GLuint>(LOCAL_GL_SAMPLER_BINDING);
+            mGL.fBindSampler(i, 0);
+        }
+        mOldTex[i] = mGL.GetIntAs<GLuint>(texBinding);
+    }
+}
+
+ScopedSaveMultiTex::~ScopedSaveMultiTex()
+{
+    for (uint8_t i = 0; i < mTexCount; i++) {
+        mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + i);
+        if (mGL.IsSupported(GLFeature::sampler_objects)) {
+            mGL.fBindSampler(i, mOldTexSampler[i]);
+        }
+        mGL.fBindTexture(mTexTarget, mOldTex[i]);
+    }
+    mGL.fActiveTexture(mOldTexUnit);
+}
+
+// --
+
+class ScopedBindArrayBuffer final
+{
+    GLContext& mGL;
+    const GLuint mOldVBO;
+
+public:
+    ScopedBindArrayBuffer(GLContext* const gl, const GLuint vbo)
+        : mGL(*gl)
+        , mOldVBO(mGL.GetIntAs<GLuint>(LOCAL_GL_ARRAY_BUFFER_BINDING))
+    {
+        mGL.fBindBuffer(LOCAL_GL_ARRAY_BUFFER, vbo);
+    }
+
+    ~ScopedBindArrayBuffer()
+    {
+        mGL.fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mOldVBO);
+    }
+};
+
+// --
+
+class ScopedBindVAO final
+{
+    GLContext& mGL;
+    const GLuint mOldVAO;
+
+public:
+    ScopedBindVAO(GLContext* const gl, const GLuint vao)
+        : mGL(*gl)
+        , mOldVAO(mGL.GetIntAs<GLuint>(LOCAL_GL_VERTEX_ARRAY_BINDING))
+    {
+        mGL.fBindVertexArray(vao);
+    }
+
+    ~ScopedBindVAO()
+    {
+        mGL.fBindVertexArray(mOldVAO);
+    }
+};
+
+// --
+
+class ScopedShader final
+{
+    GLContext& mGL;
+    const GLuint mName;
+
+public:
+    ScopedShader(GLContext* const gl, const GLenum shaderType)
+        : mGL(*gl)
+        , mName(mGL.fCreateShader(shaderType))
+    { }
+
+    ~ScopedShader()
+    {
+        mGL.fDeleteShader(mName);
+    }
+
+    operator GLuint() const { return mName; }
+};
+
+// --
+
+class SaveRestoreCurrentProgram final
+{
+    GLContext& mGL;
+    const GLuint mOld;
+
+public:
+    explicit SaveRestoreCurrentProgram(GLContext* const gl)
+        : mGL(*gl)
+        , mOld(mGL.GetIntAs<GLuint>(LOCAL_GL_CURRENT_PROGRAM))
+    { }
+
+    ~SaveRestoreCurrentProgram()
+    {
+        mGL.fUseProgram(mOld);
+    }
+};
+
+// --
+
+class ScopedDrawBlitState final
+{
+    GLContext& mGL;
+
+    const bool blend;
+    const bool cullFace;
+    const bool depthTest;
+    const bool dither;
+    const bool polyOffsFill;
+    const bool sampleAToC;
+    const bool sampleCover;
+    const bool scissor;
+    const bool stencil;
+    Maybe<bool> rasterizerDiscard;
+
+    realGLboolean colorMask[4];
+    GLint viewport[4];
+
+public:
+    ScopedDrawBlitState(GLContext* const gl, const gfx::IntSize& destSize)
+        : mGL(*gl)
+        , blend       (mGL.PushEnabled(LOCAL_GL_BLEND,                    false))
+        , cullFace    (mGL.PushEnabled(LOCAL_GL_CULL_FACE,                false))
+        , depthTest   (mGL.PushEnabled(LOCAL_GL_DEPTH_TEST,               false))
+        , dither      (mGL.PushEnabled(LOCAL_GL_DITHER,                   true))
+        , polyOffsFill(mGL.PushEnabled(LOCAL_GL_POLYGON_OFFSET_FILL,      false))
+        , sampleAToC  (mGL.PushEnabled(LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, false))
+        , sampleCover (mGL.PushEnabled(LOCAL_GL_SAMPLE_COVERAGE,          false))
+        , scissor     (mGL.PushEnabled(LOCAL_GL_SCISSOR_TEST,             false))
+        , stencil     (mGL.PushEnabled(LOCAL_GL_STENCIL_TEST,             false))
+    {
+        if (mGL.IsSupported(GLFeature::transform_feedback2)) {
+            // Technically transform_feedback2 requires transform_feedback, which actually
+            // adds RASTERIZER_DISCARD.
+            rasterizerDiscard = Some(mGL.PushEnabled(LOCAL_GL_RASTERIZER_DISCARD, false));
+        }
+
+        mGL.fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorMask);
+        mGL.fColorMask(true, true, true, true);
+
+        mGL.fGetIntegerv(LOCAL_GL_VIEWPORT, viewport);
+        mGL.fViewport(0, 0, destSize.width, destSize.height);
+    }
+
+    ~ScopedDrawBlitState()
+    {
+        mGL.SetEnabled(LOCAL_GL_BLEND,                    blend       );
+        mGL.SetEnabled(LOCAL_GL_CULL_FACE,                cullFace    );
+        mGL.SetEnabled(LOCAL_GL_DEPTH_TEST,               depthTest   );
+        mGL.SetEnabled(LOCAL_GL_DITHER,                   dither      );
+        mGL.SetEnabled(LOCAL_GL_POLYGON_OFFSET_FILL,      polyOffsFill);
+        mGL.SetEnabled(LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, sampleAToC  );
+        mGL.SetEnabled(LOCAL_GL_SAMPLE_COVERAGE,          sampleCover );
+        mGL.SetEnabled(LOCAL_GL_SCISSOR_TEST,             scissor     );
+        mGL.SetEnabled(LOCAL_GL_STENCIL_TEST,             stencil     );
+        if (rasterizerDiscard) {
+            mGL.SetEnabled(LOCAL_GL_RASTERIZER_DISCARD, rasterizerDiscard.value());
+        }
+
+        mGL.fColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
+        mGL.fViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
+    }
+};
+
+// --
+
+DrawBlitProg::DrawBlitProg(GLBlitHelper* const parent, const GLuint prog)
+    : mParent(*parent)
+    , mProg(prog)
+    , mLoc_u1ForYFlip(mParent.mGL->fGetUniformLocation(mProg, "u1ForYFlip"))
+    , mLoc_uClipRect(mParent.mGL->fGetUniformLocation(mProg, "uClipRect"))
+    , mLoc_uTexSize0(mParent.mGL->fGetUniformLocation(mProg, "uTexSize0"))
+    , mLoc_uTexSize1(mParent.mGL->fGetUniformLocation(mProg, "uTexSize1"))
+    , mLoc_uDivisors(mParent.mGL->fGetUniformLocation(mProg, "uDivisors"))
+    , mLoc_uColorMatrix(mParent.mGL->fGetUniformLocation(mProg, "uColorMatrix"))
+{
+    MOZ_ASSERT(mLoc_u1ForYFlip != -1);
+    MOZ_ASSERT(mLoc_uClipRect != -1);
+    MOZ_ASSERT(mLoc_uTexSize0 != -1);
+    if (mLoc_uColorMatrix != -1) {
+        MOZ_ASSERT(mLoc_uTexSize1 != -1);
+        MOZ_ASSERT(mLoc_uDivisors != -1);
+    }
+}
+
+DrawBlitProg::~DrawBlitProg()
+{
+    const auto& gl = mParent.mGL;
+    if (!gl->MakeCurrent())
+        return;
+
+    gl->fDeleteProgram(mProg);
+}
+
+void
+DrawBlitProg::Draw(const BaseArgs& args, const YUVArgs* const argsYUV) const
+{
+    const auto& gl = mParent.mGL;
+
+    const SaveRestoreCurrentProgram oldProg(gl);
+    gl->fUseProgram(mProg);
+
+    // --
+
+    gl->fUniform1f(mLoc_u1ForYFlip, args.yFlip ? 1 : 0);
+    gl->fUniform4f(mLoc_uClipRect,
+                   args.clipRect.x, args.clipRect.y,
+                   args.clipRect.width, args.clipRect.height);
+    gl->fUniform2f(mLoc_uTexSize0, args.texSize0.width, args.texSize0.height);
+
+    MOZ_ASSERT(bool(argsYUV) == (mLoc_uColorMatrix != -1));
+    if (argsYUV) {
+        gl->fUniform2f(mLoc_uTexSize1, argsYUV->texSize1.width, argsYUV->texSize1.height);
+        gl->fUniform2f(mLoc_uDivisors, argsYUV->divisors.width, argsYUV->divisors.height);
+        const auto& colorMatrix = gfxUtils::YuvToRgbMatrix4x4ColumnMajor(argsYUV->colorSpace);
+        gl->fUniformMatrix4fv(mLoc_uColorMatrix, 1, false, colorMatrix);
+    }
+
+    // --
+
+    const ScopedDrawBlitState drawState(gl, args.destSize);
+    const ScopedBindVAO bindVAO(gl, mParent.mQuadVAO);
+    gl->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
+}
+
+// --
+
+GLBlitHelper::GLBlitHelper(GLContext* const gl)
     : mGL(gl)
-    , mTexBlit_Buffer(0)
-    , mTexBlit_VertShader(0)
-    , mTex2DBlit_FragShader(0)
-    , mTex2DRectBlit_FragShader(0)
-    , mTex2DBlit_Program(0)
-    , mTex2DRectBlit_Program(0)
-    , mYFlipLoc(-1)
-    , mTextureTransformLoc(-1)
-    , mTexExternalBlit_FragShader(0)
-    , mTexYUVPlanarBlit_FragShader(0)
-    , mTexNV12PlanarBlit_FragShader(0)
-    , mTexExternalBlit_Program(0)
-    , mTexYUVPlanarBlit_Program(0)
-    , mTexNV12PlanarBlit_Program(0)
-    , mFBO(0)
-    , mSrcTexY(0)
-    , mSrcTexCb(0)
-    , mSrcTexCr(0)
-    , mSrcTexEGL(0)
-    , mYTexScaleLoc(-1)
-    , mCbCrTexScaleLoc(-1)
-    , mYuvColorMatrixLoc(-1)
-    , mTexWidth(0)
-    , mTexHeight(0)
-    , mCurYScale(1.0f)
-    , mCurCbCrScale(1.0f)
+    , mQuadVAO(0)
+    , mYuvUploads{0}
+    , mYuvUploads_YSize(0, 0)
+    , mYuvUploads_UVSize(0, 0)
 {
+    if (!mGL->IsSupported(GLFeature::vertex_array_object)) {
+        gfxCriticalError() << "GLBlitHelper requires vertex_array_object.";
+        return;
+    }
+
+    GLuint vbo = 0;
+    mGL->fGenBuffers(1, &vbo);
+    {
+        const ScopedBindArrayBuffer bindVBO(mGL, vbo);
+
+        const float quadData[] = {
+            0, 0,
+            1, 0,
+            0, 1,
+            1, 1
+        };
+        const HeapCopyOfStackArray<float> heapQuadData(quadData);
+        mGL->fBufferData(LOCAL_GL_ARRAY_BUFFER, heapQuadData.ByteLength(),
+                         heapQuadData.Data(), LOCAL_GL_STATIC_DRAW);
+
+        mGL->fGenVertexArrays(1, &mQuadVAO);
+        const ScopedBindVAO bindVAO(mGL, mQuadVAO);
+        mGL->fEnableVertexAttribArray(0);
+        mGL->fVertexAttribPointer(0, 2, LOCAL_GL_FLOAT, false, 0, 0);
+    }
+    mGL->fDeleteBuffers(1, &vbo);
+
+    // --
+
+    const char kVertSource[] = "\
+        attribute vec2 aVert;                                                \n\
+                                                                             \n\
+        uniform float u1ForYFlip;                                            \n\
+        uniform vec4 uClipRect;                                              \n\
+        uniform vec2 uTexSize0;                                              \n\
+        uniform vec2 uTexSize1;                                              \n\
+        uniform vec2 uDivisors;                                              \n\
+                                                                             \n\
+        varying vec2 vTexCoord0;                                             \n\
+        varying vec2 vTexCoord1;                                             \n\
+                                                                             \n\
+        void main(void)                                                      \n\
+        {                                                                    \n\
+            vec2 vertPos = aVert * 2.0 - 1.0;                                \n\
+            gl_Position = vec4(vertPos, 0.0, 1.0);                           \n\
+                                                                             \n\
+            vec2 texCoord = aVert;                                           \n\
+            texCoord.y = abs(u1ForYFlip - texCoord.y);                       \n\
+            texCoord = texCoord * uClipRect.zw + uClipRect.xy;               \n\
+                                                                             \n\
+            vTexCoord0 = texCoord / uTexSize0;                               \n\
+            vTexCoord1 = texCoord / (uTexSize1 * uDivisors);                 \n\
+        }                                                                    \n\
+    ";
+    const ScopedShader vs(mGL, LOCAL_GL_VERTEX_SHADER);
+    const char* const parts[] = {
+        kVertSource
+    };
+    mGL->fShaderSource(vs, 1, parts, nullptr);
+    mGL->fCompileShader(vs);
+
+    const auto fnCreateProgram = [&](const DrawBlitType type,
+                                     const char* const fragHeader,
+                                     const char* const fragBody)
+    {
+        const ScopedShader fs(mGL, LOCAL_GL_FRAGMENT_SHADER);
+        const char* const parts[] = {
+            fragHeader,
+            fragBody
+        };
+        mGL->fShaderSource(fs, 2, parts, nullptr);
+        mGL->fCompileShader(fs);
+
+        const auto prog = mGL->fCreateProgram();
+        mGL->fAttachShader(prog, vs);
+        mGL->fAttachShader(prog, fs);
+
+        mGL->fBindAttribLocation(prog, 0, "aPosition");
+        mGL->fLinkProgram(prog);
+
+        GLenum status = 0;
+        mGL->fGetProgramiv(prog, LOCAL_GL_LINK_STATUS, (GLint*)&status);
+        if (status == LOCAL_GL_TRUE) {
+            mGL->fUseProgram(prog);
+            const char* samplerNames[] = {
+                "uTex0",
+                "uTex1",
+                "uTex2"
+            };
+            for (int i = 0; i < 3; i++) {
+                const auto loc = mGL->fGetUniformLocation(prog, samplerNames[i]);
+                if (loc == -1)
+                    break;
+                mGL->fUniform1i(loc, i);
+            }
+
+            auto obj = MakeUnique<DrawBlitProg>(this, prog);
+            mDrawBlitProgs.insert({uint8_t(type), Move(obj)});
+            return;
+        }
+
+        GLuint progLogLen = 0;
+        mGL->fGetProgramiv(prog, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&progLogLen);
+        const UniquePtr<char[]> progLog(new char[progLogLen]);
+        mGL->fGetProgramInfoLog(prog, progLogLen, nullptr, progLog.get());
+
+        GLuint vsLogLen = 0;
+        mGL->fGetShaderiv(vs, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&vsLogLen);
+        const UniquePtr<char[]> vsLog(new char[vsLogLen]);
+        mGL->fGetShaderInfoLog(vs, vsLogLen, nullptr, vsLog.get());
+
+        GLuint fsLogLen = 0;
+        mGL->fGetShaderiv(fs, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&fsLogLen);
+        const UniquePtr<char[]> fsLog(new char[fsLogLen]);
+        mGL->fGetShaderInfoLog(fs, fsLogLen, nullptr, fsLog.get());
+
+        gfxCriticalError() << "Link failed for DrawBlitType: " << uint8_t(type) << ":\n"
+                           << "progLog: " << progLog.get() << "\n"
+                           << "vsLog: " << vsLog.get() << "\n"
+                           << "fsLog: " << fsLog.get() << "\n";
+    };
+
+    const char kFragHeader_Tex2D[] = "\
+        #define SAMPLER sampler2D                                            \n\
+        #define TEXTURE texture2D                                            \n\
+    ";
+    const char kFragHeader_Tex2DRect[] = "\
+        #define SAMPLER sampler2DRect                                        \n\
+        #define TEXTURE texture2DRect                                        \n\
+    ";
+    const char kFragHeader_TexExt[] = "\
+        #extension GL_OES_EGL_image_external : require                       \n\
+        #define SAMPLER samplerExternalOES                                   \n\
+        #define TEXTURE texture2D                                            \n\
+    ";
+
+    const char kFragBody_RGBA[] = "\
+        #ifdef GL_FRAGMENT_PRECISION_HIGH                                    \n\
+            precision highp float;                                           \n\
+        #else                                                                \n\
+            precision mediump float;                                         \n\
+        #endif                                                               \n\
+                                                                             \n\
+        varying vec2 vTexCoord0;                                             \n\
+        uniform SAMPLER uTex0;                                               \n\
+                                                                             \n\
+        void main(void)                                                      \n\
+        {                                                                    \n\
+            gl_FragColor = TEXTURE(uTex0, vTexCoord0);                       \n\
+        }                                                                    \n\
+    ";
+    /*
+    const char kFragBody_YUV[] = "\
+        #ifdef GL_FRAGMENT_PRECISION_HIGH                                    \n\
+            precision highp float;                                           \n\
+        #else                                                                \n\
+            precision mediump float;                                         \n\
+        #endif                                                               \n\
+                                                                             \n\
+        varying vec2 vTexCoord0;                                             \n\
+        uniform SAMPLER uTex0;                                               \n\
+        uniform mat4 uColorMatrix;                                           \n\
+                                                                             \n\
+        void main(void)                                                      \n\
+        {                                                                    \n\
+            vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).xyz,                  \n\
+                            1.0);                                            \n\
+            vec4 rgb = uColorMatrix * yuv;                                   \n\
+            gl_FragColor = vec4(rgb.rgb, 1.0);                               \n\
+        }                                                                    \n\
+    ";
+    */
+    const char kFragBody_NV12[] = "\
+        #ifdef GL_FRAGMENT_PRECISION_HIGH                                    \n\
+            precision highp float;                                           \n\
+        #else                                                                \n\
+            precision mediump float;                                         \n\
+        #endif                                                               \n\
+                                                                             \n\
+        varying vec2 vTexCoord0;                                             \n\
+        varying vec2 vTexCoord1;                                             \n\
+        uniform SAMPLER uTex0;                                               \n\
+        uniform SAMPLER uTex1;                                               \n\
+        uniform mat4 uColorMatrix;                                           \n\
+                                                                             \n\
+        void main(void)                                                      \n\
+        {                                                                    \n\
+            vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).x,                    \n\
+                            TEXTURE(uTex1, vTexCoord1).xy,                   \n\
+                            1.0);                                            \n\
+            vec4 rgb = uColorMatrix * yuv;                                   \n\
+            gl_FragColor = vec4(rgb.rgb, 1.0);                               \n\
+            //gl_FragColor = yuv;                               \n\
+        }                                                                    \n\
+    ";
+    const char kFragBody_PlanarYUV[] = "\
+        #ifdef GL_FRAGMENT_PRECISION_HIGH                                    \n\
+            precision highp float;                                           \n\
+        #else                                                                \n\
+            precision mediump float;                                         \n\
+        #endif                                                               \n\
+                                                                             \n\
+        varying vec2 vTexCoord0;                                             \n\
+        varying vec2 vTexCoord1;                                             \n\
+        uniform SAMPLER uTex0;                                               \n\
+        uniform SAMPLER uTex1;                                               \n\
+        uniform SAMPLER uTex2;                                               \n\
+        uniform mat4 uColorMatrix;                                           \n\
+                                                                             \n\
+        void main(void)                                                      \n\
+        {                                                                    \n\
+            vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).x,                    \n\
+                            TEXTURE(uTex1, vTexCoord1).x,                    \n\
+                            TEXTURE(uTex2, vTexCoord1).x,                    \n\
+                            1.0);                                            \n\
+            vec4 rgb = uColorMatrix * yuv;                                   \n\
+            gl_FragColor = vec4(rgb.rgb, 1.0);                               \n\
+        }                                                                    \n\
+    ";
+
+    const SaveRestoreCurrentProgram oldProg(mGL);
+
+    fnCreateProgram(DrawBlitType::Tex2DRGBA, kFragHeader_Tex2D, kFragBody_RGBA);
+    fnCreateProgram(DrawBlitType::Tex2DPlanarYUV, kFragHeader_Tex2D, kFragBody_PlanarYUV);
+    if (mGL->IsExtensionSupported(GLContext::ARB_texture_rectangle)) {
+        fnCreateProgram(DrawBlitType::TexRectRGBA, kFragHeader_Tex2DRect, kFragBody_RGBA);
+    }
+    if (mGL->IsExtensionSupported(GLContext::OES_EGL_image_external)) {
+        fnCreateProgram(DrawBlitType::TexExtNV12, kFragHeader_TexExt, kFragBody_NV12);
+        fnCreateProgram(DrawBlitType::TexExtPlanarYUV, kFragHeader_TexExt, kFragBody_PlanarYUV);
+    }
 }
 
 GLBlitHelper::~GLBlitHelper()
 {
     if (!mGL->MakeCurrent())
         return;
 
-    DeleteTexBlitProgram();
+    mGL->fDeleteVertexArrays(1, &mQuadVAO);
+}
 
-    GLuint tex[] = {
-        mSrcTexY,
-        mSrcTexCb,
-        mSrcTexCr,
-        mSrcTexEGL,
-    };
-
-    mSrcTexY = mSrcTexCb = mSrcTexCr = mSrcTexEGL = 0;
-    mGL->fDeleteTextures(ArrayLength(tex), tex);
-
-    if (mFBO) {
-        mGL->fDeleteFramebuffers(1, &mFBO);
-    }
-    mFBO = 0;
+const DrawBlitProg*
+GLBlitHelper::GetDrawBlitProg(const DrawBlitType type) const
+{
+    const auto itr = mDrawBlitProgs.find(uint8_t(type));
+    if (itr == mDrawBlitProgs.end())
+        return nullptr;
+    return itr->second.get();
 }
 
-// Allowed to be destructive of state we restore in functions below.
-bool
-GLBlitHelper::InitTexQuadProgram(BlitType target)
-{
-    const char kTexBlit_VertShaderSource[] = "\
-        #version 100                                  \n\
-        #ifdef GL_ES                                  \n\
-        precision mediump float;                      \n\
-        #endif                                        \n\
-        attribute vec2 aPosition;                     \n\
-                                                      \n\
-        uniform float uYflip;                         \n\
-        varying vec2 vTexCoord;                       \n\
-                                                      \n\
-        void main(void)                               \n\
-        {                                             \n\
-            vTexCoord = aPosition;                    \n\
-            vTexCoord.y = abs(vTexCoord.y - uYflip);  \n\
-            vec2 vertPos = aPosition * 2.0 - 1.0;     \n\
-            gl_Position = vec4(vertPos, 0.0, 1.0);    \n\
-        }                                             \n\
-    ";
-
-    const char kTex2DBlit_FragShaderSource[] = "\
-        #version 100                                        \n\
-        #ifdef GL_ES                                        \n\
-        #ifdef GL_FRAGMENT_PRECISION_HIGH                   \n\
-            precision highp float;                          \n\
-        #else                                               \n\
-            precision mediump float;                        \n\
-        #endif                                              \n\
-        #endif                                              \n\
-        uniform sampler2D uTexUnit;                         \n\
-                                                            \n\
-        varying vec2 vTexCoord;                             \n\
-                                                            \n\
-        void main(void)                                     \n\
-        {                                                   \n\
-            gl_FragColor = texture2D(uTexUnit, vTexCoord);  \n\
-        }                                                   \n\
-    ";
+// -----------------------------------------------------------------------------
 
-    const char kTex2DRectBlit_FragShaderSource[] = "\
-        #version 100                                                  \n\
-        #ifdef GL_FRAGMENT_PRECISION_HIGH                             \n\
-            precision highp float;                                    \n\
-        #else                                                         \n\
-            precision mediump float;                                  \n\
-        #endif                                                        \n\
-                                                                      \n\
-        uniform sampler2D uTexUnit;                                   \n\
-        uniform vec2 uTexCoordMult;                                   \n\
-                                                                      \n\
-        varying vec2 vTexCoord;                                       \n\
-                                                                      \n\
-        void main(void)                                               \n\
-        {                                                             \n\
-            gl_FragColor = texture2DRect(uTexUnit,                    \n\
-                                         vTexCoord * uTexCoordMult);  \n\
-        }                                                             \n\
-    ";
-#ifdef ANDROID /* MOZ_WIDGET_ANDROID */
-    const char kTexExternalBlit_FragShaderSource[] = "\
-        #version 100                                                    \n\
-        #extension GL_OES_EGL_image_external : require                  \n\
-        #ifdef GL_FRAGMENT_PRECISION_HIGH                               \n\
-            precision highp float;                                      \n\
-        #else                                                           \n\
-            precision mediump float;                                    \n\
-        #endif                                                          \n\
-        varying vec2 vTexCoord;                                         \n\
-        uniform mat4 uTextureTransform;                                 \n\
-        uniform samplerExternalOES uTexUnit;                            \n\
-                                                                        \n\
-        void main()                                                     \n\
-        {                                                               \n\
-            gl_FragColor = texture2D(uTexUnit,                          \n\
-                (uTextureTransform * vec4(vTexCoord, 0.0, 1.0)).xy);    \n\
-        }                                                               \n\
-    ";
-#endif
-    /* From Rec601:
-    [R]   [1.1643835616438356,  0.0,                 1.5960267857142858]      [ Y -  16]
-    [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708]    x [Cb - 128]
-    [B]   [1.1643835616438356,  2.017232142857143,   8.862867620416422e-17]   [Cr - 128]
+bool
+GLBlitHelper::BlitImageToFramebuffer(layers::Image* srcImage,
+                                     const gfx::IntSize& destSize,
+                                     OriginPos destOrigin)
+{
+    switch (srcImage->GetFormat()) {
+    case ImageFormat::PLANAR_YCBCR:
+        return BlitImage(static_cast<PlanarYCbCrImage*>(srcImage), destSize, destOrigin);
 
-    For [0,1] instead of [0,255], and to 5 places:
-    [R]   [1.16438,  0.00000,  1.59603]   [ Y - 0.06275]
-    [G] = [1.16438, -0.39176, -0.81297] x [Cb - 0.50196]
-    [B]   [1.16438,  2.01723,  0.00000]   [Cr - 0.50196]
+#ifdef MOZ_WIDGET_ANDROID
+    case ImageFormat::SURFACE_TEXTURE:
+        return BlitImage(static_cast<layers::SurfaceTextureImage*>(srcImage));
 
-    From Rec709:
-    [R]   [1.1643835616438356,  4.2781193979771426e-17, 1.7927410714285714]     [ Y -  16]
-    [G] = [1.1643835616438358, -0.21324861427372963,   -0.532909328559444]    x [Cb - 128]
-    [B]   [1.1643835616438356,  2.1124017857142854,     0.0]                    [Cr - 128]
-
-    For [0,1] instead of [0,255], and to 5 places:
-    [R]   [1.16438,  0.00000,  1.79274]   [ Y - 0.06275]
-    [G] = [1.16438, -0.21325, -0.53291] x [Cb - 0.50196]
-    [B]   [1.16438,  2.11240,  0.00000]   [Cr - 0.50196]
-    */
-    const char kTexYUVPlanarBlit_FragShaderSource[] = "\
-        #version 100                                                        \n\
-        #ifdef GL_ES                                                        \n\
-        precision mediump float;                                            \n\
-        #endif                                                              \n\
-        varying vec2 vTexCoord;                                             \n\
-        uniform sampler2D uYTexture;                                        \n\
-        uniform sampler2D uCbTexture;                                       \n\
-        uniform sampler2D uCrTexture;                                       \n\
-        uniform vec2 uYTexScale;                                            \n\
-        uniform vec2 uCbCrTexScale;                                         \n\
-        uniform mat3 uYuvColorMatrix;                                       \n\
-        void main()                                                         \n\
-        {                                                                   \n\
-            float y = texture2D(uYTexture, vTexCoord * uYTexScale).r;       \n\
-            float cb = texture2D(uCbTexture, vTexCoord * uCbCrTexScale).r;  \n\
-            float cr = texture2D(uCrTexture, vTexCoord * uCbCrTexScale).r;  \n\
-            y = y - 0.06275;                                                \n\
-            cb = cb - 0.50196;                                              \n\
-            cr = cr - 0.50196;                                              \n\
-            vec3 yuv = vec3(y, cb, cr);                                     \n\
-            gl_FragColor.rgb = uYuvColorMatrix * yuv;                       \n\
-            gl_FragColor.a = 1.0;                                           \n\
-        }                                                                   \n\
-    ";
-
+    case ImageFormat::EGLIMAGE:
+        return BlitImage(static_cast<layers::EGLImageImage*>(srcImage), destSize,
+                         destOrigin);
+#endif
 #ifdef XP_MACOSX
-    const char kTexNV12PlanarBlit_FragShaderSource[] = "\
-        #version 100                                                             \n\
-        #extension GL_ARB_texture_rectangle : require                            \n\
-        #ifdef GL_ES                                                             \n\
-        precision mediump float                                                  \n\
-        #endif                                                                   \n\
-        varying vec2 vTexCoord;                                                  \n\
-        uniform sampler2DRect uYTexture;                                         \n\
-        uniform sampler2DRect uCbCrTexture;                                      \n\
-        uniform vec2 uYTexScale;                                                 \n\
-        uniform vec2 uCbCrTexScale;                                              \n\
-        void main()                                                              \n\
-        {                                                                        \n\
-            float y = texture2DRect(uYTexture, vTexCoord * uYTexScale).r;        \n\
-            float cb = texture2DRect(uCbCrTexture, vTexCoord * uCbCrTexScale).r; \n\
-            float cr = texture2DRect(uCbCrTexture, vTexCoord * uCbCrTexScale).a; \n\
-            y = (y - 0.06275) * 1.16438;                                         \n\
-            cb = cb - 0.50196;                                                   \n\
-            cr = cr - 0.50196;                                                   \n\
-            gl_FragColor.r = y + cr * 1.59603;                                   \n\
-            gl_FragColor.g = y - 0.81297 * cr - 0.39176 * cb;                    \n\
-            gl_FragColor.b = y + cb * 2.01723;                                   \n\
-            gl_FragColor.a = 1.0;                                                \n\
-        }                                                                        \n\
-    ";
+    case ImageFormat::MAC_IOSURFACE:
+        return BlitImage(srcImage->AsMacIOSurfaceImage());
 #endif
-
-    bool success = false;
-
-    GLuint* programPtr;
-    GLuint* fragShaderPtr;
-    const char* fragShaderSource;
-    switch (target) {
-    case ConvertEGLImage:
-    case BlitTex2D:
-        programPtr = &mTex2DBlit_Program;
-        fragShaderPtr = &mTex2DBlit_FragShader;
-        fragShaderSource = kTex2DBlit_FragShaderSource;
-        break;
-    case BlitTexRect:
-        programPtr = &mTex2DRectBlit_Program;
-        fragShaderPtr = &mTex2DRectBlit_FragShader;
-        fragShaderSource = kTex2DRectBlit_FragShaderSource;
-        break;
-#ifdef ANDROID
-    case ConvertSurfaceTexture:
-        programPtr = &mTexExternalBlit_Program;
-        fragShaderPtr = &mTexExternalBlit_FragShader;
-        fragShaderSource = kTexExternalBlit_FragShaderSource;
-        break;
-#endif
-    case ConvertPlanarYCbCr:
-        programPtr = &mTexYUVPlanarBlit_Program;
-        fragShaderPtr = &mTexYUVPlanarBlit_FragShader;
-        fragShaderSource = kTexYUVPlanarBlit_FragShaderSource;
-        break;
-#ifdef XP_MACOSX
-    case ConvertMacIOSurfaceImage:
-        programPtr = &mTexNV12PlanarBlit_Program;
-        fragShaderPtr = &mTexNV12PlanarBlit_FragShader;
-        fragShaderSource = kTexNV12PlanarBlit_FragShaderSource;
-        break;
+#ifdef XP_WIN
+    case ImageFormat::GPU_VIDEO:
+        return BlitImage(static_cast<layers::GPUVideoImage*>(srcImage), destSize,
+                         destOrigin);
+    case ImageFormat::D3D11_YCBCR_IMAGE:
+        return BlitImage((layers::D3D11YCbCrImage*)srcImage, destSize,
+                         destOrigin);
 #endif
     default:
+        gfxCriticalError() << "Unhandled srcImage->GetFormat(): "
+                           << uint32_t(srcImage->GetFormat());
         return false;
     }
-
-    GLuint& program = *programPtr;
-    GLuint& fragShader = *fragShaderPtr;
-
-    // Use do-while(false) to let us break on failure
-    do {
-        if (program) {
-            // Already have it...
-            success = true;
-            break;
-        }
-
-        if (!mTexBlit_Buffer) {
-
-            /* 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
-            };
-            HeapCopyOfStackArray<GLfloat> vertsOnHeap(verts);
-
-            MOZ_ASSERT(!mTexBlit_Buffer);
-            mGL->fGenBuffers(1, &mTexBlit_Buffer);
-            mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTexBlit_Buffer);
-
-            // Make sure we have a sane size.
-            mGL->fBufferData(LOCAL_GL_ARRAY_BUFFER, vertsOnHeap.ByteLength(), vertsOnHeap.Data(), LOCAL_GL_STATIC_DRAW);
-        }
-
-        if (!mTexBlit_VertShader) {
-
-            const char* vertShaderSource = kTexBlit_VertShaderSource;
-
-            mTexBlit_VertShader = mGL->fCreateShader(LOCAL_GL_VERTEX_SHADER);
-            mGL->fShaderSource(mTexBlit_VertShader, 1, &vertShaderSource, nullptr);
-            mGL->fCompileShader(mTexBlit_VertShader);
-        }
-
-        MOZ_ASSERT(!fragShader);
-        fragShader = mGL->fCreateShader(LOCAL_GL_FRAGMENT_SHADER);
-        mGL->fShaderSource(fragShader, 1, &fragShaderSource, nullptr);
-        mGL->fCompileShader(fragShader);
-
-        program = mGL->fCreateProgram();
-        mGL->fAttachShader(program, mTexBlit_VertShader);
-        mGL->fAttachShader(program, fragShader);
-        mGL->fBindAttribLocation(program, 0, "aPosition");
-        mGL->fLinkProgram(program);
-
-        if (GLContext::ShouldSpew()) {
-            GLint status = 0;
-            mGL->fGetShaderiv(mTexBlit_VertShader, LOCAL_GL_COMPILE_STATUS, &status);
-            if (status != LOCAL_GL_TRUE) {
-                NS_ERROR("Vert shader compilation failed.");
-
-                GLint length = 0;
-                mGL->fGetShaderiv(mTexBlit_VertShader, LOCAL_GL_INFO_LOG_LENGTH, &length);
-                if (!length) {
-                    printf_stderr("No shader info log available.\n");
-                    break;
-                }
-
-                auto buffer = MakeUnique<char[]>(length);
-                mGL->fGetShaderInfoLog(mTexBlit_VertShader, length, nullptr, buffer.get());
-
-                printf_stderr("Shader info log (%d bytes): %s\n", length, buffer.get());
-                break;
-            }
-
-            status = 0;
-            mGL->fGetShaderiv(fragShader, LOCAL_GL_COMPILE_STATUS, &status);
-            if (status != LOCAL_GL_TRUE) {
-                NS_ERROR("Frag shader compilation failed.");
-
-                GLint length = 0;
-                mGL->fGetShaderiv(fragShader, LOCAL_GL_INFO_LOG_LENGTH, &length);
-                if (!length) {
-                    printf_stderr("No shader info log available.\n");
-                    break;
-                }
-
-                auto buffer = MakeUnique<char[]>(length);
-                mGL->fGetShaderInfoLog(fragShader, length, nullptr, buffer.get());
-
-                printf_stderr("Shader info log (%d bytes): %s\n", length, buffer.get());
-                break;
-            }
-        }
-
-        GLint status = 0;
-        mGL->fGetProgramiv(program, LOCAL_GL_LINK_STATUS, &status);
-        if (status != LOCAL_GL_TRUE) {
-            if (GLContext::ShouldSpew()) {
-                NS_ERROR("Linking blit program failed.");
-                GLint length = 0;
-                mGL->fGetProgramiv(program, LOCAL_GL_INFO_LOG_LENGTH, &length);
-                if (!length) {
-                    printf_stderr("No program info log available.\n");
-                    break;
-                }
-
-                auto buffer = MakeUnique<char[]>(length);
-                mGL->fGetProgramInfoLog(program, length, nullptr, buffer.get());
-
-                printf_stderr("Program info log (%d bytes): %s\n", length, buffer.get());
-            }
-            break;
-        }
-
-        // Cache and set attribute and uniform
-        mGL->fUseProgram(program);
-        switch (target) {
-#ifdef ANDROID
-            case ConvertSurfaceTexture:
-#endif
-            case BlitTex2D:
-            case BlitTexRect:
-            case ConvertEGLImage: {
-                GLint texUnitLoc = mGL->fGetUniformLocation(program, "uTexUnit");
-                MOZ_ASSERT(texUnitLoc != -1, "uniform uTexUnit not found");
-                mGL->fUniform1i(texUnitLoc, 0);
-                break;
-            }
-            case ConvertPlanarYCbCr: {
-                GLint texY = mGL->fGetUniformLocation(program, "uYTexture");
-                GLint texCb = mGL->fGetUniformLocation(program, "uCbTexture");
-                GLint texCr = mGL->fGetUniformLocation(program, "uCrTexture");
-                mYTexScaleLoc = mGL->fGetUniformLocation(program, "uYTexScale");
-                mCbCrTexScaleLoc = mGL->fGetUniformLocation(program, "uCbCrTexScale");
-                mYuvColorMatrixLoc = mGL->fGetUniformLocation(program, "uYuvColorMatrix");
-
-                DebugOnly<bool> hasUniformLocations = texY != -1 &&
-                                                      texCb != -1 &&
-                                                      texCr != -1 &&
-                                                      mYTexScaleLoc != -1 &&
-                                                      mCbCrTexScaleLoc != -1 &&
-                                                      mYuvColorMatrixLoc != -1;
-                MOZ_ASSERT(hasUniformLocations, "uniforms not found");
-
-                mGL->fUniform1i(texY, Channel_Y);
-                mGL->fUniform1i(texCb, Channel_Cb);
-                mGL->fUniform1i(texCr, Channel_Cr);
-                break;
-            }
-            case ConvertMacIOSurfaceImage: {
-#ifdef XP_MACOSX
-                GLint texY = mGL->fGetUniformLocation(program, "uYTexture");
-                GLint texCbCr = mGL->fGetUniformLocation(program, "uCbCrTexture");
-                mYTexScaleLoc = mGL->fGetUniformLocation(program, "uYTexScale");
-                mCbCrTexScaleLoc= mGL->fGetUniformLocation(program, "uCbCrTexScale");
-
-                DebugOnly<bool> hasUniformLocations = texY != -1 &&
-                                                      texCbCr != -1 &&
-                                                      mYTexScaleLoc != -1 &&
-                                                      mCbCrTexScaleLoc != -1;
-                MOZ_ASSERT(hasUniformLocations, "uniforms not found");
-
-                mGL->fUniform1i(texY, Channel_Y);
-                mGL->fUniform1i(texCbCr, Channel_Cb);
-#endif
-                break;
-            }
-            default:
-                return false;
-        }
-        MOZ_ASSERT(mGL->fGetAttribLocation(program, "aPosition") == 0);
-        mYFlipLoc = mGL->fGetUniformLocation(program, "uYflip");
-        MOZ_ASSERT(mYFlipLoc != -1, "uniform: uYflip not found");
-        mTextureTransformLoc = mGL->fGetUniformLocation(program, "uTextureTransform");
-        if (mTextureTransformLoc >= 0) {
-            // Set identity matrix as default
-            gfx::Matrix4x4 identity;
-            mGL->fUniformMatrix4fv(mTextureTransformLoc, 1, false, &identity._11);
-        }
-        success = true;
-    } while (false);
-
-    if (!success) {
-        // Clean up:
-        DeleteTexBlitProgram();
-        return false;
-    }
-
-    mGL->fUseProgram(program);
-    mGL->fEnableVertexAttribArray(0);
-    mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTexBlit_Buffer);
-    mGL->fVertexAttribPointer(0,
-                              2,
-                              LOCAL_GL_FLOAT,
-                              false,
-                              0,
-                              nullptr);
-    return true;
-}
-
-bool
-GLBlitHelper::UseTexQuadProgram(BlitType target, const gfx::IntSize& srcSize)
-{
-    if (!InitTexQuadProgram(target)) {
-        return false;
-    }
-
-    if (target == BlitTexRect) {
-        GLint texCoordMultLoc = mGL->fGetUniformLocation(mTex2DRectBlit_Program, "uTexCoordMult");
-        MOZ_ASSERT(texCoordMultLoc != -1, "uniform not found");
-        mGL->fUniform2f(texCoordMultLoc, srcSize.width, srcSize.height);
-    }
-
-    return true;
-}
-
-void
-GLBlitHelper::DeleteTexBlitProgram()
-{
-    if (mTexBlit_Buffer) {
-        mGL->fDeleteBuffers(1, &mTexBlit_Buffer);
-        mTexBlit_Buffer = 0;
-    }
-    if (mTexBlit_VertShader) {
-        mGL->fDeleteShader(mTexBlit_VertShader);
-        mTexBlit_VertShader = 0;
-    }
-    if (mTex2DBlit_FragShader) {
-        mGL->fDeleteShader(mTex2DBlit_FragShader);
-        mTex2DBlit_FragShader = 0;
-    }
-    if (mTex2DRectBlit_FragShader) {
-        mGL->fDeleteShader(mTex2DRectBlit_FragShader);
-        mTex2DRectBlit_FragShader = 0;
-    }
-    if (mTex2DBlit_Program) {
-        mGL->fDeleteProgram(mTex2DBlit_Program);
-        mTex2DBlit_Program = 0;
-    }
-    if (mTex2DRectBlit_Program) {
-        mGL->fDeleteProgram(mTex2DRectBlit_Program);
-        mTex2DRectBlit_Program = 0;
-    }
-    if (mTexExternalBlit_FragShader) {
-        mGL->fDeleteShader(mTexExternalBlit_FragShader);
-        mTexExternalBlit_FragShader = 0;
-    }
-    if (mTexYUVPlanarBlit_FragShader) {
-        mGL->fDeleteShader(mTexYUVPlanarBlit_FragShader);
-        mTexYUVPlanarBlit_FragShader = 0;
-    }
-    if (mTexNV12PlanarBlit_FragShader) {
-        mGL->fDeleteShader(mTexNV12PlanarBlit_FragShader);
-        mTexNV12PlanarBlit_FragShader = 0;
-    }
-    if (mTexExternalBlit_Program) {
-        mGL->fDeleteProgram(mTexExternalBlit_Program);
-        mTexExternalBlit_Program = 0;
-    }
-    if (mTexYUVPlanarBlit_Program) {
-        mGL->fDeleteProgram(mTexYUVPlanarBlit_Program);
-        mTexYUVPlanarBlit_Program = 0;
-    }
-    if (mTexNV12PlanarBlit_Program) {
-        mGL->fDeleteProgram(mTexNV12PlanarBlit_Program);
-        mTexNV12PlanarBlit_Program = 0;
-    }
 }
 
-void
-GLBlitHelper::BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
-                                           const gfx::IntSize& srcSize,
-                                           const gfx::IntSize& destSize,
-                                           bool internalFBs)
-{
-    MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
-    MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
-
-    MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));
-
-    ScopedBindFramebuffer boundFB(mGL);
-    ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
-
-    if (internalFBs) {
-        mGL->Screen()->BindReadFB_Internal(srcFB);
-        mGL->Screen()->BindDrawFB_Internal(destFB);
-    } else {
-        mGL->BindReadFB(srcFB);
-        mGL->BindDrawFB(destFB);
-    }
-
-    mGL->fBlitFramebuffer(0, 0,  srcSize.width,  srcSize.height,
-                          0, 0, destSize.width, destSize.height,
-                          LOCAL_GL_COLOR_BUFFER_BIT,
-                          LOCAL_GL_NEAREST);
-}
-
-void
-GLBlitHelper::BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
-                                           const gfx::IntSize& srcSize,
-                                           const gfx::IntSize& destSize,
-                                           const GLFormats& srcFormats,
-                                           bool internalFBs)
-{
-    MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
-    MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
-
-    if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
-        BlitFramebufferToFramebuffer(srcFB, destFB,
-                                     srcSize, destSize,
-                                     internalFBs);
-        return;
-    }
-
-    GLuint tex = CreateTextureForOffscreen(mGL, srcFormats, srcSize);
-    MOZ_ASSERT(tex);
-
-    BlitFramebufferToTexture(srcFB, tex, srcSize, srcSize, internalFBs);
-    BlitTextureToFramebuffer(tex, destFB, srcSize, destSize, internalFBs);
-
-    mGL->fDeleteTextures(1, &tex);
-}
-
-void
-GLBlitHelper::BindAndUploadYUVTexture(Channel which,
-                                      uint32_t width,
-                                      uint32_t height,
-                                      void* data,
-                                      bool needsAllocation)
-{
-    MOZ_ASSERT(which < Channel_Max, "Invalid channel!");
-    GLuint* srcTexArr[3] = {&mSrcTexY, &mSrcTexCb, &mSrcTexCr};
-    GLuint& tex = *srcTexArr[which];
-
-    // RED textures aren't valid in GLES2, and ALPHA textures are not valid in desktop GL Core Profiles.
-    // So use R8 textures on GL3.0+ and GLES3.0+, but LUMINANCE/LUMINANCE/UNSIGNED_BYTE otherwise.
-    GLenum format;
-    GLenum internalFormat;
-    if (mGL->IsAtLeast(gl::ContextProfile::OpenGLCore, 300) ||
-        mGL->IsAtLeast(gl::ContextProfile::OpenGLES, 300)) {
-        format = LOCAL_GL_RED;
-        internalFormat = LOCAL_GL_R8;
-    } else {
-        format = LOCAL_GL_LUMINANCE;
-        internalFormat = LOCAL_GL_LUMINANCE;
-    }
-
-    if (!tex) {
-        MOZ_ASSERT(needsAllocation);
-        tex = CreateTexture(mGL, internalFormat, format, LOCAL_GL_UNSIGNED_BYTE,
-                            gfx::IntSize(width, height), false);
-    }
-    mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + which);
-
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, tex);
-    if (!needsAllocation) {
-        mGL->fTexSubImage2D(LOCAL_GL_TEXTURE_2D,
-                            0,
-                            0,
-                            0,
-                            width,
-                            height,
-                            format,
-                            LOCAL_GL_UNSIGNED_BYTE,
-                            data);
-    } else {
-        mGL->fTexImage2D(LOCAL_GL_TEXTURE_2D,
-                         0,
-                         internalFormat,
-                         width,
-                         height,
-                         0,
-                         format,
-                         LOCAL_GL_UNSIGNED_BYTE,
-                         data);
-    }
-}
-
-void
-GLBlitHelper::BindAndUploadEGLImage(EGLImage image, GLuint target)
-{
-    MOZ_ASSERT(image != EGL_NO_IMAGE, "Bad EGLImage");
-
-    if (!mSrcTexEGL) {
-        mGL->fGenTextures(1, &mSrcTexEGL);
-        mGL->fBindTexture(target, mSrcTexEGL);
-        mGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-        mGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
-        mGL->fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
-        mGL->fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
-    } else {
-        mGL->fBindTexture(target, mSrcTexEGL);
-    }
-    mGL->fEGLImageTargetTexture2D(target, image);
-}
+// -------------------------------------
 
 #ifdef MOZ_WIDGET_ANDROID
-
-#define ATTACH_WAIT_MS 50
-
 bool
-GLBlitHelper::BlitSurfaceTextureImage(layers::SurfaceTextureImage* stImage)
+GLBlitHelper::BlitImage(layers::SurfaceTextureImage* srcImage)
 {
     // FIXME
+    gfxCriticalError() << "BlitImage(SurfaceTextureImage) not implemented.";
     return false;
 }
 
 bool
-GLBlitHelper::BlitEGLImageImage(layers::EGLImageImage* image)
+GLBlitHelper::BlitImage(layers::EGLImageImage* const srcImage,
+                        const gfx::IntSize& destSize, const OriginPos destOrigin)
 {
-    EGLImage eglImage = image->GetImage();
-    EGLSync eglSync = image->GetSync();
-
+    const EGLImage eglImage = srcImage->GetImage();
+    const EGLSync eglSync = srcImage->GetSync();
     if (eglSync) {
         EGLint status = sEGLLibrary.fClientWaitSync(EGL_DISPLAY(), eglSync, 0, LOCAL_EGL_FOREVER);
         if (status != LOCAL_EGL_CONDITION_SATISFIED) {
             return false;
         }
     }
 
-    ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
-
-    int oldBinding = 0;
-    mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &oldBinding);
-
-    BindAndUploadEGLImage(eglImage, LOCAL_GL_TEXTURE_2D);
-
-    mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
-
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, oldBinding);
-    return true;
-}
-
-#endif
-
-bool
-GLBlitHelper::BlitPlanarYCbCrImage(layers::PlanarYCbCrImage* yuvImage)
-{
-    ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
-    const PlanarYCbCrData* yuvData = yuvImage->GetData();
+    GLuint tex = 0;
+    mGL->fGenTextures(1, &tex);
 
-    bool needsAllocation = false;
-    if (mTexWidth != yuvData->mYStride || mTexHeight != yuvData->mYSize.height) {
-        mTexWidth = yuvData->mYStride;
-        mTexHeight = yuvData->mYSize.height;
-        needsAllocation = true;
-    }
-
-    GLint oldTex[3];
-    for (int i = 0; i < 3; i++) {
-        mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
-        mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &oldTex[i]);
-    }
-
-    {
-        const ResetUnpackState reset(mGL);
-        BindAndUploadYUVTexture(Channel_Y, yuvData->mYStride, yuvData->mYSize.height, yuvData->mYChannel, needsAllocation);
-        BindAndUploadYUVTexture(Channel_Cb, yuvData->mCbCrStride, yuvData->mCbCrSize.height, yuvData->mCbChannel, needsAllocation);
-        BindAndUploadYUVTexture(Channel_Cr, yuvData->mCbCrStride, yuvData->mCbCrSize.height, yuvData->mCrChannel, needsAllocation);
-    }
-
-    if (needsAllocation) {
-        mGL->fUniform2f(mYTexScaleLoc, (float)yuvData->mYSize.width/yuvData->mYStride, 1.0f);
-        mGL->fUniform2f(mCbCrTexScaleLoc, (float)yuvData->mCbCrSize.width/yuvData->mCbCrStride, 1.0f);
-    }
-
-    const auto& yuvToRgb = gfxUtils::YuvToRgbMatrix3x3ColumnMajor(yuvData->mYUVColorSpace);
-    mGL->fUniformMatrix3fv(mYuvColorMatrixLoc, 1, 0, yuvToRgb);
+    const ScopedSaveMultiTex saveTex(mGL, 1, LOCAL_GL_TEXTURE_2D);
+    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, tex);
+    mGL->TexParams_SetClampNoMips();
+    mGL->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, eglImage);
 
-    mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
-    for (int i = 0; i < 3; i++) {
-        mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
-        mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, oldTex[i]);
-    }
-    return true;
-}
-
-#ifdef XP_MACOSX
-bool
-GLBlitHelper::BlitMacIOSurfaceImage(layers::MacIOSurfaceImage* ioImage)
-{
-    ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
-    MacIOSurface* surf = ioImage->GetSurface();
-
-    GLint oldTex[2];
-    for (int i = 0; i < 2; i++) {
-        mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
-        mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &oldTex[i]);
-    }
-
-    GLuint textures[2];
-    mGL->fGenTextures(2, textures);
+    const auto& srcOrigin = srcImage->GetOriginPos();
+    const bool yFlip = destOrigin != srcOrigin;
+    const gfx::IntRect clipRect(0, 0, 1, 1);
+    const gfx::IntSize texSizeDivisor(1, 1);
+    const DrawBlitProg::DrawArgs baseArgs = { destSize, yFlip, clipRect, texSizeDivisor };
 
-    mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textures[0]);
-    mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
-    mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    surf->CGLTexImageIOSurface2D(mGL,
-                                 gl::GLContextCGL::Cast(mGL)->GetCGLContext(),
-                                 0);
-    mGL->fUniform2f(mYTexScaleLoc, surf->GetWidth(0), surf->GetHeight(0));
+    const auto& prog = GetDrawBlitProg(DrawBlitType::Tex2DRGB);
+    MOZ_RELEASE_ASSERT(prog);
+    prog->Draw(baseArgs);
 
-    mGL->fActiveTexture(LOCAL_GL_TEXTURE1);
-    mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textures[1]);
-    mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
-    mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-    surf->CGLTexImageIOSurface2D(mGL,
-                                 gl::GLContextCGL::Cast(mGL)->GetCGLContext(),
-                                 1);
-    mGL->fUniform2f(mCbCrTexScaleLoc, surf->GetWidth(1), surf->GetHeight(1));
-
-    mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
-    for (int i = 0; i < 2; i++) {
-        mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
-        mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, oldTex[i]);
-    }
-
-    mGL->fDeleteTextures(2, textures);
+    mGL->fDeleteTextures(1, &tex);
     return true;
 }
 #endif
 
-bool
-GLBlitHelper::BlitImageToFramebuffer(layers::Image* srcImage,
-                                     const gfx::IntSize& destSize,
-                                     GLuint destFB,
-                                     OriginPos destOrigin)
-{
-    ScopedGLDrawState autoStates(mGL);
-
-    BlitType type;
-    OriginPos srcOrigin;
+// -------------------------------------
 
-    switch (srcImage->GetFormat()) {
-    case ImageFormat::PLANAR_YCBCR:
-        type = ConvertPlanarYCbCr;
-#if defined(MOZ_WIDGET_ANDROID)
-        srcOrigin = OriginPos::TopLeft;
-#else
-        srcOrigin = OriginPos::BottomLeft;
-#endif // defined(MOZ_WIDGET_ANDROID)
-        break;
-
-#ifdef MOZ_WIDGET_ANDROID
-    case ImageFormat::SURFACE_TEXTURE:
-        type = ConvertSurfaceTexture;
-        srcOrigin = srcImage->AsSurfaceTextureImage()->GetOriginPos();
-        break;
-    case ImageFormat::EGLIMAGE:
-        type = ConvertEGLImage;
-        srcOrigin = srcImage->AsEGLImageImage()->GetOriginPos();
-        break;
-#endif
-#ifdef XP_MACOSX
-    case ImageFormat::MAC_IOSURFACE:
-        type = ConvertMacIOSurfaceImage;
-        srcOrigin = OriginPos::TopLeft;
-        break;
-#endif
-
-    default:
+bool
+GuessDivisors(const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
+              gfx::IntSize* const out_divisors)
+{
+    const uint8_t widthDivisor  = (ySize.width  == uvSize.width ) ? 1 : 2;
+    const uint8_t heightDivisor = (ySize.height == uvSize.height) ? 1 : 2;
+    const gfx::IntSize divisors((ySize.width  == uvSize.width ) ? 1 : 2,
+                                (ySize.height == uvSize.height) ? 1 : 2);
+    if (uvSize.width  * divisors.width != ySize.width ||
+        uvSize.height * divisors.height != ySize.height)
+    {
         return false;
     }
-
-    bool init = InitTexQuadProgram(type);
-    if (!init) {
-        return false;
-    }
-
-    const bool needsYFlip = (srcOrigin != destOrigin);
-    mGL->fUniform1f(mYFlipLoc, needsYFlip ? (float)1.0 : (float)0.0);
-
-    ScopedBindFramebuffer boundFB(mGL, destFB);
-    mGL->fColorMask(LOCAL_GL_TRUE, LOCAL_GL_TRUE, LOCAL_GL_TRUE, LOCAL_GL_TRUE);
-    mGL->fViewport(0, 0, destSize.width, destSize.height);
-
-    switch (type) {
-    case ConvertPlanarYCbCr:
-        return BlitPlanarYCbCrImage(static_cast<PlanarYCbCrImage*>(srcImage));
-
-#ifdef MOZ_WIDGET_ANDROID
-    case ConvertSurfaceTexture:
-        return BlitSurfaceTextureImage(static_cast<layers::SurfaceTextureImage*>(srcImage));
-
-    case ConvertEGLImage:
-        return BlitEGLImageImage(static_cast<layers::EGLImageImage*>(srcImage));
-#endif
-
-#ifdef XP_MACOSX
-    case ConvertMacIOSurfaceImage:
-        return BlitMacIOSurfaceImage(srcImage->AsMacIOSurfaceImage());
-#endif
-
-    default:
-        return false;
-    }
+    *out_divisors = divisors;
+    return true;
 }
 
 bool
-GLBlitHelper::BlitImageToTexture(layers::Image* srcImage,
-                                 const gfx::IntSize& destSize,
-                                 GLuint destTex,
-                                 GLenum destTarget,
-                                 OriginPos destOrigin)
+GLBlitHelper::BlitImage(layers::PlanarYCbCrImage* const yuvImage,
+                        const gfx::IntSize& destSize, const OriginPos destOrigin)
 {
-    ScopedFramebufferForTexture autoFBForTex(mGL, destTex, destTarget);
-    if (!autoFBForTex.IsComplete())
+    const auto& prog = GetDrawBlitProg(DrawBlitType::Tex2DPlanarYUV);
+    MOZ_RELEASE_ASSERT(prog);
+
+    if (!mYuvUploads[0]) {
+        mGL->fGenTextures(3, mYuvUploads);
+        const ScopedBindTexture bindTex(mGL, mYuvUploads[0]);
+        mGL->TexParams_SetClampNoMips();
+        mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[1]);
+        mGL->TexParams_SetClampNoMips();
+        mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[2]);
+        mGL->TexParams_SetClampNoMips();
+    }
+
+    // --
+
+    const PlanarYCbCrData* const yuvData = yuvImage->GetData();
+
+    if (yuvData->mYSkip || yuvData->mCbSkip || yuvData->mCrSkip ||
+        yuvData->mYSize.width < 0 || yuvData->mYSize.height < 0 ||
+        yuvData->mCbCrSize.width < 0 || yuvData->mCbCrSize.height < 0 ||
+        yuvData->mYStride < 0 || yuvData->mCbCrStride < 0)
+    {
+        gfxCriticalError() << "Unusual PlanarYCbCrData: "
+                           << yuvData->mYSkip << ","
+                           << yuvData->mCbSkip << ","
+                           << yuvData->mCrSkip << ", "
+                           << yuvData->mYSize.width << ","
+                           << yuvData->mYSize.height << ", "
+                           << yuvData->mCbCrSize.width << ","
+                           << yuvData->mCbCrSize.height << ", "
+                           << yuvData->mYStride << ","
+                           << yuvData->mCbCrStride;
+        return false;
+    }
+
+    const gfx::IntSize yTexSize(yuvData->mYStride, yuvData->mYSize.height);
+    const gfx::IntSize uvTexSize(yuvData->mCbCrStride, yuvData->mCbCrSize.height);
+    gfx::IntSize divisors;
+    if (!GuessDivisors(yTexSize, uvTexSize, &divisors)) {
+        gfxCriticalError() << "GuessDivisors failed:"
+                           << yTexSize.width << ","
+                           << yTexSize.height << ", "
+                           << uvTexSize.width << ","
+                           << uvTexSize.height;
         return false;
+    }
 
-    return BlitImageToFramebuffer(srcImage, destSize, autoFBForTex.FB(), destOrigin);
+    // --
+
+    // RED textures aren't valid in GLES2, and ALPHA textures are not valid in desktop GL Core Profiles.
+    // So use R8 textures on GL3.0+ and GLES3.0+, but LUMINANCE/LUMINANCE/UNSIGNED_BYTE otherwise.
+    GLenum internalFormat;
+    GLenum unpackFormat;
+    if (mGL->IsAtLeast(gl::ContextProfile::OpenGLCore, 300) ||
+        mGL->IsAtLeast(gl::ContextProfile::OpenGLES, 300))
+    {
+        internalFormat = LOCAL_GL_R8;
+        unpackFormat = LOCAL_GL_RED;
+    } else {
+        internalFormat = LOCAL_GL_LUMINANCE;
+        unpackFormat = LOCAL_GL_LUMINANCE;
+    }
+
+    // --
+
+    const ScopedSaveMultiTex saveTex(mGL, 3, LOCAL_GL_TEXTURE_2D);
+    const ResetUnpackState reset(mGL);
+
+    if (yTexSize != mYuvUploads_YSize ||
+        uvTexSize != mYuvUploads_UVSize)
+    {
+        mYuvUploads_YSize = yTexSize;
+        mYuvUploads_UVSize = uvTexSize;
+
+        mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
+        mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[0]);
+        mGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat,
+                         yTexSize.width, yTexSize.height, 0,
+                         unpackFormat, LOCAL_GL_UNSIGNED_BYTE, nullptr);
+        for (int i = 1; i < 2; i++) {
+            mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
+            mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[i]);
+            mGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat,
+                             uvTexSize.width, uvTexSize.height, 0,
+                             unpackFormat, LOCAL_GL_UNSIGNED_BYTE, nullptr);
+        }
+    }
+
+    // --
+
+    mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
+    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[0]);
+    mGL->fTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0,
+                        yTexSize.width, yTexSize.height,
+                        unpackFormat, LOCAL_GL_UNSIGNED_BYTE, yuvData->mYChannel);
+    mGL->fActiveTexture(LOCAL_GL_TEXTURE1);
+    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[1]);
+    mGL->fTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0,
+                        uvTexSize.width, uvTexSize.height,
+                        unpackFormat, LOCAL_GL_UNSIGNED_BYTE, yuvData->mCbChannel);
+    mGL->fActiveTexture(LOCAL_GL_TEXTURE2);
+    mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mYuvUploads[2]);
+    mGL->fTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0,
+                        uvTexSize.width, uvTexSize.height,
+                        unpackFormat, LOCAL_GL_UNSIGNED_BYTE, yuvData->mCrChannel);
+
+    // --
+
+    const auto srcOrigin = OriginPos::BottomLeft;
+    const bool yFlip = (destOrigin != srcOrigin);
+    const auto& clipRect = yuvData->GetPictureRect();
+    const auto& colorSpace = yuvData->mYUVColorSpace;
+
+    const DrawBlitProg::BaseArgs baseArgs = { destSize, yFlip, clipRect, yTexSize };
+    const DrawBlitProg::YUVArgs yuvArgs = { uvTexSize, divisors, colorSpace };
+
+    prog->Draw(baseArgs, &yuvArgs);
+    return true;
+}
+
+// -------------------------------------
+
+#ifdef XP_MACOSX
+#error TODO
+bool
+GLBlitHelper::BlitImage(layers::MacIOSurfaceImage* ioImage)
+{
+    MacIOSurface* const iosurf = ioImage->GetSurface();
+MacIOSurfaceLib::IOSurfaceGetPixelFormat
+    const uint32_t pixelFormat = MacIOSurfaceLib::iosurf->GetPixelFormat();
+    DrawBlitType type;
+    int planes;
+    Maybe<YUVColorSpace> colorSpace = Nothing();
+    if (pixelFormat == '420v') {
+        type = DrawBlitType::TexRectNV12;
+        planes = 2;
+        colorSpace = Some(
+    } else if (pixelFormat == '2vuy') {
+        type = DrawBlitType::TexRectRGB;
+        planes = 1;
+    } else {
+        gfxCriticalError() << "Unrecognized pixelFormat: " << pixelFormat;
+        return false;
+    }
+
+    const auto& prog = GetDrawBlitProg(type);
+    MOZ_RELEASE_ASSERT(prog);
+
+    if (!mIOSurfaceTexs[0]) {
+        mGL->fGenTextures(2, &mIOSurfaceTexs);
+        const ScopedBindTexture bindTex(mGL, mIOSurfaceTexs[0], LOCAL_GL_TEXTURE_RECTANGLE);
+        mGL->TexParams_SetClampNoMips(LOCAL_GL_TEXTURE_RECTANGLE);
+        mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE, mIOSurfaceTexs[1]);
+        mGL->TexParams_SetClampNoMips(LOCAL_GL_TEXTURE_RECTANGLE);
+    }
+
+    const ScopedBindMultiTex bindTex(mGL, LOCAL_GL_TEXTURE_RECTANGLE, planes,
+                                     mIOSurfaceTexs);
+    const auto& cglContext = gl::GLContextCGL::Cast(mGL)->GetCGLContext();
+    for (int i = 0; i < planes; i++) {
+        mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
+        surf->CGLTexImageIOSurface2D(mGL, cglContext, i);
+    }
+    mGL->fUniform2f(mYTexScaleLoc, surf->GetWidth(0), surf->GetHeight(0));
+    mGL->fUniform2f(mCbCrTexScaleLoc, surf->GetWidth(1), surf->GetHeight(1));
+
+    const auto& srcOrigin = OriginPos::TopLeft;
+    const auto& texMatrix = TexMatrixForOrigins(srcOrigin, destOrigin);
+    prog->Draw(texMatrix, destSize);
+    return true;
+}
+#endif
+
+// -----------------------------------------------------------------------------
+
+void
+GLBlitHelper::DrawBlitTextureToFramebuffer(const GLuint srcTex,
+                                           const gfx::IntSize& srcSize,
+                                           const gfx::IntSize& destSize,
+                                           const GLenum srcTarget) const
+{
+    const gfx::IntRect clipRect(0, 0, srcSize.width, srcSize.height);
+
+    DrawBlitType type;
+    gfx::IntSize texSizeDivisor;
+    switch (srcTarget) {
+    case LOCAL_GL_TEXTURE_2D:
+        type = DrawBlitType::Tex2DRGBA;
+        texSizeDivisor = srcSize;
+        break;
+    case LOCAL_GL_TEXTURE_RECTANGLE_ARB:
+        type = DrawBlitType::TexRectRGBA;
+        texSizeDivisor = gfx::IntSize(1, 1);
+        break;
+    default:
+        gfxCriticalError() << "Unexpected srcTarget: " << srcTarget;
+    }
+    const auto& prog = GetDrawBlitProg(type);
+    MOZ_ASSERT(prog);
+
+    const ScopedSaveMultiTex saveTex(mGL, 1, srcTarget);
+    mGL->fBindTexture(srcTarget, srcTex);
+
+    const bool yFlip = false;
+    const DrawBlitProg::BaseArgs baseArgs = { destSize, yFlip, clipRect, texSizeDivisor };
+    prog->Draw(baseArgs);
+}
+
+// -----------------------------------------------------------------------------
+
+void
+GLBlitHelper::BlitFramebuffer(const gfx::IntSize& srcSize,
+                              const gfx::IntSize& destSize) const
+{
+    MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));
+
+    const ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
+    mGL->fBlitFramebuffer(0, 0,  srcSize.width,  srcSize.height,
+                          0, 0, destSize.width, destSize.height,
+                          LOCAL_GL_COLOR_BUFFER_BIT,
+                          LOCAL_GL_NEAREST);
+}
+
+// --
+
+void
+GLBlitHelper::BlitFramebufferToFramebuffer(const GLuint srcFB, const GLuint destFB,
+                                           const gfx::IntSize& srcSize,
+                                           const gfx::IntSize& destSize) const
+{
+    MOZ_ASSERT(mGL->IsSupported(GLFeature::framebuffer_blit));
+    MOZ_ASSERT(!srcFB || mGL->fIsFramebuffer(srcFB));
+    MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
+
+    const ScopedBindFramebuffer boundFB(mGL);
+    mGL->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, srcFB);
+    mGL->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFB);
+
+    BlitFramebuffer(srcSize, destSize);
 }
 
 void
-GLBlitHelper::BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
-                                       const gfx::IntSize& srcSize,
+GLBlitHelper::BlitTextureToFramebuffer(GLuint srcTex, const gfx::IntSize& srcSize,
                                        const gfx::IntSize& destSize,
-                                       GLenum srcTarget,
-                                       bool internalFBs)
+                                       GLenum srcTarget) const
 {
     MOZ_ASSERT(mGL->fIsTexture(srcTex));
-    MOZ_ASSERT(!destFB || mGL->fIsFramebuffer(destFB));
 
     if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
-        ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
-        MOZ_DIAGNOSTIC_ASSERT(srcWrapper.IsComplete());
-
-        BlitFramebufferToFramebuffer(srcWrapper.FB(), destFB,
-                                     srcSize, destSize,
-                                     internalFBs);
+        const ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
+        const ScopedBindFramebuffer bindFB(mGL);
+        mGL->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, srcWrapper.FB());
+        BlitFramebuffer(srcSize, destSize);
         return;
     }
 
-    DrawBlitTextureToFramebuffer(srcTex, destFB, srcSize, destSize, srcTarget,
-                                 internalFBs);
-}
-
-
-void
-GLBlitHelper::DrawBlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
-                                           const gfx::IntSize& srcSize,
-                                           const gfx::IntSize& destSize,
-                                           GLenum srcTarget,
-                                           bool internalFBs)
-{
-    BlitType type;
-    switch (srcTarget) {
-    case LOCAL_GL_TEXTURE_2D:
-        type = BlitTex2D;
-        break;
-    case LOCAL_GL_TEXTURE_RECTANGLE_ARB:
-        type = BlitTexRect;
-        break;
-    default:
-        MOZ_CRASH("GFX: Fatal Error: Bad `srcTarget`.");
-        break;
-    }
-
-    ScopedGLDrawState autoStates(mGL);
-    const ScopedBindFramebuffer bindFB(mGL);
-    if (internalFBs) {
-        mGL->Screen()->BindFB_Internal(destFB);
-    } else {
-        mGL->BindFB(destFB);
-    }
-
-    // Does destructive things to (only!) what we just saved above.
-    bool good = UseTexQuadProgram(type, srcSize);
-    if (!good) {
-        // We're up against the wall, so bail.
-        MOZ_DIAGNOSTIC_ASSERT(false,
-                              "Error: Failed to prepare to blit texture->framebuffer.\n");
-        mGL->fScissor(0, 0, destSize.width, destSize.height);
-        mGL->fColorMask(1, 1, 1, 1);
-        mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
-        return;
-    }
-
-    const ScopedBindTexture bindTex(mGL, srcTex, srcTarget);
-    mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
+    DrawBlitTextureToFramebuffer(srcTex, srcSize, destSize, srcTarget);
 }
 
 void
-GLBlitHelper::BlitFramebufferToTexture(GLuint srcFB, GLuint destTex,
+GLBlitHelper::BlitFramebufferToTexture(GLuint destTex,
                                        const gfx::IntSize& srcSize,
                                        const gfx::IntSize& destSize,
-                                       GLenum destTarget,
-                                       bool internalFBs)
+                                       GLenum destTarget) const
 {
-    // On the Android 4.3 emulator, IsFramebuffer may return false incorrectly.
-    MOZ_ASSERT_IF(mGL->Renderer() != GLRenderer::AndroidEmulator, !srcFB || mGL->fIsFramebuffer(srcFB));
     MOZ_ASSERT(mGL->fIsTexture(destTex));
 
     if (mGL->IsSupported(GLFeature::framebuffer_blit)) {
-        ScopedFramebufferForTexture destWrapper(mGL, destTex, destTarget);
-
-        BlitFramebufferToFramebuffer(srcFB, destWrapper.FB(),
-                                     srcSize, destSize,
-                                     internalFBs);
+        const ScopedFramebufferForTexture destWrapper(mGL, destTex, destTarget);
+        const ScopedBindFramebuffer bindFB(mGL);
+        mGL->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destWrapper.FB());
+        BlitFramebuffer(srcSize, destSize);
         return;
     }
 
     ScopedBindTexture autoTex(mGL, destTex, destTarget);
-
-    ScopedBindFramebuffer boundFB(mGL);
-    if (internalFBs) {
-        mGL->Screen()->BindFB_Internal(srcFB);
-    } else {
-        mGL->BindFB(srcFB);
-    }
-
     ScopedGLState scissor(mGL, LOCAL_GL_SCISSOR_TEST, false);
     mGL->fCopyTexSubImage2D(destTarget, 0,
-                       0, 0,
-                       0, 0,
-                       srcSize.width, srcSize.height);
+                            0, 0,
+                            0, 0,
+                            srcSize.width, srcSize.height);
 }
 
 void
 GLBlitHelper::BlitTextureToTexture(GLuint srcTex, GLuint destTex,
                                    const gfx::IntSize& srcSize,
                                    const gfx::IntSize& destSize,
-                                   GLenum srcTarget, GLenum destTarget)
+                                   GLenum srcTarget, GLenum destTarget) const
 {
     MOZ_ASSERT(mGL->fIsTexture(srcTex));
     MOZ_ASSERT(mGL->fIsTexture(destTex));
 
-    // Generally, just use the CopyTexSubImage path
-    ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
-
-    BlitFramebufferToTexture(srcWrapper.FB(), destTex,
-                             srcSize, destSize, destTarget);
+    // Start down the CopyTexSubImage path, not the DrawBlit path.
+    const ScopedFramebufferForTexture srcWrapper(mGL, srcTex, srcTarget);
+    const ScopedBindFramebuffer bindFB(mGL, srcWrapper.FB());
+    BlitFramebufferToTexture(destTex, srcSize, destSize, destTarget);
 }
 
 } // namespace gl
 } // namespace mozilla
--- a/gfx/gl/GLBlitHelper.h
+++ b/gfx/gl/GLBlitHelper.h
@@ -5,160 +5,186 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GLBLITHELPER_H_
 #define GLBLITHELPER_H_
 
 #include "GLContextTypes.h"
 #include "GLConsts.h"
 #include "nsSize.h"
+#include "ipc/IPCMessageUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/gfx/Point.h"
 
+#ifdef XP_WIN
+#include <windows.h>
+#endif
+
 namespace mozilla {
 
 namespace layers {
+class D3D11YCbCrImage;
 class Image;
+class GPUVideoImage;
 class PlanarYCbCrImage;
 class SurfaceTextureImage;
 class MacIOSurfaceImage;
 class EGLImageImage;
+class SurfaceDescriptorD3D10;
+class SurfaceDescriptorDXGIYCbCr;
 } // namespace layers
 
 namespace gl {
 
+class BindAnglePlanes;
 class GLContext;
 
+bool
+GuessDivisors(const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
+              gfx::IntSize* const out_divisors);
+
+class DrawBlitProg final
+{
+    GLBlitHelper& mParent;
+    const GLuint mProg;
+    const GLint mLoc_u1ForYFlip;
+    const GLint mLoc_uClipRect;
+    const GLint mLoc_uTexSize0;
+    const GLint mLoc_uTexSize1;
+    const GLint mLoc_uDivisors;
+    const GLint mLoc_uColorMatrix;
+
+public:
+    DrawBlitProg(GLBlitHelper* parent, GLuint prog);
+    ~DrawBlitProg();
+
+    struct BaseArgs final {
+        gfx::IntSize destSize;
+        bool yFlip;
+        gfx::IntRect clipRect;
+        gfx::IntSize texSize0;
+    };
+    struct YUVArgs final {
+        gfx::IntSize texSize1;
+        gfx::IntSize divisors;
+        YUVColorSpace colorSpace;
+    };
+
+    void Draw(const BaseArgs& args, const YUVArgs* argsYUV = nullptr) const;
+};
+
+class ScopedSaveMultiTex final
+{
+    GLContext& mGL;
+    const uint8_t mTexCount;
+    const GLenum mTexTarget;
+    const GLuint mOldTexUnit;
+    GLuint mOldTexSampler[3];
+    GLuint mOldTex[3];
+
+public:
+    ScopedSaveMultiTex(GLContext* gl, uint8_t texCount, GLenum texTarget);
+    ~ScopedSaveMultiTex();
+};
+
 /** Buffer blitting helper */
 class GLBlitHelper final
 {
-    enum Channel
+    friend class BindAnglePlanes;
+    friend class DrawBlitProg;
+    friend class GLContext;
+
+    enum class DrawBlitType : uint8_t
     {
-        Channel_Y = 0,
-        Channel_Cb,
-        Channel_Cr,
-        Channel_Max,
+        Tex2DRGBA,
+        Tex2DPlanarYUV,
+        TexRectRGBA,
+        //TexExtYUV,
+        TexExtNV12,
+        TexExtPlanarYUV,
     };
 
-    /**
-     * BlitTex2D is used to copy blit the content of a GL_TEXTURE_2D object,
-     * BlitTexRect is used to copy blit the content of a GL_TEXTURE_RECT object,
-     * The difference between BlitTex2D and BlitTexRect is the texture type, which affect
-     * the fragment shader a bit.
-     *
-     * ConvertPlnarYcbCr is used to color convert copy blit the PlanarYCbCrImage
-     * into a normal RGB texture by create textures of each color channel, and
-     * convert it in GPU.
-     * Convert type is created for canvas.
-     */
-    enum BlitType
-    {
-        BlitTex2D,
-        BlitTexRect,
-        ConvertPlanarYCbCr,
-        ConvertSurfaceTexture,
-        ConvertEGLImage,
-        ConvertMacIOSurfaceImage
-    };
-    // The GLContext is the sole owner of the GLBlitHelper.
-    GLContext* mGL;
+    GLContext* const mGL;
+    std::map<uint8_t, UniquePtr<DrawBlitProg>> mDrawBlitProgs;
 
-    GLuint mTexBlit_Buffer;
-    GLuint mTexBlit_VertShader;
-    GLuint mTex2DBlit_FragShader;
-    GLuint mTex2DRectBlit_FragShader;
-    GLuint mTex2DBlit_Program;
-    GLuint mTex2DRectBlit_Program;
+    GLuint mQuadVAO;
 
-    GLint mYFlipLoc;
+    GLuint mYuvUploads[3];
+    gfx::IntSize mYuvUploads_YSize;
+    gfx::IntSize mYuvUploads_UVSize;
 
-    GLint mTextureTransformLoc;
+#ifdef XP_WIN
+    mutable RefPtr<ID3D11Device> mD3D11;
+
+    ID3D11Device* GetD3D11() const;
+#endif
 
-    // Data for image blit path
-    GLuint mTexExternalBlit_FragShader;
-    GLuint mTexYUVPlanarBlit_FragShader;
-    GLuint mTexNV12PlanarBlit_FragShader;
-    GLuint mTexExternalBlit_Program;
-    GLuint mTexYUVPlanarBlit_Program;
-    GLuint mTexNV12PlanarBlit_Program;
-    GLuint mFBO;
-    GLuint mSrcTexY;
-    GLuint mSrcTexCb;
-    GLuint mSrcTexCr;
-    GLuint mSrcTexEGL;
-    GLint mYTexScaleLoc;
-    GLint mCbCrTexScaleLoc;
-    GLint mYuvColorMatrixLoc;
-    int mTexWidth;
-    int mTexHeight;
+
 
-    // Cache some uniform values
-    float mCurYScale;
-    float mCurCbCrScale;
-
-    void UseBlitProgram();
-    void SetBlitFramebufferForDestTexture(GLuint aTexture);
+    const DrawBlitProg* GetDrawBlitProg(DrawBlitType type) const;
 
-    bool UseTexQuadProgram(BlitType target, const gfx::IntSize& srcSize);
-    bool InitTexQuadProgram(BlitType target = BlitTex2D);
-    void DeleteTexBlitProgram();
-    void BindAndUploadYUVTexture(Channel which, uint32_t width, uint32_t height, void* data, bool allocation);
-    void BindAndUploadEGLImage(EGLImage image, GLuint target);
-
-    bool BlitPlanarYCbCrImage(layers::PlanarYCbCrImage* yuvImage);
+    bool BlitImage(layers::PlanarYCbCrImage* yuvImage, const gfx::IntSize& destSize,
+                   OriginPos destOrigin);
 #ifdef MOZ_WIDGET_ANDROID
     // Blit onto the current FB.
-    bool BlitSurfaceTextureImage(layers::SurfaceTextureImage* stImage);
-    bool BlitEGLImageImage(layers::EGLImageImage* eglImage);
+    bool BlitImage(layers::SurfaceTextureImage* stImage);
+    bool BlitImage(layers::EGLImageImage* eglImage);
 #endif
 #ifdef XP_MACOSX
-    bool BlitMacIOSurfaceImage(layers::MacIOSurfaceImage* ioImage);
+    bool BlitImage(layers::MacIOSurfaceImage* ioImage);
 #endif
 
     explicit GLBlitHelper(GLContext* gl);
-
-    friend class GLContext;
-
 public:
     ~GLBlitHelper();
 
-    // If you don't have |srcFormats| for the 2nd definition,
-    // then you'll need the framebuffer_blit extensions to use
-    // the first BlitFramebufferToFramebuffer.
-    void BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
-                                      const gfx::IntSize& srcSize,
-                                      const gfx::IntSize& destSize,
-                                      bool internalFBs = false);
+    void BlitFramebuffer(const gfx::IntSize& srcSize,
+                         const gfx::IntSize& destSize) const;
     void BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
                                       const gfx::IntSize& srcSize,
-                                      const gfx::IntSize& destSize,
-                                      const GLFormats& srcFormats,
-                                      bool internalFBs = false);
-    void BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
-                                  const gfx::IntSize& srcSize,
+                                      const gfx::IntSize& destSize) const;
+    void BlitFramebufferToTexture(GLuint destTex, const gfx::IntSize& srcSize,
                                   const gfx::IntSize& destSize,
-                                  GLenum srcTarget = LOCAL_GL_TEXTURE_2D,
-                                  bool internalFBs = false);
-    void DrawBlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
-                                      const gfx::IntSize& srcSize,
-                                      const gfx::IntSize& destSize,
-                                      GLenum srcTarget = LOCAL_GL_TEXTURE_2D,
-                                      bool internalFBs = false);
-    void BlitFramebufferToTexture(GLuint srcFB, GLuint destTex,
-                                  const gfx::IntSize& srcSize,
+                                  GLenum destTarget = LOCAL_GL_TEXTURE_2D) const;
+    void BlitTextureToFramebuffer(GLuint srcTex, const gfx::IntSize& srcSize,
                                   const gfx::IntSize& destSize,
-                                  GLenum destTarget = LOCAL_GL_TEXTURE_2D,
-                                  bool internalFBs = false);
+                                  GLenum srcTarget = LOCAL_GL_TEXTURE_2D) const;
     void BlitTextureToTexture(GLuint srcTex, GLuint destTex,
                               const gfx::IntSize& srcSize,
                               const gfx::IntSize& destSize,
                               GLenum srcTarget = LOCAL_GL_TEXTURE_2D,
-                              GLenum destTarget = LOCAL_GL_TEXTURE_2D);
+                              GLenum destTarget = LOCAL_GL_TEXTURE_2D) const;
+
+
+    void DrawBlitTextureToFramebuffer(GLuint srcTex, const gfx::IntSize& srcSize,
+                                      const gfx::IntSize& destSize,
+                                      GLenum srcTarget = LOCAL_GL_TEXTURE_2D) const;
+
     bool BlitImageToFramebuffer(layers::Image* srcImage, const gfx::IntSize& destSize,
-                                GLuint destFB, OriginPos destOrigin);
-    bool BlitImageToTexture(layers::Image* srcImage, const gfx::IntSize& destSize,
-                            GLuint destTex, GLenum destTarget, OriginPos destOrigin);
+                                OriginPos destOrigin);
+
+private:
+#ifdef XP_WIN
+    // GLBlitHelperD3D.cpp:
+    bool BlitImage(layers::GPUVideoImage* srcImage, const gfx::IntSize& destSize,
+                   OriginPos destOrigin) const;
+    bool BlitImage(layers::D3D11YCbCrImage* srcImage, const gfx::IntSize& destSize,
+                   OriginPos destOrigin) const;
+
+    bool BlitDescriptor(const layers::SurfaceDescriptorD3D10& desc,
+                        const gfx::IntSize& destSize, OriginPos destOrigin) const;
+
+    bool BlitAngleYCbCr(const WindowsHandle (&handleList)[3],
+                        const gfx::IntRect& clipRect,
+                        const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
+                        const YUVColorSpace colorSpace,
+                        const gfx::IntSize& destSize, OriginPos destOrigin) const;
+
+    bool BlitAnglePlanes(uint8_t numPlanes, const RefPtr<ID3D11Texture2D>* texD3DList,
+                         const DrawBlitProg* prog, const DrawBlitProg::BaseArgs& baseArgs,
+                         const DrawBlitProg::YUVArgs* const yuvArgs) const;
+#endif
 };
 
 } // namespace gl
 } // namespace mozilla
 
 #endif // GLBLITHELPER_H_
new file mode 100644
--- /dev/null
+++ b/gfx/gl/GLBlitHelperD3D.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* 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 "GLBlitHelper.h"
+
+#include <d3d11.h>
+
+#include "GLContext.h"
+#include "GLLibraryEGL.h"
+#include "GPUVideoImage.h"
+#include "ScopedGLHelpers.h"
+
+#include "mozilla/layers/D3D11YCbCrImage.h"
+#include "mozilla/layers/TextureD3D11.h"
+
+namespace mozilla {
+namespace gl {
+
+static EGLStreamKHR
+StreamFromD3DTexture(ID3D11Texture2D* const texD3D,
+                     const EGLAttrib* const postAttribs)
+{
+    auto& egl = sEGLLibrary;
+    const auto& display = egl.Display();
+    const auto stream = egl.fCreateStreamKHR(display, nullptr);
+    MOZ_ASSERT(stream);
+    if (!stream)
+        return 0;
+    EGLBoolean ok = 1;
+    MOZ_ALWAYS_TRUE( ok &= egl.fStreamConsumerGLTextureExternalAttribsNV(display, stream,
+                                                                         nullptr) );
+    MOZ_ALWAYS_TRUE( ok &= egl.fCreateStreamProducerD3DTextureNV12ANGLE(display, stream,
+                                                                        nullptr) );
+    MOZ_ALWAYS_TRUE( ok &= egl.fStreamPostD3DTextureNV12ANGLE(display, stream, texD3D,
+                                                              postAttribs) );
+    if (ok)
+        return stream;
+
+    (void)egl.fDestroyStreamKHR(display, stream);
+    return 0;
+}
+
+static RefPtr<ID3D11Texture2D>
+OpenSharedTexture(ID3D11Device* const d3d, const WindowsHandle handle)
+{
+    RefPtr<ID3D11Texture2D> tex;
+    auto hr = d3d->OpenSharedResource((HANDLE)handle, __uuidof(ID3D11Texture2D),
+                                      (void**)(ID3D11Texture2D**)getter_AddRefs(tex));
+    if (FAILED(hr)) {
+        MOZ_ASSERT(false, "OpenSharedResource should not fail");
+        return nullptr;
+    }
+    return tex;
+}
+
+// -------------------------------------
+
+class BindAnglePlanes final
+{
+    const GLBlitHelper& mParent;
+    const uint8_t mNumPlanes;
+    const ScopedSaveMultiTex mMultiTex;
+    GLuint mTempTexs[3];
+    EGLStreamKHR mStreams[3];
+    RefPtr<IDXGIKeyedMutex> mMutexList[3];
+    bool mSuccess;
+
+public:
+    BindAnglePlanes(const GLBlitHelper* const parent, const uint8_t numPlanes,
+                    const RefPtr<ID3D11Texture2D>* const texD3DList,
+                    const EGLAttrib* const* postAttribsList = nullptr)
+        : mParent(*parent)
+        , mNumPlanes(numPlanes)
+        , mMultiTex(mParent.mGL, mNumPlanes, LOCAL_GL_TEXTURE_EXTERNAL)
+        , mTempTexs{0}
+        , mStreams{0}
+        , mSuccess(true)
+    {
+        MOZ_RELEASE_ASSERT(numPlanes >= 1 && numPlanes <= 3);
+
+        const auto& gl = mParent.mGL;
+        auto& egl = sEGLLibrary;
+        const auto& display = egl.Display();
+
+        gl->fGenTextures(numPlanes, mTempTexs);
+
+        for (uint8_t i = 0; i < mNumPlanes; i++) {
+            gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
+            gl->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL, mTempTexs[i]);
+            const EGLAttrib* postAttribs = nullptr;
+            if (postAttribsList) {
+                postAttribs = postAttribsList[i];
+            }
+            mStreams[i] = StreamFromD3DTexture(texD3DList[i], postAttribs);
+            mSuccess &= bool(mStreams[i]);
+        }
+
+        if (mSuccess) {
+            for (uint8_t i = 0; i < mNumPlanes; i++) {
+                MOZ_ALWAYS_TRUE( egl.fStreamConsumerAcquireKHR(display, mStreams[i]) );
+
+                auto& mutex = mMutexList[i];
+                texD3DList[i]->QueryInterface(_uuidof(IDXGIKeyedMutex),
+                                              (void**)getter_AddRefs(mutex));
+                if (mutex) {
+                    HRESULT hr = mutex->AcquireSync(0, 100);
+                    if (hr != S_OK) {
+                      NS_WARNING("Acquire sync didn't manage to return within 2 seconds.");
+                      mSuccess = false;
+                    }
+                }
+            }
+        }
+    }
+
+    ~BindAnglePlanes()
+    {
+        const auto& gl = mParent.mGL;
+        auto& egl = sEGLLibrary;
+        const auto& display = egl.Display();
+
+        if (mSuccess) {
+            for (uint8_t i = 0; i < mNumPlanes; i++) {
+                MOZ_ALWAYS_TRUE( egl.fStreamConsumerReleaseKHR(display, mStreams[i]) );
+                if (mMutexList[i]) {
+                    mMutexList[i]->ReleaseSync(0);
+                }
+            }
+        }
+
+        for (uint8_t i = 0; i < mNumPlanes; i++) {
+            (void)egl.fDestroyStreamKHR(display, mStreams[i]);
+        }
+
+        gl->fDeleteTextures(mNumPlanes, mTempTexs);
+    }
+
+    const bool& Success() const { return mSuccess; }
+};
+
+// -------------------------------------
+
+ID3D11Device*
+GLBlitHelper::GetD3D11() const
+{
+    if (mD3D11)
+        return mD3D11;
+
+    if (!mGL->IsANGLE())
+        return nullptr;
+
+    auto& egl = sEGLLibrary;
+    EGLDeviceEXT deviceEGL = 0;
+    MOZ_ALWAYS_TRUE( egl.fQueryDisplayAttribEXT(egl.Display(), LOCAL_EGL_DEVICE_EXT,
+                                                (EGLAttrib*)&deviceEGL) );
+    if (!egl.fQueryDeviceAttribEXT(deviceEGL, LOCAL_EGL_D3D11_DEVICE_ANGLE,
+                                   (EGLAttrib*)(ID3D11Device**)getter_AddRefs(mD3D11)))
+    {
+        MOZ_ASSERT(false, "d3d9?");
+        return nullptr;
+    }
+    return mD3D11;
+}
+
+// -------------------------------------
+
+bool
+GLBlitHelper::BlitImage(layers::GPUVideoImage* const srcImage,
+                        const gfx::IntSize& destSize, const OriginPos destOrigin) const
+{
+    const auto& data = srcImage->GetData();
+    if (!data)
+        return false;
+
+    const auto& desc = data->SD();
+    const auto& subdescUnion = desc.subdesc();
+    switch (subdescUnion.type()) {
+    case subdescUnion.TSurfaceDescriptorD3D10:
+        {
+            const auto& subdesc = subdescUnion.get_SurfaceDescriptorD3D10();
+            return BlitDescriptor(subdesc, destSize, destOrigin);
+        }
+    case subdescUnion.TSurfaceDescriptorDXGIYCbCr:
+        {
+            const auto& subdesc = subdescUnion.get_SurfaceDescriptorDXGIYCbCr();
+
+            const auto& clipSize = subdesc.size();
+            const auto& ySize = subdesc.sizeY();
+            const auto& uvSize = subdesc.sizeCbCr();
+
+            const gfx::IntRect clipRect(0, 0, clipSize.width, clipSize.height);
+            const auto colorSpace = YUVColorSpace::BT601;
+
+            const WindowsHandle handles[3] = {
+                subdesc.handleY(),
+                subdesc.handleCb(),
+                subdesc.handleCr()
+            };
+            return BlitAngleYCbCr(handles, clipRect, ySize, uvSize, colorSpace, destSize,
+                                  destOrigin);
+        }
+    default:
+        gfxCriticalError() << "Unhandled subdesc type: " << uint32_t(subdescUnion.type());
+        return false;
+    }
+}
+
+// -------------------------------------
+
+bool
+GLBlitHelper::BlitImage(layers::D3D11YCbCrImage* const srcImage,
+                        const gfx::IntSize& destSize, const OriginPos destOrigin) const
+{
+    const auto& data = srcImage->GetData();
+    if (!data)
+        return false;
+
+    const auto& clipRect = srcImage->mPictureRect;
+    const auto& colorSpace = srcImage->mColorSpace;
+
+    const WindowsHandle handles[3] = {
+        (WindowsHandle)data->mHandles[0],
+        (WindowsHandle)data->mHandles[1],
+        (WindowsHandle)data->mHandles[2]
+    };
+    return BlitAngleYCbCr(handles, srcImage->mPictureRect, srcImage->mYSize,
+                          srcImage->mCbCrSize, srcImage->mColorSpace, destSize,
+                          destOrigin);
+}
+
+// -------------------------------------
+
+bool
+GLBlitHelper::BlitDescriptor(const layers::SurfaceDescriptorD3D10& desc,
+                             const gfx::IntSize& destSize, OriginPos destOrigin) const
+{
+    const auto& d3d = GetD3D11();
+    if (!d3d)
+        return false;
+
+    const auto& handle = desc.handle();
+    const auto& format = desc.format();
+    const auto& clipSize = desc.size();
+
+    const auto srcOrigin = OriginPos::BottomLeft;
+    const gfx::IntRect clipRect(0, 0, clipSize.width, clipSize.height);
+    const auto colorSpace = YUVColorSpace::BT601;
+
+    if (format != gfx::SurfaceFormat::NV12) {
+        gfxCriticalError() << "Non-NV12 format for SurfaceDescriptorD3D10: "
+                           << uint32_t(format);
+        return nullptr;
+    }
+
+    const auto tex = OpenSharedTexture(d3d, handle);
+    const RefPtr<ID3D11Texture2D> texList[2] = { tex, tex };
+    const EGLAttrib postAttribs0[] = {
+        LOCAL_EGL_NATIVE_BUFFER_PLANE_OFFSET_IMG, 0,
+        LOCAL_EGL_NONE
+    };
+    const EGLAttrib postAttribs1[] = {
+        LOCAL_EGL_NATIVE_BUFFER_PLANE_OFFSET_IMG, 1,
+        LOCAL_EGL_NONE
+    };
+    const EGLAttrib* const postAttribsList[2] = { postAttribs0, postAttribs1 };
+    // /layers/d3d11/CompositorD3D11.cpp uses bt601 for EffectTypes::NV12.
+    //return BlitAngleNv12(tex, YUVColorSpace::BT601, destSize, destOrigin);
+
+    const BindAnglePlanes bindPlanes(this, 2, texList, postAttribsList);
+
+    D3D11_TEXTURE2D_DESC texDesc = {0};
+    tex->GetDesc(&texDesc);
+
+    const gfx::IntSize ySize(texDesc.Width, texDesc.Height);
+    const gfx::IntSize divisors(2, 2);
+    MOZ_ASSERT(ySize.width % divisors.width == 0);
+    MOZ_ASSERT(ySize.height % divisors.height == 0);
+    const gfx::IntSize uvSize(ySize.width / divisors.width,
+                              ySize.height / divisors.height);
+
+    const bool yFlip = destOrigin != srcOrigin;
+    const DrawBlitProg::BaseArgs baseArgs = { destSize, yFlip, clipRect, ySize };
+    const DrawBlitProg::YUVArgs yuvArgs = { uvSize, divisors, colorSpace };
+
+    const auto& prog = GetDrawBlitProg(DrawBlitType::TexExtNV12);
+    MOZ_RELEASE_ASSERT(prog);
+    prog->Draw(baseArgs, &yuvArgs);
+    return true;
+}
+
+// --
+
+bool
+GLBlitHelper::BlitAngleYCbCr(const WindowsHandle (&handleList)[3],
+                             const gfx::IntRect& clipRect,
+                             const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
+                             const YUVColorSpace colorSpace,
+                             const gfx::IntSize& destSize, OriginPos destOrigin) const
+{
+    const auto& d3d = GetD3D11();
+    if (!d3d)
+        return false;
+
+    const auto srcOrigin = OriginPos::BottomLeft;
+
+    gfx::IntSize divisors;
+    if (!GuessDivisors(ySize, uvSize, &divisors))
+        return false;
+
+    const RefPtr<ID3D11Texture2D> texList[3] = {
+        OpenSharedTexture(d3d, handleList[0]),
+        OpenSharedTexture(d3d, handleList[1]),
+        OpenSharedTexture(d3d, handleList[2])
+    };
+    const BindAnglePlanes bindPlanes(this, 3, texList);
+
+    const bool yFlip = destOrigin != srcOrigin;
+    const DrawBlitProg::BaseArgs baseArgs = { destSize, yFlip, clipRect, ySize };
+    const DrawBlitProg::YUVArgs yuvArgs = { uvSize, divisors, colorSpace };
+
+    const auto& prog = GetDrawBlitProg(DrawBlitType::TexExtPlanarYUV);
+    MOZ_RELEASE_ASSERT(prog);
+    prog->Draw(baseArgs, &yuvArgs);
+    return true;
+}
+
+} // namespace gl
+} // namespace mozilla
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -1412,16 +1412,30 @@ public:
 
     realGLboolean fIsEnabled(GLenum capability) {
         BEFORE_GL_CALL;
         realGLboolean retval = mSymbols.fIsEnabled(capability);
         AFTER_GL_CALL;
         return retval;
     }
 
+    void SetEnabled(const GLenum cap, const bool val) {
+        if (val) {
+            fEnable(cap);
+        } else {
+            fDisable(cap);
+        }
+    }
+
+    bool PushEnabled(const GLenum cap, const bool newVal) {
+        const auto oldVal = fIsEnabled(cap);
+        SetEnabled(cap, newVal);
+        return oldVal;
+    }
+
     realGLboolean fIsProgram(GLuint program) {
         BEFORE_GL_CALL;
         realGLboolean retval = mSymbols.fIsProgram(program);
         AFTER_GL_CALL;
         return retval;
     }
 
     realGLboolean fIsShader(GLuint shader) {
--- a/gfx/gl/GLContextEGL.h
+++ b/gfx/gl/GLContextEGL.h
@@ -109,17 +109,17 @@ public:
                                      const SurfaceCaps& minCaps,
                                      nsACString* const out_FailureId);
 
 protected:
     friend class GLContextProviderEGL;
     friend class GLContextEGLFactory;
 
 public:
-    const EGLConfig  mConfig;
+    const EGLConfig mConfig;
 protected:
     EGLSurface mSurface;
 public:
     const EGLContext mContext;
 protected:
     EGLSurface mSurfaceOverride;
     RefPtr<gfxASurface> mThebesSurface;
     bool mBound;
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -603,16 +603,17 @@ GLLibraryEGL::EnsureInitialized(bool for
         if (!fnLoadSymbols(streamSymbols)) {
             NS_ERROR("EGL supports KHR_stream without exposing its functions!");
             MarkExtensionUnsupported(KHR_stream);
         }
     }
 
     if (IsExtensionSupported(KHR_stream_consumer_gltexture)) {
         const GLLibraryLoader::SymLoadStruct streamConsumerSymbols[] = {
+            SYMBOL(StreamConsumerGLTextureExternalKHR),
             SYMBOL(StreamConsumerAcquireKHR),
             SYMBOL(StreamConsumerReleaseKHR),
             END_OF_SYMBOLS
         };
         if (!fnLoadSymbols(streamConsumerSymbols)) {
             NS_ERROR("EGL supports KHR_stream_consumer_gltexture without exposing its functions!");
             MarkExtensionUnsupported(KHR_stream_consumer_gltexture);
         }
--- a/gfx/gl/GLLibraryEGL.h
+++ b/gfx/gl/GLLibraryEGL.h
@@ -293,16 +293,19 @@ public:
 
     EGLBoolean  fDestroyStreamKHR(EGLDisplay dpy, EGLStreamKHR stream) const
         WRAP(   fDestroyStreamKHR(dpy, stream) )
 
     EGLBoolean  fQueryStreamKHR(EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint* value) const
         WRAP(   fQueryStreamKHR(dpy, stream, attribute, value) )
 
     // KHR_stream_consumer_gltexture
+    EGLBoolean  fStreamConsumerGLTextureExternalKHR(EGLDisplay dpy, EGLStreamKHR stream) const
+        WRAP(   fStreamConsumerGLTextureExternalKHR(dpy, stream) )
+
     EGLBoolean  fStreamConsumerAcquireKHR(EGLDisplay dpy, EGLStreamKHR stream) const
         WRAP(   fStreamConsumerAcquireKHR(dpy, stream) )
 
     EGLBoolean  fStreamConsumerReleaseKHR(EGLDisplay dpy, EGLStreamKHR stream) const
         WRAP(   fStreamConsumerReleaseKHR(dpy, stream) )
 
     // EXT_device_query
     EGLBoolean  fQueryDisplayAttribEXT(EGLDisplay dpy, EGLint attribute, EGLAttrib* value) const
@@ -453,16 +456,18 @@ private:
         //KHR_stream
         EGLStreamKHR (GLAPIENTRY * fCreateStreamKHR)(EGLDisplay dpy, const EGLint* attrib_list);
         EGLBoolean (GLAPIENTRY * fDestroyStreamKHR)(EGLDisplay dpy, EGLStreamKHR stream);
         EGLBoolean (GLAPIENTRY * fQueryStreamKHR)(EGLDisplay dpy,
                                                   EGLStreamKHR stream,
                                                   EGLenum attribute,
                                                   EGLint* value);
         // KHR_stream_consumer_gltexture
+        EGLBoolean (GLAPIENTRY * fStreamConsumerGLTextureExternalKHR)(EGLDisplay dpy,
+                                                                      EGLStreamKHR stream);
         EGLBoolean (GLAPIENTRY * fStreamConsumerAcquireKHR)(EGLDisplay dpy,
                                                             EGLStreamKHR stream);
         EGLBoolean (GLAPIENTRY * fStreamConsumerReleaseKHR)(EGLDisplay dpy,
                                                             EGLStreamKHR stream);
         // EXT_device_query
         EGLBoolean (GLAPIENTRY * fQueryDisplayAttribEXT)(EGLDisplay dpy,
                                                          EGLint attribute,
                                                          EGLAttrib* value);
--- a/gfx/gl/HeapCopyOfStackArray.h
+++ b/gfx/gl/HeapCopyOfStackArray.h
@@ -18,17 +18,17 @@ namespace mozilla {
 // Useful to retain the convenience of declaring static arrays, while
 // avoiding passing stack pointers to the GL (see bug 1005658).
 
 template <typename ElemType>
 class HeapCopyOfStackArray
 {
 public:
   template<size_t N>
-  MOZ_IMPLICIT HeapCopyOfStackArray(ElemType (&array)[N])
+  MOZ_IMPLICIT HeapCopyOfStackArray(const ElemType (&array)[N])
     : mArrayLength(N)
     , mArrayData(MakeUnique<ElemType[]>(N))
   {
     memcpy(mArrayData.get(), &array[0], N * sizeof(ElemType));
   }
 
   ElemType* Data() const { return mArrayData.get(); }
   size_t ArrayLength() const { return mArrayLength; }
--- a/gfx/gl/ScopedGLHelpers.cpp
+++ b/gfx/gl/ScopedGLHelpers.cpp
@@ -418,85 +418,16 @@ ScopedVertexAttribPointer::UnwrapImpl()
     mGL->fVertexAttribPointer(mAttribIndex, mAttribSize, mAttribType, mAttribNormalized, mAttribStride, mAttribPointer);
     if (mAttribEnabled)
         mGL->fEnableVertexAttribArray(mAttribIndex);
     else
         mGL->fDisableVertexAttribArray(mAttribIndex);
     mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundBuffer);
 }
 
-ScopedGLDrawState::ScopedGLDrawState(GLContext* aGL)
-    : blend       (aGL, LOCAL_GL_BLEND,      false)
-    , cullFace    (aGL, LOCAL_GL_CULL_FACE,  false)
-    , depthTest   (aGL, LOCAL_GL_DEPTH_TEST, false)
-    , dither      (aGL, LOCAL_GL_DITHER,     false)
-    , polyOffsFill(aGL, LOCAL_GL_POLYGON_OFFSET_FILL,      false)
-    , sampleAToC  (aGL, LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, false)
-    , sampleCover (aGL, LOCAL_GL_SAMPLE_COVERAGE, false)
-    , scissor     (aGL, LOCAL_GL_SCISSOR_TEST,    false)
-    , stencil     (aGL, LOCAL_GL_STENCIL_TEST,    false)
-    , mGL(aGL)
-{
-    mGL->GetUIntegerv(LOCAL_GL_CURRENT_PROGRAM, &boundProgram);
-    mGL->GetUIntegerv(LOCAL_GL_ARRAY_BUFFER_BINDING, &boundBuffer);
-    mGL->GetUIntegerv(LOCAL_GL_MAX_VERTEX_ATTRIBS, &maxAttrib);
-    attrib_enabled = MakeUnique<GLint[]>(maxAttrib);
-
-    for (GLuint i = 0; i < maxAttrib; i++) {
-        mGL->fGetVertexAttribiv(i, LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED, &attrib_enabled[i]);
-        mGL->fDisableVertexAttribArray(i);
-    }
-    // Only Attrib0's client side state affected
-    mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE, &attrib0_size);
-    mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_STRIDE, &attrib0_stride);
-    mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE, &attrib0_type);
-    mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &attrib0_normalized);
-    mGL->fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &attrib0_bufferBinding);
-    mGL->fGetVertexAttribPointerv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER, &attrib0_pointer);
-    mGL->fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorMask);
-    mGL->fGetIntegerv(LOCAL_GL_VIEWPORT, viewport);
-    mGL->fGetIntegerv(LOCAL_GL_SCISSOR_BOX, scissorBox);
-}
-
-ScopedGLDrawState::~ScopedGLDrawState()
-{
-    MOZ_ASSERT(mGL->IsCurrent());
-
-    mGL->fScissor(scissorBox[0], scissorBox[1],
-                  scissorBox[2], scissorBox[3]);
-
-    mGL->fViewport(viewport[0], viewport[1],
-                   viewport[2], viewport[3]);
-
-    mGL->fColorMask(colorMask[0],
-                    colorMask[1],
-                    colorMask[2],
-                    colorMask[3]);
-
-    for (unsigned int i = 0; i < maxAttrib; i++) {
-        if (attrib_enabled[i])
-            mGL->fEnableVertexAttribArray(i);
-        else
-            mGL->fDisableVertexAttribArray(i);
-    }
-
-
-    mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0_bufferBinding);
-    mGL->fVertexAttribPointer(0,
-                              attrib0_size,
-                              attrib0_type,
-                              attrib0_normalized,
-                              attrib0_stride,
-                              attrib0_pointer);
-
-    mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, boundBuffer);
-
-    mGL->fUseProgram(boundProgram);
-}
-
 ////////////////////////////////////////////////////////////////////////
 // ScopedPackState
 
 ScopedPackState::ScopedPackState(GLContext* gl)
     : ScopedGLWrapper<ScopedPackState>(gl)
 {
     mGL->fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, &mAlignment);
 
--- a/gfx/gl/ScopedGLHelpers.h
+++ b/gfx/gl/ScopedGLHelpers.h
@@ -117,17 +117,17 @@ struct ScopedTexture
 {
     friend struct ScopedGLWrapper<ScopedTexture>;
 
 protected:
     GLuint mTexture;
 
 public:
     explicit ScopedTexture(GLContext* aGL);
-    GLuint Texture() { return mTexture; }
+    GLuint Texture() const { return mTexture; }
 
 protected:
     void UnwrapImpl();
 };
 
 
 struct ScopedFramebuffer
     : public ScopedGLWrapper<ScopedFramebuffer>
@@ -304,49 +304,16 @@ public:
                               GLsizei stride, GLuint buffer, const GLvoid* pointer);
     explicit ScopedVertexAttribPointer(GLContext* aGL, GLuint index);
 
 protected:
     void WrapImpl(GLuint index);
     void UnwrapImpl();
 };
 
-struct ScopedGLDrawState
-{
-    explicit ScopedGLDrawState(GLContext* gl);
-    ~ScopedGLDrawState();
-
-    GLuint boundProgram;
-    GLuint boundBuffer;
-
-    ScopedGLState blend;
-    ScopedGLState cullFace;
-    ScopedGLState depthTest;
-    ScopedGLState dither;
-    ScopedGLState polyOffsFill;
-    ScopedGLState sampleAToC;
-    ScopedGLState sampleCover;
-    ScopedGLState scissor;
-    ScopedGLState stencil;
-
-    GLuint maxAttrib;
-    UniquePtr<GLint[]> attrib_enabled;
-    GLint attrib0_size;
-    GLint attrib0_stride;
-    GLint attrib0_type;
-    GLint attrib0_normalized;
-    GLint attrib0_bufferBinding;
-    void* attrib0_pointer;
-
-    realGLboolean colorMask[4];
-    GLint viewport[4];
-    GLint scissorBox[4];
-    GLContext* const mGL;
-};
-
 struct ScopedPackState
     : public ScopedGLWrapper<ScopedPackState>
 {
     friend struct ScopedGLWrapper<ScopedPackState>;
 
 protected:
     GLint mAlignment;
 
--- a/gfx/gl/SharedSurface.cpp
+++ b/gfx/gl/SharedSurface.cpp
@@ -60,17 +60,19 @@ SharedSurface::ProdCopy(SharedSurface* s
             src->LockProd();
             srcNeedsUnlock = true;
         }
 
         if (dest->mAttachType == AttachmentType::GLTexture) {
             GLuint destTex = dest->ProdTexture();
             GLenum destTarget = dest->ProdTextureTarget();
 
-            gl->BlitHelper()->BlitFramebufferToTexture(0, destTex,
+            const ScopedBindFramebuffer bindFB(gl, 0);
+
+            gl->BlitHelper()->BlitFramebufferToTexture(destTex,
                                                        src->mSize,
                                                        dest->mSize,
                                                        destTarget,
                                                        true);
         } else if (dest->mAttachType == AttachmentType::GLRenderbuffer) {
             GLuint destRB = dest->ProdRenderbuffer();
             ScopedFramebufferForRenderbuffer destWrapper(gl, destRB);
 
@@ -105,17 +107,19 @@ SharedSurface::ProdCopy(SharedSurface* s
             dest->LockProd();
             destNeedsUnlock = true;
         }
 
         if (src->mAttachType == AttachmentType::GLTexture) {
             GLuint srcTex = src->ProdTexture();
             GLenum srcTarget = src->ProdTextureTarget();
 
-            gl->BlitHelper()->BlitTextureToFramebuffer(srcTex, 0,
+            const ScopedBindFramebuffer bindFB(gl, 0);
+
+            gl->BlitHelper()->BlitTextureToFramebuffer(srcTex,
                                                        src->mSize,
                                                        dest->mSize,
                                                        srcTarget,
                                                        !!gl->Screen());
         } else if (src->mAttachType == AttachmentType::GLRenderbuffer) {
             GLuint srcRB = src->ProdRenderbuffer();
             ScopedFramebufferForRenderbuffer srcWrapper(gl, srcRB);
 
@@ -153,36 +157,37 @@ SharedSurface::ProdCopy(SharedSurface* s
                                                    srcTarget, destTarget);
 
             return;
         }
 
         if (dest->mAttachType == AttachmentType::GLRenderbuffer) {
             GLuint destRB = dest->ProdRenderbuffer();
             ScopedFramebufferForRenderbuffer destWrapper(gl, destRB);
-
-            gl->BlitHelper()->BlitTextureToFramebuffer(srcTex, destWrapper.FB(),
-                                                       src->mSize, dest->mSize, srcTarget);
+            const ScopedBindFramebuffer bindFB(gl, destWrapper.FB());
+            gl->BlitHelper()->BlitTextureToFramebuffer(srcTex, src->mSize, dest->mSize,
+                                                       srcTarget);
 
             return;
         }
 
         MOZ_CRASH("GFX: Unhandled dest->mAttachType 3.");
     }
 
     if (src->mAttachType == AttachmentType::GLRenderbuffer) {
         GLuint srcRB = src->ProdRenderbuffer();
         ScopedFramebufferForRenderbuffer srcWrapper(gl, srcRB);
 
         if (dest->mAttachType == AttachmentType::GLTexture) {
             GLuint destTex = dest->ProdTexture();
             GLenum destTarget = dest->ProdTextureTarget();
+            const ScopedBindFramebuffer bindFB(gl, srcWrapper.FB());
 
-            gl->BlitHelper()->BlitFramebufferToTexture(srcWrapper.FB(), destTex,
-                                                       src->mSize, dest->mSize, destTarget);
+            gl->BlitHelper()->BlitFramebufferToTexture(destTex, src->mSize, dest->mSize,
+                                                       destTarget);
 
             return;
         }
 
         if (dest->mAttachType == AttachmentType::GLRenderbuffer) {
             GLuint destRB = dest->ProdRenderbuffer();
             ScopedFramebufferForRenderbuffer destWrapper(gl, destRB);
 
--- a/gfx/gl/SharedSurfaceD3D11Interop.cpp
+++ b/gfx/gl/SharedSurfaceD3D11Interop.cpp
@@ -466,18 +466,18 @@ SharedSurface_D3D11Interop::ProducerAcqu
 }
 
 void
 SharedSurface_D3D11Interop::ProducerReleaseImpl()
 {
     MOZ_ASSERT(mLockedForGL);
 
     if (mProdTex) {
-        mGL->BlitHelper()->DrawBlitTextureToFramebuffer(mProdTex, mInteropFB, mSize,
-                                                        mSize);
+        const ScopedBindFramebuffer bindFB(mGL, mInteropFB);
+        mGL->BlitHelper()->DrawBlitTextureToFramebuffer(mProdTex, mSize, mSize);
     }
 
     if (mNeedsFinish) {
         mGL->fFinish();
     } else {
         // We probably don't even need this.
         mGL->fFlush();
     }
--- a/gfx/gl/moz.build
+++ b/gfx/gl/moz.build
@@ -64,16 +64,17 @@ if CONFIG['MOZ_X11']:
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXPORTS += [
         'GLContextWGL.h',
         'SharedSurfaceANGLE.h', # Needs <windows.h> for `HANDLE`.
         'SharedSurfaceD3D11Interop.h',
         'WGLLibrary.h',
     ]
     UNIFIED_SOURCES += [
+        'GLBlitHelperD3D.cpp',
         'GLContextProviderWGL.cpp',
         'SharedSurfaceANGLE.cpp',
         'SharedSurfaceD3D11Interop.cpp',
     ]
 if CONFIG['MOZ_ENABLE_SKIA_GPU']:
     EXPORTS += ['SkiaGLGlue.h']
     SOURCES += [
         'SkiaGLGlue.cpp',
--- a/gfx/layers/D3D11YCbCrImage.cpp
+++ b/gfx/layers/D3D11YCbCrImage.cpp
@@ -92,32 +92,41 @@ D3D11YCbCrImage::SetData(KnowsCompositor
                          aData.mCbCrStride * aData.mCbCrSize.height);
   ctx->UpdateSubresource(textureCr,
                          0,
                          nullptr,
                          aData.mCrChannel,
                          aData.mCbCrStride,
                          aData.mCbCrStride * aData.mCbCrSize.height);
 
-  
+
   return true;
 }
 
 IntSize
 D3D11YCbCrImage::GetSize()
 {
   return mPictureRect.Size();
 }
 
 TextureClient*
 D3D11YCbCrImage::GetTextureClient(KnowsCompositor* aForwarder)
 {
   return mTextureClient;
 }
 
+const DXGIYCbCrTextureData*
+D3D11YCbCrImage::GetData() const
+{
+  if (!mTextureClient)
+    return nullptr;
+
+  return static_cast<DXGIYCbCrTextureData*>(mTextureClient->GetInternalData());
+}
+
 already_AddRefed<SourceSurface>
 D3D11YCbCrImage::GetAsSourceSurface()
 {
   if (!mTextureClient) {
     gfxWarning()
       << "GetAsSourceSurface() called on uninitialized D3D11YCbCrImage.";
     return nullptr;
   }
--- a/gfx/layers/D3D11YCbCrImage.h
+++ b/gfx/layers/D3D11YCbCrImage.h
@@ -7,20 +7,24 @@
 #define GFX_D3D11_YCBCR_IMAGE_H
 
 #include "d3d11.h"
 #include "mozilla/layers/TextureClientRecycleAllocator.h"
 #include "mozilla/Maybe.h"
 #include "ImageContainer.h"
 
 namespace mozilla {
+namespace gl {
+class GLBlitHelper;
+}
 namespace layers {
 
 class ImageContainer;
 class DXGIYCbCrTextureClient;
+class DXGIYCbCrTextureData;
 
 class D3D11YCbCrRecycleAllocator : public TextureClientRecycleAllocator
 {
 public:
   explicit D3D11YCbCrRecycleAllocator(KnowsCompositor* aAllocator,
                                       ID3D11Device* aDevice)
     : TextureClientRecycleAllocator(aAllocator)
     , mDevice(aDevice)
@@ -41,16 +45,17 @@ protected:
 
   RefPtr<ID3D11Device> mDevice;
   Maybe<gfx::IntSize> mYSize;
   Maybe<gfx::IntSize> mCbCrSize;
 };
 
 class D3D11YCbCrImage : public Image
 {
+  friend class gl::GLBlitHelper;
 public:
   D3D11YCbCrImage();
   virtual ~D3D11YCbCrImage();
 
   // Copies the surface into a sharable texture's surface, and initializes
   // the image.
   bool SetData(KnowsCompositor* aAllocator,
                ImageContainer* aContainer,
@@ -60,16 +65,19 @@ public:
 
   already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() override;
 
   TextureClient* GetTextureClient(KnowsCompositor* aForwarder) override;
 
   gfx::IntRect GetPictureRect() override { return mPictureRect; }
 
 private:
+  const DXGIYCbCrTextureData* GetData() const;
+
+private:
   gfx::IntSize mYSize;
   gfx::IntSize mCbCrSize;
   gfx::IntRect mPictureRect;
   YUVColorSpace mColorSpace;
   RefPtr<TextureClient> mTextureClient;
 };
 
 } // namepace layers
--- a/gfx/layers/GLImages.cpp
+++ b/gfx/layers/GLImages.cpp
@@ -73,22 +73,21 @@ GLImage::GetAsSourceSurface()
 
   ScopedFramebufferForTexture autoFBForTex(sSnapshotContext, scopedTex.Texture());
   if (!autoFBForTex.IsComplete()) {
       gfxCriticalError() << "GetAsSourceSurface: ScopedFramebufferForTexture failed.";
       return nullptr;
   }
 
   const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
-
-  if (!sSnapshotContext->BlitHelper()->BlitImageToFramebuffer(this, size,
-                                                              autoFBForTex.FB(),
-                                                              destOrigin))
   {
-    return nullptr;
+    const ScopedBindFramebuffer bindFB(sSnapshotContext, autoFBForTex.FB());
+    if (!sSnapshotContext->BlitHelper()->BlitImageToFramebuffer(this, size, destOrigin)) {
+      return nullptr;
+    }
   }
 
   RefPtr<gfx::DataSourceSurface> source =
         gfx::Factory::CreateDataSourceSurface(size, gfx::SurfaceFormat::B8G8R8A8);
   if (NS_WARN_IF(!source)) {
     return nullptr;
   }
 
--- a/gfx/layers/GPUVideoImage.h
+++ b/gfx/layers/GPUVideoImage.h
@@ -40,22 +40,29 @@ public:
                                     TextureFlags::RECYCLE,
                                     ImageBridgeChild::GetSingleton().get());
   }
 
   ~GPUVideoImage() override {}
 
   gfx::IntSize GetSize() override { return mSize; }
 
-  virtual already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() override
-  {
+  GPUVideoTextureData* GetData() const {
     if (!mTextureClient) {
       return nullptr;
     }
-    GPUVideoTextureData* data = mTextureClient->GetInternalData()->AsGPUVideoTextureData();
+    return mTextureClient->GetInternalData()->AsGPUVideoTextureData();
+  }
+
+  virtual already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() override
+  {
+    GPUVideoTextureData* data = GetData();
+    if (!data) {
+      return nullptr;
+    }
     return data->GetAsSourceSurface();
   }
 
   virtual TextureClient* GetTextureClient(KnowsCompositor* aForwarder) override
   {
     MOZ_ASSERT(aForwarder == ImageBridgeChild::GetSingleton(), "Must only use GPUVideo on ImageBridge");
     return mTextureClient;
   }
--- a/gfx/layers/client/GPUVideoTextureClient.h
+++ b/gfx/layers/client/GPUVideoTextureClient.h
@@ -43,14 +43,17 @@ public:
   {
     return this;
   }
 
 protected:
   RefPtr<dom::VideoDecoderManagerChild> mManager;
   SurfaceDescriptorGPUVideo mSD;
   gfx::IntSize mSize;
+
+public:
+  const SurfaceDescriptorGPUVideo& SD() const { return mSD; }
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // MOZILLA_GFX_GPUVIDEOTEXTURECLIENT_H
--- a/gfx/layers/d3d11/TextureD3D11.h
+++ b/gfx/layers/d3d11/TextureD3D11.h
@@ -13,16 +13,20 @@
 #include "mozilla/layers/TextureHost.h"
 #include "gfxWindowsPlatform.h"
 #include "mozilla/GfxMessageUtils.h"
 #include <d3d11.h>
 #include "d3d9.h"
 #include <vector>
 
 namespace mozilla {
+namespace gl {
+class GLBlitHelper;
+}
+
 namespace layers {
 
 class MOZ_RAII AutoTextureLock
 {
 public:
   AutoTextureLock(IDXGIKeyedMutex* aMutex, HRESULT& aResult,
                   uint32_t aTimeout = 0);
   ~AutoTextureLock();
@@ -123,16 +127,17 @@ protected:
 already_AddRefed<TextureClient>
 CreateD3D11extureClientWithDevice(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                                   TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags,
                                   ID3D11Device* aDevice,
                                   LayersIPCChannel* aAllocator);
 
 class DXGIYCbCrTextureData : public TextureData
 {
+  friend class gl::GLBlitHelper;
 public:
   static DXGIYCbCrTextureData*
   Create(IDirect3DTexture9* aTextureY,
          IDirect3DTexture9* aTextureCb,
          IDirect3DTexture9* aTextureCr,
          HANDLE aHandleY,
          HANDLE aHandleCb,
          HANDLE aHandleCr,
--- a/gfx/layers/ipc/LayersSurfaces.ipdlh
+++ b/gfx/layers/ipc/LayersSurfaces.ipdlh
@@ -84,17 +84,17 @@ struct SurfaceDescriptorSharedGLTexture 
 union GPUVideoSubDescriptor {
   SurfaceDescriptorD3D10;
   SurfaceDescriptorDXGIYCbCr;
   null_t;
 };
 
 struct SurfaceDescriptorGPUVideo {
   uint64_t handle;
-  GPUVideoSubDescriptor desc;
+  GPUVideoSubDescriptor subdesc;
 };
 
 struct RGBDescriptor {
   IntSize size;
   SurfaceFormat format;
   bool hasIntermediateBuffer;
 };
 
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -1106,57 +1106,39 @@ gfxUtils::EncodeSourceSurface(SourceSurf
                               const nsAString& aOutputOptions,
                               BinaryOrData aBinaryOrData,
                               FILE* aFile)
 {
   return EncodeSourceSurfaceInternal(aSurface, aMimeType, aOutputOptions,
                                      aBinaryOrData, aFile, nullptr);
 }
 
-/* From Rec601:
-[R]   [1.1643835616438356,  0.0,                 1.5960267857142858]      [ Y -  16]
-[G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708]    x [Cb - 128]
-[B]   [1.1643835616438356,  2.017232142857143,   8.862867620416422e-17]   [Cr - 128]
-
-For [0,1] instead of [0,255], and to 5 places:
-[R]   [1.16438,  0.00000,  1.59603]   [ Y - 0.06275]
-[G] = [1.16438, -0.39176, -0.81297] x [Cb - 0.50196]
-[B]   [1.16438,  2.01723,  0.00000]   [Cr - 0.50196]
-
-From Rec709:
-[R]   [1.1643835616438356,  4.2781193979771426e-17, 1.7927410714285714]     [ Y -  16]
-[G] = [1.1643835616438358, -0.21324861427372963,   -0.532909328559444]    x [Cb - 128]
-[B]   [1.1643835616438356,  2.1124017857142854,     0.0]                    [Cr - 128]
-
-For [0,1] instead of [0,255], and to 5 places:
-[R]   [1.16438,  0.00000,  1.79274]   [ Y - 0.06275]
-[G] = [1.16438, -0.21325, -0.53291] x [Cb - 0.50196]
-[B]   [1.16438,  2.11240,  0.00000]   [Cr - 0.50196]
-*/
-
-static const float kRec601[9] = {
-  1.16438f, 0.00000f, 1.59603f,
-  1.16438f,-0.39176f,-0.81297f,
-  1.16438f, 2.01723f, 0.00000f,
+// https://jdashg.github.io/misc/colors/from-coeffs.html
+const float kBT601NarrowYCbCrToRGB_RowMajor[16] = {
+  1.16438f, 0.00000f, 1.59603f,-0.87420f,
+  1.16438f,-0.39176f,-0.81297f, 0.53167f,
+  1.16438f, 2.01723f, 0.00000f,-1.08563f,
+  0.00000f, 0.00000f, 0.00000f, 1.00000f
 };
-static const float kRec709[9] = {
-  1.16438f, 0.00000f, 1.79274f,
-  1.16438f,-0.21325f,-0.53291f,
-  1.16438f, 2.11240f, 0.00000f,
+const float kBT709NarrowYCbCrToRGB_RowMajor[16] = {
+  1.16438f, 0.00000f, 1.79274f,-0.97295f,
+  1.16438f,-0.21325f,-0.53291f, 0.30148f,
+  1.16438f, 2.11240f, 0.00000f,-1.13340f,
+  0.00000f, 0.00000f, 0.00000f, 1.00000f
 };
 
 /* static */ const float*
 gfxUtils::YuvToRgbMatrix4x3RowMajor(YUVColorSpace aYUVColorSpace)
 {
-  #define X(x) { x[0], x[1], x[2], 0.0f, \
-                 x[3], x[4], x[5], 0.0f, \
-                 x[6], x[7], x[8], 0.0f }
+  #define X(x) { x[0], x[1], x[ 2], 0.0f, \
+                 x[4], x[5], x[ 6], 0.0f, \
+                 x[8], x[9], x[10], 0.0f }
 
-  static const float rec601[12] = X(kRec601);
-  static const float rec709[12] = X(kRec709);
+  static const float rec601[12] = X(kBT601NarrowYCbCrToRGB_RowMajor);
+  static const float rec709[12] = X(kBT709NarrowYCbCrToRGB_RowMajor);
 
   #undef X
 
   switch (aYUVColorSpace) {
   case YUVColorSpace::BT601:
     return rec601;
   case YUVColorSpace::BT709:
     return rec709;
@@ -1164,22 +1146,46 @@ gfxUtils::YuvToRgbMatrix4x3RowMajor(YUVC
     MOZ_ASSERT(false, "unknown aYUVColorSpace");
     return rec601;
   }
 }
 
 /* static */ const float*
 gfxUtils::YuvToRgbMatrix3x3ColumnMajor(YUVColorSpace aYUVColorSpace)
 {
-  #define X(x) { x[0], x[3], x[6], \
-                 x[1], x[4], x[7], \
-                 x[2], x[5], x[8] }
+  #define X(x) { x[0], x[4], x[ 8], \
+                 x[1], x[5], x[ 9], \
+                 x[2], x[6], x[10] }
+
+  static const float rec601[9] = X(kBT601NarrowYCbCrToRGB_RowMajor);
+  static const float rec709[9] = X(kBT709NarrowYCbCrToRGB_RowMajor);
+
+  #undef X
 
-  static const float rec601[9] = X(kRec601);
-  static const float rec709[9] = X(kRec709);
+  switch (aYUVColorSpace) {
+  case YUVColorSpace::BT601:
+    return rec601;
+  case YUVColorSpace::BT709:
+    return rec709;
+  default: // YUVColorSpace::UNKNOWN
+    MOZ_ASSERT(false, "unknown aYUVColorSpace");
+    return rec601;
+  }
+}
+
+/* static */ const float*
+gfxUtils::YuvToRgbMatrix4x4ColumnMajor(YUVColorSpace aYUVColorSpace)
+{
+  #define X(x) { x[0], x[4], x[ 8], x[12], \
+                 x[1], x[5], x[ 9], x[13], \
+                 x[2], x[6], x[10], x[14], \
+                 x[3], x[7], x[11], x[15] }
+
+  static const float rec601[16] = X(kBT601NarrowYCbCrToRGB_RowMajor);
+  static const float rec709[16] = X(kBT709NarrowYCbCrToRGB_RowMajor);
 
   #undef X
 
   switch (aYUVColorSpace) {
   case YUVColorSpace::BT601:
     return rec601;
   case YUVColorSpace::BT709:
     return rec709;
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -153,16 +153,17 @@ public:
 
     /**
      * Clears surface to aColor (which defaults to transparent black).
      */
     static void ClearThebesSurface(gfxASurface* aSurface);
 
     static const float* YuvToRgbMatrix4x3RowMajor(mozilla::YUVColorSpace aYUVColorSpace);
     static const float* YuvToRgbMatrix3x3ColumnMajor(mozilla::YUVColorSpace aYUVColorSpace);
+    static const float* YuvToRgbMatrix4x4ColumnMajor(mozilla::YUVColorSpace aYUVColorSpace);
 
     /**
      * Creates a copy of aSurface, but having the SurfaceFormat aFormat.
      *
      * This function always creates a new surface. Do not call it if aSurface's
      * format is the same as aFormat. Such a non-conversion would just be an
      * unnecessary and wasteful copy (this function asserts to prevent that).
      *