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:
parent
97f0bc6a1a
commit
9f545bc18a
109
gm/fontcache.cpp
109
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<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;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user