diff --git a/src/gpu/GrAtlas.h b/src/gpu/GrAtlas.h index 9ccbc53bc6..1e91d1e90e 100644 --- a/src/gpu/GrAtlas.h +++ b/src/gpu/GrAtlas.h @@ -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(plot)); + } +#endif + private: SkTDArray 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); diff --git a/src/gpu/GrLayerCache.cpp b/src/gpu/GrLayerCache.cpp index 32f5a5b6c4..2a57dccb4c 100644 --- a/src/gpu/GrLayerCache.cpp +++ b/src/gpu/GrLayerCache.cpp @@ -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::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 toBeRemoved; + + SkTDynamicHash::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 0fdd6c97b6..e66d3330a6 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 @@ 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 fAtlas; // TODO: could lazily allocate @@ -180,12 +200,28 @@ private: SkAutoTUnref 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 c35ff41219..0abf9d84b5 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 @@ 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); diff --git a/tests/GpuLayerCacheTest.cpp b/tests/GpuLayerCacheTest.cpp index eb7d92c8e2..d14711633f 100644 --- a/tests/GpuLayerCacheTest.cpp +++ b/tests/GpuLayerCacheTest.cpp @@ -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();