Add support for using glCopyTexSubImage2D when possible to copy surfaces.
Review URL: https://codereview.chromium.org/13915011

git-svn-id: http://skia.googlecode.com/svn/trunk@8675 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp
index 9036a57..e875f0f 100644
--- a/src/gpu/GrDrawTarget.cpp
+++ b/src/gpu/GrDrawTarget.cpp
@@ -432,11 +432,9 @@
     // MSAA consideration: When there is support for reading MSAA samples in the shader we could
     // have per-sample dst values by making the copy multisampled.
     GrTextureDesc desc;
-    desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+    this->initCopySurfaceDstDesc(rt, &desc);
     desc.fWidth = copyRect.width();
     desc.fHeight = copyRect.height();
-    desc.fSampleCnt = 0;
-    desc.fConfig = rt->config();
 
     GrAutoScratchTexture ast(fContext, desc, GrContext::kApprox_ScratchTexMatch);
 
@@ -447,7 +445,7 @@
     SkIPoint dstPoint = {0, 0};
     if (this->copySurface(ast.texture(), rt, copyRect, dstPoint)) {
         info->fDstCopy.setTexture(ast.texture());
-        info->fDstCopy.setOffset(copyRect.fLeft, copyRect.fTop);
+        info->fDstCopy.setOffset(copyRect.fLeft, copyRect.fTop);    
         return true;
     } else {
         return false;
@@ -881,6 +879,13 @@
     return true;
 }
 
+void GrDrawTarget::initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) {
+    // Make the dst of the copy be a render target because the default copySurface draws to the dst.
+    desc->fOrigin = kDefault_GrSurfaceOrigin;
+    desc->fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+    desc->fConfig = src->config();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 SK_DEFINE_INST_COUNT(GrDrawTargetCaps)
diff --git a/src/gpu/GrDrawTarget.h b/src/gpu/GrDrawTarget.h
index 13abee9..8f5f09f 100644
--- a/src/gpu/GrDrawTarget.h
+++ b/src/gpu/GrDrawTarget.h
@@ -432,6 +432,14 @@
                         const SkIPoint& dstPoint);
 
     /**
+     * This is can be called before allocating a texture to be a dst for copySurface. It will
+     * populate the origin, config, and flags fields of the desc such that copySurface is more
+     * likely to succeed and be efficient.
+     */
+    virtual void initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc);
+
+
+    /**
      * Release any resources that are cached but not currently in use. This
      * is intended to give an application some recourse when resources are low.
      */
diff --git a/src/gpu/GrInOrderDrawBuffer.cpp b/src/gpu/GrInOrderDrawBuffer.cpp
index 481a395..7bd3f5d 100644
--- a/src/gpu/GrInOrderDrawBuffer.cpp
+++ b/src/gpu/GrInOrderDrawBuffer.cpp
@@ -540,6 +540,10 @@
     return fDstGpu->canCopySurface(dst, src, srcRect, dstPoint);
 }
 
+void GrInOrderDrawBuffer::initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) {
+    fDstGpu->initCopySurfaceDstDesc(src, desc);
+}
+
 void GrInOrderDrawBuffer::willReserveVertexAndIndexSpace(
                                 int vertexCount,
                                 int indexCount) {
diff --git a/src/gpu/GrInOrderDrawBuffer.h b/src/gpu/GrInOrderDrawBuffer.h
index c365501..6090235 100644
--- a/src/gpu/GrInOrderDrawBuffer.h
+++ b/src/gpu/GrInOrderDrawBuffer.h
@@ -77,6 +77,9 @@
                        GrColor color,
                        GrRenderTarget* renderTarget = NULL) SK_OVERRIDE;
 
+    virtual void initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) SK_OVERRIDE;
+
+
 protected:
     virtual void clipWillBeSet(const GrClipData* newClip) SK_OVERRIDE;
 
diff --git a/src/gpu/gl/GrGpuGL.cpp b/src/gpu/gl/GrGpuGL.cpp
index 8719377..0fba75d 100644
--- a/src/gpu/gl/GrGpuGL.cpp
+++ b/src/gpu/gl/GrGpuGL.cpp
@@ -2229,23 +2229,142 @@
 
 namespace {
 // Determines whether glBlitFramebuffer could be used between src and dst.
-inline bool can_blit_framebuffer(const GrSurface* dst, const GrSurface* src, const GrGpuGL* gpu) {
-    return gpu->isConfigRenderable(dst->config()) && gpu->isConfigRenderable(src->config()) &&
-           (GrGLCaps::kDesktopEXT_MSFBOType == gpu->glCaps().msFBOType() ||
-            GrGLCaps::kDesktopARB_MSFBOType == gpu->glCaps().msFBOType());
+inline bool can_blit_framebuffer(const GrSurface* dst,
+                                 const GrSurface* src,
+                                 const GrGpuGL* gpu,
+                                 bool* wouldNeedTempFBO = NULL) {
+    if (gpu->isConfigRenderable(dst->config()) && gpu->isConfigRenderable(src->config()) &&
+        (GrGLCaps::kDesktopEXT_MSFBOType == gpu->glCaps().msFBOType() ||
+         GrGLCaps::kDesktopARB_MSFBOType == gpu->glCaps().msFBOType())) {
+        if (NULL != wouldNeedTempFBO) {
+            *wouldNeedTempFBO = NULL == dst->asRenderTarget() || NULL == src->asRenderTarget();
+        }
+        return true;
+    } else {
+        return false;
+    }
 }
+
+inline bool can_copy_texsubimage(const GrSurface* dst,
+                                 const GrSurface* src,
+                                 const GrGpuGL* gpu,
+                                 bool* wouldNeedTempFBO = NULL) {
+    // Table 3.9 of the ES2 spec indicates the supported formats with CopyTexSubImage
+    // and BGRA isn't in the spec. There doesn't appear to be any extension that adds it. Perhaps
+    // many drivers would allow it to work, but ANGLE does not.
+    if (kES2_GrGLBinding == gpu->glBinding() && gpu->glCaps().bgraIsInternalFormat() &&
+        (kBGRA_8888_GrPixelConfig == dst->config() || kBGRA_8888_GrPixelConfig == src->config())) {
+        return false;
+    }
+    const GrGLRenderTarget* dstRT = static_cast<const GrGLRenderTarget*>(dst->asRenderTarget());
+    // If dst is multisampled (and uses an extension where there is a separate MSAA renderbuffer)
+    // then we don't want to copy to the texture but to the MSAA buffer.
+    if (NULL != dstRT && dstRT->renderFBOID() != dstRT->textureFBOID()) {
+        return false;
+    }
+    if (gpu->isConfigRenderable(src->config()) && NULL != dst->asTexture() &&
+        dst->origin() == src->origin() && kIndex_8_GrPixelConfig != src->config()) {
+        if (NULL != wouldNeedTempFBO) {
+            *wouldNeedTempFBO = NULL == src->asRenderTarget();
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+// If a temporary FBO was created, its non-zero ID is returned. The viewport that the copy rect is
+// relative to is output.
+inline GrGLuint bind_surface_as_fbo(const GrGLInterface* gl,
+                                    GrSurface* surface,
+                                    GrGLenum fboTarget,
+                                    GrGLIRect* viewport) {
+    GrGLRenderTarget* rt = static_cast<GrGLRenderTarget*>(surface->asRenderTarget());
+    GrGLuint tempFBOID;
+    if (NULL == rt) {
+        GrAssert(NULL != surface->asTexture());
+        GrGLuint texID = static_cast<GrGLTexture*>(surface->asTexture())->textureID();
+        GR_GL_CALL(gl, GenFramebuffers(1, &tempFBOID));
+        GR_GL_CALL(gl, BindFramebuffer(fboTarget, tempFBOID));
+        GR_GL_CALL(gl, FramebufferTexture2D(fboTarget,
+                                            GR_GL_COLOR_ATTACHMENT0,
+                                            GR_GL_TEXTURE_2D,
+                                            texID,
+                                            0));
+        viewport->fLeft = 0;
+        viewport->fBottom = 0;
+        viewport->fWidth = surface->width();
+        viewport->fHeight = surface->height();
+    } else {
+        tempFBOID = 0;
+        GR_GL_CALL(gl, BindFramebuffer(fboTarget, rt->renderFBOID()));
+        *viewport = rt->getViewport();
+    }
+    return tempFBOID;
+}
+
+}
+
+void GrGpuGL::initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) {
+    // Check for format issues with glCopyTexSubImage2D
+    if (kES2_GrGLBinding == this->glBinding() && this->glCaps().bgraIsInternalFormat() &&
+        kBGRA_8888_GrPixelConfig == src->config()) {
+        // glCopyTexSubImage2D doesn't work with this config. We'll want to make it a render target
+        // to in order to call glBlitFramebuffer or to copy to it by rendering.
+        INHERITED::initCopySurfaceDstDesc(src, desc);
+    } else if (NULL == src->asRenderTarget()) {
+        // We don't want to have to create an FBO just to use glCopyTexSubImage2D. Let the base
+        // class handle it by rendering.
+        INHERITED::initCopySurfaceDstDesc(src, desc);
+    } else {
+        desc->fConfig = src->config();
+        desc->fOrigin = src->origin();
+        desc->fFlags = kNone_GrTextureFlags;
+    }
 }
 
 bool GrGpuGL::onCopySurface(GrSurface* dst,
                             GrSurface* src,
                             const SkIRect& srcRect,
                             const SkIPoint& dstPoint) {
-    // TODO: Add support for glCopyTexSubImage for cases when src is an FBO and dst is not
-    // renderable or we don't have glBlitFramebuffer.
+    bool inheritedCouldCopy = INHERITED::onCanCopySurface(dst, src, srcRect, dstPoint);
     bool copied = false;
-    // Check whether both src and dst could be attached to an FBO and we're on a GL that supports
-    // glBlitFramebuffer.
-    if (can_blit_framebuffer(dst, src, this)) {
+    bool wouldNeedTempFBO = false;
+    if (can_copy_texsubimage(dst, src, this, &wouldNeedTempFBO) &&
+        (!wouldNeedTempFBO || !inheritedCouldCopy)) {
+        GrGLuint srcFBO;
+        GrGLIRect srcVP;
+        srcFBO = bind_surface_as_fbo(this->glInterface(), src, GR_GL_FRAMEBUFFER, &srcVP);
+        GrGLTexture* dstTex = static_cast<GrGLTexture*>(dst->asTexture());
+        GrAssert(NULL != dstTex);
+        // We modified the bound FBO
+        fHWBoundRenderTarget = NULL;
+        GrGLIRect srcGLRect;
+        srcGLRect.setRelativeTo(srcVP,
+                                srcRect.fLeft,
+                                srcRect.fTop,
+                                srcRect.width(),
+                                srcRect.height(),
+                                src->origin());
+
+        this->setSpareTextureUnit();
+        GL_CALL(BindTexture(GR_GL_TEXTURE_2D, dstTex->textureID()));
+        GrGLint dstY;
+        if (kBottomLeft_GrSurfaceOrigin == dst->origin()) {
+            dstY = dst->height() - (dstPoint.fY + srcGLRect.fHeight);
+        } else {
+            dstY = dstPoint.fY;
+        }
+        GL_CALL(CopyTexSubImage2D(GR_GL_TEXTURE_2D, 0,
+                                  dstPoint.fX, dstY,
+                                  srcGLRect.fLeft, srcGLRect.fBottom,
+                                  srcGLRect.fWidth, srcGLRect.fHeight));
+        copied = true;
+        if (srcFBO) {
+            GL_CALL(DeleteFramebuffers(1, &srcFBO));
+        }
+    } else if (can_blit_framebuffer(dst, src, this, &wouldNeedTempFBO) &&
+               (!wouldNeedTempFBO || !inheritedCouldCopy)) {
         SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
                                             srcRect.width(), srcRect.height());
         bool selfOverlap = false;
@@ -2254,50 +2373,13 @@
         }
 
         if (!selfOverlap) {
-            GrGLuint dstFBO = 0;
-            GrGLuint srcFBO = 0;
+            GrGLuint dstFBO;
+            GrGLuint srcFBO;
             GrGLIRect dstVP;
             GrGLIRect srcVP;
-            GrGLRenderTarget* dstRT = static_cast<GrGLRenderTarget*>(dst->asRenderTarget());
-            GrGLRenderTarget* srcRT = static_cast<GrGLRenderTarget*>(src->asRenderTarget());
-            if (NULL == dstRT) {
-                GrAssert(NULL != dst->asTexture());
-                GrGLuint texID = static_cast<GrGLTexture*>(dst->asTexture())->textureID();
-                GL_CALL(GenFramebuffers(1, &dstFBO));
-                GL_CALL(BindFramebuffer(GR_GL_DRAW_FRAMEBUFFER, dstFBO));
-                GL_CALL(FramebufferTexture2D(GR_GL_DRAW_FRAMEBUFFER,
-                                             GR_GL_COLOR_ATTACHMENT0,
-                                             GR_GL_TEXTURE_2D,
-                                             texID,
-                                             0));
-                dstVP.fLeft = 0;
-                dstVP.fBottom = 0;
-                dstVP.fWidth = dst->width();
-                dstVP.fHeight = dst->height();
-            } else {
-                GL_CALL(BindFramebuffer(GR_GL_DRAW_FRAMEBUFFER, dstRT->renderFBOID()));
-                dstVP = dstRT->getViewport();
-            }
-            if (NULL == srcRT) {
-                GrAssert(NULL != src->asTexture());
-                GrGLuint texID = static_cast<GrGLTexture*>(src->asTexture())->textureID();
-                GL_CALL(GenFramebuffers(1, &srcFBO));
-                GL_CALL(BindFramebuffer(GR_GL_READ_FRAMEBUFFER, srcFBO));
-                GL_CALL(FramebufferTexture2D(GR_GL_READ_FRAMEBUFFER,
-                                             GR_GL_COLOR_ATTACHMENT0,
-                                             GR_GL_TEXTURE_2D,
-                                             texID,
-                                             0));
-                srcVP.fLeft = 0;
-                srcVP.fBottom = 0;
-                srcVP.fWidth = src->width();
-                srcVP.fHeight = src->height();
-            } else {
-                GL_CALL(BindFramebuffer(GR_GL_READ_FRAMEBUFFER, srcRT->renderFBOID()));
-                srcVP = srcRT->getViewport();
-            }
-
-            // We modified the bound FB
+            dstFBO = bind_surface_as_fbo(this->glInterface(), dst, GR_GL_DRAW_FRAMEBUFFER, &dstVP);
+            srcFBO = bind_surface_as_fbo(this->glInterface(), src, GR_GL_READ_FRAMEBUFFER, &srcVP);
+            // We modified the bound FBO
             fHWBoundRenderTarget = NULL;
             GrGLIRect srcGLRect;
             GrGLIRect dstGLRect;
@@ -2349,8 +2431,9 @@
             copied = true;
         }
     }
-    if (!copied) {
+    if (!copied && inheritedCouldCopy) {
         copied = INHERITED::onCopySurface(dst, src, srcRect, dstPoint);
+        GrAssert(copied);
     }
     return copied;
 }
@@ -2360,21 +2443,21 @@
                                const SkIRect& srcRect,
                                const SkIPoint& dstPoint) {
     // This mirrors the logic in onCopySurface.
-    bool canBlitFramebuffer = false;
+    if (can_copy_texsubimage(dst, src, this)) {
+        return true;
+    }
     if (can_blit_framebuffer(dst, src, this)) {
-        SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
-                                            srcRect.width(), srcRect.height());
         if (dst->isSameAs(src)) {
-            canBlitFramebuffer = !SkIRect::IntersectsNoEmptyCheck(dstRect, srcRect);
+            SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
+                                                srcRect.width(), srcRect.height());
+            if(!SkIRect::IntersectsNoEmptyCheck(dstRect, srcRect)) {
+                return true;
+            }
         } else {
-            canBlitFramebuffer = true;
+            return true;
         }
     }
-    if (canBlitFramebuffer) {
-        return true;
-    } else {
-        return INHERITED::onCanCopySurface(dst, src, srcRect, dstPoint);
-    }
+    return INHERITED::onCanCopySurface(dst, src, srcRect, dstPoint);
 }
 
 
diff --git a/src/gpu/gl/GrGpuGL.h b/src/gpu/gl/GrGpuGL.h
index 4ea4e12..0f75280 100644
--- a/src/gpu/gl/GrGpuGL.h
+++ b/src/gpu/gl/GrGpuGL.h
@@ -55,6 +55,8 @@
                                     size_t rowBytes) const SK_OVERRIDE;
     virtual bool fullReadPixelsIsFasterThanPartial() const SK_OVERRIDE;
 
+    virtual void initCopySurfaceDstDesc(const GrSurface* src, GrTextureDesc* desc) SK_OVERRIDE;
+
     virtual void abandonResources() SK_OVERRIDE;
 
     const GrGLCaps& glCaps() const { return *fGLContext.info().caps(); }
@@ -96,7 +98,6 @@
                                   const SkIRect& srcRect,
                                   const SkIPoint& dstPoint) SK_OVERRIDE;
 
-
 private:
     // GrGpu overrides
     virtual void onResetContext() SK_OVERRIDE;