/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#if SK_SUPPORT_GPU
#include "GrContext.h"
#include "GrLayerCache.h"
#include "GrResourceCache.h"
#include "SkPictureRecorder.h"
#include "Test.h"
class TestingAccess {
public:
static int NumPlots() {
return GrLayerCache::kNumPlotsX * GrLayerCache::kNumPlotsY;
}
static SkISize PlotSize() {
return SkISize::Make(GrLayerCache::kAtlasTextureWidth / GrLayerCache::kNumPlotsX,
GrLayerCache::kAtlasTextureHeight / GrLayerCache::kNumPlotsY);
}
static GrTexture* GetBackingTexture(GrLayerCache* cache) {
return cache->fAtlas->getTextureOrNull();
}
static int NumLayers(GrLayerCache* cache) {
return cache->numLayers();
}
static void Purge(GrLayerCache* cache, uint32_t pictureID) {
cache->purge(pictureID);
}
static int Uses(GrCachedLayer* layer) {
return layer->uses();
}
static GrCachedLayer* Find(GrLayerCache* cache, uint32_t pictureID,
const SkMatrix& initialMat,
const int* key, int keySize) {
return cache->findLayer(pictureID, initialMat, key, keySize);
}
};
// Add several layers to the cache
static void create_layers(skiatest::Reporter* reporter,
GrLayerCache* cache,
const SkPicture& picture,
int numToAdd,
int idOffset) {
for (int i = 0; i < numToAdd; ++i) {
int key[1] = { idOffset+i+1 };
GrCachedLayer* layer = cache->findLayerOrCreate(picture.uniqueID(),
idOffset+i+1, idOffset+i+2,
SkIRect::MakeEmpty(),
SkIRect::MakeEmpty(),
SkMatrix::I(),
key, 1,
nullptr);
REPORTER_ASSERT(reporter, layer);
GrCachedLayer* temp = TestingAccess::Find(cache, picture.uniqueID(), SkMatrix::I(),
key, 1);
REPORTER_ASSERT(reporter, temp == layer);
REPORTER_ASSERT(reporter, TestingAccess::NumLayers(cache) == idOffset + i + 1);
REPORTER_ASSERT(reporter, picture.uniqueID() == layer->pictureID());
REPORTER_ASSERT(reporter, layer->start() == idOffset + i + 1);
REPORTER_ASSERT(reporter, layer->stop() == idOffset + i + 2);
REPORTER_ASSERT(reporter, nullptr == layer->texture());
REPORTER_ASSERT(reporter, nullptr == layer->paint());
REPORTER_ASSERT(reporter, !layer->isAtlased());
}
}
static void lock_layer(skiatest::Reporter* reporter,
GrLayerCache* cache,
GrCachedLayer* layer) {
// Make each layer big enough to consume one whole plot in the atlas
GrSurfaceDesc desc;
desc.fFlags = kRenderTarget_GrSurfaceFlag;
desc.fWidth = TestingAccess::PlotSize().fWidth;
desc.fHeight = TestingAccess::PlotSize().fHeight;
desc.fConfig = kSkia8888_GrPixelConfig;
bool needsRerendering;
bool inAtlas = cache->tryToAtlas(layer, desc, &needsRerendering);
if (!inAtlas) {
cache->lock(layer, desc, &needsRerendering);
}
REPORTER_ASSERT(reporter, needsRerendering);
cache->lock(layer, desc, &needsRerendering);
REPORTER_ASSERT(reporter, !needsRerendering);
REPORTER_ASSERT(reporter, layer->texture());
REPORTER_ASSERT(reporter, layer->locked());
cache->addUse(layer);
REPORTER_ASSERT(reporter, 1 == TestingAccess::Uses(layer));
}
// 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_FOR_RENDERING_CONTEXTS(GpuLayerCache, reporter, context) {
// Add one more layer than can fit in the atlas
static const int kInitialNumLayers = TestingAccess::NumPlots() + 1;
#if GR_CACHE_STATS
GrResourceCache::Stats stats;
#endif
SkAutoTUnref<const SkPicture> picture;
{
SkPictureRecorder recorder;
SkCanvas* c = recorder.beginRecording(1, 1);
// Draw something, anything, to prevent an empty-picture optimization,
// which is a singleton and never purged.
c->drawRect(SkRect::MakeWH(1,1), SkPaint());
picture.reset(recorder.endRecording());
}
GrResourceCache* resourceCache = context->getResourceCache();
GrLayerCache cache(context);
create_layers(reporter, &cache, *picture, kInitialNumLayers, 0);
for (int i = 0; i < kInitialNumLayers; ++i) {
int key[1] = { i + 1 };
GrCachedLayer* layer = TestingAccess::Find(&cache, picture->uniqueID(), SkMatrix::I(),
key, 1);
REPORTER_ASSERT(reporter, layer);
lock_layer(reporter, &cache, layer);
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
#endif
// The first 4 layers should be in the atlas (and thus have non-empty rects)
if (i < TestingAccess::NumPlots()) {
REPORTER_ASSERT(reporter, layer->isAtlased());
#if GR_CACHE_STATS
REPORTER_ASSERT(reporter, 1 == stats.fTotal);
#endif
} else {
// The 5th layer couldn't fit in the atlas
REPORTER_ASSERT(reporter, !layer->isAtlased());
#if GR_CACHE_STATS
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
#endif
}
}
// Unlock the textures
for (int i = 0; i < kInitialNumLayers; ++i) {
int key[1] = { i+1 };
GrCachedLayer* layer = TestingAccess::Find(&cache, picture->uniqueID(), SkMatrix::I(),
key, 1);
REPORTER_ASSERT(reporter, layer);
cache.removeUse(layer);
}
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
// The floating layer is purgeable the cache is not
REPORTER_ASSERT(reporter, 1 == stats.fNumPurgeable);
REPORTER_ASSERT(reporter, 1 == stats.fNumNonPurgeable);
#endif
for (int i = 0; i < kInitialNumLayers; ++i) {
int key[1] = { i+1 };
GrCachedLayer* layer = TestingAccess::Find(&cache, picture->uniqueID(), SkMatrix::I(),
key, 1);
REPORTER_ASSERT(reporter, layer);
// All the layers should be unlocked
REPORTER_ASSERT(reporter, !layer->locked());
// When hoisted layers aren't cached they are aggressively removed
// from the atlas
#if GR_CACHE_HOISTED_LAYERS
// The first 4 layers should still be in the atlas.
if (i < 4) {
REPORTER_ASSERT(reporter, layer->texture());
REPORTER_ASSERT(reporter, layer->isAtlased());
} else {
#endif
// The final layer should not be atlased.
REPORTER_ASSERT(reporter, nullptr == layer->texture());
REPORTER_ASSERT(reporter, !layer->isAtlased());
#if GR_CACHE_HOISTED_LAYERS
}
#endif
}
// Let go of the backing texture
cache.end();
REPORTER_ASSERT(reporter, nullptr == TestingAccess::GetBackingTexture(&cache));
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
// Now both the floater and the atlas are purgeable
REPORTER_ASSERT(reporter, 2 == stats.fNumPurgeable);
#endif
// re-attach to the backing texture
cache.begin();
REPORTER_ASSERT(reporter, TestingAccess::GetBackingTexture(&cache));
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
// The atlas is restored to being non-purgeable
REPORTER_ASSERT(reporter, 1 == stats.fNumPurgeable);
REPORTER_ASSERT(reporter, 1 == stats.fNumNonPurgeable);
#endif
{
int key[1] = { kInitialNumLayers+1 };
// 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 = TestingAccess::Find(&cache, picture->uniqueID(), SkMatrix::I(),
key, 1);
REPORTER_ASSERT(reporter, layer);
lock_layer(reporter, &cache, layer);
cache.removeUse(layer);
}
for (int i = 0; i < kInitialNumLayers+1; ++i) {
int key[1] = { i+1 };
GrCachedLayer* layer = TestingAccess::Find(&cache, picture->uniqueID(), SkMatrix::I(),
key, 1);
#if GR_CACHE_HOISTED_LAYERS
// 3 old layers plus the new one should be in the atlas.
if (1 == i || 2 == i || 3 == i || 5 == i) {
REPORTER_ASSERT(reporter, layer);
REPORTER_ASSERT(reporter, !layer->locked());
REPORTER_ASSERT(reporter, 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, layer);
REPORTER_ASSERT(reporter, nullptr == layer->texture());
REPORTER_ASSERT(reporter, !layer->isAtlased());
#if GR_CACHE_HOISTED_LAYERS
} else {
// The one bumped out of the atlas (i.e., 0) should be gone
REPORTER_ASSERT(reporter, nullptr == layer);
}
#endif
}
//--------------------------------------------------------------------
// Free them all SkGpuDevice-style. This will not free up the
// atlas' texture but will eliminate all the layers.
TestingAccess::Purge(&cache, picture->uniqueID());
REPORTER_ASSERT(reporter, TestingAccess::NumLayers(&cache) == 0);
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
// Atlas isn't purgeable
REPORTER_ASSERT(reporter, 1 == stats.fNumPurgeable);
REPORTER_ASSERT(reporter, 1 == stats.fNumNonPurgeable);
#endif
//--------------------------------------------------------------------
// Test out the GrContext-style purge. This should remove all the layers
// and the atlas.
// Re-create the layers
create_layers(reporter, &cache, *picture, kInitialNumLayers, 0);
// Free them again GrContext-style. This should free up everything.
cache.freeAll();
REPORTER_ASSERT(reporter, TestingAccess::NumLayers(&cache) == 0);
REPORTER_ASSERT(reporter, nullptr == TestingAccess::GetBackingTexture(&cache));
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
REPORTER_ASSERT(reporter, 2 == stats.fNumPurgeable);
#endif
// Purge the resource cache ...
resourceCache->purgeAllUnlocked();
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 0 == stats.fTotal);
#endif
// and try to re-attach to the backing texture. This should fail
cache.begin();
REPORTER_ASSERT(reporter, nullptr == TestingAccess::GetBackingTexture(&cache));
//--------------------------------------------------------------------
// Test out the MessageBus-style purge. This will not free the atlas
// but should eliminate the free-floating layers.
create_layers(reporter, &cache, *picture, kInitialNumLayers, 0);
// Allocate/use the layers
for (int i = 0; i < kInitialNumLayers; ++i) {
int key[1] = { i + 1 };
GrCachedLayer* layer = TestingAccess::Find(&cache, picture->uniqueID(), SkMatrix::I(),
key, 1);
REPORTER_ASSERT(reporter, layer);
lock_layer(reporter, &cache, layer);
}
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
REPORTER_ASSERT(reporter, 2 == stats.fNumNonPurgeable);
#endif
// Unlock the textures
for (int i = 0; i < kInitialNumLayers; ++i) {
int key[1] = { i+1 };
GrCachedLayer* layer = TestingAccess::Find(&cache, picture->uniqueID(), SkMatrix::I(),
key, 1);
REPORTER_ASSERT(reporter, layer);
cache.removeUse(layer);
}
picture.reset(nullptr);
cache.processDeletedPictures();
REPORTER_ASSERT(reporter, TestingAccess::NumLayers(&cache) == 0);
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
REPORTER_ASSERT(reporter, 1 == stats.fNumPurgeable);
REPORTER_ASSERT(reporter, 1 == stats.fNumNonPurgeable);
#endif
cache.end();
#if GR_CACHE_STATS
resourceCache->getStats(&stats);
REPORTER_ASSERT(reporter, 2 == stats.fTotal);
REPORTER_ASSERT(reporter, 2 == stats.fNumPurgeable);
#endif
}
#endif