GL: Unbind textures from FBO after calls to frameBufferTexture2D

OOPR canvas uncovered an Nvidia driver bug in which binding a texture
level > 0 to a framebuffer and then later binding a renderbuffer to
the same FBO causes the FBO to be marked as having an incomplete
attachment. This CL expands UnbindResources() in BlitGL.cpp to unbind
textures in addition to RBOs and adds new calls to unbind in functions
that call framebufferTexture2D.

Also adds a GL workaround--alwaysUnbindFramebufferTexture2D--
that forces FramebufferGL to first unbind any existing attachments
using framebufferTexture2D before attaching a new render buffer.

Bug: angleproject:5536
Change-Id: I46c115b3895f8fccb251dbf4531d5c1bd4705ebc
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3527465
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>
Commit-Queue: Jonah Ryan-Davis <jonahr@google.com>
diff --git a/include/platform/FeaturesGL.h b/include/platform/FeaturesGL.h
index 477b5c6..2d11a65 100644
--- a/include/platform/FeaturesGL.h
+++ b/include/platform/FeaturesGL.h
@@ -559,6 +559,15 @@
     Feature emulateRGB10 = {"emulate_rgb10", FeatureCategory::OpenGLWorkarounds,
                             "Emulate RGB10 support using RGB10_A2.", &members,
                             "https://crbug.com/1300575"};
+
+    // On NVIDIA, binding a texture level > 0 to a framebuffer color attachment and then
+    // binding a renderbuffer to the same attachment causes the driver to report that the FBO
+    // has an incomplete attachment. This workaround forces a call to framebufferTexture2D with
+    // texture_id and level set to 0 before binding a renderbuffer to bypass the issue.
+    Feature alwaysUnbindFramebufferTexture2D = {
+        "always_unbind_framebuffer_texture_2d", FeatureCategory::OpenGLWorkarounds,
+        "Force unbind framebufferTexture2D before binding renderbuffer to work around driver bug.",
+        &members, "https://anglebug.com/5536"};
 };
 
 inline FeaturesGL::FeaturesGL()  = default;
diff --git a/src/libANGLE/renderer/gl/BlitGL.cpp b/src/libANGLE/renderer/gl/BlitGL.cpp
index 0cce8a4..b980494 100644
--- a/src/libANGLE/renderer/gl/BlitGL.cpp
+++ b/src/libANGLE/renderer/gl/BlitGL.cpp
@@ -199,6 +199,19 @@
     return angle::Result::Continue;
 }
 
+angle::Result UnbindAttachment(const gl::Context *context,
+                               const FunctionsGL *functions,
+                               GLenum framebufferTarget,
+                               GLenum attachment)
+{
+    // Always use framebufferTexture2D as a workaround for an Nvidia driver bug. See
+    // https://anglebug.com/5536 and FeaturesGL.alwaysUnbindFramebufferTexture2D
+    ANGLE_GL_TRY(context,
+                 functions->framebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, 0, 0));
+
+    return angle::Result::Continue;
+}
+
 angle::Result UnbindAttachments(const gl::Context *context,
                                 const FunctionsGL *functions,
                                 GLenum framebufferTarget,
@@ -206,8 +219,7 @@
 {
     for (GLenum bindTarget : bindTargets)
     {
-        ANGLE_GL_TRY(context, functions->framebufferRenderbuffer(framebufferTarget, bindTarget,
-                                                                 GL_RENDERBUFFER, 0));
+        ANGLE_TRY(UnbindAttachment(context, functions, framebufferTarget, bindTarget));
     }
     return angle::Result::Continue;
 }
@@ -389,6 +401,7 @@
 
     // Finally orphan the scratch textures so they can be GCed by the driver.
     ANGLE_TRY(orphanScratchTextures(context));
+    ANGLE_TRY(UnbindAttachment(context, mFunctions, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0));
 
     ANGLE_TRY(scopedState.exit(context));
     return angle::Result::Continue;
@@ -417,8 +430,7 @@
     angle::Result result = blitColorBufferWithShader(context, source, mScratchFBO, sourceAreaIn,
                                                      destAreaIn, filter, writeAlpha);
     // Unbind the texture from the the scratch framebuffer.
-    ANGLE_GL_TRY(context, mFunctions->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-                                                              GL_RENDERBUFFER, 0));
+    ANGLE_TRY(UnbindAttachment(context, mFunctions, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0));
     return result;
 }
 
@@ -666,6 +678,7 @@
 
     ANGLE_TRY(setVAOState(context));
     ANGLE_GL_TRY(context, mFunctions->drawArrays(GL_TRIANGLES, 0, 3));
+    ANGLE_TRY(UnbindAttachment(context, mFunctions, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0));
 
     *copySucceededOut = true;
     ANGLE_TRY(scopedState.exit(context));
@@ -816,6 +829,8 @@
                               destOffset.y, readPixelsArea.width, readPixelsArea.height,
                               texSubImageFormat.format, texSubImageFormat.type, destMemory));
 
+    ANGLE_TRY(UnbindAttachment(context, mFunctions, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0));
+
     return angle::Result::Continue;
 }
 
@@ -850,6 +865,7 @@
                                                destOffset.x, destOffset.y, sourceArea.x,
                                                sourceArea.y, sourceArea.width, sourceArea.height));
 
+    ANGLE_TRY(UnbindAttachment(context, mFunctions, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0));
     *copySucceededOut = true;
     return angle::Result::Continue;
 }
@@ -1029,8 +1045,7 @@
     ANGLE_GL_TRY(context, mFunctions->clear(GL_COLOR_BUFFER_BIT));
 
     // Unbind the texture from the the scratch framebuffer
-    ANGLE_GL_TRY(context, mFunctions->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-                                                              GL_RENDERBUFFER, 0));
+    ANGLE_TRY(UnbindAttachment(context, mFunctions, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0));
 
     return angle::Result::Continue;
 }
@@ -1109,6 +1124,7 @@
     }
 
     ANGLE_TRY(orphanScratchTextures(context));
+    ANGLE_TRY(UnbindAttachment(context, mFunctions, GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0));
 
     ANGLE_TRY(scopedState.exit(context));
     return angle::Result::Continue;
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index 71a9944..3d20ff5 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -76,7 +76,8 @@
 
 void BindFramebufferAttachment(const FunctionsGL *functions,
                                GLenum attachmentPoint,
-                               const FramebufferAttachment *attachment)
+                               const FramebufferAttachment *attachment,
+                               const angle::FeaturesGL &features)
 {
     if (attachment)
     {
@@ -160,6 +161,12 @@
             const Renderbuffer *renderbuffer     = attachment->getRenderbuffer();
             const RenderbufferGL *renderbufferGL = GetImplAs<RenderbufferGL>(renderbuffer);
 
+            if (features.alwaysUnbindFramebufferTexture2D.enabled)
+            {
+                functions->framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_2D, 0,
+                                                0);
+            }
+
             functions->framebufferRenderbuffer(GL_FRAMEBUFFER, attachmentPoint, GL_RENDERBUFFER,
                                                renderbufferGL->getRenderbufferID());
         }
@@ -1266,7 +1273,8 @@
             case Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT:
             {
                 const FramebufferAttachment *newAttachment = mState.getDepthAttachment();
-                BindFramebufferAttachment(functions, GL_DEPTH_ATTACHMENT, newAttachment);
+                BindFramebufferAttachment(functions, GL_DEPTH_ATTACHMENT, newAttachment,
+                                          GetFeaturesGL(context));
                 if (newAttachment)
                 {
                     attachment = newAttachment;
@@ -1276,7 +1284,8 @@
             case Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT:
             {
                 const FramebufferAttachment *newAttachment = mState.getStencilAttachment();
-                BindFramebufferAttachment(functions, GL_STENCIL_ATTACHMENT, newAttachment);
+                BindFramebufferAttachment(functions, GL_STENCIL_ATTACHMENT, newAttachment,
+                                          GetFeaturesGL(context));
                 if (newAttachment)
                 {
                     attachment = newAttachment;
@@ -1329,7 +1338,7 @@
                     const FramebufferAttachment *newAttachment = mState.getColorAttachment(index);
                     BindFramebufferAttachment(functions,
                                               static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + index),
-                                              newAttachment);
+                                              newAttachment, GetFeaturesGL(context));
                     if (newAttachment)
                     {
                         attachment = newAttachment;
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index 9e40111..d6eff0d 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -2208,6 +2208,10 @@
 
     // https://crbug.com/1300575
     ANGLE_FEATURE_CONDITION(features, emulateRGB10, functions->standard == STANDARD_GL_DESKTOP);
+
+    // https://anglebug.com/5536
+    ANGLE_FEATURE_CONDITION(features, alwaysUnbindFramebufferTexture2D,
+                            isNvidia && (IsWindows() || IsLinux()));
 }
 
 void InitializeFrontendFeatures(const FunctionsGL *functions, angle::FrontendFeatures *features)