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
diff --git a/src/gpu/GrAtlas.h b/src/gpu/GrAtlas.h
index 9ccbc53..1e91d1e 100644
--- a/src/gpu/GrAtlas.h
+++ b/src/gpu/GrAtlas.h
@@ -81,6 +81,12 @@
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 @@
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);
diff --git a/src/gpu/GrLayerCache.cpp b/src/gpu/GrLayerCache.cpp
index 32f5a5b..2a57dcc 100644
--- a/src/gpu/GrLayerCache.cpp
+++ b/src/gpu/GrLayerCache.cpp
@@ -27,6 +27,7 @@
} else {
SkASSERT(fRect.isEmpty());
SkASSERT(NULL == fPlot);
+ SkASSERT(!fLocked); // layers without a texture cannot be locked
}
if (NULL != fPlot) {
@@ -35,6 +36,13 @@
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 @@
GrLayerCache::GrLayerCache(GrContext* context)
: fContext(context) {
this->initAtlas();
+ memset(fPlotLocks, 0, sizeof(fPlotLocks));
}
GrLayerCache::~GrLayerCache() {
@@ -81,12 +90,8 @@
}
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 @@
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 @@
}
#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 @@
}
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 @@
// 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 @@
}
}
+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 };
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index 0fdd6c9..e66d333 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -10,7 +10,6 @@
#define USE_ATLAS 0
-#include "GrAllocPool.h"
#include "GrAtlas.h"
#include "GrPictureUtils.h"
#include "GrRect.h"
@@ -47,6 +46,9 @@
// 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 @@
: fKey(pictureID, layerID)
, fTexture(NULL)
, fRect(GrIRect16::MakeEmpty())
- , fPlot(NULL) {
+ , fPlot(NULL)
+ , fLocked(false) {
SkASSERT(SK_InvalidGenID != pictureID && layerID >= 0);
}
@@ -104,14 +107,17 @@
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 @@
// 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 @@
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 @@
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(); }
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index c35ff41..0abf9d8 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -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 @@
// 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);