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:
parent
21bbc2816f
commit
7801faaab9
@ -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;
|
||||
// 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();
|
||||
// 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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "GrTextContext.h"
|
||||
|
||||
class GrTextStrike;
|
||||
class GrAtlasMgr;
|
||||
|
||||
/*
|
||||
* This class implements GrTextContext using standard bitmap fonts
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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]));
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user