Add CTM to the cached layers' key and reduce render target pingponging in layer pre-rendering
The CTM is required on the key so we will re-render when necessary.
The SkGpuDevice change ensures big layers in the midst of atlas-able layers don't cause a render target switch.
R=bsalomon@google.com
Author: robertphillips@google.com
Review URL: https://codereview.chromium.org/433553002
diff --git a/src/gpu/GrLayerCache.cpp b/src/gpu/GrLayerCache.cpp
index 2c97387..5fb76d7 100644
--- a/src/gpu/GrLayerCache.cpp
+++ b/src/gpu/GrLayerCache.cpp
@@ -13,8 +13,8 @@
#ifdef SK_DEBUG
void GrCachedLayer::validate(const GrTexture* backingTexture) const {
- SkASSERT(SK_InvalidGenID != fKey.getPictureID());
- SkASSERT(-1 != fKey.getLayerID());
+ SkASSERT(SK_InvalidGenID != fKey.pictureID());
+ SkASSERT(fKey.start() > 0 && fKey.stop() > 0);
if (NULL != fTexture) {
@@ -117,24 +117,31 @@
this->initAtlas();
}
-GrCachedLayer* GrLayerCache::createLayer(const SkPicture* picture, int layerID) {
- SkASSERT(picture->uniqueID() != SK_InvalidGenID && layerID >= 0);
+GrCachedLayer* GrLayerCache::createLayer(const SkPicture* picture,
+ int start, int stop,
+ const SkMatrix& ctm) {
+ SkASSERT(picture->uniqueID() != SK_InvalidGenID && start > 0 && stop > 0);
- GrCachedLayer* layer = SkNEW_ARGS(GrCachedLayer, (picture->uniqueID(), layerID));
+ GrCachedLayer* layer = SkNEW_ARGS(GrCachedLayer, (picture->uniqueID(), start, stop, ctm));
fLayerHash.add(layer);
return layer;
}
-GrCachedLayer* GrLayerCache::findLayer(const SkPicture* picture, int layerID) {
- SkASSERT(picture->uniqueID() != SK_InvalidGenID && layerID >= 0);
- return fLayerHash.find(GrCachedLayer::Key(picture->uniqueID(), layerID));
+GrCachedLayer* GrLayerCache::findLayer(const SkPicture* picture,
+ int start, int stop,
+ const SkMatrix& ctm) {
+ SkASSERT(picture->uniqueID() != SK_InvalidGenID && start > 0 && stop > 0);
+ return fLayerHash.find(GrCachedLayer::Key(picture->uniqueID(), start, stop, ctm));
}
-GrCachedLayer* GrLayerCache::findLayerOrCreate(const SkPicture* picture, int layerID) {
- SkASSERT(picture->uniqueID() != SK_InvalidGenID && layerID >= 0);
- GrCachedLayer* layer = fLayerHash.find(GrCachedLayer::Key(picture->uniqueID(), layerID));
+GrCachedLayer* GrLayerCache::findLayerOrCreate(const SkPicture* picture,
+ int start, int stop,
+ const SkMatrix& ctm) {
+ SkASSERT(picture->uniqueID() != SK_InvalidGenID && start > 0 && stop > 0);
+ GrCachedLayer* layer = fLayerHash.find(GrCachedLayer::Key(picture->uniqueID(),
+ start, stop, ctm));
if (NULL == layer) {
- layer = this->createLayer(picture, layerID);
+ layer = this->createLayer(picture, start, stop, ctm);
}
return layer;
@@ -221,6 +228,19 @@
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.
+#if 0
+ // This testing code aggressively removes the atlased layers. This
+ // can be used to separate the performance contribution of less
+ // render target pingponging from that due to the re-use of cached layers
+ GrPictureInfo* pictInfo = fPictureHash.find(layer->pictureID());
+ SkASSERT(NULL != pictInfo);
+
+ GrAtlas::RemovePlot(&pictInfo->fPlotUsage, layer->plot());
+
+ layer->setPlot(NULL);
+ layer->setTexture(NULL, GrIRect16::MakeEmpty());
+#endif
+
} else {
fContext->unlockScratchTexture(layer->texture());
layer->setTexture(NULL, GrIRect16::MakeEmpty());
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index f4087d6..f027a26 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -44,50 +44,70 @@
// 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
+// 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
struct Key {
- Key(uint32_t pictureID, int layerID) : fPictureID(pictureID) , fLayerID(layerID) {}
+ Key(uint32_t pictureID, int start, int stop, const SkMatrix& ctm)
+ : fPictureID(pictureID)
+ , fStart(start)
+ , fStop(stop)
+ , fCTM(ctm) {
+ fCTM.getType(); // force initialization of type so hashes match
- bool operator==(const Key& other) const {
- return fPictureID == other.fPictureID && fLayerID == other.fLayerID;
+ // Key needs to be tightly packed.
+ GR_STATIC_ASSERT(sizeof(Key) == sizeof(uint32_t) + 2 * sizeof(int) +
+ 9 * sizeof(SkScalar) + sizeof(uint32_t));
}
- uint32_t getPictureID() const { return fPictureID; }
- int getLayerID() const { return fLayerID; }
+ bool operator==(const Key& other) const {
+ return fPictureID == other.fPictureID &&
+ fStart == other.fStart &&
+ fStop == other.fStop &&
+ fCTM.cheapEqualTo(other.fCTM);
+ }
+
+ uint32_t pictureID() const { return fPictureID; }
+ int start() const { return fStart; }
+ int stop() const { return fStop; }
+ const SkMatrix& ctm() const { return fCTM; }
private:
// ID of the picture of which this layer is a part
const uint32_t fPictureID;
- // fLayerID is the index of this layer in the picture (one of 0 .. #layers).
- const int fLayerID;
+ // The range of commands in the picture this layer represents
+ const int fStart;
+ const int fStop;
+ // The CTM applied to this layer in the picture
+ SkMatrix fCTM;
};
static const Key& GetKey(const GrCachedLayer& layer) { return layer.fKey; }
static uint32_t Hash(const Key& key) {
- return SkChecksum::Mix((key.getPictureID() << 16) | key.getLayerID());
+ return SkChecksum::Murmur3(reinterpret_cast<const uint32_t*>(&key), sizeof(Key));
}
// GrCachedLayer proper
- GrCachedLayer(uint32_t pictureID, int layerID)
- : fKey(pictureID, layerID)
+ GrCachedLayer(uint32_t pictureID, int start, int stop, const SkMatrix& ctm)
+ : fKey(pictureID, start, stop, ctm)
, fTexture(NULL)
, fRect(GrIRect16::MakeEmpty())
, fPlot(NULL)
, fLocked(false) {
- SkASSERT(SK_InvalidGenID != pictureID && layerID >= 0);
+ SkASSERT(SK_InvalidGenID != pictureID && start >= 0 && stop >= 0);
}
~GrCachedLayer() {
SkSafeUnref(fTexture);
}
- uint32_t pictureID() const { return fKey.getPictureID(); }
- int layerID() const { return fKey.getLayerID(); }
+ uint32_t pictureID() const { return fKey.pictureID(); }
+ int start() const { return fKey.start(); }
+ int stop() const { return fKey.stop(); }
+ const SkMatrix& ctm() const { return fKey.ctm(); }
void setTexture(GrTexture* texture, const GrIRect16& rect) {
SkRefCnt_SafeAssign(fTexture, texture);
@@ -151,8 +171,10 @@
// elements by the GrContext
void freeAll();
- GrCachedLayer* findLayer(const SkPicture* picture, int layerID);
- GrCachedLayer* findLayerOrCreate(const SkPicture* picture, int layerID);
+ GrCachedLayer* findLayer(const SkPicture* picture, int start, int stop, const SkMatrix& ctm);
+ GrCachedLayer* findLayerOrCreate(const SkPicture* picture,
+ int start, int stop,
+ const SkMatrix& ctm);
// Inform the cache that layer's cached image is now required. Return true
// if it was found in the ResourceCache and doesn't need to be regenerated.
@@ -206,7 +228,7 @@
int fPlotLocks[kNumPlotsX * kNumPlotsY];
void initAtlas();
- GrCachedLayer* createLayer(const SkPicture* picture, int layerID);
+ GrCachedLayer* createLayer(const SkPicture* picture, int start, int stop, const SkMatrix& ctm);
// Remove all the layers (and unlock any resources) associated with 'pictureID'
void purge(uint32_t pictureID);
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 2853805..b1e23f9 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1871,7 +1871,7 @@
result->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (info, texture)))->unref();
}
-bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, const SkPicture* picture) {
+bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* mainCanvas, const SkPicture* picture) {
fContext->getLayerCache()->processDeletedPictures();
SkPicture::AccelData::Key key = GPUAccelData::ComputeAccelDataKey();
@@ -1893,7 +1893,7 @@
}
SkRect clipBounds;
- if (!canvas->getClipBounds(&clipBounds)) {
+ if (!mainCanvas->getClipBounds(&clipBounds)) {
return true;
}
SkIRect query;
@@ -1970,13 +1970,19 @@
SkPictureReplacementPlayback::PlaybackReplacements replacements;
+ SkTDArray<GrCachedLayer*> atlased, nonAtlased;
+ atlased.setReserve(gpuData->numSaveLayers());
+
// Generate the layer and/or ensure it is locked
for (int i = 0; i < gpuData->numSaveLayers(); ++i) {
if (pullForward[i]) {
- GrCachedLayer* layer = fContext->getLayerCache()->findLayerOrCreate(picture, i);
-
const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(i);
+ GrCachedLayer* layer = fContext->getLayerCache()->findLayerOrCreate(picture,
+ info.fSaveLayerOpID,
+ info.fRestoreOpID,
+ info.fCTM);
+
SkPictureReplacementPlayback::PlaybackReplacements::ReplacementInfo* layerInfo =
replacements.push();
layerInfo->fStart = info.fSaveLayerOpID;
@@ -2009,56 +2015,110 @@
layer->rect().width(),
layer->rect().height());
-
if (needsRendering) {
- SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTargetDirect(
- layer->texture()->asRenderTarget(),
- SkSurface::kStandard_TextRenderMode,
- SkSurface::kDontClear_RenderTargetFlag));
-
- SkCanvas* canvas = surface->getCanvas();
-
- // Add a rect clip to make sure the rendering doesn't
- // extend beyond the boundaries of the atlased sub-rect
- SkRect bound = SkRect::Make(layerInfo->fSrcRect);
- canvas->clipRect(bound);
-
if (layer->isAtlased()) {
- // Since 'clear' doesn't respect the clip we need to draw a rect
- // 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);
+ *atlased.append() = layer;
} else {
- canvas->clear(SK_ColorTRANSPARENT);
+ *nonAtlased.append() = layer;
}
-
- // info.fCTM maps the layer's top/left to the origin.
- // If this layer is atlased the top/left corner needs
- // to be offset to some arbitrary location in the backing
- // texture.
- canvas->translate(bound.fLeft, bound.fTop);
- canvas->concat(info.fCTM);
-
- SkPictureRangePlayback rangePlayback(picture,
- info.fSaveLayerOpID,
- info.fRestoreOpID);
- rangePlayback.draw(canvas, NULL);
-
- canvas->flush();
}
}
}
- // Playback using new layers
+ // Render the atlased layers that require it
+ if (atlased.count() > 0) {
+ // All the atlased layers are rendered into the same GrTexture
+ SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTargetDirect(
+ atlased[0]->texture()->asRenderTarget(),
+ SkSurface::kStandard_TextRenderMode,
+ SkSurface::kDontClear_RenderTargetFlag));
+
+ SkCanvas* atlasCanvas = surface->getCanvas();
+
+ SkPaint paint;
+ paint.setColor(SK_ColorTRANSPARENT);
+ paint.setXfermode(SkXfermode::Create(SkXfermode::kSrc_Mode))->unref();
+
+ for (int i = 0; i < atlased.count(); ++i) {
+ GrCachedLayer* layer = atlased[i];
+
+ atlasCanvas->save();
+
+ // Add a rect clip to make sure the rendering doesn't
+ // extend beyond the boundaries of the atlased sub-rect
+ SkRect bound = SkRect::MakeXYWH(SkIntToScalar(layer->rect().fLeft),
+ SkIntToScalar(layer->rect().fTop),
+ SkIntToScalar(layer->rect().width()),
+ SkIntToScalar(layer->rect().height()));
+ atlasCanvas->clipRect(bound);
+
+ // Since 'clear' doesn't respect the clip we need to draw a rect
+ // TODO: ensure none of the atlased layers contain a clear call!
+ atlasCanvas->drawRect(bound, paint);
+
+ // info.fCTM maps the layer's top/left to the origin.
+ // Since this layer is atlased, the top/left corner needs
+ // to be offset to the correct location in the backing texture.
+ atlasCanvas->translate(bound.fLeft, bound.fTop);
+ atlasCanvas->concat(layer->ctm());
+
+ SkPictureRangePlayback rangePlayback(picture,
+ layer->start(),
+ layer->stop());
+ rangePlayback.draw(atlasCanvas, NULL);
+
+ atlasCanvas->restore();
+ }
+
+ atlasCanvas->flush();
+ }
+
+ // Render the non-atlased layers that require it
+ for (int i = 0; i < nonAtlased.count(); ++i) {
+ GrCachedLayer* layer = nonAtlased[i];
+
+ // Each non-atlased layer has its own GrTexture
+ SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTargetDirect(
+ layer->texture()->asRenderTarget(),
+ SkSurface::kStandard_TextRenderMode,
+ SkSurface::kDontClear_RenderTargetFlag));
+
+ SkCanvas* layerCanvas = surface->getCanvas();
+
+ // Add a rect clip to make sure the rendering doesn't
+ // extend beyond the boundaries of the atlased sub-rect
+ SkRect bound = SkRect::MakeXYWH(SkIntToScalar(layer->rect().fLeft),
+ SkIntToScalar(layer->rect().fTop),
+ SkIntToScalar(layer->rect().width()),
+ SkIntToScalar(layer->rect().height()));
+
+ layerCanvas->clipRect(bound); // TODO: still useful?
+
+ layerCanvas->clear(SK_ColorTRANSPARENT);
+
+ layerCanvas->concat(layer->ctm());
+
+ SkPictureRangePlayback rangePlayback(picture,
+ layer->start(),
+ layer->stop());
+ rangePlayback.draw(layerCanvas, NULL);
+
+ layerCanvas->flush();
+ }
+
+ // Render the entire picture using new layers
SkPictureReplacementPlayback playback(picture, &replacements, ops.get());
- playback.draw(canvas, NULL);
+ playback.draw(mainCanvas, NULL);
// unlock the layers
for (int i = 0; i < gpuData->numSaveLayers(); ++i) {
- GrCachedLayer* layer = fContext->getLayerCache()->findLayer(picture, i);
+ const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(i);
+
+ GrCachedLayer* layer = fContext->getLayerCache()->findLayer(picture,
+ info.fSaveLayerOpID,
+ info.fRestoreOpID,
+ info.fCTM);
fContext->getLayerCache()->unlock(layer);
}
diff --git a/tests/GpuLayerCacheTest.cpp b/tests/GpuLayerCacheTest.cpp
index 8ca7b2b..789854c 100644
--- a/tests/GpuLayerCacheTest.cpp
+++ b/tests/GpuLayerCacheTest.cpp
@@ -31,15 +31,19 @@
int idOffset) {
for (int i = 0; i < numToAdd; ++i) {
- GrCachedLayer* layer = cache->findLayerOrCreate(&picture, idOffset+i);
+ GrCachedLayer* layer = cache->findLayerOrCreate(&picture,
+ idOffset+i+1, idOffset+i+2,
+ SkMatrix::I());
REPORTER_ASSERT(reporter, NULL != layer);
- GrCachedLayer* temp = cache->findLayer(&picture, idOffset+i);
+ GrCachedLayer* temp = cache->findLayer(&picture, idOffset+i+1, idOffset+i+2, SkMatrix::I());
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->layerID() == idOffset + i);
+ REPORTER_ASSERT(reporter, layer->start() == idOffset + i + 1);
+ REPORTER_ASSERT(reporter, layer->stop() == idOffset + i + 2);
+ REPORTER_ASSERT(reporter, layer->ctm() == SkMatrix::I());
REPORTER_ASSERT(reporter, NULL == layer->texture());
REPORTER_ASSERT(reporter, !layer->isAtlased());
}
@@ -95,7 +99,7 @@
create_layers(reporter, &cache, *picture, kInitialNumLayers, 0);
for (int i = 0; i < kInitialNumLayers; ++i) {
- GrCachedLayer* layer = cache.findLayer(picture, i);
+ GrCachedLayer* layer = cache.findLayer(picture, i+1, i+2, SkMatrix::I());
REPORTER_ASSERT(reporter, NULL != layer);
lock_layer(reporter, &cache, layer);
@@ -112,14 +116,14 @@
// Unlock the textures
for (int i = 0; i < kInitialNumLayers; ++i) {
- GrCachedLayer* layer = cache.findLayer(picture, i);
+ GrCachedLayer* layer = cache.findLayer(picture, i+1, i+2, SkMatrix::I());
REPORTER_ASSERT(reporter, NULL != layer);
cache.unlock(layer);
}
for (int i = 0; i < kInitialNumLayers; ++i) {
- GrCachedLayer* layer = cache.findLayer(picture, i);
+ GrCachedLayer* layer = cache.findLayer(picture, i+1, i+2, SkMatrix::I());
REPORTER_ASSERT(reporter, NULL != layer);
REPORTER_ASSERT(reporter, !layer->locked());
@@ -138,7 +142,9 @@
// 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);
+ GrCachedLayer* layer = cache.findLayer(picture,
+ kInitialNumLayers+1, kInitialNumLayers+2,
+ SkMatrix::I());
REPORTER_ASSERT(reporter, NULL != layer);
lock_layer(reporter, &cache, layer);
@@ -146,7 +152,7 @@
}
for (int i = 0; i < kInitialNumLayers+1; ++i) {
- GrCachedLayer* layer = cache.findLayer(picture, i);
+ GrCachedLayer* layer = cache.findLayer(picture, i+1, i+2, SkMatrix::I());
// 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);