Add GrContext::resetGLTextureBindings().

This function binds texture ID 0 to any texture unit/target combo that
Skia has modified.

Bug: chromium:926017
Change-Id: I3ac8f8050c863232886102886e60d3b91a5380c9
Reviewed-on: https://skia-review.googlesource.com/c/190663
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index 4df8bae..c3901af 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -263,6 +263,7 @@
   "$_tests/TableColorFilterTest.cpp",
   "$_tests/TemplatesTest.cpp",
   "$_tests/TessellatingPathRendererTests.cpp",
+  "$_tests/TextureBindingsResetTest.cpp",
   "$_tests/Test.cpp",
   "$_tests/TestTest.cpp",
   "$_tests/TestUtils.h",
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 67319d5..0ff2e0d 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -90,6 +90,17 @@
     void resetContext(uint32_t state = kAll_GrBackendState);
 
     /**
+     * If the backend is GrBackendApi::kOpenGL, then all texture unit/target combinations for which
+     * the GrContext has modified the bound texture will have texture id 0 bound. This does not
+     * flush the GrContext. Calling resetContext() does not change the set that will be bound
+     * to texture id 0 on the next call to resetGLTextureBindings(). After this is called
+     * all unit/target combinations are considered to have unmodified bindings until the GrContext
+     * subsequently modifies them (meaning if this is called twice in a row with no intervening
+     * GrContext usage then the second call is a no-op.)
+     */
+    void resetGLTextureBindings();
+
+    /**
      * Abandons all GPU resources and assumes the underlying backend 3D API context is no longer
      * usable. Call this if you have lost the associated GPU context, and thus internal texture,
      * buffer, etc. references/IDs are now invalid. Calling this ensures that the destructors of the
diff --git a/include/private/GrTypesPriv.h b/include/private/GrTypesPriv.h
index f0f85c7..06f14a7 100644
--- a/include/private/GrTypesPriv.h
+++ b/include/private/GrTypesPriv.h
@@ -241,8 +241,8 @@
 };
 
 struct GrMipLevel {
-    const void* fPixels;
-    size_t fRowBytes;
+    const void* fPixels = nullptr;
+    size_t fRowBytes = 0;
 };
 
 /**
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 8d7f945..fc8c7f5 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -193,6 +193,13 @@
     fTextBlobCache->freeAll();
 }
 
+void GrContext::resetGLTextureBindings() {
+    if (this->abandoned() || this->backend() != GrBackendApi::kOpenGL) {
+        return;
+    }
+    fGpu->resetTextureBindings();
+}
+
 void GrContext::resetContext(uint32_t state) {
     ASSERT_SINGLE_OWNER
     fGpu->markContextDirty(state);
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 2600076..a1a0ea1 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -346,6 +346,11 @@
     return false;
 }
 
+void GrGpu::resetTextureBindings() {
+    this->handleDirtyContext();
+    this->onResetTextureBindings();
+}
+
 void GrGpu::resolveRenderTarget(GrRenderTarget* target) {
     SkASSERT(target);
     this->handleDirtyContext();
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 867af54..8d1a803 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -154,6 +154,11 @@
     bool regenerateMipMapLevels(GrTexture*);
 
     /**
+     * If the backend API has stateful texture bindings, this resets them back to defaults.
+     */
+    void resetTextureBindings();
+
+    /**
      * Reads a rectangle of pixels from a render target. No sRGB/linear conversions are performed.
      *
      * @param surface       The surface to read from
@@ -453,6 +458,9 @@
     // assumed 3D context state and dirty any state cache.
     virtual void onResetContext(uint32_t resetBits) = 0;
 
+    // Implementation of resetTextureBindings.
+    virtual void onResetTextureBindings() {}
+
     // Called before certain draws in order to guarantee coherent results from dst reads.
     virtual void xferBarrier(GrRenderTarget*, GrXferBarrierType) = 0;
 
diff --git a/src/gpu/gl/GrGLDefines.h b/src/gpu/gl/GrGLDefines.h
index 995dbf3..aa7e883 100644
--- a/src/gpu/gl/GrGLDefines.h
+++ b/src/gpu/gl/GrGLDefines.h
@@ -1038,9 +1038,11 @@
 
 /* GL_OES_EGL_image_external */
 #define GR_GL_TEXTURE_EXTERNAL                              0x8D65
+#define GR_GL_TEXTURE_BINDING_EXTERNAL                      0x8D67
 
 /* GL_ARB_texture_rectangle or GL_ANGLE_texture_rectangle */
 #define GR_GL_TEXTURE_RECTANGLE                             0x84F5
+#define GR_GL_TEXTURE_BINDING_RECTANGLE                     0x84F6
 
 /* GL_EXT_window_rectangles */
 #define GR_GL_MAX_WINDOW_RECTANGLES                         0x8f14
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index d8c43f3..a2cab2e 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -199,20 +199,29 @@
 }
 
 GrGpuResource::UniqueID GrGLGpu::TextureUnitBindings::boundID(GrGLenum target) const {
-    return fBoundResourceIDs[gl_target_to_binding_index(target)];
+    return fTargetBindings[gl_target_to_binding_index(target)].fBoundResourceID;
+}
+
+bool GrGLGpu::TextureUnitBindings::hasBeenModified(GrGLenum target) const {
+    return fTargetBindings[gl_target_to_binding_index(target)].fHasBeenModified;
 }
 
 void GrGLGpu::TextureUnitBindings::setBoundID(GrGLenum target, GrGpuResource::UniqueID resourceID) {
-    fBoundResourceIDs[gl_target_to_binding_index(target)] = resourceID;
+    int targetIndex = gl_target_to_binding_index(target);
+    fTargetBindings[targetIndex].fBoundResourceID = resourceID;
+    fTargetBindings[targetIndex].fHasBeenModified = true;
 }
 
-void GrGLGpu::TextureUnitBindings::invalidate(GrGLenum target) {
-    fBoundResourceIDs[gl_target_to_binding_index(target)].makeInvalid();
+void GrGLGpu::TextureUnitBindings::invalidateForScratchUse(GrGLenum target) {
+    this->setBoundID(target, GrGpuResource::UniqueID());
 }
 
-void GrGLGpu::TextureUnitBindings::invalidateAllTargets() {
-    for (auto& resourceID : fBoundResourceIDs) {
-        resourceID.makeInvalid();
+void GrGLGpu::TextureUnitBindings::invalidateAllTargets(bool markUnmodified) {
+    for (auto& targetBinding : fTargetBindings) {
+        targetBinding.fBoundResourceID.makeInvalid();
+        if (markUnmodified) {
+            targetBinding.fHasBeenModified = false;
+        }
     }
 }
 
@@ -586,7 +595,7 @@
 
     if (resetBits & kTextureBinding_GrGLBackendState) {
         for (int s = 0; s < this->numTextureUnits(); ++s) {
-            fHWTextureUnitBindings[s].invalidateAllTargets();
+            fHWTextureUnitBindings[s].invalidateAllTargets(false);
         }
         if (fSamplerObjectCache) {
             fSamplerObjectCache->invalidateBindings();
@@ -3107,6 +3116,20 @@
     texture->setCachedParams(samplerParamsToRecord, newNonSamplerParams, this->getResetTimestamp());
 }
 
+void GrGLGpu::onResetTextureBindings() {
+    static constexpr GrGLenum kTargets[] = {GR_GL_TEXTURE_2D, GR_GL_TEXTURE_RECTANGLE,
+                                            GR_GL_TEXTURE_EXTERNAL};
+    for (int i = 0; i < this->numTextureUnits(); ++i) {
+        this->setTextureUnit(i);
+        for (auto target : kTargets) {
+            if (fHWTextureUnitBindings[i].hasBeenModified(target)) {
+                GL_CALL(BindTexture(target, 0));
+            }
+        }
+        fHWTextureUnitBindings[i].invalidateAllTargets(true);
+    }
+}
+
 void GrGLGpu::flushColorWrite(bool writeColor) {
     if (!writeColor) {
         if (kNo_TriState != fHWWriteToColor) {
@@ -3150,7 +3173,7 @@
     }
     // Clear out the this field so that if a GrGLProgram does use this unit it will rebind the
     // correct texture.
-    fHWTextureUnitBindings[lastUnitIdx].invalidate(target);
+    fHWTextureUnitBindings[lastUnitIdx].invalidateForScratchUse(target);
     GL_CALL(BindTexture(target, textureID));
 }
 
@@ -3980,10 +4003,8 @@
     info.fTarget = GR_GL_TEXTURE_2D;
     info.fID = 0;
     GL_CALL(GenTextures(1, &info.fID));
-    GL_CALL(ActiveTexture(GR_GL_TEXTURE0));
+    this->bindTextureToScratchUnit(info.fTarget, info.fID);
     GL_CALL(PixelStorei(GR_GL_UNPACK_ALIGNMENT, 1));
-    GL_CALL(BindTexture(info.fTarget, info.fID));
-    fHWTextureUnitBindings[0].invalidate(info.fTarget);
     GL_CALL(TexParameteri(info.fTarget, GR_GL_TEXTURE_MAG_FILTER, GR_GL_NEAREST));
     GL_CALL(TexParameteri(info.fTarget, GR_GL_TEXTURE_MIN_FILTER, GR_GL_NEAREST));
     GL_CALL(TexParameteri(info.fTarget, GR_GL_TEXTURE_WRAP_S, GR_GL_CLAMP_TO_EDGE));
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index b559dee..f14bd23 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -182,6 +182,8 @@
     // GrGpu overrides
     void onResetContext(uint32_t resetBits) override;
 
+    void onResetTextureBindings() override;
+
     void xferBarrier(GrRenderTarget*, GrXferBarrierType) override;
 
     sk_sp<GrTexture> onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
@@ -603,12 +605,17 @@
         TextureUnitBindings& operator=(const TextureUnitBindings&) = delete;
 
         GrGpuResource::UniqueID boundID(GrGLenum target) const;
+        bool hasBeenModified(GrGLenum target) const;
         void setBoundID(GrGLenum target, GrGpuResource::UniqueID);
-        void invalidate(GrGLenum target);
-        void invalidateAllTargets();
+        void invalidateForScratchUse(GrGLenum target);
+        void invalidateAllTargets(bool markUnmodified);
 
     private:
-        GrGpuResource::UniqueID fBoundResourceIDs[3];
+        struct TargetBinding {
+            GrGpuResource::UniqueID fBoundResourceID;
+            bool fHasBeenModified = false;
+        };
+        TargetBinding fTargetBindings[3];
     };
     SkAutoTArray<TextureUnitBindings> fHWTextureUnitBindings;
 
diff --git a/tests/TextureBindingsResetTest.cpp b/tests/TextureBindingsResetTest.cpp
new file mode 100644
index 0000000..b338754
--- /dev/null
+++ b/tests/TextureBindingsResetTest.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrContextPriv.h"
+#include "SkSurface.h"
+#include "Test.h"
+#include "gl/GrGLDefines.h"
+#include "gl/GrGLGpu.h"
+#include "gl/GrGLUtil.h"
+
+DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(TextureBindingsResetTest, reporter, ctxInfo) {
+#define GL(F) GR_GL_CALL(ctxInfo.glContext()->gl(), F)
+
+    GrContext* context = ctxInfo.grContext();
+    GrGLGpu* gpu = static_cast<GrGLGpu*>(context->priv().getGpu());
+
+    struct Target {
+        GrGLenum fName;
+        GrGLenum fQuery;
+    };
+    SkTDArray<Target> targets;
+    targets.push_back({GR_GL_TEXTURE_2D, GR_GL_TEXTURE_BINDING_2D});
+    bool supportExternal;
+    if ((supportExternal = gpu->glCaps().shaderCaps()->externalTextureSupport())) {
+        targets.push_back({GR_GL_TEXTURE_EXTERNAL, GR_GL_TEXTURE_BINDING_EXTERNAL});
+    }
+    bool supportRectangle;
+    if ((supportRectangle = gpu->glCaps().rectangleTextureSupport())) {
+        targets.push_back({GR_GL_TEXTURE_RECTANGLE, GR_GL_TEXTURE_BINDING_RECTANGLE});
+    }
+    GrGLint numUnits;
+    GL(GetIntegerv(GR_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &numUnits));
+    SkTDArray<GrGLuint> claimedIDs;
+    claimedIDs.setCount(numUnits * targets.count());
+    GL(GenTextures(claimedIDs.count(), claimedIDs.begin()));
+
+    auto resetBindings = [&] {
+        int i = 0;
+        for (int u = 0; u < numUnits; ++u) {
+            GL(ActiveTexture(GR_GL_TEXTURE0 + u));
+            for (auto target : targets) {
+                GL(BindTexture(target.fName, claimedIDs[i++]));
+            }
+        }
+    };
+    auto checkBindings = [&] {
+        int i = 0;
+        for (int u = 0; u < numUnits; ++u) {
+            GL(ActiveTexture(GR_GL_TEXTURE0 + u));
+            for (auto target : targets) {
+                GrGLuint boundID = ~0;
+                GL(GetIntegerv(target.fQuery, reinterpret_cast<GrGLint*>(&boundID)));
+                if (boundID != claimedIDs[i] && boundID != 0) {
+                    ERRORF(reporter, "Unit %d, target 0x%04x has ID %d bound. Expected %d or 0.", u,
+                           target.fName, boundID, claimedIDs[i]);
+                    return;
+                }
+                ++i;
+            }
+        }
+    };
+
+    // Initialize texture unit/target combo bindings to 0.
+    context->flush();
+    resetBindings();
+    context->resetContext();
+
+    // Test creating a texture and then resetting bindings.
+    GrSurfaceDesc desc;
+    desc.fWidth = desc.fHeight = 10;
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    auto tex = gpu->createTexture(desc, SkBudgeted::kNo);
+    REPORTER_ASSERT(reporter, tex);
+    context->resetGLTextureBindings();
+    checkBindings();
+    resetBindings();
+    context->resetContext();
+
+    // Test drawing and then resetting bindings. This should force a MIP regeneration if MIP
+    // maps are supported as well.
+    auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+    auto surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 1, nullptr);
+    surf->getCanvas()->clear(0x80FF0000);
+    auto img = surf->makeImageSnapshot();
+    surf->getCanvas()->clear(SK_ColorBLUE);
+    surf->getCanvas()->save();
+    surf->getCanvas()->scale(0.25, 0.25);
+    SkPaint paint;
+    paint.setFilterQuality(kHigh_SkFilterQuality);
+    surf->getCanvas()->drawImage(img, 0, 0, &paint);
+    surf->getCanvas()->restore();
+    surf->flush();
+    context->resetGLTextureBindings();
+    checkBindings();
+    resetBindings();
+    context->resetContext();
+
+    if (supportExternal) {
+        GrBackendTexture texture2D = gpu->createTestingOnlyBackendTexture(
+                nullptr, 10, 10, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
+        GrGLTextureInfo info2D;
+        REPORTER_ASSERT(reporter, texture2D.getGLTextureInfo(&info2D));
+        GrEGLImage eglImage = ctxInfo.glContext()->texture2DToEGLImage(info2D.fID);
+        REPORTER_ASSERT(reporter, eglImage);
+        GrGLTextureInfo infoExternal;
+        infoExternal.fID = ctxInfo.glContext()->eglImageToExternalTexture(eglImage);
+        infoExternal.fTarget = GR_GL_TEXTURE_EXTERNAL;
+        infoExternal.fFormat = info2D.fFormat;
+        REPORTER_ASSERT(reporter, infoExternal.fID);
+        GrBackendTexture backendTexture(10, 10, GrMipMapped::kNo, infoExternal);
+        // Above texture creation will have messed with GL state and bindings.
+        resetBindings();
+        context->resetContext();
+        img = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
+                                       kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);
+        REPORTER_ASSERT(reporter, img);
+        surf->getCanvas()->drawImage(img, 0, 0);
+        img.reset();
+        surf->flush();
+        context->resetGLTextureBindings();
+        checkBindings();
+        resetBindings();
+        GL(DeleteTextures(1, &infoExternal.fID));
+        ctxInfo.glContext()->destroyEGLImage(eglImage);
+        gpu->deleteTestingOnlyBackendTexture(texture2D);
+        context->resetContext();
+    }
+
+    if (supportRectangle) {
+        GrGLuint id = ctxInfo.glContext()->createTextureRectangle(10, 10, GR_GL_RGBA, GR_GL_RGBA,
+                                                                  GR_GL_UNSIGNED_BYTE, nullptr);
+        // Above texture creation will have messed with GL state and bindings.
+        resetBindings();
+        context->resetContext();
+        if (id) {
+            GrGLTextureInfo info;
+            info.fTarget = GR_GL_TEXTURE_RECTANGLE;
+            info.fFormat = GR_GL_RGBA8;
+            info.fID = id;
+            GrBackendTexture backendTexture(10, 10, GrMipMapped::kNo, info);
+            img = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
+                                           kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);
+            REPORTER_ASSERT(reporter, img);
+            surf->getCanvas()->drawImage(img, 0, 0);
+            img.reset();
+            surf->flush();
+            context->resetGLTextureBindings();
+            checkBindings();
+            resetBindings();
+            GL(DeleteTextures(1, &id));
+            context->resetContext();
+        }
+    }
+
+    GL(DeleteTextures(claimedIDs.count(), claimedIDs.begin()));
+
+#undef GL
+}
diff --git a/tools/gpu/gl/GLTestContext.cpp b/tools/gpu/gl/GLTestContext.cpp
index e11f1b0..2e12b47 100644
--- a/tools/gpu/gl/GLTestContext.cpp
+++ b/tools/gpu/gl/GLTestContext.cpp
@@ -314,9 +314,9 @@
     }
 }
 
-GrGLint GLTestContext::createTextureRectangle(int width, int height, GrGLenum internalFormat,
-                                          GrGLenum externalFormat, GrGLenum externalType,
-                                          GrGLvoid* data) {
+GrGLuint GLTestContext::createTextureRectangle(int width, int height, GrGLenum internalFormat,
+                                               GrGLenum externalFormat, GrGLenum externalType,
+                                               GrGLvoid* data) {
     // Should match GrGLCaps check for fRectangleTextureSupport.
     if (kGL_GrGLStandard != fGL->fStandard ||
         (GrGLGetVersion(fGL.get()) < GR_GL_VER(3, 1) &&
diff --git a/tools/gpu/gl/GLTestContext.h b/tools/gpu/gl/GLTestContext.h
index d376ed6..c431664 100644
--- a/tools/gpu/gl/GLTestContext.h
+++ b/tools/gpu/gl/GLTestContext.h
@@ -32,9 +32,8 @@
     virtual void destroyEGLImage(GrEGLImage) const { }
 
     /** Used for testing GL_TEXTURE_RECTANGLE integration. */
-    GrGLint createTextureRectangle(int width, int height, GrGLenum internalFormat,
-                                   GrGLenum externalFormat, GrGLenum externalType,
-                                   GrGLvoid *data);
+    GrGLuint createTextureRectangle(int width, int height, GrGLenum internalFormat,
+                                    GrGLenum externalFormat, GrGLenum externalType, GrGLvoid* data);
 
     /**
      * Used for testing EGLImage integration. Takes a EGLImage and wraps it in a