Add CPU backing store for GrAtlas to reduce texture uploads.

This change creates a temporary copy of each GrPlot on the CPU
side. As we add glyphs to a plot, a dirty rectangle is tracked,
and just before we render text we upload all of the dirty areas
to the atlas texture. Once a plot is nearly full, we assume that
we may only be adding one or two glyphs before rendering, so we
delete the CPU backing memory to save space, and upload directly.

BUG=366225
R=robertphillips@google.com

Author: jvanverth@google.com

Review URL: https://codereview.chromium.org/269423007

git-svn-id: http://skia.googlecode.com/svn/trunk@14729 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2014-05-14 15:14:51 +00:00
parent 21bbc2816f
commit 7801faaab9
8 changed files with 129 additions and 22 deletions

View File

@ -24,19 +24,28 @@ GrPlot::GrPlot() : fDrawToken(NULL, 0)
, fRects(NULL)
, fAtlasMgr(NULL)
, fBytesPerPixel(1)
, fDirty(false)
, fBatchUploads(false)
{
fOffset.set(0, 0);
}
GrPlot::~GrPlot() {
SkDELETE_ARRAY(fPlotData);
fPlotData = NULL;
delete fRects;
}
void GrPlot::init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp) {
void GrPlot::init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp,
bool batchUploads) {
fRects = GrRectanizer::Factory(width, height);
fAtlasMgr = mgr;
fOffset.set(offX * width, offY * height);
fBytesPerPixel = bpp;
fPlotData = NULL;
fDirtyRect.setEmpty();
fDirty = false;
fBatchUploads = batchUploads;
}
static inline void adjust_for_offset(GrIPoint16* loc, const GrIPoint16& offset) {
@ -46,20 +55,46 @@ static inline void adjust_for_offset(GrIPoint16* loc, const GrIPoint16& offset)
bool GrPlot::addSubImage(int width, int height, const void* image,
GrIPoint16* loc) {
float percentFull = fRects->percentFull();
if (!fRects->addRect(width, height, loc)) {
return false;
}
SkAutoSMalloc<1024> storage;
adjust_for_offset(loc, fOffset);
GrContext* context = fTexture->getContext();
// We pass the flag that does not force a flush. We assume our caller is
// smart and hasn't referenced the part of the texture we're about to update
// since the last flush.
context->writeTexturePixels(fTexture,
loc->fX, loc->fY, width, height,
fTexture->config(), image, 0,
GrContext::kDontFlush_PixelOpsFlag);
// if batching uploads, create backing memory on first use
// once the plot is nearly full we will revert to uploading each subimage individually
int plotWidth = fRects->width();
int plotHeight = fRects->height();
if (fBatchUploads && NULL == fPlotData && 0.0f == percentFull) {
fPlotData = SkNEW_ARRAY(unsigned char, fBytesPerPixel*plotWidth*plotHeight);
memset(fPlotData, 0, fBytesPerPixel*plotWidth*plotHeight);
}
// if we have backing memory, copy to the memory and set for future upload
if (NULL != fPlotData) {
const unsigned char* imagePtr = (const unsigned char*) image;
// point ourselves at the right starting spot
unsigned char* dataPtr = fPlotData;
dataPtr += fBytesPerPixel*plotWidth*loc->fY;
dataPtr += fBytesPerPixel*loc->fX;
// copy into the data buffer
for (int i = 0; i < height; ++i) {
memcpy(dataPtr, imagePtr, fBytesPerPixel*width);
dataPtr += fBytesPerPixel*plotWidth;
imagePtr += fBytesPerPixel*width;
}
fDirtyRect.join(loc->fX, loc->fY, loc->fX + width, loc->fY + height);
adjust_for_offset(loc, fOffset);
fDirty = true;
// otherwise, just upload the image directly
} else {
adjust_for_offset(loc, fOffset);
GrContext* context = fTexture->getContext();
context->writeTexturePixels(fTexture,
loc->fX, loc->fY, width, height,
fTexture->config(), image, 0,
GrContext::kDontFlush_PixelOpsFlag);
}
#if FONT_CACHE_STATS
++g_UploadCount;
@ -68,6 +103,39 @@ bool GrPlot::addSubImage(int width, int height, const void* image,
return true;
}
void GrPlot::uploadToTexture() {
static const float kNearlyFullTolerance = 0.85f;
// should only do this if batching is enabled
SkASSERT(fBatchUploads);
if (fDirty) {
SkASSERT(NULL != fTexture);
GrContext* context = fTexture->getContext();
// We pass the flag that does not force a flush. We assume our caller is
// smart and hasn't referenced the part of the texture we're about to update
// since the last flush.
int rowBytes = fBytesPerPixel*fRects->width();
const unsigned char* dataPtr = fPlotData;
dataPtr += rowBytes*fDirtyRect.fTop;
dataPtr += fBytesPerPixel*fDirtyRect.fLeft;
context->writeTexturePixels(fTexture,
fOffset.fX + fDirtyRect.fLeft, fOffset.fY + fDirtyRect.fTop,
fDirtyRect.width(), fDirtyRect.height(),
fTexture->config(), dataPtr,
rowBytes,
GrContext::kDontFlush_PixelOpsFlag);
fDirtyRect.setEmpty();
fDirty = false;
// If the Plot is nearly full, anything else we add will probably be small and one
// at a time, so free up the memory and after this upload any new images directly.
if (fRects->percentFull() > kNearlyFullTolerance) {
SkDELETE_ARRAY(fPlotData);
fPlotData = NULL;
}
}
}
void GrPlot::resetRects() {
SkASSERT(NULL != fRects);
fRects->reset();
@ -77,19 +145,23 @@ void GrPlot::resetRects() {
GrAtlasMgr::GrAtlasMgr(GrGpu* gpu, GrPixelConfig config,
const SkISize& backingTextureSize,
int numPlotsX, int numPlotsY) {
int numPlotsX, int numPlotsY, bool batchUploads) {
fGpu = SkRef(gpu);
fPixelConfig = config;
fBackingTextureSize = backingTextureSize;
fNumPlotsX = numPlotsX;
fNumPlotsY = numPlotsY;
fBatchUploads = batchUploads;
fTexture = NULL;
int plotWidth = fBackingTextureSize.width() / fNumPlotsX;
int plotHeight = fBackingTextureSize.height() / fNumPlotsY;
int textureWidth = fBackingTextureSize.width();
int textureHeight = fBackingTextureSize.height();
SkASSERT(plotWidth * fNumPlotsX == fBackingTextureSize.width());
SkASSERT(plotHeight * fNumPlotsY == fBackingTextureSize.height());
int plotWidth = textureWidth / fNumPlotsX;
int plotHeight = textureHeight / fNumPlotsY;
SkASSERT(plotWidth * fNumPlotsX == textureWidth);
SkASSERT(plotHeight * fNumPlotsY == textureHeight);
// set up allocated plots
size_t bpp = GrBytesPerPixel(fPixelConfig);
@ -98,7 +170,7 @@ GrAtlasMgr::GrAtlasMgr(GrGpu* gpu, GrPixelConfig config,
GrPlot* currPlot = fPlotArray;
for (int y = numPlotsY-1; y >= 0; --y) {
for (int x = numPlotsX-1; x >= 0; --x) {
currPlot->init(this, x, y, plotWidth, plotHeight, bpp);
currPlot->init(this, x, y, plotWidth, plotHeight, bpp, batchUploads);
// build LRU list
fPlotList.addToHead(currPlot);
@ -201,3 +273,15 @@ GrPlot* GrAtlasMgr::getUnusedPlot() {
return NULL;
}
void GrAtlasMgr::uploadPlotsToTexture() {
if (fBatchUploads) {
GrPlotList::Iter plotIter;
plotIter.init(fPlotList, GrPlotList::Iter::kHead_IterStart);
GrPlot* plot;
while (NULL != (plot = plotIter.get())) {
plot->uploadToTexture();
plotIter.next();
}
}
}

View File

@ -40,21 +40,28 @@ public:
GrDrawTarget::DrawToken drawToken() const { return fDrawToken; }
void setDrawToken(GrDrawTarget::DrawToken draw) { fDrawToken = draw; }
void uploadToTexture();
void resetRects();
private:
GrPlot();
~GrPlot(); // does not try to delete the fNext field
void init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp);
void init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp,
bool batchUploads);
// for recycling
GrDrawTarget::DrawToken fDrawToken;
unsigned char* fPlotData;
GrTexture* fTexture;
GrRectanizer* fRects;
GrAtlasMgr* fAtlasMgr;
GrIPoint16 fOffset; // the offset of the plot in the backing texture
size_t fBytesPerPixel;
SkIRect fDirtyRect;
bool fDirty;
bool fBatchUploads;
friend class GrAtlasMgr;
};
@ -64,7 +71,7 @@ typedef SkTInternalLList<GrPlot> GrPlotList;
class GrAtlasMgr {
public:
GrAtlasMgr(GrGpu*, GrPixelConfig, const SkISize& backingTextureSize,
int numPlotsX, int numPlotsY);
int numPlotsX, int numPlotsY, bool batchUploads);
~GrAtlasMgr();
// add subimage of width, height dimensions to atlas
@ -82,6 +89,8 @@ public:
return fTexture;
}
void uploadPlotsToTexture();
private:
void moveToHead(GrPlot* plot);
@ -91,6 +100,7 @@ private:
SkISize fBackingTextureSize;
int fNumPlotsX;
int fNumPlotsY;
bool fBatchUploads;
// allocated array of GrPlots
GrPlot* fPlotArray;

View File

@ -68,6 +68,8 @@ void GrBitmapTextContext::flushGlyphs() {
drawState->setFromPaint(fPaint, SkMatrix::I(), fContext->getRenderTarget());
if (fCurrVertex > 0) {
fContext->getFontCache()->updateTextures();
// setup our sampler state for our text texture/atlas
SkASSERT(SkIsAlign4(fCurrVertex));
SkASSERT(fCurrTexture);

View File

@ -11,6 +11,7 @@
#include "GrTextContext.h"
class GrTextStrike;
class GrAtlasMgr;
/*
* This class implements GrTextContext using standard bitmap fonts

View File

@ -101,6 +101,8 @@ void GrDistanceFieldTextContext::flushGlyphs() {
drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
if (fCurrVertex > 0) {
fContext->getFontCache()->updateTextures();
// setup our sampler state for our text texture/atlas
SkASSERT(SkIsAlign4(fCurrVertex));
SkASSERT(fCurrTexture);

View File

@ -58,7 +58,7 @@ void GrLayerCache::init() {
// The layer cache only gets 1 plot
SkISize textureSize = SkISize::Make(kAtlasTextureWidth, kAtlasTextureHeight);
fAtlasMgr.reset(SkNEW_ARGS(GrAtlasMgr, (fGpu, kSkia8888_GrPixelConfig,
textureSize, 1, 1)));
textureSize, 1, 1, false)));
}
void GrLayerCache::freeAll() {

View File

@ -5,7 +5,6 @@
* found in the LICENSE file.
*/
#include "GrAtlas.h"
#include "GrGpu.h"
#include "GrRectanizer.h"
#include "GrTextStrike.h"
@ -86,7 +85,8 @@ GrTextStrike* GrFontCache::generateStrike(GrFontScaler* scaler,
fAtlasMgr[atlasIndex] = SkNEW_ARGS(GrAtlasMgr, (fGpu, config,
textureSize,
GR_NUM_PLOTS_X,
GR_NUM_PLOTS_Y));
GR_NUM_PLOTS_Y,
true));
}
GrTextStrike* strike = SkNEW_ARGS(GrTextStrike,
(this, scaler->getKey(), format, fAtlasMgr[atlasIndex]));

View File

@ -90,6 +90,14 @@ public:
}
GrTextStrike* getHeadStrike() const { return fHead; }
void updateTextures() {
for (int i = 0; i < kAtlasCount; ++i) {
if (fAtlasMgr[i]) {
fAtlasMgr[i]->uploadPlotsToTexture();
}
}
}
#ifdef SK_DEBUG
void validate() const;
#else