Modify fontcache GM to actually spill atlas.

Adds an option to GrDrawOpAtlas to disable multitexturing.

Adds option to GrContextOptions to disable multitexturing for glyph atlases.


Change-Id: If413ab7061538fa0e75628d252be4fd14215b6ba
Reviewed-on: https://skia-review.googlesource.com/67802
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/gm/fontcache.cpp b/gm/fontcache.cpp
index a8c5b76..b93cc3f 100644
--- a/gm/fontcache.cpp
+++ b/gm/fontcache.cpp
@@ -5,11 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "gm.h"
-#include "sk_tool_utils.h"
+#include "GrContext.h"
+#include "GrContextOptions.h"
 #include "SkCanvas.h"
 #include "SkGraphics.h"
+#include "SkImage.h"
 #include "SkTypeface.h"
+#include "gm.h"
+#include "sk_tool_utils.h"
 
 // GM to stress the GPU font cache
 
@@ -21,58 +24,94 @@
 
 class FontCacheGM : public skiagm::GM {
 public:
-    FontCacheGM() {}
+    FontCacheGM() { this->setBGColor(SK_ColorLTGRAY); }
+
+    void modifyGrContextOptions(GrContextOptions* options) override {
+        options->fGlyphCacheTextureMaximumBytes = 0;
+        options->fAllowMultipleGlyphCacheTextures = GrContextOptions::Enable::kNo;
+    }
 
 protected:
     SkString onShortName() override {
         return SkString("fontcache");
     }
 
-    SkISize onISize() override {
-        return SkISize::Make(1280, 640);
-    }
+    SkISize onISize() override { return SkISize::Make(kSize, kSize); }
 
     void onOnceBeforeDraw() override {
         fTypefaces[0] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Italic());
         fTypefaces[1] = sk_tool_utils::create_portable_typeface("sans-serif",SkFontStyle::Italic());
+        fTypefaces[2] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Normal());
+        fTypefaces[3] =
+                sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
+        fTypefaces[4] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Bold());
+        fTypefaces[5] = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
     }
 
     void onDraw(SkCanvas* canvas) override {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
-        paint.setSubpixelText(true);
-        paint.setTypeface(fTypefaces[0]);
-        paint.setTextSize(192);
-
-        // Make sure the nul character does not cause problems.
-        paint.measureText("\0", 1);
-
-        SkScalar x = 20;
-        SkScalar y = 128;
-        SkString text("ABCDEFGHIJ");
-        draw_string(canvas, text, x, y, paint);
-        y += 100;
-        SkString text2("KLMNOPQRS");
-        draw_string(canvas, text2, x, y, paint);
-        y += 100;
-        SkString text3("TUVWXYZ012");
-        draw_string(canvas, text3, x, y, paint);
-        y += 100;
-        paint.setTypeface(fTypefaces[1]);
-        draw_string(canvas, text, x, y, paint);
-        y += 100;
-        draw_string(canvas, text2, x, y, paint);
-        y += 100;
-        draw_string(canvas, text3, x, y, paint);
-        y += 100;
+        canvas->clear(SK_ColorLTGRAY);
+        this->drawText(canvas);
+        //  Debugging tool for GPU.
+        static const bool kShowAtlas = false;
+        if (kShowAtlas) {
+            if (auto ctx = canvas->getGrContext()) {
+                auto img = ctx->getFontAtlasImage_ForTesting(kA8_GrMaskFormat);
+                canvas->drawImage(img, 0, 0);
+            }
+        }
     }
 
 private:
-    sk_sp<SkTypeface> fTypefaces[2];
+    void drawText(SkCanvas* canvas) {
+        static const int kSizes[] = {8, 9, 10, 11, 12, 13, 18, 20, 25};
+
+        static const SkString kTexts[] = {SkString("ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+                                          SkString("abcdefghijklmnopqrstuvwxyz"),
+                                          SkString("0123456789"),
+                                          SkString("!@#$%^&*()<>[]{}")};
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setLCDRenderText(false);
+        paint.setSubpixelText(true);
+
+        static const SkScalar kSubPixelInc = 1 / 2.f;
+        SkScalar x = 0;
+        SkScalar y = 10;
+        SkScalar subpixelX = 0;
+        SkScalar subpixelY = 0;
+        bool offsetX = true;
+
+        do {
+            for (auto s : kSizes) {
+                auto size = 2 * s;
+                paint.setTextSize(size);
+                for (const auto& typeface : fTypefaces) {
+                    paint.setTypeface(typeface);
+                    for (const auto& text : kTexts) {
+                        x = size + draw_string(canvas, text, x + subpixelX, y + subpixelY, paint);
+                        x = SkScalarCeilToScalar(x);
+                        if (x + 100 > kSize) {
+                            x = 0;
+                            y += SkScalarCeilToScalar(size + 3);
+                            if (y > kSize) {
+                                return;
+                            }
+                        }
+                    }
+                }
+                (offsetX ? subpixelX : subpixelY) += kSubPixelInc;
+                offsetX = !offsetX;
+            }
+        } while (true);
+    }
+
+    static constexpr SkScalar kSize = 1280;
+
+    sk_sp<SkTypeface> fTypefaces[6];
     typedef GM INHERITED;
 };
 
+constexpr SkScalar FontCacheGM::kSize;
 
 //////////////////////////////////////////////////////////////////////////////
 
diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h
index 7aa85e0..a3c914a 100644
--- a/include/gpu/GrContextOptions.h
+++ b/include/gpu/GrContextOptions.h
@@ -114,6 +114,12 @@
     float fGlyphCacheTextureMaximumBytes = 2048 * 1024 * 4;
 
     /**
+     * Can the glyph atlas use multiple textures. If allowed, the each texture's size is bound by
+     * fGlypheCacheTextureMaximumBytes.
+     */
+    Enable fAllowMultipleGlyphCacheTextures = Enable::kDefault;
+
+    /**
      * Bugs on certain drivers cause stencil buffers to leak. This flag causes Skia to avoid
      * allocating stencil buffers and use alternate rasterization paths, avoiding the leak.
      */
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 2146858..d3194be 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -204,7 +204,14 @@
     }
     fDrawingManager.reset(new GrDrawingManager(this, prcOptions, &fSingleOwner));
 
-    fAtlasGlyphCache = new GrAtlasGlyphCache(this, options.fGlyphCacheTextureMaximumBytes);
+    GrDrawOpAtlas::AllowMultitexturing allowMultitexturing;
+    if (options.fAllowMultipleGlyphCacheTextures == GrContextOptions::Enable::kNo) {
+        allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kNo;
+    } else {
+        allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kYes;
+    }
+    fAtlasGlyphCache = new GrAtlasGlyphCache(this, options.fGlyphCacheTextureMaximumBytes,
+                                             allowMultitexturing);
     this->contextPriv().addOnFlushCallbackObject(fAtlasGlyphCache);
 
     fTextBlobCache.reset(new GrTextBlobCache(TextBlobCacheOverBudgetCB, this));
diff --git a/src/gpu/GrDrawOpAtlas.cpp b/src/gpu/GrDrawOpAtlas.cpp
index 8d81285..c0358b7 100644
--- a/src/gpu/GrDrawOpAtlas.cpp
+++ b/src/gpu/GrDrawOpAtlas.cpp
@@ -14,13 +14,12 @@
 #include "GrTexture.h"
 #include "GrTracing.h"
 
-std::unique_ptr<GrDrawOpAtlas> GrDrawOpAtlas::Make(GrContext* ctx, GrPixelConfig config,
-                                                   int width, int height,
-                                                   int numPlotsX, int numPlotsY,
-                                                   GrDrawOpAtlas::EvictionFunc func,
-                                                   void* data) {
-    std::unique_ptr<GrDrawOpAtlas> atlas(
-            new GrDrawOpAtlas(ctx, config, width, height, numPlotsX, numPlotsY));
+std::unique_ptr<GrDrawOpAtlas> GrDrawOpAtlas::Make(GrContext* ctx, GrPixelConfig config, int width,
+                                                   int height, int numPlotsX, int numPlotsY,
+                                                   AllowMultitexturing allowMultitexturing,
+                                                   GrDrawOpAtlas::EvictionFunc func, void* data) {
+    std::unique_ptr<GrDrawOpAtlas> atlas(new GrDrawOpAtlas(ctx, config, width, height, numPlotsX,
+                                                           numPlotsY, allowMultitexturing));
     if (!atlas->getProxies()[0]) {
         return nullptr;
     }
@@ -147,13 +146,14 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 GrDrawOpAtlas::GrDrawOpAtlas(GrContext* context, GrPixelConfig config, int width, int height,
-                             int numPlotsX, int numPlotsY)
+                             int numPlotsX, int numPlotsY, AllowMultitexturing allowMultitexturing)
         : fContext(context)
         , fPixelConfig(config)
         , fTextureWidth(width)
         , fTextureHeight(height)
         , fAtlasGeneration(kInvalidAtlasGeneration + 1)
         , fPrevFlushToken(GrDeferredUploadToken::AlreadyFlushedToken())
+        , fAllowMultitexturing(allowMultitexturing)
         , fNumPages(0) {
     fPlotWidth = fTextureWidth / numPlotsX;
     fPlotHeight = fTextureHeight / numPlotsY;
@@ -242,7 +242,7 @@
     for (unsigned int pageIdx = 0; pageIdx < fNumPages; ++pageIdx) {
         Plot* plot = fPages[pageIdx].fPlotList.tail();
         SkASSERT(plot);
-        if ((fNumPages == kMaxPages && plot->lastUseToken() < target->nextTokenToFlush()) ||
+        if ((fNumPages == this->maxPages() && plot->lastUseToken() < target->nextTokenToFlush()) ||
             plot->flushesSinceLastUsed() >= kRecentlyUsedCount) {
             this->processEvictionAndResetRects(plot);
             SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
@@ -448,7 +448,7 @@
 }
 
 bool GrDrawOpAtlas::createNewPage() {
-    if (fNumPages == kMaxPages) {
+    if (fNumPages == this->maxPages()) {
         return false;
     }
 
diff --git a/src/gpu/GrDrawOpAtlas.h b/src/gpu/GrDrawOpAtlas.h
index 91a88ec..edab2cb 100644
--- a/src/gpu/GrDrawOpAtlas.h
+++ b/src/gpu/GrDrawOpAtlas.h
@@ -51,7 +51,13 @@
  * and passes in the given GrDrawUploadToken.
  */
 class GrDrawOpAtlas {
+private:
+    static constexpr auto kMaxMultitexturePages = 4;
+
 public:
+    /** Is the atlas allowed to use more than one texture? */
+    enum class AllowMultitexturing : bool { kNo, kYes };
+
     /**
      * An AtlasID is an opaque handle which callers can use to determine if the atlas contains
      * a specific piece of data.
@@ -77,15 +83,16 @@
      *                          direction
      *  @param numPlotsY        The number of plots the atlas should be broken up into in the Y
      *                          direction
+     *  @param allowMultitexturing Can the atlas use more than one texture.
      *  @param func             An eviction function which will be called whenever the atlas has to
      *                          evict data
-     *  @param data             User supplied data which will be passed into func whenver an
+     *  @param data             User supplied data which will be passed into func whenever an
      *                          eviction occurs
      *  @return                 An initialized GrDrawOpAtlas, or nullptr if creation fails
      */
-    static std::unique_ptr<GrDrawOpAtlas> Make(GrContext*, GrPixelConfig,
-                                               int width, int height,
+    static std::unique_ptr<GrDrawOpAtlas> Make(GrContext*, GrPixelConfig, int width, int height,
                                                int numPlotsX, int numPlotsY,
+                                               AllowMultitexturing allowMultitexturing,
                                                GrDrawOpAtlas::EvictionFunc func, void* data);
 
     /**
@@ -134,7 +141,6 @@
         data->fData = userData;
     }
 
-    static constexpr auto kMaxPages = 4;
     uint32_t pageCount() { return fNumPages; }
 
     /**
@@ -186,7 +192,7 @@
         static constexpr int kMinItems = 4;
         static constexpr int kMaxPlots = 32;
         SkSTArray<kMinItems, PlotData, true> fPlotsToUpdate;
-        uint32_t fPlotAlreadyUpdated[kMaxPages];
+        uint32_t fPlotAlreadyUpdated[kMaxMultitexturePages];
 
         friend class GrDrawOpAtlas;
     };
@@ -217,8 +223,12 @@
     }
 
 private:
-    GrDrawOpAtlas(GrContext*, GrPixelConfig config, int width, int height,
-                  int numPlotsX, int numPlotsY);
+    uint32_t maxPages() const {
+        return AllowMultitexturing::kYes == fAllowMultitexturing ? kMaxMultitexturePages : 1;
+    }
+
+    GrDrawOpAtlas(GrContext*, GrPixelConfig config, int width, int height, int numPlotsX,
+                  int numPlotsY, AllowMultitexturing allowMultitexturing);
 
     /**
      * The backing GrTexture for a GrDrawOpAtlas is broken into a spatial grid of Plots. The Plots
@@ -282,7 +292,7 @@
         static GrDrawOpAtlas::AtlasID CreateId(uint32_t pageIdx, uint32_t plotIdx,
                                                uint64_t generation) {
             SkASSERT(pageIdx < (1 << 8));
-            SkASSERT(pageIdx < kMaxPages);
+            SkASSERT(pageIdx < kMaxMultitexturePages);
             SkASSERT(plotIdx < (1 << 8));
             SkASSERT(generation < ((uint64_t)1 << 48));
             return generation << 16 | plotIdx << 8 | pageIdx;
@@ -376,8 +386,9 @@
         PlotList fPlotList;
     };
     // proxies kept separate to make it easier to pass them up to client
-    sk_sp<GrTextureProxy> fProxies[kMaxPages];
-    Page fPages[kMaxPages];
+    sk_sp<GrTextureProxy> fProxies[kMaxMultitexturePages];
+    Page fPages[kMaxMultitexturePages];
+    AllowMultitexturing fAllowMultitexturing;
     uint32_t fNumPages;
 };
 
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index d308498..4f4dcc1 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -181,10 +181,9 @@
         fHelper.visitProxies(func);
 
         const sk_sp<GrTextureProxy>* proxies = fAtlas->getProxies();
-        for (int i = 0; i < GrDrawOpAtlas::kMaxPages; ++i) {
-            if (proxies[i].get()) {
-                func(proxies[i].get());
-            }
+        for (uint32_t i = 0; i < fAtlas->pageCount(); ++i) {
+            SkASSERT(proxies[i]);
+            func(proxies[i].get());
         }
     }
 
@@ -793,6 +792,7 @@
                                      kAlpha_8_GrPixelConfig,
                                      ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
                                      NUM_PLOTS_X, NUM_PLOTS_Y,
+                                     GrDrawOpAtlas::AllowMultitexturing::kYes,
                                      &GrSmallPathRenderer::HandleEviction,
                                      (void*)this);
         if (!fAtlas) {
@@ -861,6 +861,7 @@
         gTestStruct.fAtlas = GrDrawOpAtlas::Make(context, kAlpha_8_GrPixelConfig,
                                                  ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
                                                  NUM_PLOTS_X, NUM_PLOTS_Y,
+                                                 GrDrawOpAtlas::AllowMultitexturing::kYes,
                                                  &PathTestStruct::HandleEviction,
                                                  (void*)&gTestStruct);
     }
diff --git a/src/gpu/text/GrAtlasGlyphCache.cpp b/src/gpu/text/GrAtlasGlyphCache.cpp
index 25575be..80f4314 100644
--- a/src/gpu/text/GrAtlasGlyphCache.cpp
+++ b/src/gpu/text/GrAtlasGlyphCache.cpp
@@ -25,9 +25,9 @@
         int numPlotsX = fAtlasConfigs[index].numPlotsX();
         int numPlotsY = fAtlasConfigs[index].numPlotsY();
 
-        fAtlases[index] = GrDrawOpAtlas::Make(
-                fContext, config, width, height, numPlotsX, numPlotsY,
-                &GrAtlasGlyphCache::HandleEviction, (void*)this);
+        fAtlases[index] = GrDrawOpAtlas::Make(fContext, config, width, height, numPlotsX, numPlotsY,
+                                              fAllowMultitexturing,
+                                              &GrAtlasGlyphCache::HandleEviction, (void*)this);
         if (!fAtlases[index]) {
             return false;
         }
@@ -35,11 +35,12 @@
     return true;
 }
 
-GrAtlasGlyphCache::GrAtlasGlyphCache(GrContext* context, float maxTextureBytes)
-        : fContext(context), fPreserveStrike(nullptr) {
-    // Calculate RGBA size. Must be between 1024 x 512 and MaxTextureSize x MaxTextureSize / 2
+GrAtlasGlyphCache::GrAtlasGlyphCache(GrContext* context, float maxTextureBytes,
+                                     GrDrawOpAtlas::AllowMultitexturing allowMultitexturing)
+        : fContext(context), fAllowMultitexturing(allowMultitexturing), fPreserveStrike(nullptr) {
+    // Calculate RGBA size. Must be between 512 x 256 and MaxTextureSize x MaxTextureSize / 2
     int log2MaxTextureSize = SkPrevLog2(context->caps()->maxTextureSize());
-    int log2MaxDim = 10;
+    int log2MaxDim = 9;
     for (; log2MaxDim <= log2MaxTextureSize; ++log2MaxDim) {
         int maxDim = 1 << log2MaxDim;
         int minDim = 1 << (log2MaxDim - 1);
@@ -177,17 +178,16 @@
     for (int i = 0; i < kMaskFormatCount; ++i) {
         if (fAtlases[i]) {
             const sk_sp<GrTextureProxy>* proxies = fAtlases[i]->getProxies();
-            for (int pageIdx = 0; pageIdx < GrDrawOpAtlas::kMaxPages; ++pageIdx) {
-                if (proxies[pageIdx]) {
-                    SkString filename;
+            for (uint32_t pageIdx = 0; pageIdx < fAtlases[i]->pageCount(); ++pageIdx) {
+                SkASSERT(proxies[pageIdx]);
+                SkString filename;
 #ifdef SK_BUILD_FOR_ANDROID
-                    filename.printf("/sdcard/fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
+                filename.printf("/sdcard/fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
 #else
-                    filename.printf("fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
+                filename.printf("fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
 #endif
 
-                    save_pixels(fContext, proxies[pageIdx].get(), filename.c_str());
-                }
+                save_pixels(fContext, proxies[pageIdx].get(), filename.c_str());
             }
         }
     }
diff --git a/src/gpu/text/GrAtlasGlyphCache.h b/src/gpu/text/GrAtlasGlyphCache.h
index 20bc32a..a75ef53 100644
--- a/src/gpu/text/GrAtlasGlyphCache.h
+++ b/src/gpu/text/GrAtlasGlyphCache.h
@@ -111,7 +111,7 @@
  */
 class GrAtlasGlyphCache : public GrOnFlushCallbackObject {
 public:
-    GrAtlasGlyphCache(GrContext*, float maxTextureBytes);
+    GrAtlasGlyphCache(GrContext*, float maxTextureBytes, GrDrawOpAtlas::AllowMultitexturing);
     ~GrAtlasGlyphCache() override;
     // The user of the cache may hold a long-lived ref to the returned strike. However, actions by
     // another client of the cache may cause the strike to be purged while it is still reffed.
@@ -256,6 +256,7 @@
     using StrikeHash = SkTDynamicHash<GrAtlasTextStrike, SkDescriptor>;
     GrContext* fContext;
     StrikeHash fCache;
+    GrDrawOpAtlas::AllowMultitexturing fAllowMultitexturing;
     std::unique_ptr<GrDrawOpAtlas> fAtlases[kMaskFormatCount];
     GrAtlasTextStrike* fPreserveStrike;
     GrDrawOpAtlasConfig fAtlasConfigs[kMaskFormatCount];