diff --git a/gm/fontcache.cpp b/gm/fontcache.cpp index a8c5b768c3..b93cc3f031 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 @@ static SkScalar draw_string(SkCanvas* canvas, const SkString& text, SkScalar x, 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 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 fTypefaces[6]; typedef GM INHERITED; }; +constexpr SkScalar FontCacheGM::kSize; ////////////////////////////////////////////////////////////////////////////// diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h index 7aa85e0ed3..a3c914aa3b 100644 --- a/include/gpu/GrContextOptions.h +++ b/include/gpu/GrContextOptions.h @@ -113,6 +113,12 @@ struct GrContextOptions { */ 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 2146858f79..d3194be8f2 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -204,7 +204,14 @@ bool GrContext::init(const GrContextOptions& options) { } 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 8d812857a6..c0358b79ea 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::Make(GrContext* ctx, GrPixelConfig config, - int width, int height, - int numPlotsX, int numPlotsY, - GrDrawOpAtlas::EvictionFunc func, - void* data) { - std::unique_ptr atlas( - new GrDrawOpAtlas(ctx, config, width, height, numPlotsX, numPlotsY)); +std::unique_ptr GrDrawOpAtlas::Make(GrContext* ctx, GrPixelConfig config, int width, + int height, int numPlotsX, int numPlotsY, + AllowMultitexturing allowMultitexturing, + GrDrawOpAtlas::EvictionFunc func, void* data) { + std::unique_ptr atlas(new GrDrawOpAtlas(ctx, config, width, height, numPlotsX, + numPlotsY, allowMultitexturing)); if (!atlas->getProxies()[0]) { return nullptr; } @@ -147,13 +146,14 @@ void GrDrawOpAtlas::Plot::resetRects() { /////////////////////////////////////////////////////////////////////////////// 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 @@ bool GrDrawOpAtlas::addToAtlas(AtlasID* id, GrDeferredUploadTarget* target, int 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 @@ void GrDrawOpAtlas::compact(GrDeferredUploadToken startTokenForNextFlush) { } 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 91a88ecdba..edab2cbd24 100644 --- a/src/gpu/GrDrawOpAtlas.h +++ b/src/gpu/GrDrawOpAtlas.h @@ -51,7 +51,13 @@ struct GrDrawOpAtlasConfig { * 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 @@ public: * 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 Make(GrContext*, GrPixelConfig, - int width, int height, + static std::unique_ptr Make(GrContext*, GrPixelConfig, int width, int height, int numPlotsX, int numPlotsY, + AllowMultitexturing allowMultitexturing, GrDrawOpAtlas::EvictionFunc func, void* data); /** @@ -134,7 +141,6 @@ public: data->fData = userData; } - static constexpr auto kMaxPages = 4; uint32_t pageCount() { return fNumPages; } /** @@ -186,7 +192,7 @@ public: static constexpr int kMinItems = 4; static constexpr int kMaxPlots = 32; SkSTArray fPlotsToUpdate; - uint32_t fPlotAlreadyUpdated[kMaxPages]; + uint32_t fPlotAlreadyUpdated[kMaxMultitexturePages]; friend class GrDrawOpAtlas; }; @@ -217,8 +223,12 @@ public: } 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 @@ private: 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 @@ private: PlotList fPlotList; }; // proxies kept separate to make it easier to pass them up to client - sk_sp fProxies[kMaxPages]; - Page fPages[kMaxPages]; + sk_sp 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 d3084981f8..4f4dcc1f3b 100644 --- a/src/gpu/ops/GrSmallPathRenderer.cpp +++ b/src/gpu/ops/GrSmallPathRenderer.cpp @@ -181,10 +181,9 @@ public: fHelper.visitProxies(func); const sk_sp* 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 @@ bool GrSmallPathRenderer::onDrawPath(const DrawPathArgs& args) { 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 @@ GR_DRAW_OP_TEST_DEFINE(SmallPathOp) { 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 25575bef52..80f4314b91 100644 --- a/src/gpu/text/GrAtlasGlyphCache.cpp +++ b/src/gpu/text/GrAtlasGlyphCache.cpp @@ -25,9 +25,9 @@ bool GrAtlasGlyphCache::initAtlas(GrMaskFormat format) { 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 @@ bool GrAtlasGlyphCache::initAtlas(GrMaskFormat format) { 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 @@ void GrAtlasGlyphCache::dump() const { for (int i = 0; i < kMaskFormatCount; ++i) { if (fAtlases[i]) { const sk_sp* 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 20bc32a408..a75ef53b09 100644 --- a/src/gpu/text/GrAtlasGlyphCache.h +++ b/src/gpu/text/GrAtlasGlyphCache.h @@ -111,7 +111,7 @@ private: */ 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 @@ private: using StrikeHash = SkTDynamicHash; GrContext* fContext; StrikeHash fCache; + GrDrawOpAtlas::AllowMultitexturing fAllowMultitexturing; std::unique_ptr fAtlases[kMaskFormatCount]; GrAtlasTextStrike* fPreserveStrike; GrDrawOpAtlasConfig fAtlasConfigs[kMaskFormatCount];