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);