Rework how initial clearing of texture works.

1) It only applies when a texture is created, not when recycled from cache

2) It is all textures or none, not a flag GrSurfaceDesc

3) It is implemented by GrGpu clearing the texture after creation if
such a thing is supported in underlying API. Otherwise, GrResourceProvider
must provide pre-zeroed mip levels.

4) Works for MIP mapped textures (all levels without initial data are cleared)

This could cause performance regressions in WebGL until we re-add the
ability to clear using glCear() in GL. Doing that requires making the "can
clear using GrGpu" caps query be per-format. Deferring doing that until
GrPixelConfig work is farther along.

Bug: skia:6718


Change-Id: I234715b9faaf61e8b44d54464497a17cd553585d

start

Change-Id: Ib84a8c3ece010cc3164b18895107e78484cbf76b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/226977
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h
index 4b9e8f9..7c2d97b 100644
--- a/include/gpu/GrContextOptions.h
+++ b/include/gpu/GrContextOptions.h
@@ -235,6 +235,11 @@
     bool fCacheSKSL = false;
 
     /**
+     * Enforces clearing of all textures when they're created.
+     */
+    bool fClearAllTextures = false;
+
+    /**
      * Include or exclude specific GPU path renderers.
      */
     GpuPathRenderers fGpuPathRenderers = GpuPathRenderers::kAll;
diff --git a/include/private/GrTypesPriv.h b/include/private/GrTypesPriv.h
index 6f81205..192744d 100644
--- a/include/private/GrTypesPriv.h
+++ b/include/private/GrTypesPriv.h
@@ -153,11 +153,6 @@
      * GrTexture::asRenderTarget() to access.
      */
     kRenderTarget_GrSurfaceFlag = 0x1,
-    /**
-     * Clears to zero on creation. It will cause creation failure if initial data is supplied to the
-     * texture. This only affects the base level if the texture is created with MIP levels.
-     */
-    kPerformInitialClear_GrSurfaceFlag = 0x2
 };
 GR_MAKE_BITFIELD_OPS(GrSurfaceFlags)
 
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index daffcef..145c3aa 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -111,6 +111,9 @@
     if (options.fSuppressGeometryShaders) {
         fShaderCaps->fGeometryShaderSupport = false;
     }
+    if (options.fClearAllTextures) {
+        fShouldInitializeTextures = true;
+    }
 #endif
 
     if (fMaxWindowRectangles > GrWindowRectangles::kMaxWindows) {
diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h
index 4e30936..5bd4234 100644
--- a/src/gpu/GrCaps.h
+++ b/src/gpu/GrCaps.h
@@ -291,6 +291,16 @@
      */
     bool shouldInitializeTextures() const { return fShouldInitializeTextures; }
 
+    /**
+     * When this is true it is required that all textures are initially cleared. However, the
+     * clearing must be implemented by passing level data to GrGpu::createTexture() rather than
+     * be implemeted by GrGpu::createTexture().
+     *
+     * TODO: Make this take GrBacknedFormat when canClearTextureOnCreation() does as well.
+     */
+    bool createTextureMustSpecifyAllLevels() const {
+        return this->shouldInitializeTextures() && !this->canClearTextureOnCreation();
+    }
 
     /** Returns true if the given backend supports importing AHardwareBuffers via the
      * GrAHardwarebufferImageGenerator. This will only ever be supported on Android devices with API
@@ -392,6 +402,17 @@
     virtual GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const = 0;
 
     /**
+     * Used by implementation of shouldInitializeTextures(). Indicates whether GrGpu implements the
+     * clear in GrGpu::createTexture() or if false then the caller must provide cleared MIP level
+     * data or GrGpu::createTexture() will fail.
+     *
+     * TODO: Make this take a GrBackendFormat so that GL can make this faster for cases
+     * when the format is renderable and glTexClearImage is not available. Doing this
+     * is overly complicated until the GrPixelConfig/format mess is straightened out..
+     */
+    virtual bool canClearTextureOnCreation() const = 0;
+
+    /**
      * The CLAMP_TO_BORDER wrap mode for texture coordinates was added to desktop GL in 1.3, and
      * GLES 3.2, but is also available in extensions. Vulkan and Metal always have support.
      */
diff --git a/src/gpu/GrDrawOpAtlas.cpp b/src/gpu/GrDrawOpAtlas.cpp
index 2142647..7fb8aca 100644
--- a/src/gpu/GrDrawOpAtlas.cpp
+++ b/src/gpu/GrDrawOpAtlas.cpp
@@ -514,13 +514,6 @@
     SkASSERT(SkIsPow2(fTextureWidth) && SkIsPow2(fTextureHeight));
 
     GrSurfaceDesc desc;
-    if (proxyProvider->caps()->shouldInitializeTextures()) {
-        // The atlas isn't guaranteed to touch all its pixels so, for platforms that benefit
-        // from complete initialization, clear everything.
-        desc.fFlags = kPerformInitialClear_GrSurfaceFlag;
-    } else {
-        desc.fFlags = kNone_GrSurfaceFlags;
-    }
     desc.fWidth = fTextureWidth;
     desc.fHeight = fTextureHeight;
     desc.fConfig = GrColorTypeToPixelConfig(fColorType);
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index f007304..addbc15 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -100,7 +100,7 @@
 }
 
 static bool validate_levels(int w, int h, const GrMipLevel texels[], int mipLevelCount, int bpp,
-                            const GrCaps* caps) {
+                            const GrCaps* caps, bool mustHaveDataForAllLevels = false) {
     SkASSERT(mipLevelCount > 0);
     bool hasBasePixels = texels[0].fPixels;
     int levelsWithPixelsCnt = 0;
@@ -138,7 +138,10 @@
     if (!hasBasePixels) {
         return levelsWithPixelsCnt == 0;
     }
-    return levelsWithPixelsCnt == 1 || levelsWithPixelsCnt == mipLevelCount;
+    if (levelsWithPixelsCnt == 1 && !mustHaveDataForAllLevels) {
+        return true;
+    }
+    return levelsWithPixelsCnt == mipLevelCount;
 }
 
 sk_sp<GrTexture> GrGpu::createTexture(const GrSurfaceDesc& origDesc, SkBudgeted budgeted,
@@ -162,14 +165,15 @@
     // Attempt to catch un- or wrongly initialized sample counts.
     SkASSERT(desc.fSampleCnt > 0 && desc.fSampleCnt <= 64);
 
+    bool mustHaveDataForAllLevels = this->caps()->createTextureMustSpecifyAllLevels();
     if (mipLevelCount) {
-        if (desc.fFlags & kPerformInitialClear_GrSurfaceFlag) {
-            return nullptr;
-        }
         int bpp = GrBytesPerPixel(desc.fConfig);
-        if (!validate_levels(desc.fWidth, desc.fHeight, texels, mipLevelCount, bpp, this->caps())) {
+        if (!validate_levels(desc.fWidth, desc.fHeight, texels, mipLevelCount, bpp, this->caps(),
+                             mustHaveDataForAllLevels)) {
             return nullptr;
         }
+    } else if (mustHaveDataForAllLevels) {
+        return nullptr;
     }
 
     this->handleDirtyContext();
@@ -201,6 +205,8 @@
         height < 1 || height > this->caps()->maxTextureSize()) {
         return nullptr;
     }
+    // Note if we relax the requirement that data must be provided then we must check
+    // caps()->shouldInitializeTextures() here.
     if (!data) {
         return nullptr;
     }
diff --git a/src/gpu/GrResourceProvider.cpp b/src/gpu/GrResourceProvider.cpp
index fc3d061..69f6c0f 100644
--- a/src/gpu/GrResourceProvider.cpp
+++ b/src/gpu/GrResourceProvider.cpp
@@ -45,13 +45,20 @@
 // Ensures the row bytes are populated (not 0) and makes a copy to a temporary
 // to make the row bytes tight if necessary. Returns false if the input row bytes are invalid.
 static bool prepare_level(const GrMipLevel& inLevel, size_t bpp, int w, int h, bool rowBytesSupport,
-                          GrMipLevel* outLevel, std::unique_ptr<char[]>* data) {
+                          bool mustInitializeAllLevels, GrMipLevel* outLevel,
+                          std::unique_ptr<char[]>* data) {
+    size_t minRB = w * bpp;
     if (!inLevel.fPixels) {
-        outLevel->fPixels = nullptr;
-        outLevel->fRowBytes = 0;
+        if (mustInitializeAllLevels) {
+            data->reset(new char[minRB * h]());
+            outLevel->fPixels = data->get();
+            outLevel->fRowBytes = minRB;
+        } else {
+            outLevel->fPixels = nullptr;
+            outLevel->fRowBytes = 0;
+        }
         return true;
     }
-    size_t minRB = w * bpp;
     size_t actualRB = inLevel.fRowBytes ? inLevel.fRowBytes : minRB;
     if (actualRB < minRB) {
         return false;
@@ -83,7 +90,8 @@
     if (!fCaps->validateSurfaceDesc(desc, mipMapped)) {
         return nullptr;
     }
-
+    bool mustInitializeAllLevels = this->caps()->createTextureMustSpecifyAllLevels();
+    bool rowBytesSupport = this->caps()->writePixelsRowBytesSupport();
     SkAutoSTMalloc<14, GrMipLevel> tmpTexels;
     SkAutoSTArray<14, std::unique_ptr<char[]>> tmpDatas;
     if (mipLevelCount > 0 && texels) {
@@ -93,7 +101,7 @@
         int h = desc.fHeight;
         size_t bpp = GrBytesPerPixel(desc.fConfig);
         for (int i = 0; i < mipLevelCount; ++i) {
-            if (!prepare_level(texels[i], bpp, w, h, this->caps()->writePixelsRowBytesSupport(),
+            if (!prepare_level(texels[i], bpp, w, h, rowBytesSupport, mustInitializeAllLevels,
                                &tmpTexels[i], &tmpDatas[i])) {
                 return nullptr;
             }
@@ -136,11 +144,14 @@
     GrContext* context = fGpu->getContext();
     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
 
+    bool mustInitialize = this->caps()->createTextureMustSpecifyAllLevels();
+    bool rowBytesSupport = this->caps()->writePixelsRowBytesSupport();
+
     size_t bpp = GrBytesPerPixel(desc.fConfig);
     std::unique_ptr<char[]> tmpData;
     GrMipLevel tmpLevel;
-    if (!prepare_level(mipLevel, bpp, desc.fWidth, desc.fHeight,
-                       this->caps()->writePixelsRowBytesSupport(), &tmpLevel, &tmpData)) {
+    if (!prepare_level(mipLevel, bpp, desc.fWidth, desc.fHeight, rowBytesSupport, mustInitialize,
+                       &tmpLevel, &tmpData)) {
         return nullptr;
     }
 
@@ -200,6 +211,16 @@
         }
     }
 
+    if (fCaps->createTextureMustSpecifyAllLevels()) {
+        size_t rowBytes = GrBytesPerPixel(desc.fConfig) * desc.fWidth;
+        size_t size = rowBytes * desc.fHeight;
+        std::unique_ptr<char[]> zeros(new char[size]());
+        GrMipLevel level;
+        level.fRowBytes = rowBytes;
+        level.fPixels = zeros.get();
+        return fGpu->createTexture(desc, budgeted, &level, 1);
+    }
+
     return fGpu->createTexture(desc, budgeted);
 }
 
@@ -254,8 +275,7 @@
     SkTCopyOnFirstWrite<GrSurfaceDesc> copyDesc(desc);
 
     // bin by some multiple or power of 2 with a reasonable min
-    if (!SkToBool(desc.fFlags & kPerformInitialClear_GrSurfaceFlag) &&
-        (fGpu->caps()->reuseScratchTextures() || (desc.fFlags & kRenderTarget_GrSurfaceFlag))) {
+    if (fGpu->caps()->reuseScratchTextures() || (desc.fFlags & kRenderTarget_GrSurfaceFlag)) {
         GrSurfaceDesc* wdesc = copyDesc.writable();
         wdesc->fWidth = MakeApprox(wdesc->fWidth);
         wdesc->fHeight = MakeApprox(wdesc->fHeight);
@@ -265,6 +285,15 @@
         return tex;
     }
 
+    if (this->caps()->createTextureMustSpecifyAllLevels()) {
+        size_t rowBytes = GrBytesPerPixel(copyDesc->fConfig) * copyDesc->fWidth;
+        size_t size = rowBytes * copyDesc->fHeight;
+        std::unique_ptr<char[]> zeros(new char[size]());
+        GrMipLevel level;
+        level.fRowBytes = rowBytes;
+        level.fPixels = zeros.get();
+        return fGpu->createTexture(*copyDesc, SkBudgeted::kYes, &level, 1);
+    }
     return fGpu->createTexture(*copyDesc, SkBudgeted::kYes);
 }
 
@@ -276,9 +305,7 @@
 
     // We could make initial clears work with scratch textures but it is a rare case so we just opt
     // to fall back to making a new texture.
-    if (!SkToBool(desc.fFlags & kPerformInitialClear_GrSurfaceFlag) &&
-        (fGpu->caps()->reuseScratchTextures() || (desc.fFlags & kRenderTarget_GrSurfaceFlag))) {
-
+    if (fGpu->caps()->reuseScratchTextures() || (desc.fFlags & kRenderTarget_GrSurfaceFlag)) {
         GrScratchKey key;
         GrTexturePriv::ComputeScratchKey(desc, &key);
         auto scratchFlags = GrResourceCache::ScratchFlags::kNone;
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index 8f3b39e..afd687a 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -107,10 +107,6 @@
         return nullptr;
     }
 
-    auto clearFlag = kNone_GrSurfaceFlags;
-    if (context->priv().caps()->shouldInitializeTextures() && fit == SkBackingFit::kApprox) {
-        clearFlag = kPerformInitialClear_GrSurfaceFlag;
-    }
-    return context->priv().proxyProvider()->createTextureProxy(
-            std::move(img), clearFlag, 1, SkBudgeted::kYes, fit);
+    return context->priv().proxyProvider()->createTextureProxy(std::move(img), kNone_GrSurfaceFlags,
+                                                               1, SkBudgeted::kYes, fit);
 }
diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp
index 9bcfa7c..b24dc45 100644
--- a/src/gpu/GrSurfaceProxy.cpp
+++ b/src/gpu/GrSurfaceProxy.cpp
@@ -69,7 +69,6 @@
         , fBudgeted(budgeted)
         , fLazyInstantiateCallback(std::move(callback))
         , fLazyInstantiationType(lazyType)
-        , fNeedsClear(SkToBool(desc.fFlags & kPerformInitialClear_GrSurfaceFlag))
         , fIsProtected(desc.fIsProtected)
         , fGpuMemorySize(kInvalidGpuMemorySize)
         , fLastOpList(nullptr) {
@@ -103,7 +102,6 @@
                             ? SkBudgeted::kYes
                             : SkBudgeted::kNo)
         , fUniqueID(fTarget->uniqueID())  // Note: converting from unique resource ID to a proxy ID!
-        , fNeedsClear(false)
         , fIsProtected(fTarget->isProtected() ? GrProtected::kYes : GrProtected::kNo)
         , fGpuMemorySize(kInvalidGpuMemorySize)
         , fLastOpList(nullptr) {
@@ -141,9 +139,6 @@
     SkASSERT(!fTarget);
     GrSurfaceDesc desc;
     desc.fFlags = descFlags;
-    if (fNeedsClear) {
-        desc.fFlags |= kPerformInitialClear_GrSurfaceFlag;
-    }
     desc.fWidth = fWidth;
     desc.fHeight = fHeight;
     desc.fIsProtected = fIsProtected;
diff --git a/src/gpu/GrSurfaceProxy.h b/src/gpu/GrSurfaceProxy.h
index 9dd5f92..522cdc8 100644
--- a/src/gpu/GrSurfaceProxy.h
+++ b/src/gpu/GrSurfaceProxy.h
@@ -423,7 +423,6 @@
 
     virtual size_t onUninstantiatedGpuMemorySize() const = 0;
 
-    bool                   fNeedsClear;
     bool                   fIgnoredByResourceAllocator = false;
     GrProtected            fIsProtected;
 
diff --git a/src/gpu/GrYUVProvider.cpp b/src/gpu/GrYUVProvider.cpp
index 21da8a0..f7a1e50 100644
--- a/src/gpu/GrYUVProvider.cpp
+++ b/src/gpu/GrYUVProvider.cpp
@@ -151,9 +151,6 @@
 
         auto proxyProvider = ctx->priv().proxyProvider();
         auto clearFlag = kNone_GrSurfaceFlags;
-        if (ctx->priv().caps()->shouldInitializeTextures() && fit == SkBackingFit::kApprox) {
-            clearFlag = kPerformInitialClear_GrSurfaceFlag;
-        }
         yuvTextureProxies[i] = proxyProvider->createTextureProxy(yuvImage, clearFlag,
                                                                  1, SkBudgeted::kYes, fit);
 
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 802586e..a3ecdd9 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -3919,6 +3919,8 @@
     return {};
 }
 
+bool GrGLCaps::canClearTextureOnCreation() const { return fClearTextureSupport; }
+
 #ifdef SK_DEBUG
 static bool format_color_type_valid_pair(GrGLenum format, GrColorType colorType) {
     switch (colorType) {
diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h
index 839a4fa..7b8c61a 100644
--- a/src/gpu/gl/GrGLCaps.h
+++ b/src/gpu/gl/GrGLCaps.h
@@ -438,6 +438,8 @@
     GrBackendFormat getBackendFormatFromColorType(GrColorType ct) const override;
     GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override;
 
+    bool canClearTextureOnCreation() const override;
+
     GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override;
     GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const override;
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index fa92dff..1cd837c 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1469,31 +1469,9 @@
                                           int mipLevelCount) {
     // We fail if the MSAA was requested and is not available.
     if (GrGLCaps::kNone_MSFBOType == this->glCaps().msFBOType() && desc.fSampleCnt > 1) {
-        //SkDebugf("MSAA RT requested but not supported on this platform.");
         return return_null_texture();
     }
 
-    GrGLFormat glFormat =
-            GrGLFormatFromGLEnum(this->glCaps().configSizedInternalFormat(desc.fConfig));
-
-    bool performClear = (desc.fFlags & kPerformInitialClear_GrSurfaceFlag) &&
-                        !GrGLFormatIsCompressed(glFormat);
-
-    GrMipLevel zeroLevel;
-    std::unique_ptr<uint8_t[]> zeros;
-    if (performClear && !this->glCaps().clearTextureSupport() &&
-        !this->glCaps().canConfigBeFBOColorAttachment(desc.fConfig)) {
-        size_t rowBytes = GrGLBytesPerFormat(glFormat) * desc.fWidth;
-        size_t size = rowBytes * desc.fHeight;
-        zeros.reset(new uint8_t[size]);
-        memset(zeros.get(), 0, size);
-        zeroLevel.fPixels = zeros.get();
-        zeroLevel.fRowBytes = rowBytes;
-        texels = &zeroLevel;
-        mipLevelCount = 1;
-        performClear = false;
-    }
-
     bool isRenderTarget = SkToBool(desc.fFlags & kRenderTarget_GrSurfaceFlag);
 
     GrGLTexture::IDDesc idDesc;
@@ -1529,19 +1507,17 @@
     SkDebugf("--- new texture [%d] size=(%d %d) config=%d\n",
              idDesc.fInfo.fID, desc.fWidth, desc.fHeight, desc.fConfig);
 #endif
-    if (tex && performClear) {
-        if (this->glCaps().clearTextureSupport()) {
-            static constexpr uint32_t kZero = 0;
-            GL_CALL(ClearTexImage(tex->textureID(), 0, GR_GL_RGBA, GR_GL_UNSIGNED_BYTE, &kZero));
-        } else {
-            this->bindSurfaceFBOForPixelOps(tex.get(), GR_GL_FRAMEBUFFER, kDst_TempFBOTarget);
-            this->disableScissor();
-            this->disableWindowRectangles();
-            this->flushColorWrite(true);
-            this->flushClearColor(0, 0, 0, 0);
-            GL_CALL(Clear(GR_GL_COLOR_BUFFER_BIT));
-            this->unbindTextureFBOForPixelOps(GR_GL_FRAMEBUFFER, tex.get());
-            fHWBoundRenderTargetUniqueID.makeInvalid();
+    bool clearLevelsWithoutData =
+            this->caps()->shouldInitializeTextures() && this->glCaps().clearTextureSupport();
+
+    if (clearLevelsWithoutData) {
+        static constexpr uint32_t kZero = 0;
+        int levelCnt = SkTMax(1, tex->texturePriv().maxMipMapLevel());
+        for (int i = 0; i < levelCnt; ++i) {
+            if (i >= mipLevelCount || !texels[i].fPixels) {
+                GL_CALL(ClearTexImage(tex->textureID(), i, GR_GL_RGBA, GR_GL_UNSIGNED_BYTE,
+                                      &kZero));
+            }
         }
     }
     return std::move(tex);
diff --git a/src/gpu/mock/GrMockCaps.h b/src/gpu/mock/GrMockCaps.h
index 08ce87f..461b332 100644
--- a/src/gpu/mock/GrMockCaps.h
+++ b/src/gpu/mock/GrMockCaps.h
@@ -144,6 +144,8 @@
         return {};
     }
 
+    bool canClearTextureOnCreation() const override { return true; }
+
     GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override {
         return GrSwizzle();
     }
diff --git a/src/gpu/mtl/GrMtlCaps.h b/src/gpu/mtl/GrMtlCaps.h
index d431022..5271019 100644
--- a/src/gpu/mtl/GrMtlCaps.h
+++ b/src/gpu/mtl/GrMtlCaps.h
@@ -75,6 +75,8 @@
     GrBackendFormat getBackendFormatFromColorType(GrColorType ct) const override;
     GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override;
 
+    bool canClearTextureOnCreation() const override { return true; }
+
     GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override;
     GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const override;
 
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index 42f684d..4b68cff 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -208,8 +208,8 @@
     // Function that uploads data onto textures with private storage mode (GPU access only).
     bool uploadToTexture(GrMtlTexture* tex, int left, int top, int width, int height,
                          GrColorType dataColorType, const GrMipLevel texels[], int mipLevels);
-    // Function that fills texture with transparent black
-    bool clearTexture(GrMtlTexture*, GrColorType);
+    // Function that fills texture levels with transparent black based on levelMask.
+    bool clearTexture(GrMtlTexture*, GrColorType, uint32_t levelMask);
 
     GrStencilAttachment* createStencilAttachmentForRenderTarget(
             const GrRenderTarget*, int width, int height, int numStencilSamples) override;
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index a474256..6cf0f8c 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -302,9 +302,13 @@
     return true;
 }
 
-bool GrMtlGpu::clearTexture(GrMtlTexture* tex, GrColorType dataColorType) {
+bool GrMtlGpu::clearTexture(GrMtlTexture* tex, GrColorType dataColorType, uint32_t levelMask) {
     SkASSERT(this->caps()->isConfigTexturable(tex->config()));
 
+    if (!levelMask) {
+        return true;
+    }
+
     id<MTLTexture> mtlTexture = tex->mtlTexture();
     SkASSERT(mtlTexture);
     // Either upload only the first miplevel or all miplevels
@@ -314,34 +318,30 @@
     size_t bpp = GrColorTypeBytesPerPixel(dataColorType);
 
     SkTArray<size_t> individualMipOffsets(mipLevelCount);
-    individualMipOffsets.push_back(0);
-    int width = tex->width();
-    int height = tex->height();
-    size_t combinedBufferSize = width * bpp * height;
-    int currentWidth = width;
-    int currentHeight = height;
+    size_t combinedBufferSize = 0;
+    int currentWidth = tex->width();
+    int currentHeight = tex->height();
 
     // The alignment must be at least 4 bytes and a multiple of the bytes per pixel of the image
     // config. This works with the assumption that the bytes in pixel config is always a power of 2.
-    // TODO: can we just copy from a single buffer the size of the top level w/o a perf penalty?
+    // TODO: can we just copy from a single buffer the size of the largest cleared level w/o a perf
+    // penalty?
     SkASSERT((bpp & (bpp - 1)) == 0);
     const size_t alignmentMask = 0x3 | (bpp - 1);
-    for (int currentMipLevel = 1; currentMipLevel < mipLevelCount; currentMipLevel++) {
+    for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
+        if (levelMask & (1 << currentMipLevel)) {
+            const size_t trimmedSize = currentWidth * bpp * currentHeight;
+            const size_t alignmentDiff = combinedBufferSize & alignmentMask;
+            if (alignmentDiff != 0) {
+                combinedBufferSize += alignmentMask - alignmentDiff + 1;
+            }
+            individualMipOffsets.push_back(combinedBufferSize);
+            combinedBufferSize += trimmedSize;
+        }
         currentWidth = SkTMax(1, currentWidth/2);
         currentHeight = SkTMax(1, currentHeight/2);
-
-        const size_t trimmedSize = currentWidth * bpp * currentHeight;
-        const size_t alignmentDiff = combinedBufferSize & alignmentMask;
-        if (alignmentDiff != 0) {
-            combinedBufferSize += alignmentMask - alignmentDiff + 1;
-        }
-        individualMipOffsets.push_back(combinedBufferSize);
-        combinedBufferSize += trimmedSize;
     }
-    if (0 == combinedBufferSize) {
-        // We don't actually have any data to upload so just return success
-        return true;
-    }
+    SkASSERT(combinedBufferSize > 0 && !individualMipOffsets.empty());
 
     // TODO: Create GrMtlTransferBuffer
     id<MTLBuffer> transferBuffer = [fDevice newBufferWithLength: combinedBufferSize
@@ -360,21 +360,23 @@
                          value: 0];
 
     // now copy buffer to texture
-    currentWidth = width;
-    currentHeight = height;
+    currentWidth = tex->width();
+    currentHeight = tex->height();
     MTLOrigin origin = MTLOriginMake(0, 0, 0);
     for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
-        const size_t rowBytes = currentWidth * bpp;
+        if (levelMask & (1 << currentMipLevel)) {
+            const size_t rowBytes = currentWidth * bpp;
 
-        [blitCmdEncoder copyFromBuffer: transferBuffer
-                          sourceOffset: individualMipOffsets[currentMipLevel]
-                     sourceBytesPerRow: rowBytes
-                   sourceBytesPerImage: rowBytes*currentHeight
-                            sourceSize: MTLSizeMake(currentWidth, currentHeight, 1)
-                             toTexture: mtlTexture
-                      destinationSlice: 0
-                      destinationLevel: currentMipLevel
-                     destinationOrigin: origin];
+            [blitCmdEncoder copyFromBuffer: transferBuffer
+                              sourceOffset: individualMipOffsets[currentMipLevel]
+                         sourceBytesPerRow: rowBytes
+                       sourceBytesPerImage: rowBytes * currentHeight
+                                sourceSize: MTLSizeMake(currentWidth, currentHeight, 1)
+                                 toTexture: mtlTexture
+                          destinationSlice: 0
+                          destinationLevel: currentMipLevel
+                         destinationOrigin: origin];
+        }
         currentWidth = SkTMax(1, currentWidth/2);
         currentHeight = SkTMax(1, currentHeight/2);
     }
@@ -473,8 +475,15 @@
         }
     }
 
-    if (desc.fFlags & kPerformInitialClear_GrSurfaceFlag) {
-        this->clearTexture(tex.get(), colorType);
+    if (this->caps()->shouldInitializeTextures()) {
+        uint32_t levelMask = ~0;
+        SkASSERT(mipLevelCount < 32);
+        for (int i = 0; i < mipLevelCount; ++i) {
+            if (!texels[i].fPixels) {
+                levelMask &= ~(1 << i);
+            }
+        }
+        this->clearTexture(tex.get(), colorType, levelMask);
     }
 
     return std::move(tex);
diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index ff56ebe..3fbc63b 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -1147,6 +1147,8 @@
     return {};
 }
 
+bool GrVkCaps::canClearTextureOnCreation() const { return true; }
+
 #ifdef SK_DEBUG
 static bool format_color_type_valid_pair(VkFormat vkFormat, GrColorType colorType) {
     switch (colorType) {
diff --git a/src/gpu/vk/GrVkCaps.h b/src/gpu/vk/GrVkCaps.h
index 911c80f..8e3eff6 100644
--- a/src/gpu/vk/GrVkCaps.h
+++ b/src/gpu/vk/GrVkCaps.h
@@ -169,6 +169,8 @@
     GrBackendFormat getBackendFormatFromColorType(GrColorType ct) const override;
     GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override;
 
+    bool canClearTextureOnCreation() const override;
+
     GrSwizzle getTextureSwizzle(const GrBackendFormat&, GrColorType) const override;
     GrSwizzle getOutputSwizzle(const GrBackendFormat&, GrColorType) const override;
 
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 7a9811a..0cbafbe 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -1018,18 +1018,35 @@
         }
     }
 
-    if (SkToBool(desc.fFlags & kPerformInitialClear_GrSurfaceFlag)) {
-        VkClearColorValue zeroClearColor;
-        memset(&zeroClearColor, 0, sizeof(zeroClearColor));
-        VkImageSubresourceRange range;
-        range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-        range.baseArrayLayer = 0;
-        range.baseMipLevel = 0;
-        range.layerCount = 1;
-        range.levelCount = 1;
-        tex->setImageLayout(this, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-                            VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, false);
-        this->currentCommandBuffer()->clearColorImage(this, tex.get(), &zeroClearColor, 1, &range);
+    if (this->caps()->shouldInitializeTextures()) {
+        SkSTArray<1, VkImageSubresourceRange> ranges;
+        bool inRange = false;
+        for (uint32_t i = 0; i < tex->mipLevels(); ++i) {
+            if (i >= static_cast<uint32_t>(mipLevelCount) || !texels[i].fPixels) {
+                if (inRange) {
+                    ranges.back().levelCount++;
+                } else {
+                    auto& range = ranges.push_back();
+                    range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+                    range.baseArrayLayer = 0;
+                    range.baseMipLevel = i;
+                    range.layerCount = 1;
+                    range.levelCount = 1;
+                    inRange = true;
+                }
+            } else if (inRange) {
+                inRange = false;
+            }
+        }
+
+        if (!ranges.empty()) {
+            static constexpr VkClearColorValue kZeroClearColor = {};
+            tex->setImageLayout(this, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+                                VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+                                false);
+            this->currentCommandBuffer()->clearColorImage(this, tex.get(), &kZeroClearColor,
+                                                          ranges.count(), ranges.begin());
+        }
     }
     return std::move(tex);
 }
diff --git a/tests/GrSurfaceTest.cpp b/tests/GrSurfaceTest.cpp
index e95fd47..e16eb89 100644
--- a/tests/GrSurfaceTest.cpp
+++ b/tests/GrSurfaceTest.cpp
@@ -194,33 +194,40 @@
 #include "src/gpu/GrSurfaceProxy.h"
 #include "src/gpu/GrTextureContext.h"
 
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(InitialTextureClear, reporter, context_info) {
+DEF_GPUTEST(InitialTextureClear, reporter, baseOptions) {
+    GrContextOptions options = baseOptions;
+    options.fClearAllTextures = true;
     static constexpr int kSize = 100;
-    GrSurfaceDesc desc;
-    desc.fWidth = desc.fHeight = kSize;
     std::unique_ptr<uint32_t[]> data(new uint32_t[kSize * kSize]);
-
-    GrContext* context = context_info.grContext();
-    const GrCaps* caps = context->priv().caps();
-    GrProxyProvider* proxyProvider = context->priv().proxyProvider();
-
-    for (int c = 0; c <= kLast_GrPixelConfig; ++c) {
-        desc.fConfig = static_cast<GrPixelConfig>(c);
-        if (!caps->isConfigTexturable(desc.fConfig)) {
+    for (int ct = 0; ct < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++ct) {
+        sk_gpu_test::GrContextFactory factory(options);
+        auto contextType = static_cast<sk_gpu_test::GrContextFactory::ContextType>(ct);
+        if (!sk_gpu_test::GrContextFactory::IsRenderingContext(contextType)) {
             continue;
         }
-        desc.fFlags = kPerformInitialClear_GrSurfaceFlag;
-        for (bool rt : {false, true}) {
-            if (rt && !caps->isConfigRenderable(desc.fConfig)) {
+        auto context = factory.get(contextType);
+        if (!context) {
+            continue;
+        }
+        GrSurfaceDesc desc;
+        desc.fWidth = desc.fHeight = kSize;
+
+        const GrCaps* caps = context->priv().caps();
+        GrProxyProvider* proxyProvider = context->priv().proxyProvider();
+
+        for (int c = 0; c <= kLast_GrPixelConfig; ++c) {
+            desc.fConfig = static_cast<GrPixelConfig>(c);
+            if (!caps->isConfigTexturable(desc.fConfig)) {
                 continue;
             }
-            desc.fFlags |= rt ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
-            for (GrSurfaceOrigin origin :
-                 {kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin}) {
-                for (auto fit : { SkBackingFit::kApprox, SkBackingFit::kExact }) {
-                    // Try directly creating the texture.
-                    // Do this twice in an attempt to hit the cache on the second time through.
-                    for (int i = 0; i < 2; ++i) {
+            for (bool rt : {false, true}) {
+                if (rt && !caps->isConfigRenderable(desc.fConfig)) {
+                    continue;
+                }
+                desc.fFlags |= rt ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
+                for (GrSurfaceOrigin origin :
+                     {kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin}) {
+                    for (auto fit : {SkBackingFit::kApprox, SkBackingFit::kExact}) {
                         auto proxy = proxyProvider->testingOnly_createInstantiatedProxy(
                                 desc, origin, fit, SkBudgeted::kYes);
                         if (!proxy) {
@@ -229,8 +236,8 @@
                         auto texCtx = context->priv().makeWrappedSurfaceContext(
                                 std::move(proxy), GrPixelConfigToColorType(desc.fConfig),
                                 kPremul_SkAlphaType);
-                        SkImageInfo info = SkImageInfo::Make(
-                                kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+                        SkImageInfo info = SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType,
+                                                             kPremul_SkAlphaType);
                         memset(data.get(), 0xAB, kSize * kSize * sizeof(uint32_t));
                         if (texCtx->readPixels(info, data.get(), 0, {0, 0})) {
                             uint32_t cmp = GrPixelConfigIsOpaque(desc.fConfig) ? 0xFF000000 : 0;
@@ -241,73 +248,64 @@
                                 }
                             }
                         }
-                        memset(data.get(), 0xBC, kSize * kSize * sizeof(uint32_t));
-                        // Here we overwrite the texture so that the second time through we
-                        // test against recycling without reclearing.
-                        if (0 == i) {
-                            texCtx->writePixels(info, data.get(), 0, {0, 0});
-                        }
-                    }
-                    context->priv().testingOnly_purgeAllUnlockedResources();
+                        context->priv().testingOnly_purgeAllUnlockedResources();
 
-                    // We don't round trip correctly going from pixelConfig to colorType to
-                    // backendFormat with the RGBX config. The actual config stored on the GrSurface
-                    // will be RGBA_8888 but the format we create below will say it is RGB_888.
-                    if (desc.fConfig == kRGB_888X_GrPixelConfig) {
-                        continue;
-                    }
-
-                    // The specific kAlpha_* pixel configs also cause difficulties with OpenGL.
-                    // The mapping from config -> colorType -> format doesn't necessarily
-                    // resolve back to the expected pixel config. In this case we just test
-                    // the generics.
-                    if (GrBackendApi::kOpenGL == context->backend() &&
-                        (desc.fConfig == kAlpha_8_as_Alpha_GrPixelConfig ||
-                         desc.fConfig == kAlpha_8_as_Red_GrPixelConfig ||
-                         desc.fConfig == kAlpha_half_as_Red_GrPixelConfig)) {
-                        continue;
-                    }
-
-                    // The specific kGray_8_* pixel configs also cause difficulties with OpenGL.
-                    // The mapping from config -> colorType -> format doesn't necessarily
-                    // resolve back to the expected pixel config. In this case we just test
-                    // the generic.
-                    if (GrBackendApi::kOpenGL == context->backend() &&
-                        (desc.fConfig == kGray_8_as_Lum_GrPixelConfig ||
-                         desc.fConfig == kGray_8_as_Red_GrPixelConfig)) {
-                        continue;
-                    }
-
-                    GrColorType colorType = GrPixelConfigToColorType(desc.fConfig);
-                    const GrBackendFormat format = caps->getBackendFormatFromColorType(colorType);
-
-                    // Try creating the texture as a deferred proxy.
-                    for (int i = 0; i < 2; ++i) {
-                        auto surfCtx = context->priv().makeDeferredSurfaceContext(
-                                format, desc, origin, GrMipMapped::kNo, fit, SkBudgeted::kYes,
-                                colorType, kPremul_SkAlphaType);
-                        if (!surfCtx) {
+                        // We don't round trip correctly going from pixelConfig to colorType to
+                        // backendFormat with the RGBX config. The actual config stored on the
+                        // GrSurface will be RGBA_8888 but the format we create below will say it is
+                        // RGB_888.
+                        if (desc.fConfig == kRGB_888X_GrPixelConfig) {
                             continue;
                         }
-                        SkImageInfo info = SkImageInfo::Make(
-                                kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
-                        memset(data.get(), 0xAB, kSize * kSize * sizeof(uint32_t));
-                        if (surfCtx->readPixels(info, data.get(), 0, {0, 0})) {
-                            uint32_t cmp = GrPixelConfigIsOpaque(desc.fConfig) ? 0xFF000000 : 0;
-                            for (int i = 0; i < kSize * kSize; ++i) {
-                                if (cmp != data.get()[i]) {
-                                    ERRORF(reporter, "Failed on config %d", desc.fConfig);
-                                    break;
+
+                        // The specific kAlpha_* pixel configs also cause difficulties with OpenGL.
+                        // The mapping from config -> colorType -> format doesn't necessarily
+                        // resolve back to the expected pixel config. In this case we just test
+                        // the generics.
+                        if (GrBackendApi::kOpenGL == context->backend() &&
+                            (desc.fConfig == kAlpha_8_as_Alpha_GrPixelConfig ||
+                             desc.fConfig == kAlpha_8_as_Red_GrPixelConfig ||
+                             desc.fConfig == kAlpha_half_as_Red_GrPixelConfig)) {
+                            continue;
+                        }
+
+                        // The specific kGray_8_* pixel configs also cause difficulties with OpenGL.
+                        // The mapping from config -> colorType -> format doesn't necessarily
+                        // resolve back to the expected pixel config. In this case we just test
+                        // the generic.
+                        if (GrBackendApi::kOpenGL == context->backend() &&
+                            (desc.fConfig == kGray_8_as_Lum_GrPixelConfig ||
+                             desc.fConfig == kGray_8_as_Red_GrPixelConfig)) {
+                            continue;
+                        }
+
+                        GrColorType colorType = GrPixelConfigToColorType(desc.fConfig);
+                        const GrBackendFormat format =
+                                caps->getBackendFormatFromColorType(colorType);
+
+                        // Try creating the texture as a deferred proxy.
+                        for (int i = 0; i < 2; ++i) {
+                            auto surfCtx = context->priv().makeDeferredSurfaceContext(
+                                    format, desc, origin, GrMipMapped::kNo, fit, SkBudgeted::kYes,
+                                    colorType, kPremul_SkAlphaType);
+                            if (!surfCtx) {
+                                continue;
+                            }
+                            SkImageInfo info = SkImageInfo::Make(
+                                    kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+                            memset(data.get(), 0xAB, kSize * kSize * sizeof(uint32_t));
+                            if (surfCtx->readPixels(info, data.get(), 0, {0, 0})) {
+                                uint32_t cmp = GrPixelConfigIsOpaque(desc.fConfig) ? 0xFF000000 : 0;
+                                for (int i = 0; i < kSize * kSize; ++i) {
+                                    if (cmp != data.get()[i]) {
+                                        ERRORF(reporter, "Failed on config %d", desc.fConfig);
+                                        break;
+                                    }
                                 }
                             }
-                        }
-                        // Here we overwrite the texture so that the second time through we
-                        // test against recycling without reclearing.
-                        if (0 == i) {
-                            surfCtx->writePixels(info, data.get(), 0, {0, 0});
+                            context->priv().testingOnly_purgeAllUnlockedResources();
                         }
                     }
-                    context->priv().testingOnly_purgeAllUnlockedResources();
                 }
             }
         }