Add plot-based purging to GrLayerCache

This CL allows a GrPlot full of atlased layer to be purged from the atlas to make room for new layers.

R=jvanverth@google.com

Author: robertphillips@google.com

Review URL: https://codereview.chromium.org/411703003
This commit is contained in:
robertphillips 2014-07-29 06:07:19 -07:00 committed by Commit bot
parent e7416bfc98
commit 320c92380f
5 changed files with 270 additions and 63 deletions

View File

@ -81,6 +81,12 @@ public:
public:
bool isEmpty() const { return 0 == fPlots.count(); }
#ifdef SK_DEBUG
bool contains(const GrPlot* plot) const {
return fPlots.contains(const_cast<GrPlot*>(plot));
}
#endif
private:
SkTDArray<GrPlot*> fPlots;
@ -112,6 +118,18 @@ public:
void uploadPlotsToTexture();
enum IterOrder {
kLRUFirst_IterOrder,
kMRUFirst_IterOrder
};
typedef GrPlotList::Iter PlotIter;
GrPlot* iterInit(PlotIter* iter, IterOrder order) {
return iter->init(fPlotList, kLRUFirst_IterOrder == order
? GrPlotList::Iter::kTail_IterStart
: GrPlotList::Iter::kHead_IterStart);
}
private:
void makeMRU(GrPlot* plot);

View File

@ -27,6 +27,7 @@ void GrCachedLayer::validate(const GrTexture* backingTexture) const {
} else {
SkASSERT(fRect.isEmpty());
SkASSERT(NULL == fPlot);
SkASSERT(!fLocked); // layers without a texture cannot be locked
}
if (NULL != fPlot) {
@ -35,6 +36,13 @@ void GrCachedLayer::validate(const GrTexture* backingTexture) const {
SkASSERT(NULL != fTexture && backingTexture == fTexture);
SkASSERT(!fRect.isEmpty());
}
if (fLocked) {
// If a layer is locked it must have a texture (though it need not be
// the atlas-backing texture) and occupy some space.
SkASSERT(NULL != fTexture);
SkASSERT(!fRect.isEmpty());
}
}
class GrAutoValidateLayer : ::SkNoncopyable {
@ -65,6 +73,7 @@ private:
GrLayerCache::GrLayerCache(GrContext* context)
: fContext(context) {
this->initAtlas();
memset(fPlotLocks, 0, sizeof(fPlotLocks));
}
GrLayerCache::~GrLayerCache() {
@ -81,12 +90,8 @@ GrLayerCache::~GrLayerCache() {
}
void GrLayerCache::initAtlas() {
static const int kAtlasTextureWidth = 1024;
static const int kAtlasTextureHeight = 1024;
SkASSERT(NULL == fAtlas.get());
// The layer cache only gets 1 plot
SkISize textureSize = SkISize::Make(kAtlasTextureWidth, kAtlasTextureHeight);
fAtlas.reset(SkNEW_ARGS(GrAtlas, (fContext->getGpu(), kSkia8888_GrPixelConfig,
kRenderTarget_GrTextureFlagBit,
@ -138,7 +143,7 @@ GrCachedLayer* GrLayerCache::findLayerOrCreate(const SkPicture* picture, int lay
bool GrLayerCache::lock(GrCachedLayer* layer, const GrTextureDesc& desc) {
SkDEBUGCODE(GrAutoValidateLayer avl(fAtlas->getTexture(), layer);)
if (NULL != layer->texture()) {
if (layer->locked()) {
// This layer is already locked
#ifdef SK_DEBUG
if (layer->isAtlased()) {
@ -151,7 +156,13 @@ bool GrLayerCache::lock(GrCachedLayer* layer, const GrTextureDesc& desc) {
}
#if USE_ATLAS
{
if (layer->isAtlased()) {
// Hooray it is still in the atlas - make sure it stays there
layer->setLocked(true);
fPlotLocks[layer->plot()->id()]++;
return true;
} else if (PlausiblyAtlasable(desc.fWidth, desc.fHeight)) {
// Not in the atlas - will it fit?
GrPictureInfo* pictInfo = fPictureHash.find(layer->pictureID());
if (NULL == pictInfo) {
pictInfo = SkNEW_ARGS(GrPictureInfo, (layer->pictureID()));
@ -159,17 +170,29 @@ bool GrLayerCache::lock(GrCachedLayer* layer, const GrTextureDesc& desc) {
}
SkIPoint16 loc;
GrPlot* plot = fAtlas->addToAtlas(&pictInfo->fPlotUsage,
desc.fWidth, desc.fHeight,
NULL, &loc);
// addToAtlas can allocate the backing texture
SkDEBUGCODE(avl.setBackingTexture(fAtlas->getTexture()));
if (NULL != plot) {
GrIRect16 bounds = GrIRect16::MakeXYWH(loc.fX, loc.fY,
SkToS16(desc.fWidth), SkToS16(desc.fHeight));
layer->setTexture(fAtlas->getTexture(), bounds);
layer->setPlot(plot);
return false;
for (int i = 0; i < 2; ++i) { // extra pass in case we fail to add but are able to purge
GrPlot* plot = fAtlas->addToAtlas(&pictInfo->fPlotUsage,
desc.fWidth, desc.fHeight,
NULL, &loc);
// addToAtlas can allocate the backing texture
SkDEBUGCODE(avl.setBackingTexture(fAtlas->getTexture()));
if (NULL != plot) {
// The layer was successfully added to the atlas
GrIRect16 bounds = GrIRect16::MakeXYWH(loc.fX, loc.fY,
SkToS16(desc.fWidth),
SkToS16(desc.fHeight));
layer->setTexture(fAtlas->getTexture(), bounds);
layer->setPlot(plot);
layer->setLocked(true);
fPlotLocks[layer->plot()->id()]++;
return false;
}
// The layer was rejected by the atlas (even though we know it is
// plausibly atlas-able). See if a plot can be purged and try again.
if (!this->purgePlot()) {
break; // We weren't able to purge any plots
}
}
}
#endif
@ -179,35 +202,69 @@ bool GrLayerCache::lock(GrCachedLayer* layer, const GrTextureDesc& desc) {
// This can yield a lot of re-rendering
layer->setTexture(fContext->lockAndRefScratchTexture(desc, GrContext::kApprox_ScratchTexMatch),
GrIRect16::MakeWH(SkToS16(desc.fWidth), SkToS16(desc.fHeight)));
layer->setLocked(true);
return false;
}
void GrLayerCache::unlock(GrCachedLayer* layer) {
SkDEBUGCODE(GrAutoValidateLayer avl(fAtlas->getTexture(), layer);)
if (NULL == layer || NULL == layer->texture()) {
if (NULL == layer || !layer->locked()) {
// invalid or not locked
return;
}
if (layer->isAtlased()) {
SkASSERT(layer->texture() == fAtlas->getTexture());
const int plotID = layer->plot()->id();
GrPictureInfo* pictInfo = fPictureHash.find(layer->pictureID());
SkASSERT(NULL != pictInfo);
pictInfo->fPlotUsage.isEmpty(); // just to silence compiler warnings for the time being
// TODO: purging from atlas goes here
SkASSERT(fPlotLocks[plotID] > 0);
fPlotLocks[plotID]--;
// At this point we could aggressively clear out un-locked plots but
// by delaying we may be able to reuse some of the atlased layers later.
} else {
fContext->unlockScratchTexture(layer->texture());
layer->setTexture(NULL, GrIRect16::MakeEmpty());
}
layer->setLocked(false);
}
#ifdef SK_DEBUG
void GrLayerCache::validate() const {
int plotLocks[kNumPlotsX * kNumPlotsY];
memset(plotLocks, 0, sizeof(plotLocks));
SkTDynamicHash<GrCachedLayer, GrCachedLayer::Key>::ConstIter iter(&fLayerHash);
for (; !iter.done(); ++iter) {
(*iter).validate(fAtlas->getTexture());
const GrCachedLayer* layer = &(*iter);
layer->validate(fAtlas->getTexture());
const GrPictureInfo* pictInfo = fPictureHash.find(layer->pictureID());
if (NULL != pictInfo) {
// In aggressive cleanup mode a picture info should only exist if
// it has some atlased layers
SkASSERT(!pictInfo->fPlotUsage.isEmpty());
} else {
// If there is no picture info for this layer then all of its
// layers should be non-atlased.
SkASSERT(!layer->isAtlased());
}
if (NULL != layer->plot()) {
SkASSERT(NULL != pictInfo);
SkASSERT(pictInfo->fPictureID == layer->pictureID());
SkASSERT(pictInfo->fPlotUsage.contains(layer->plot()));
if (layer->locked()) {
plotLocks[layer->plot()->id()]++;
}
}
}
for (int i = 0; i < kNumPlotsX*kNumPlotsY; ++i) {
SkASSERT(plotLocks[i] == fPlotLocks[i]);
}
}
@ -252,6 +309,53 @@ void GrLayerCache::purge(uint32_t pictureID) {
}
}
bool GrLayerCache::purgePlot() {
SkDEBUGCODE(GrAutoValidateCache avc(this);)
GrAtlas::PlotIter iter;
GrPlot* plot;
for (plot = fAtlas->iterInit(&iter, GrAtlas::kLRUFirst_IterOrder);
NULL != plot;
plot = iter.prev()) {
if (fPlotLocks[plot->id()] > 0) {
continue;
}
// We need to find all the layers in 'plot' and remove them.
SkTDArray<GrCachedLayer*> toBeRemoved;
SkTDynamicHash<GrCachedLayer, GrCachedLayer::Key>::Iter iter(&fLayerHash);
for (; !iter.done(); ++iter) {
if (plot == (*iter).plot()) {
*toBeRemoved.append() = &(*iter);
}
}
for (int i = 0; i < toBeRemoved.count(); ++i) {
SkASSERT(!toBeRemoved[i]->locked());
GrPictureInfo* pictInfo = fPictureHash.find(toBeRemoved[i]->pictureID());
SkASSERT(NULL != pictInfo);
GrAtlas::RemovePlot(&pictInfo->fPlotUsage, plot);
// Aggressively remove layers and, if now totally uncached, picture info
fLayerHash.remove(GrCachedLayer::GetKey(*toBeRemoved[i]));
SkDELETE(toBeRemoved[i]);
if (pictInfo->fPlotUsage.isEmpty()) {
fPictureHash.remove(pictInfo->fPictureID);
SkDELETE(pictInfo);
}
}
plot->resetRects();
return true;
}
return false;
}
class GrPictureDeletionListener : public SkPicture::DeletionListener {
virtual void onDeletion(uint32_t pictureID) SK_OVERRIDE{
const GrPictureDeletedMessage message = { pictureID };

View File

@ -10,7 +10,6 @@
#define USE_ATLAS 0
#include "GrAllocPool.h"
#include "GrAtlas.h"
#include "GrPictureUtils.h"
#include "GrRect.h"
@ -47,6 +46,9 @@ public:
// get a ref to the GrTexture in which they reside. In both cases 'fRect'
// contains the layer's extent in its texture.
// Atlased layers also get a pointer to the plot in which they reside.
// For non-atlased layers the lock field just corresponds to locking in
// the resource cache. For atlased layers it implements an additional level
// of locking to allow atlased layers to be reused multiple times.
struct GrCachedLayer {
public:
// For SkTDynamicHash
@ -77,7 +79,8 @@ public:
: fKey(pictureID, layerID)
, fTexture(NULL)
, fRect(GrIRect16::MakeEmpty())
, fPlot(NULL) {
, fPlot(NULL)
, fLocked(false) {
SkASSERT(SK_InvalidGenID != pictureID && layerID >= 0);
}
@ -104,14 +107,17 @@ public:
bool isAtlased() const { return NULL != fPlot; }
void setLocked(bool locked) { fLocked = locked; }
bool locked() const { return fLocked; }
SkDEBUGCODE(const GrPlot* plot() const { return fPlot; })
SkDEBUGCODE(void validate(const GrTexture* backingTexture) const;)
private:
const Key fKey;
// fTexture is a ref on the atlasing texture for atlased layers and a
// ref on a GrTexture for non-atlased textures. In both cases, if this is
// non-NULL, that means that the texture is locked in the texture cache.
// ref on a GrTexture for non-atlased textures.
GrTexture* fTexture;
// For both atlased and non-atlased layers 'fRect' contains the bound of
@ -122,6 +128,14 @@ private:
// For atlased layers, fPlot stores the atlas plot in which the layer rests.
// It is always NULL for non-atlased layers.
GrPlot* fPlot;
// For non-atlased layers 'fLocked' should always match "NULL != fTexture".
// (i.e., if there is a texture it is locked).
// For atlased layers, 'fLocked' is true if the layer is in a plot and
// actively required for rendering. If the layer is in a plot but not
// actively required for rendering, then 'fLocked' is false. If the
// layer isn't in a plot then is can never be locked.
bool fLocked;
};
// The GrLayerCache caches pre-computed saveLayers for later rendering.
@ -161,9 +175,15 @@ public:
SkDEBUGCODE(void validate() const;)
private:
static const int kAtlasTextureWidth = 1024;
static const int kAtlasTextureHeight = 1024;
static const int kNumPlotsX = 2;
static const int kNumPlotsY = 2;
static const int kPlotWidth = kAtlasTextureWidth / kNumPlotsX;
static const int kPlotHeight = kAtlasTextureHeight / kNumPlotsY;
GrContext* fContext; // pointer back to owning context
SkAutoTDelete<GrAtlas> fAtlas; // TODO: could lazily allocate
@ -180,12 +200,28 @@ private:
SkAutoTUnref<SkPicture::DeletionListener> fDeletionListener;
// This implements a plot-centric locking mechanism (since the atlas
// backing texture is always locked). Each layer that is locked (i.e.,
// needed for the current rendering) in a plot increments the plot lock
// count for that plot. Similarly, once a rendering is complete all the
// layers used in it decrement the lock count for the used plots.
// Plots with a 0 lock count are open for recycling/purging.
int fPlotLocks[kNumPlotsX * kNumPlotsY];
void initAtlas();
GrCachedLayer* createLayer(const SkPicture* picture, int layerID);
// Remove all the layers (and unlock any resources) associated with 'pictureID'
void purge(uint32_t pictureID);
static bool PlausiblyAtlasable(int width, int height) {
return width <= kPlotWidth && height <= kPlotHeight;
}
// Try to find a purgeable plot and clear it out. Return true if a plot
// was purged; false otherwise.
bool purgePlot();
// for testing
friend class TestingAccess;
int numLayers() const { return fLayerHash.count(); }

View File

@ -38,6 +38,7 @@
#include "SkTLazy.h"
#include "SkUtils.h"
#include "SkVertState.h"
#include "SkXfermode.h"
#include "SkErrorInternals.h"
#define CACHE_COMPATIBLE_DEVICE_TEXTURES 1
@ -2023,6 +2024,7 @@ bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, const SkPicture* pi
// TODO: ensure none of the atlased layers contain a clear call!
SkPaint paint;
paint.setColor(SK_ColorTRANSPARENT);
paint.setXfermode(SkXfermode::Create(SkXfermode::kSrc_Mode))->unref();
canvas->drawRect(bound, paint);
} else {
canvas->clear(SK_ColorTRANSPARENT);

View File

@ -13,8 +13,6 @@
#include "SkPictureRecorder.h"
#include "Test.h"
static const int kNumLayers = 5;
class TestingAccess {
public:
static int NumLayers(GrLayerCache* cache) {
@ -28,31 +26,53 @@ public:
// Add several layers to the cache
static void create_layers(skiatest::Reporter* reporter,
GrLayerCache* cache,
const SkPicture& picture) {
GrCachedLayer* layers[kNumLayers];
const SkPicture& picture,
int numToAdd,
int idOffset) {
for (int i = 0; i < kNumLayers; ++i) {
layers[i] = cache->findLayerOrCreate(&picture, i);
REPORTER_ASSERT(reporter, NULL != layers[i]);
GrCachedLayer* layer = cache->findLayer(&picture, i);
REPORTER_ASSERT(reporter, layer == layers[i]);
for (int i = 0; i < numToAdd; ++i) {
GrCachedLayer* layer = cache->findLayerOrCreate(&picture, idOffset+i);
REPORTER_ASSERT(reporter, NULL != layer);
GrCachedLayer* temp = cache->findLayer(&picture, idOffset+i);
REPORTER_ASSERT(reporter, temp == layer);
REPORTER_ASSERT(reporter, TestingAccess::NumLayers(cache) == i + 1);
REPORTER_ASSERT(reporter, TestingAccess::NumLayers(cache) == idOffset + i + 1);
REPORTER_ASSERT(reporter, picture.uniqueID() == layers[i]->pictureID());
REPORTER_ASSERT(reporter, layers[i]->layerID() == i);
REPORTER_ASSERT(reporter, NULL == layers[i]->texture());
REPORTER_ASSERT(reporter, !layers[i]->isAtlased());
REPORTER_ASSERT(reporter, picture.uniqueID() == layer->pictureID());
REPORTER_ASSERT(reporter, layer->layerID() == idOffset + i);
REPORTER_ASSERT(reporter, NULL == layer->texture());
REPORTER_ASSERT(reporter, !layer->isAtlased());
}
cache->trackPicture(&picture);
}
static void lock_layer(skiatest::Reporter* reporter,
GrLayerCache* cache,
GrCachedLayer* layer) {
// Make the layer 512x512 (so it can be atlased)
GrTextureDesc desc;
desc.fWidth = 512;
desc.fHeight = 512;
desc.fConfig = kSkia8888_GrPixelConfig;
bool foundInCache = cache->lock(layer, desc);
REPORTER_ASSERT(reporter, !foundInCache);
foundInCache = cache->lock(layer, desc);
REPORTER_ASSERT(reporter, foundInCache);
REPORTER_ASSERT(reporter, NULL != layer->texture());
REPORTER_ASSERT(reporter, layer->locked());
}
// This test case exercises the public API of the GrLayerCache class.
// In particular it checks its interaction with the resource cache (w.r.t.
// locking & unlocking textures).
// TODO: need to add checks on VRAM usage!
DEF_GPUTEST(GpuLayerCache, reporter, factory) {
static const int kInitialNumLayers = 5;
for (int i= 0; i < GrContextFactory::kGLContextTypeCnt; ++i) {
GrContextFactory::GLContextType glCtxType = (GrContextFactory::GLContextType) i;
@ -72,24 +92,14 @@ DEF_GPUTEST(GpuLayerCache, reporter, factory) {
GrLayerCache cache(context);
create_layers(reporter, &cache, *picture);
create_layers(reporter, &cache, *picture, kInitialNumLayers, 0);
// Lock the layers making them all 512x512
GrTextureDesc desc;
desc.fWidth = 512;
desc.fHeight = 512;
desc.fConfig = kSkia8888_GrPixelConfig;
for (int i = 0; i < kNumLayers; ++i) {
for (int i = 0; i < kInitialNumLayers; ++i) {
GrCachedLayer* layer = cache.findLayer(picture, i);
REPORTER_ASSERT(reporter, NULL != layer);
bool foundInCache = cache.lock(layer, desc);
REPORTER_ASSERT(reporter, !foundInCache);
foundInCache = cache.lock(layer, desc);
REPORTER_ASSERT(reporter, foundInCache);
lock_layer(reporter, &cache, layer);
REPORTER_ASSERT(reporter, NULL != layer->texture());
#if USE_ATLAS
// The first 4 layers should be in the atlas (and thus have non-empty
// rects)
@ -97,32 +107,34 @@ DEF_GPUTEST(GpuLayerCache, reporter, factory) {
REPORTER_ASSERT(reporter, layer->isAtlased());
} else {
#endif
REPORTER_ASSERT(reporter, !layer->isAtlased());
// The 5th layer couldn't fit in the atlas
REPORTER_ASSERT(reporter, !layer->isAtlased());
#if USE_ATLAS
}
#endif
}
// Unlock the textures
for (int i = 0; i < kNumLayers; ++i) {
for (int i = 0; i < kInitialNumLayers; ++i) {
GrCachedLayer* layer = cache.findLayer(picture, i);
REPORTER_ASSERT(reporter, NULL != layer);
cache.unlock(layer);
}
for (int i = 0; i < kNumLayers; ++i) {
for (int i = 0; i < kInitialNumLayers; ++i) {
GrCachedLayer* layer = cache.findLayer(picture, i);
REPORTER_ASSERT(reporter, NULL != layer);
REPORTER_ASSERT(reporter, !layer->locked());
#if USE_ATLAS
// The first 4 layers should be in the atlas (and thus do not
// currently unlock). The final layer should be unlocked.
// The first 4 layers should still be in the atlas.
if (i < 4) {
REPORTER_ASSERT(reporter, NULL != layer->texture());
REPORTER_ASSERT(reporter, layer->isAtlased());
} else {
#endif
// The final layer should be unlocked.
REPORTER_ASSERT(reporter, NULL == layer->texture());
REPORTER_ASSERT(reporter, !layer->isAtlased());
#if USE_ATLAS
@ -130,6 +142,41 @@ DEF_GPUTEST(GpuLayerCache, reporter, factory) {
#endif
}
{
// Add an additional layer. Since all the layers are unlocked this
// will force out the first atlased layer
create_layers(reporter, &cache, *picture, 1, kInitialNumLayers);
GrCachedLayer* layer = cache.findLayer(picture, kInitialNumLayers);
REPORTER_ASSERT(reporter, NULL != layer);
lock_layer(reporter, &cache, layer);
cache.unlock(layer);
}
for (int i = 0; i < kInitialNumLayers+1; ++i) {
GrCachedLayer* layer = cache.findLayer(picture, i);
#if USE_ATLAS
// 3 old layers plus the new one should be in the atlas.
if (1 == i || 2 == i || 3 == i || 5 == i) {
REPORTER_ASSERT(reporter, NULL != layer);
REPORTER_ASSERT(reporter, !layer->locked());
REPORTER_ASSERT(reporter, NULL != layer->texture());
REPORTER_ASSERT(reporter, layer->isAtlased());
} else if (4 == i) {
#endif
// The one that was never atlased should still be around
REPORTER_ASSERT(reporter, NULL != layer);
REPORTER_ASSERT(reporter, NULL == layer->texture());
REPORTER_ASSERT(reporter, !layer->isAtlased());
#if USE_ATLAS
} else {
// The one bumped out of the atlas (i.e., 0) should be gone
REPORTER_ASSERT(reporter, NULL == layer);
}
#endif
}
//--------------------------------------------------------------------
// Free them all SkGpuDevice-style. This will not free up the
// atlas' texture but will eliminate all the layers.
@ -142,7 +189,7 @@ DEF_GPUTEST(GpuLayerCache, reporter, factory) {
// Test out the GrContext-style purge. This should remove all the layers
// and the atlas.
// Re-create the layers
create_layers(reporter, &cache, *picture);
create_layers(reporter, &cache, *picture, kInitialNumLayers, 0);
// Free them again GrContext-style. This should free up everything.
cache.freeAll();
@ -153,7 +200,7 @@ DEF_GPUTEST(GpuLayerCache, reporter, factory) {
//--------------------------------------------------------------------
// Test out the MessageBus-style purge. This will not free the atlas
// but should eliminate the free-floating layers.
create_layers(reporter, &cache, *picture);
create_layers(reporter, &cache, *picture, kInitialNumLayers, 0);
picture.reset(NULL);
cache.processDeletedPictures();