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>
This commit is contained in:
Brian Salomon 2017-11-06 10:36:57 -05:00 committed by Skia Commit-Bot
parent 97f0bc6a1a
commit 9f545bc18a
8 changed files with 140 additions and 75 deletions

View File

@ -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<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;
//////////////////////////////////////////////////////////////////////////////

View File

@ -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.

View File

@ -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));

View File

@ -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 @@ 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;
}

View File

@ -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<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 @@ 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<kMinItems, PlotData, true> 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<GrTextureProxy> fProxies[kMaxPages];
Page fPages[kMaxPages];
sk_sp<GrTextureProxy> fProxies[kMaxMultitexturePages];
Page fPages[kMaxMultitexturePages];
AllowMultitexturing fAllowMultitexturing;
uint32_t fNumPages;
};

View File

@ -181,10 +181,9 @@ public:
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 @@ 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);
}

View File

@ -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<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());
}
}
}

View File

@ -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<GrAtlasTextStrike, SkDescriptor>;
GrContext* fContext;
StrikeHash fCache;
GrDrawOpAtlas::AllowMultitexturing fAllowMultitexturing;
std::unique_ptr<GrDrawOpAtlas> fAtlases[kMaskFormatCount];
GrAtlasTextStrike* fPreserveStrike;
GrDrawOpAtlasConfig fAtlasConfigs[kMaskFormatCount];