Improve tracking of bound FBOs in GrGLGpu.

Review URL: https://codereview.chromium.org/949263002
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index a952c4f..c319612 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -151,9 +151,6 @@
 
     fLastSuccessfulStencilFmtIdx = 0;
     fHWProgramID = 0;
-    fTempSrcFBOID = 0;
-    fTempDstFBOID = 0;
-    fStencilClearFBOID = 0;
 
     if (this->glCaps().pathRenderingSupport()) {
         fPathRendering.reset(new GrGLPathRendering(this));
@@ -168,14 +165,17 @@
         GL_CALL(UseProgram(0));
     }
 
-    if (0 != fTempSrcFBOID) {
-        GL_CALL(DeleteFramebuffers(1, &fTempSrcFBOID));
+    if (fTempSrcFBO) {
+        fTempSrcFBO->release(this->glInterface());
+        fTempSrcFBO.reset(NULL);
     }
-    if (0 != fTempDstFBOID) {
-        GL_CALL(DeleteFramebuffers(1, &fTempDstFBOID));
+    if (fTempDstFBO) {
+        fTempDstFBO->release(this->glInterface());
+        fTempDstFBO.reset(NULL);
     }
-    if (0 != fStencilClearFBOID) {
-        GL_CALL(DeleteFramebuffers(1, &fStencilClearFBOID));
+    if (fStencilClearFBO) {
+        fStencilClearFBO->release(this->glInterface());
+        fStencilClearFBO.reset(NULL);
     }
 
     delete fProgramCache;
@@ -185,9 +185,19 @@
     INHERITED::contextAbandoned();
     fProgramCache->abandon();
     fHWProgramID = 0;
-    fTempSrcFBOID = 0;
-    fTempDstFBOID = 0;
-    fStencilClearFBOID = 0;
+    if (fTempSrcFBO) {
+        fTempSrcFBO->abandon();
+        fTempSrcFBO.reset(NULL);
+    }
+    if (fTempDstFBO) {
+        fTempDstFBO->abandon();
+        fTempDstFBO.reset(NULL);
+    }
+    if (fStencilClearFBO) {
+        fStencilClearFBO->abandon();
+        fStencilClearFBO.reset(NULL);
+    }
+
     if (this->glCaps().pathRenderingSupport()) {
         this->glPathRendering()->abandonGpuResources();
     }
@@ -331,7 +341,9 @@
     }
 
     if (resetBits & kRenderTarget_GrGLBackendState) {
-        fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID;
+        for (size_t i = 0; i < SK_ARRAY_COUNT(fHWFBOBinding); ++i) {
+            fHWFBOBinding[i].invalidate();
+        }
     }
 
     if (resetBits & kPathRendering_GrGLBackendState) {
@@ -432,9 +444,9 @@
 
 GrRenderTarget* GrGLGpu::onWrapBackendRenderTarget(const GrBackendRenderTargetDesc& wrapDesc) {
     GrGLRenderTarget::IDDesc idDesc;
-    idDesc.fRTFBOID = static_cast<GrGLuint>(wrapDesc.fRenderTargetHandle);
+    GrGLuint fboID = static_cast<GrGLuint>(wrapDesc.fRenderTargetHandle);
+    idDesc.fRenderFBO.reset(SkNEW_ARGS(GrGLFBO, (fboID)));
     idDesc.fMSColorRenderbufferID = 0;
-    idDesc.fTexFBOID = GrGLRenderTarget::kUnresolvableFBOID;
     idDesc.fLifeCycle = GrGpuResource::kWrapped_LifeCycle;
 
     GrSurfaceDesc desc;
@@ -814,8 +826,6 @@
 bool GrGLGpu::createRenderTargetObjects(const GrSurfaceDesc& desc, bool budgeted, GrGLuint texID,
                                         GrGLRenderTarget::IDDesc* idDesc) {
     idDesc->fMSColorRenderbufferID = 0;
-    idDesc->fRTFBOID = 0;
-    idDesc->fTexFBOID = 0;
     idDesc->fLifeCycle = budgeted ? GrGpuResource::kCached_LifeCycle :
                                     GrGpuResource::kUncached_LifeCycle;
 
@@ -827,21 +837,22 @@
         goto FAILED;
     }
 
-    GL_CALL(GenFramebuffers(1, &idDesc->fTexFBOID));
-    if (!idDesc->fTexFBOID) {
+    idDesc->fTextureFBO.reset(SkNEW_ARGS(GrGLFBO, (this->glInterface())));
+    if (!idDesc->fTextureFBO->isValid()) {
         goto FAILED;
     }
 
-
     // If we are using multisampling we will create two FBOS. We render to one and then resolve to
     // the texture bound to the other. The exception is the IMG multisample extension. With this
     // extension the texture is multisampled when rendered to and then auto-resolves it when it is
     // rendered from.
     if (desc.fSampleCnt > 0 && this->glCaps().usesMSAARenderBuffers()) {
-        GL_CALL(GenFramebuffers(1, &idDesc->fRTFBOID));
+        idDesc->fRenderFBO.reset(SkNEW_ARGS(GrGLFBO, (this->glInterface())));
+        if (!idDesc->fRenderFBO->isValid()) {
+            goto FAILED;
+        }
         GL_CALL(GenRenderbuffers(1, &idDesc->fMSColorRenderbufferID));
-        if (!idDesc->fRTFBOID ||
-            !idDesc->fMSColorRenderbufferID ||
+        if (!idDesc->fMSColorRenderbufferID ||
             !this->configToGLFormats(desc.fConfig,
                                      // ES2 and ES3 require sized internal formats for rb storage.
                                      kGLES_GrGLStandard == this->glStandard(),
@@ -851,12 +862,10 @@
             goto FAILED;
         }
     } else {
-        idDesc->fRTFBOID = idDesc->fTexFBOID;
+        idDesc->fRenderFBO.reset(SkRef(idDesc->fTextureFBO.get()));
     }
 
-    // below here we may bind the FBO
-    fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID;
-    if (idDesc->fRTFBOID != idDesc->fTexFBOID) {
+    if (idDesc->fRenderFBO != idDesc->fTextureFBO) {
         SkASSERT(desc.fSampleCnt > 0);
         GL_CALL(BindRenderbuffer(GR_GL_RENDERBUFFER, idDesc->fMSColorRenderbufferID));
         if (!renderbuffer_storage_msaa(fGLContext,
@@ -865,12 +874,11 @@
                                        desc.fWidth, desc.fHeight)) {
             goto FAILED;
         }
-        fStats.incRenderTargetBinds();
-        GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, idDesc->fRTFBOID));
+        this->bindFBO(kDraw_FBOBinding, idDesc->fRenderFBO);
         GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
-                                      GR_GL_COLOR_ATTACHMENT0,
-                                      GR_GL_RENDERBUFFER,
-                                      idDesc->fMSColorRenderbufferID));
+                                        GR_GL_COLOR_ATTACHMENT0,
+                                        GR_GL_RENDERBUFFER,
+                                        idDesc->fMSColorRenderbufferID));
         if ((desc.fFlags & kCheckAllocation_GrSurfaceFlag) ||
             !this->glCaps().isConfigVerifiedColorAttachment(desc.fConfig)) {
             GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
@@ -880,8 +888,7 @@
             fGLContext.caps()->markConfigAsValidColorAttachment(desc.fConfig);
         }
     }
-    fStats.incRenderTargetBinds();
-    GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, idDesc->fTexFBOID));
+    this->bindFBO(kDraw_FBOBinding, idDesc->fTextureFBO);
 
     if (this->glCaps().usesImplicitMSAAResolve() && desc.fSampleCnt > 0) {
         GL_CALL(FramebufferTexture2DMultisample(GR_GL_FRAMEBUFFER,
@@ -909,11 +916,11 @@
     if (idDesc->fMSColorRenderbufferID) {
         GL_CALL(DeleteRenderbuffers(1, &idDesc->fMSColorRenderbufferID));
     }
-    if (idDesc->fRTFBOID != idDesc->fTexFBOID) {
-        GL_CALL(DeleteFramebuffers(1, &idDesc->fRTFBOID));
+    if (idDesc->fRenderFBO) {
+        idDesc->fRenderFBO->release(this->glInterface());
     }
-    if (idDesc->fTexFBOID) {
-        GL_CALL(DeleteFramebuffers(1, &idDesc->fTexFBOID));
+    if (idDesc->fTextureFBO) {
+        idDesc->fTextureFBO->release(this->glInterface());
     }
     return false;
 }
@@ -1186,13 +1193,11 @@
                 // Clear the stencil buffer. We use a special purpose FBO for this so that the
                 // entire stencil buffer is cleared, even if it is attached to an FBO with a
                 // smaller color target.
-                if (0 == fStencilClearFBOID) {
-                    GL_CALL(GenFramebuffers(1, &fStencilClearFBOID));
+                if (!fStencilClearFBO) {
+                    fStencilClearFBO.reset(SkNEW_ARGS(GrGLFBO, (this->glInterface())));
                 }
-
-                GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, fStencilClearFBOID));
-                fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID;
-                fStats.incRenderTargetBinds();
+                SkASSERT(fStencilClearFBO->isValid());
+                this->bindFBO(kDraw_FBOBinding, fStencilClearFBO);
                 GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
                                                 GR_GL_STENCIL_ATTACHMENT,
                                                 GR_GL_RENDERBUFFER, sbDesc.fRenderbufferID));
@@ -1230,9 +1235,6 @@
 
 bool GrGLGpu::attachStencilBufferToRenderTarget(GrStencilBuffer* sb, GrRenderTarget* rt) {
     GrGLRenderTarget* glrt = static_cast<GrGLRenderTarget*>(rt);
-
-    GrGLuint fbo = glrt->renderFBOID();
-
     if (NULL == sb) {
         if (rt->renderTargetPriv().getStencilBuffer()) {
             GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
@@ -1252,9 +1254,8 @@
         GrGLStencilBuffer* glsb = static_cast<GrGLStencilBuffer*>(sb);
         GrGLuint rb = glsb->renderbufferID();
 
-        fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID;
-        fStats.incRenderTargetBinds();
-        GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, fbo));
+        this->bindFBO(kDraw_FBOBinding, glrt->renderFBO());
+
         GL_CALL(FramebufferRenderbuffer(GR_GL_FRAMEBUFFER,
                                         GR_GL_STENCIL_ATTACHMENT,
                                         GR_GL_RENDERBUFFER, rb));
@@ -1423,7 +1424,7 @@
 
     // This must come after textures are flushed because a texture may need
     // to be msaa-resolved (which will modify bound FBO state).
-    this->flushRenderTarget(glRT, NULL);
+    this->prepareToDrawToRenderTarget(glRT, NULL);
 
     return true;
 }
@@ -1520,7 +1521,7 @@
         }
     }
 
-    this->flushRenderTarget(glRT, rect);
+    this->prepareToDrawToRenderTarget(glRT, rect);
     GrScissorState scissorState;
     if (rect) {
         scissorState.set(*rect);
@@ -1548,17 +1549,13 @@
     }
 
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(renderTarget);
-    if (renderTarget->getUniqueID() != fHWBoundRenderTargetUniqueID) {
-        fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID;
-        fStats.incRenderTargetBinds();
-        GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, glRT->renderFBOID()));
-    }
+    this->bindFBO(kDraw_FBOBinding, glRT->renderFBO());
     switch (this->glCaps().invalidateFBType()) {
         case GrGLCaps::kNone_InvalidateFBType:
             SkFAIL("Should never get here.");
             break;
         case GrGLCaps::kInvalidate_InvalidateFBType:
-            if (0 == glRT->renderFBOID()) {
+            if (glRT->renderFBO()->isDefaultFramebuffer()) {
                 //  When rendering to the default framebuffer the legal values for attachments
                 //  are GL_COLOR, GL_DEPTH, GL_STENCIL, ... rather than the various FBO attachment
                 //  types.
@@ -1572,7 +1569,7 @@
             }
             break;
         case GrGLCaps::kDiscard_InvalidateFBType: {
-            if (0 == glRT->renderFBOID()) {
+            if (glRT->renderFBO()->isDefaultFramebuffer()) {
                 //  When rendering to the default framebuffer the legal values for attachments
                 //  are GL_COLOR, GL_DEPTH, GL_STENCIL, ... rather than the various FBO attachment
                 //  types. See glDiscardFramebuffer() spec.
@@ -1596,7 +1593,7 @@
         return;
     }
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(target);
-    this->flushRenderTarget(glRT, &SkIRect::EmptyIRect());
+    this->prepareToDrawToRenderTarget(glRT, &SkIRect::EmptyIRect());
 
     this->disableScissor();
 
@@ -1632,7 +1629,7 @@
         value = 0;
     }
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(target);
-    this->flushRenderTarget(glRT, &SkIRect::EmptyIRect());
+    this->prepareToDrawToRenderTarget(glRT, &SkIRect::EmptyIRect());
 
     GrScissorState scissorState;
     scissorState.set(rect);
@@ -1702,22 +1699,13 @@
 
     // resolve the render target if necessary
     GrGLRenderTarget* tgt = static_cast<GrGLRenderTarget*>(target);
-    switch (tgt->getResolveType()) {
-        case GrGLRenderTarget::kCantResolve_ResolveType:
-            return false;
-        case GrGLRenderTarget::kAutoResolves_ResolveType:
-            this->flushRenderTarget(static_cast<GrGLRenderTarget*>(target), &SkIRect::EmptyIRect());
-            break;
-        case GrGLRenderTarget::kCanResolve_ResolveType:
-            this->onResolveRenderTarget(tgt);
-            // we don't track the state of the READ FBO ID.
-            fStats.incRenderTargetBinds();
-            GL_CALL(BindFramebuffer(GR_GL_READ_FRAMEBUFFER,
-                                    tgt->textureFBOID()));
-            break;
-        default:
-            SkFAIL("Unknown resolve type");
+    if (tgt->getResolveType() == GrGLRenderTarget::kCantResolve_ResolveType) {
+        return false;
     }
+    if (tgt->getResolveType() == GrGLRenderTarget::kCanResolve_ResolveType) {
+        this->onResolveRenderTarget(tgt);
+    }
+    this->bindFBO(kRead_FBOBinding, tgt->textureFBO());
 
     const GrGLIRect& glvp = tgt->getViewport();
 
@@ -1803,34 +1791,52 @@
     return true;
 }
 
-void GrGLGpu::flushRenderTarget(GrGLRenderTarget* target, const SkIRect* bound) {
+GrGLenum GrGLGpu::bindFBO(FBOBinding binding, const GrGLFBO* fbo) {
+    SkASSERT(fbo);
+    SkASSERT(fbo->isValid());
+    GrGLenum target;
+    HWFBOBinding* hwFBOState;
+    if (!this->glCaps().usesMSAARenderBuffers()) {
+        target = GR_GL_FRAMEBUFFER;
+        hwFBOState = &fHWFBOBinding[0];
+    } else {
+        target = kDraw_FBOBinding == binding ? GR_GL_DRAW_FRAMEBUFFER : GR_GL_READ_FRAMEBUFFER;
+        hwFBOState = &fHWFBOBinding[binding];
+    }
 
-    SkASSERT(target);
-
-    uint32_t rtID = target->getUniqueID();
-    if (fHWBoundRenderTargetUniqueID != rtID) {
+    if (hwFBOState->fFBO != fbo) {
         fStats.incRenderTargetBinds();
-        GL_CALL(BindFramebuffer(GR_GL_FRAMEBUFFER, target->renderFBOID()));
+        GL_CALL(BindFramebuffer(target, fbo->fboID()));
+        hwFBOState->fFBO.reset(SkRef(fbo));
+    }
+    return target;
+}
+
+void GrGLGpu::setViewport(const GrGLIRect& viewport) {
+    if (viewport != fHWViewport) {
+        viewport.pushToGLViewport(this->glInterface());
+        fHWViewport = viewport;
+    }
+}
+
+void GrGLGpu::prepareToDrawToRenderTarget(GrGLRenderTarget* target, const SkIRect* bound) {
+    SkASSERT(target);
+    this->bindFBO(kDraw_FBOBinding, target->renderFBO());
+
 #ifdef SK_DEBUG
-        // don't do this check in Chromium -- this is causing
-        // lots of repeated command buffer flushes when the compositor is
-        // rendering with Ganesh, which is really slow; even too slow for
-        // Debug mode.
-        if (!this->glContext().isChromium()) {
-            GrGLenum status;
-            GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
-            if (status != GR_GL_FRAMEBUFFER_COMPLETE) {
-                SkDebugf("GrGLGpu::flushRenderTarget glCheckFramebufferStatus %x\n", status);
-            }
-        }
-#endif
-        fHWBoundRenderTargetUniqueID = rtID;
-        const GrGLIRect& vp = target->getViewport();
-        if (fHWViewport != vp) {
-            vp.pushToGLViewport(this->glInterface());
-            fHWViewport = vp;
+    // don't do this check in Chromium -- this is causing
+    // lots of repeated command buffer flushes when the compositor is
+    // rendering with Ganesh, which is really slow; even too slow for
+    // Debug mode.
+    if (!this->glContext().isChromium()) {
+        GrGLenum status;
+        GL_CALL_RET(status, CheckFramebufferStatus(GR_GL_FRAMEBUFFER));
+        if (status != GR_GL_FRAMEBUFFER_COMPLETE) {
+            SkDebugf("GrGLGpu::flushRenderTarget glCheckFramebufferStatus %x\n", status);
         }
     }
+#endif
+    this->setViewport(target->getViewport());
     if (NULL == bound || !bound->isEmpty()) {
         target->flagAsNeedingResolve(bound);
     }
@@ -1919,7 +1925,7 @@
     this->glPathRendering()->setProjectionMatrix(*state.fViewMatrix, size, rt->origin());
     this->flushScissor(*state.fScissor, rt->getViewport(), rt->origin());
     this->flushHWAAState(rt, state.fUseHWAA, false);
-    this->flushRenderTarget(rt, NULL);
+    this->prepareToDrawToRenderTarget(rt, NULL);
 
     fPathRendering->stencilPath(path, *state.fStencil);
 }
@@ -1952,14 +1958,9 @@
     if (rt->needsResolve()) {
         // Some extensions automatically resolves the texture when it is read.
         if (this->glCaps().usesMSAARenderBuffers()) {
-            SkASSERT(rt->textureFBOID() != rt->renderFBOID());
-            fStats.incRenderTargetBinds();
-            fStats.incRenderTargetBinds();
-            GL_CALL(BindFramebuffer(GR_GL_READ_FRAMEBUFFER, rt->renderFBOID()));
-            GL_CALL(BindFramebuffer(GR_GL_DRAW_FRAMEBUFFER, rt->textureFBOID()));
-            // make sure we go through flushRenderTarget() since we've modified
-            // the bound DRAW FBO ID.
-            fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID;
+            SkASSERT(rt->textureFBO() != rt->renderFBO());
+            this->bindFBO(kRead_FBOBinding, rt->renderFBO());
+            this->bindFBO(kDraw_FBOBinding, rt->textureFBO());
             const GrGLIRect& vp = rt->getViewport();
             const SkIRect dirtyRect = rt->getResolveRect();
 
@@ -2522,13 +2523,13 @@
     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 (dstRT && dstRT->renderFBOID() != dstRT->textureFBOID()) {
+    if (dstRT && dstRT->renderFBO() != dstRT->textureFBO()) {
         return false;
     }
     const GrGLRenderTarget* srcRT = static_cast<const GrGLRenderTarget*>(src->asRenderTarget());
     // If the src is multisampled (and uses an extension where there is a separate MSAA
     // renderbuffer) then it is an invalid operation to call CopyTexSubImage
-    if (srcRT && srcRT->renderFBOID() != srcRT->textureFBOID()) {
+    if (srcRT && srcRT->renderFBO() != srcRT->textureFBO()) {
         return false;
     }
     if (gpu->glCaps().isConfigRenderable(src->config(), src->desc().fSampleCnt > 0) &&
@@ -2545,22 +2546,31 @@
 
 // If a temporary FBO was created, its non-zero ID is returned. The viewport that the copy rect is
 // relative to is output.
-GrGLuint GrGLGpu::bindSurfaceAsFBO(GrSurface* surface, GrGLenum fboTarget, GrGLIRect* viewport,
-                                   TempFBOTarget tempFBOTarget) {
+GrGLGpu::FBOBinding GrGLGpu::bindSurfaceAsFBOForCopy(GrSurface* surface, FBOBinding binding,
+                                                     GrGLIRect* viewport) {
     GrGLRenderTarget* rt = static_cast<GrGLRenderTarget*>(surface->asRenderTarget());
     if (NULL == rt) {
         SkASSERT(surface->asTexture());
         GrGLuint texID = static_cast<GrGLTexture*>(surface->asTexture())->textureID();
-        GrGLuint* tempFBOID;
-        tempFBOID = kSrc_TempFBOTarget == tempFBOTarget ? &fTempSrcFBOID : &fTempDstFBOID;
+        GrGLFBO* tempFBO;
 
-        if (0 == *tempFBOID) {
-            GR_GL_CALL(this->glInterface(), GenFramebuffers(1, tempFBOID));
+        if (kRead_FBOBinding == binding) {
+            if (!fTempSrcFBO) {
+                fTempSrcFBO.reset(SkNEW_ARGS(GrGLFBO, (this->glInterface())));
+                SkASSERT(fTempSrcFBO->isValid());
+            }
+            tempFBO = fTempSrcFBO;
+        } else {
+            SkASSERT(kDraw_FBOBinding == binding);
+            if (!fTempDstFBO) {
+                fTempDstFBO.reset(SkNEW_ARGS(GrGLFBO, (this->glInterface())));
+                SkASSERT(fTempDstFBO->isValid());
+            }
+            tempFBO = fTempDstFBO;
         }
 
-        fStats.incRenderTargetBinds();
-        GR_GL_CALL(this->glInterface(), BindFramebuffer(fboTarget, *tempFBOID));
-        GR_GL_CALL(this->glInterface(), FramebufferTexture2D(fboTarget,
+        GrGLenum target = this->bindFBO(binding, tempFBO);
+        GR_GL_CALL(this->glInterface(), FramebufferTexture2D(target,
                                                              GR_GL_COLOR_ATTACHMENT0,
                                                              GR_GL_TEXTURE_2D,
                                                              texID,
@@ -2569,18 +2579,21 @@
         viewport->fBottom = 0;
         viewport->fWidth = surface->width();
         viewport->fHeight = surface->height();
-        return *tempFBOID;
+        return binding;
     } else {
-        GrGLuint tempFBOID = 0;
-        fStats.incRenderTargetBinds();
-        GR_GL_CALL(this->glInterface(), BindFramebuffer(fboTarget, rt->renderFBOID()));
+        this->bindFBO(binding, rt->renderFBO());
         *viewport = rt->getViewport();
-        return tempFBOID;
+        return kInvalidFBOBinding;
     }
 }
 
-void GrGLGpu::unbindTextureFromFBO(GrGLenum fboTarget) {
-    GR_GL_CALL(this->glInterface(), FramebufferTexture2D(fboTarget,
+void GrGLGpu::unbindSurfaceAsFBOForCopy(FBOBinding binding) {
+    if (kInvalidFBOBinding == binding) {
+        return;
+    }
+    GrGLFBO* tempFBO = kDraw_FBOBinding == binding ? fTempSrcFBO : fTempDstFBO;
+    GrGLenum target = this->bindFBO(binding, tempFBO);
+    GR_GL_CALL(this->glInterface(), FramebufferTexture2D(target,
                                                          GR_GL_COLOR_ATTACHMENT0,
                                                          GR_GL_TEXTURE_2D,
                                                          0,
@@ -2611,7 +2624,7 @@
     }
 
     const GrGLRenderTarget* srcRT = static_cast<const GrGLRenderTarget*>(src->asRenderTarget());
-    if (srcRT && srcRT->renderFBOID() != srcRT->textureFBOID()) {
+    if (srcRT && srcRT->renderFBO() != srcRT->textureFBO()) {
         // It's illegal to call CopyTexSubImage2D on a MSAA renderbuffer. Set up for FBO blit or
         // fail.
         if (this->caps()->isConfigRenderable(src->config(), false)) {
@@ -2636,13 +2649,10 @@
                           const SkIPoint& dstPoint) {
     bool copied = false;
     if (can_copy_texsubimage(dst, src, this)) {
-        GrGLuint srcFBO;
         GrGLIRect srcVP;
-        srcFBO = this->bindSurfaceAsFBO(src, GR_GL_FRAMEBUFFER, &srcVP, kSrc_TempFBOTarget);
+        FBOBinding srcFBOBinding = this->bindSurfaceAsFBOForCopy(src, kRead_FBOBinding, &srcVP);
         GrGLTexture* dstTex = static_cast<GrGLTexture*>(dst->asTexture());
         SkASSERT(dstTex);
-        // We modified the bound FBO
-        fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID;
         GrGLIRect srcGLRect;
         srcGLRect.setRelativeTo(srcVP,
                                 srcRect.fLeft,
@@ -2664,9 +2674,7 @@
                                   srcGLRect.fLeft, srcGLRect.fBottom,
                                   srcGLRect.fWidth, srcGLRect.fHeight));
         copied = true;
-        if (srcFBO) {
-            this->unbindTextureFromFBO(GR_GL_FRAMEBUFFER);
-        }
+        this->unbindSurfaceAsFBOForCopy(srcFBOBinding);
     } else if (can_blit_framebuffer(dst, src, this)) {
         SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
                                             srcRect.width(), srcRect.height());
@@ -2676,16 +2684,11 @@
         }
 
         if (!selfOverlap) {
-            GrGLuint dstFBO;
-            GrGLuint srcFBO;
             GrGLIRect dstVP;
             GrGLIRect srcVP;
-            dstFBO = this->bindSurfaceAsFBO(dst, GR_GL_DRAW_FRAMEBUFFER, &dstVP,
-                                            kDst_TempFBOTarget);
-            srcFBO = this->bindSurfaceAsFBO(src, GR_GL_READ_FRAMEBUFFER, &srcVP,
-                                            kSrc_TempFBOTarget);
-            // We modified the bound FBO
-            fHWBoundRenderTargetUniqueID = SK_InvalidUniqueID;
+            FBOBinding dstFBOBinding = this->bindSurfaceAsFBOForCopy(dst, kDraw_FBOBinding, &dstVP);
+            FBOBinding srcFBOBinding = this->bindSurfaceAsFBOForCopy(src, kRead_FBOBinding, &srcVP);
+
             GrGLIRect srcGLRect;
             GrGLIRect dstGLRect;
             srcGLRect.setRelativeTo(srcVP,
@@ -2723,12 +2726,8 @@
                                     dstGLRect.fLeft + dstGLRect.fWidth,
                                     dstGLRect.fBottom + dstGLRect.fHeight,
                                     GR_GL_COLOR_BUFFER_BIT, GR_GL_NEAREST));
-            if (dstFBO) {
-                this->unbindTextureFromFBO(GR_GL_DRAW_FRAMEBUFFER);
-            }
-            if (srcFBO) {
-                this->unbindTextureFromFBO(GR_GL_READ_FRAMEBUFFER);
-            }
+            this->unbindSurfaceAsFBOForCopy(dstFBOBinding);
+            this->unbindSurfaceAsFBOForCopy(srcFBOBinding);
             copied = true;
         }
     }
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index b93080b..0ec69c0 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -246,9 +246,25 @@
     // ensures that such operations don't negatively interact with tracking bound textures.
     void setScratchTextureUnit();
 
-    // bounds is region that may be modified and therefore has to be resolved.
-    // NULL means whole target. Can be an empty rect.
-    void flushRenderTarget(GrGLRenderTarget*, const SkIRect* bounds);
+    // Binds the render target, sets the viewport, tracks dirty are for resolve, and tracks whether
+    // mip maps need rebuilding. bounds is region that may be modified by the draw. NULL means whole
+    // target. Can be an empty rect.
+    void prepareToDrawToRenderTarget(GrGLRenderTarget*, const SkIRect* bounds);
+
+    // On older GLs there may not be separate FBO bindings for draw and read. In that case these
+    // alias each other.
+    enum FBOBinding {
+        kDraw_FBOBinding, // drawing or dst of blit
+        kRead_FBOBinding, // src of blit, read pixels.
+
+        kLast_FBOBinding = kRead_FBOBinding
+    };
+    static const int kFBOBindingCnt = kLast_FBOBinding + 1;
+
+    // binds the FBO and returns the GL enum of the framebuffer target it was bound to.
+    GrGLenum bindFBO(FBOBinding, const GrGLFBO*);
+
+    void setViewport(const GrGLIRect& viewport);
 
     void flushStencil(const GrStencilSettings&);
     void flushHWAAState(GrRenderTarget* rt, bool useHWAA, bool isLineDraw);
@@ -281,15 +297,14 @@
     bool createRenderTargetObjects(const GrSurfaceDesc&, bool budgeted, GrGLuint texID, 
                                    GrGLRenderTarget::IDDesc*);
 
-    enum TempFBOTarget {
-        kSrc_TempFBOTarget,
-        kDst_TempFBOTarget
-    };
+    static const FBOBinding kInvalidFBOBinding = static_cast<FBOBinding>(-1);
 
-    GrGLuint bindSurfaceAsFBO(GrSurface* surface, GrGLenum fboTarget, GrGLIRect* viewport,
-                              TempFBOTarget tempFBOTarget);
+    // Binds a surface as an FBO. A temporary FBO ID may be used if the surface is not already
+    // a render target. Afterwards unbindSurfaceAsFBOForCopy must be called with the value returned.
+    FBOBinding bindSurfaceAsFBOForCopy(GrSurface*, FBOBinding, GrGLIRect* viewport);
 
-    void unbindTextureFromFBO(GrGLenum fboTarget);
+    // Must be matched with bindSurfaceAsFBOForCopy.
+    void unbindSurfaceAsFBOForCopy(FBOBinding);
 
     GrGLContext fGLContext;
 
@@ -309,10 +324,9 @@
         kUnknown_TriState
     };
 
-    GrGLuint                    fTempSrcFBOID;
-    GrGLuint                    fTempDstFBOID;
-
-    GrGLuint                    fStencilClearFBOID;
+    SkAutoTUnref<GrGLFBO> fTempSrcFBO;
+    SkAutoTUnref<GrGLFBO> fTempDstFBO;
+    SkAutoTUnref<GrGLFBO> fStencilClearFBO;
 
     // last scissor / viewport scissor state seen by the GL.
     struct {
@@ -458,9 +472,14 @@
     GrPipelineBuilder::DrawFace fHWDrawFace;
     TriState                    fHWWriteToColor;
     TriState                    fHWDitherEnabled;
-    uint32_t                    fHWBoundRenderTargetUniqueID;
     SkTArray<uint32_t, true>    fHWBoundTextureUniqueIDs;
 
+    // Track fbo binding state.
+    struct HWFBOBinding {
+        SkAutoTUnref<const GrGLFBO> fFBO;
+        void invalidate() { fFBO.reset(NULL); }
+    } fHWFBOBinding[kFBOBindingCnt];
+
     ///@}
 
     // we record what stencil format worked last time to hopefully exit early
diff --git a/src/gpu/gl/GrGLRenderTarget.cpp b/src/gpu/gl/GrGLRenderTarget.cpp
index 3eb2ae0..8b9bb98 100644
--- a/src/gpu/gl/GrGLRenderTarget.cpp
+++ b/src/gpu/gl/GrGLRenderTarget.cpp
@@ -9,8 +9,20 @@
 
 #include "GrGLGpu.h"
 
-#define GPUGL static_cast<GrGLGpu*>(this->getGpu())
-#define GL_CALL(X) GR_GL_CALL(GPUGL->glInterface(), X)
+void GrGLFBO::release(const GrGLInterface* gl) {
+    SkASSERT(gl);
+    if (this->isValid()) {
+        GR_GL_CALL(gl, DeleteFramebuffers(1, &fID));
+        fIsValid = false;
+    }
+}
+
+void GrGLFBO::abandon() { fIsValid = false; }
+
+//////////////////////////////////////////////////////////////////////////////
+
+#define GLGPU static_cast<GrGLGpu*>(this->getGpu())
+#define GL_CALL(X) GR_GL_CALL(GLGPU->glInterface(), X)
 
 // Because this class is virtually derived from GrSurface we must explicitly call its constructor.
 GrGLRenderTarget::GrGLRenderTarget(GrGLGpu* gpu, const GrSurfaceDesc& desc, const IDDesc& idDesc)
@@ -28,8 +40,10 @@
 }
 
 void GrGLRenderTarget::init(const GrSurfaceDesc& desc, const IDDesc& idDesc) {
-    fRTFBOID                = idDesc.fRTFBOID;
-    fTexFBOID               = idDesc.fTexFBOID;
+    fRenderFBO.reset(SkRef(idDesc.fRenderFBO.get()));
+    fTextureFBO.reset(SkSafeRef(idDesc.fTextureFBO.get()));
+    SkASSERT(fRenderFBO->isValid());
+    SkASSERT(!fTextureFBO || fTextureFBO->isValid());
     fMSColorRenderbufferID  = idDesc.fMSColorRenderbufferID;
     fIsWrapped              = kWrapped_LifeCycle == idDesc.fLifeCycle;
 
@@ -40,7 +54,7 @@
 
     // We own one color value for each MSAA sample.
     fColorValuesPerPixel = SkTMax(1, fDesc.fSampleCnt);
-    if (fTexFBOID != fRTFBOID) {
+    if (fTextureFBO && fTextureFBO != fRenderFBO) {
         // If we own the resolve buffer then that is one more sample per pixel.
         fColorValuesPerPixel += 1;
     } 
@@ -56,27 +70,42 @@
 
 void GrGLRenderTarget::onRelease() {
     if (!fIsWrapped) {
-        if (fTexFBOID) {
-            GL_CALL(DeleteFramebuffers(1, &fTexFBOID));
+        const GrGLInterface* gl = GLGPU->glInterface();
+        if (fRenderFBO) {
+            fRenderFBO->release(gl);
+            fRenderFBO.reset(NULL);
         }
-        if (fRTFBOID && fRTFBOID != fTexFBOID) {
-            GL_CALL(DeleteFramebuffers(1, &fRTFBOID));
+        if (fTextureFBO) {
+            fTextureFBO->release(gl);
+            fTextureFBO.reset(NULL);
         }
         if (fMSColorRenderbufferID) {
             GL_CALL(DeleteRenderbuffers(1, &fMSColorRenderbufferID));
+            fMSColorRenderbufferID = 0;
         }
+    } else {
+        if (fRenderFBO) {
+            fRenderFBO->abandon();
+            fRenderFBO.reset(NULL);
+        }
+        if (fTextureFBO) {
+            fTextureFBO->abandon();
+            fTextureFBO.reset(NULL);
+        }
+        fMSColorRenderbufferID  = 0;
     }
-    fRTFBOID                = 0;
-    fTexFBOID               = 0;
-    fMSColorRenderbufferID  = 0;
-    fIsWrapped              = false;
     INHERITED::onRelease();
 }
 
 void GrGLRenderTarget::onAbandon() {
-    fRTFBOID                = 0;
-    fTexFBOID               = 0;
+    if (fRenderFBO) {
+        fRenderFBO->abandon();
+        fRenderFBO.reset(NULL);
+    }
+    if (fTextureFBO) {
+        fTextureFBO->abandon();
+        fTextureFBO.reset(NULL);
+    }
     fMSColorRenderbufferID  = 0;
-    fIsWrapped              = false;
     INHERITED::onAbandon();
 }
diff --git a/src/gpu/gl/GrGLRenderTarget.h b/src/gpu/gl/GrGLRenderTarget.h
index 7e73492..485ae4a 100644
--- a/src/gpu/gl/GrGLRenderTarget.h
+++ b/src/gpu/gl/GrGLRenderTarget.h
@@ -15,15 +15,59 @@
 
 class GrGLGpu;
 
+/** Represents a GL FBO object. It has a gen ID which is valid whenever the FBO ID owned by the
+    object is valid. The gen IDs are not recycled after FBOs are freed, unlike FBO IDs, and so
+    can be used to uniquely identity FBO ID instantiations. If this object owns an FBO ID, the ID
+    must be deleted or abandoned before this object is freed. FBO IDs should never be owned by
+    more than one instance. */
+class GrGLFBO : public SkNVRefCnt<GrGLFBO> {
+public:
+    SK_DECLARE_INST_COUNT(GrGLFBO);
+
+    /** Initializes to an FBO. The FBO should already be valid in the relevant GL context. */
+    GrGLFBO(GrGLint id) : fID(id), fIsValid(true) {}
+
+    /** Initializes to an FBO ID generated using the interface. */
+    GrGLFBO(const GrGLInterface* gl) {
+        GR_GL_CALL(gl, GenFramebuffers(1, &fID));
+        fIsValid = SkToBool(fID);
+    }
+
+    ~GrGLFBO() { SkASSERT(!this->isValid()); }
+
+    /** Has this object been released or abandoned? */
+    bool isValid() const { return fIsValid; }
+    
+    GrGLint fboID() const { SkASSERT(this->isValid()); return fID; }
+
+    bool isDefaultFramebuffer() const { return fIsValid && 0 == fID; }
+
+    /** Give up ownership of the FBO ID owned by this object without deleting it. */
+    void abandon();
+
+    /** Delete and give up ownership of the the FBO ID if it is valid. */
+    void release(const GrGLInterface*);
+
+private:
+    static uint32_t NextGenID() {
+        static int32_t gGenID = SK_InvalidGenID + 1;
+        return static_cast<uint32_t>(sk_atomic_inc(&gGenID));
+    }
+
+    GrGLuint    fID;
+    bool        fIsValid;
+
+    typedef SkRefCnt INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+/** GL-specific subclass of GrRenderTarget. */
 class GrGLRenderTarget : public GrRenderTarget {
 public:
-    // set fTexFBOID to this value to indicate that it is multisampled but
-    // Gr doesn't know how to resolve it.
-    enum { kUnresolvableFBOID = 0 };
-
     struct IDDesc {
-        GrGLuint                    fRTFBOID;
-        GrGLuint                    fTexFBOID;
+        SkAutoTUnref<GrGLFBO>       fRenderFBO;
+        SkAutoTUnref<GrGLFBO>       fTextureFBO;
         GrGLuint                    fMSColorRenderbufferID;
         GrGpuResource::LifeCycle    fLifeCycle;
     };
@@ -33,21 +77,33 @@
     void setViewport(const GrGLIRect& rect) { fViewport = rect; }
     const GrGLIRect& getViewport() const { return fViewport; }
 
-    // The following two functions return the same ID when a
-    // texture/render target is multisampled, and different IDs when
-    // it is.
-    // FBO ID used to render into
-    GrGLuint renderFBOID() const { return fRTFBOID; }
-    // FBO ID that has texture ID attached.
-    GrGLuint textureFBOID() const { return fTexFBOID; }
+    // For multisampled renderbuffer render targets, these will return different GrGLFBO objects. If
+    // the render target is not texturable, textureFBO() returns NULL. If the render target auto
+    // resolves to a texture, the same object is returned.
+
+    // FBO that should be rendered into. Always non-NULL unless this resource is destroyed
+    // (this->wasDestroyed()).
+    const GrGLFBO* renderFBO() const {
+        SkASSERT(fRenderFBO && fRenderFBO->isValid());
+        return fRenderFBO;
+    }
+
+    // FBO that has the target's texture ID attached. The return value may be:
+    //      * NULL when this render target is not a texture,
+    //      * the same as renderFBO() when this surface is not multisampled or auto-resolves,
+    //      * or different than renderFBO() when it requires explicit resolving via
+    //        glBlitFramebuffer.
+    const GrGLFBO* textureFBO() const {
+        SkASSERT(!fTextureFBO || fTextureFBO->isValid());
+        return fTextureFBO;
+    }
 
     // override of GrRenderTarget
     ResolveType getResolveType() const SK_OVERRIDE {
-        if (!this->isMultisampled() ||
-            fRTFBOID == fTexFBOID) {
+        if (!this->isMultisampled() || this->renderFBO() == this->textureFBO()) {
             // catches FBO 0 and non MSAA case
             return kAutoResolves_ResolveType;
-        } else if (kUnresolvableFBOID == fTexFBOID) {
+        } else if (!this->textureFBO()) {
             return kCantResolve_ResolveType;
         } else {
             return kCanResolve_ResolveType;
@@ -73,23 +129,23 @@
     size_t onGpuMemorySize() const SK_OVERRIDE;
 
 private:
-    GrGLuint      fRTFBOID;
-    GrGLuint      fTexFBOID;
-    GrGLuint      fMSColorRenderbufferID;
+    SkAutoTUnref<GrGLFBO>   fRenderFBO;
+    SkAutoTUnref<GrGLFBO>   fTextureFBO;
+    GrGLuint                fMSColorRenderbufferID;
 
     // We track this separately from GrGpuResource because this may be both a texture and a render
     // target, and the texture may be wrapped while the render target is not.
-    bool fIsWrapped;
+    bool                    fIsWrapped;
 
     // when we switch to this render target we want to set the viewport to
     // only render to content area (as opposed to the whole allocation) and
     // we want the rendering to be at top left (GL has origin in bottom left)
-    GrGLIRect fViewport;
+    GrGLIRect               fViewport;
 
     // onGpuMemorySize() needs to know what how many color values are owned per pixel. However,
     // abandon and release zero out the IDs and the cache needs to know the size even after those
     // actions.
-    uint8_t fColorValuesPerPixel;
+    uint8_t                 fColorValuesPerPixel;
 
     typedef GrRenderTarget INHERITED;
 };