Use blitframebuffer to implement copySurface.

Author: bsalomon@google.com

Reviewed By: robertphillips@google.com

Review URL: https://chromiumcodereview.appspot.com/13910009

git-svn-id: http://skia.googlecode.com/svn/trunk@8633 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/mixedxfermodes.cpp b/gm/mixedxfermodes.cpp
index beb5d54..c0045f8 100644
--- a/gm/mixedxfermodes.cpp
+++ b/gm/mixedxfermodes.cpp
@@ -45,12 +45,6 @@
     }
 
     virtual void onDraw(SkCanvas* canvas) {
-        // FIXME: Currently necessary for GPU to be able to make dst-copy in SampleApp because
-        // main layer is not a texture.
-        SkPaint layerPaint;
-        layerPaint.setXfermodeMode(SkXfermode::kSrc_Mode);
-        canvas->saveLayer(NULL, &layerPaint);
-
         SkPaint bgPaint;
         bgPaint.setShader(fBG.get());
         canvas->drawPaint(bgPaint);
@@ -86,8 +80,6 @@
                          areaSqrt/50,
                          SkIntToScalar(size.fHeight / 2),
                          txtPaint);
-
-        canvas->restore();
     }
 
 private:
diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp
index d07d625..9036a57 100644
--- a/src/gpu/GrDrawTarget.cpp
+++ b/src/gpu/GrDrawTarget.cpp
@@ -411,12 +411,6 @@
         return true;
     }
     GrRenderTarget* rt = this->drawState()->getRenderTarget();
-    // If the dst is not a texture then we don't currently have a way of copying the
-    // texture. TODO: API-specific impl of onCopySurface that can handle more cases.
-    if (NULL == rt->asTexture()) {
-        GrPrintf("Reading Dst of non-texture render target is not currently supported.\n");
-        return false;
-    }
 
     const GrClipData* clip = this->getClip();
     GrIRect copyRect;
@@ -435,11 +429,8 @@
 #endif
     }
 
-    // The draw will resolve dst if it has MSAA. Two things to consider in the future:
-    // 1) to make the dst values be pre-resolve we'd need to be able to copy to MSAA
-    // texture and sample it correctly in the shader. 2) If 1 isn't available then we
-    // should just resolve and use the resolved texture directly rather than making a
-    // copy of it.
+    // 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;
     desc.fWidth = copyRect.width();
diff --git a/src/gpu/gl/GrGLIRect.h b/src/gpu/gl/GrGLIRect.h
index cbc4cb8..f171cf1 100644
--- a/src/gpu/gl/GrGLIRect.h
+++ b/src/gpu/gl/GrGLIRect.h
@@ -36,7 +36,7 @@
         GR_GL_GetIntegerv(gl, GR_GL_VIEWPORT, (GrGLint*) this);
     }
 
-    // sometimes we have a GrIRect from the client that we
+    // sometimes we have a SkIRect from the client that we
     // want to simultaneously make relative to GL's viewport
     // and (optionally) convert from top-down to bottom-up.
     void setRelativeTo(const GrGLIRect& glRect,
diff --git a/src/gpu/gl/GrGpuGL.cpp b/src/gpu/gl/GrGpuGL.cpp
index d299656..8719377 100644
--- a/src/gpu/gl/GrGpuGL.cpp
+++ b/src/gpu/gl/GrGpuGL.cpp
@@ -2227,6 +2227,157 @@
     }
 }
 
+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());
+}
+}
+
+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 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)) {
+        SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
+                                            srcRect.width(), srcRect.height());
+        bool selfOverlap = false;
+        if (dst->isSameAs(src)) {
+            selfOverlap = SkIRect::IntersectsNoEmptyCheck(dstRect, srcRect);
+        }
+
+        if (!selfOverlap) {
+            GrGLuint dstFBO = 0;
+            GrGLuint srcFBO = 0;
+            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
+            fHWBoundRenderTarget = NULL;
+            GrGLIRect srcGLRect;
+            GrGLIRect dstGLRect;
+            srcGLRect.setRelativeTo(srcVP,
+                                    srcRect.fLeft,
+                                    srcRect.fTop,
+                                    srcRect.width(),
+                                    srcRect.height(),
+                                    src->origin());
+            dstGLRect.setRelativeTo(dstVP,
+                                    dstRect.fLeft,
+                                    dstRect.fTop,
+                                    dstRect.width(),
+                                    dstRect.height(),
+                                    dst->origin());
+
+            GrAutoTRestore<ScissorState> asr;
+            if (GrGLCaps::kDesktopEXT_MSFBOType == this->glCaps().msFBOType()) {
+                // The EXT version applies the scissor during the blit, so disable it.
+                asr.reset(&fScissorState);
+                fScissorState.fEnabled = false;
+                this->flushScissor();
+            }
+            GrGLint srcY0;
+            GrGLint srcY1;
+            // Does the blit need to y-mirror or not?
+            if (src->origin() == dst->origin()) {
+                srcY0 = srcGLRect.fBottom;
+                srcY1 = srcGLRect.fBottom + srcGLRect.fHeight;
+            } else {
+                srcY0 = srcGLRect.fBottom + srcGLRect.fHeight;
+                srcY1 = srcGLRect.fBottom;
+            }
+            GL_CALL(BlitFramebuffer(srcGLRect.fLeft,
+                                    srcY0,
+                                    srcGLRect.fLeft + srcGLRect.fWidth,
+                                    srcY1,
+                                    dstGLRect.fLeft,
+                                    dstGLRect.fBottom,
+                                    dstGLRect.fLeft + dstGLRect.fWidth,
+                                    dstGLRect.fBottom + dstGLRect.fHeight,
+                                    GR_GL_COLOR_BUFFER_BIT, GR_GL_NEAREST));
+            if (dstFBO) {
+                GL_CALL(DeleteFramebuffers(1, &dstFBO));
+            }
+            if (srcFBO) {
+                GL_CALL(DeleteFramebuffers(1, &srcFBO));
+            }
+            copied = true;
+        }
+    }
+    if (!copied) {
+        copied = INHERITED::onCopySurface(dst, src, srcRect, dstPoint);
+    }
+    return copied;
+}
+
+bool GrGpuGL::onCanCopySurface(GrSurface* dst,
+                               GrSurface* src,
+                               const SkIRect& srcRect,
+                               const SkIPoint& dstPoint) {
+    // This mirrors the logic in onCopySurface.
+    bool canBlitFramebuffer = false;
+    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);
+        } else {
+            canBlitFramebuffer = true;
+        }
+    }
+    if (canBlitFramebuffer) {
+        return true;
+    } else {
+        return INHERITED::onCanCopySurface(dst, src, srcRect, dstPoint);
+    }
+}
+
+
 ///////////////////////////////////////////////////////////////////////////////
 
 GrGLAttribArrayState* GrGpuGL::HWGeometryState::bindArrayAndBuffersToDraw(
diff --git a/src/gpu/gl/GrGpuGL.h b/src/gpu/gl/GrGpuGL.h
index a71ff39..4ea4e12 100644
--- a/src/gpu/gl/GrGpuGL.h
+++ b/src/gpu/gl/GrGpuGL.h
@@ -85,6 +85,18 @@
     void notifyTextureDelete(GrGLTexture* texture);
     void notifyRenderTargetDelete(GrRenderTarget* renderTarget);
 
+protected:
+    virtual bool onCopySurface(GrSurface* dst,
+                               GrSurface* src,
+                               const SkIRect& srcRect,
+                               const SkIPoint& dstPoint) SK_OVERRIDE;
+
+    virtual bool onCanCopySurface(GrSurface* dst,
+                                  GrSurface* src,
+                                  const SkIRect& srcRect,
+                                  const SkIPoint& dstPoint) SK_OVERRIDE;
+
+
 private:
     // GrGpu overrides
     virtual void onResetContext() SK_OVERRIDE;