Track nested picture xform state for layer hoisting

The accumulated matrix state of any enclosing SkPictures must be stored separate from the picture-local CTM. Any setMatrix calls inside a layer need to replace the picture-local CTM but concatenate with the enclosing SkPicture transform state (and the transform state needed to translate the layer to the correct location in the cached GrTexture).

Review URL: https://codereview.chromium.org/639863005
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index aa0d325..adf904f 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -11,9 +11,10 @@
 #include "GrAtlas.h"
 #include "GrPictureUtils.h"
 #include "GrRect.h"
+
 #include "SkChecksum.h"
-#include "SkTDynamicHash.h"
 #include "SkMessageBus.h"
+#include "SkTDynamicHash.h"
 
 class SkPicture;
 
@@ -75,7 +76,6 @@
         uint32_t pictureID() const { return fPictureID; }
         int start() const { return fStart; }
         const SkIRect& bound() const { return fBounds; }
-        const SkMatrix& ctm() const { return fCTM; }
 
     private:
         // ID of the picture of which this layer is a part
diff --git a/src/gpu/GrLayerHoister.cpp b/src/gpu/GrLayerHoister.cpp
index 929c122..3ae0e2d 100644
--- a/src/gpu/GrLayerHoister.cpp
+++ b/src/gpu/GrLayerHoister.cpp
@@ -25,13 +25,14 @@
                                  SkTDArray<GrHoistedLayer>* recycled) {
     const SkPicture* pict = info.fPicture ? info.fPicture : topLevelPicture;
 
+    SkMatrix combined = SkMatrix::Concat(info.fPreMat, info.fLocalMat);
+
     GrCachedLayer* layer = layerCache->findLayerOrCreate(pict->uniqueID(),
                                                          info.fSaveLayerOpID,
                                                          info.fRestoreOpID,
                                                          layerRect,
-                                                         info.fOriginXform,
+                                                         combined,
                                                          info.fPaint);
-
     GrTextureDesc desc;
     desc.fFlags = kRenderTarget_GrTextureFlagBit;
     desc.fWidth = layerRect.width();
@@ -65,7 +66,8 @@
     hl->fLayer = layer;
     hl->fPicture = pict;
     hl->fOffset = SkIPoint::Make(layerRect.fLeft, layerRect.fTop);
-    hl->fCTM = info.fOriginXform;
+    hl->fLocalMat = info.fLocalMat;
+    hl->fPreMat = info.fPreMat;
 }
 
 // Return true if any layers are suitable for hoisting
@@ -113,7 +115,6 @@
             continue;
         }
 
-
         SkIRect ir;
         layerRect.roundOut(&ir);
 
@@ -144,10 +145,12 @@
         GrCachedLayer* layer = layers[i].fLayer;
         const SkPicture* picture = layers[i].fPicture;
 
+        SkMatrix combined = SkMatrix::Concat(layers[i].fPreMat, layers[i].fLocalMat);
+
         GrReplacements::ReplacementInfo* layerInfo =
                     replacements->newReplacement(picture->uniqueID(),
                                                  layer->start(),
-                                                 layers[i].fCTM);
+                                                 combined);
         layerInfo->fStop = layer->stop();
         layerInfo->fPos = layers[i].fOffset;
 
@@ -171,7 +174,8 @@
     }
 }
 
-void GrLayerHoister::DrawLayers(const SkTDArray<GrHoistedLayer>& atlased,
+void GrLayerHoister::DrawLayers(GrContext* context,
+                                const SkTDArray<GrHoistedLayer>& atlased,
                                 const SkTDArray<GrHoistedLayer>& nonAtlased,
                                 const SkTDArray<GrHoistedLayer>& recycled,
                                 GrReplacements* replacements) {
@@ -183,14 +187,17 @@
 
         SkCanvas* atlasCanvas = surface->getCanvas();
 
-        SkPaint paint;
-        paint.setColor(SK_ColorTRANSPARENT);
-        paint.setXfermode(SkXfermode::Create(SkXfermode::kSrc_Mode))->unref();
+        SkPaint clearPaint;
+        clearPaint.setColor(SK_ColorTRANSPARENT);
+        clearPaint.setXfermode(SkXfermode::Create(SkXfermode::kSrc_Mode))->unref();
 
         for (int i = 0; i < atlased.count(); ++i) {
-            GrCachedLayer* layer = atlased[i].fLayer;
+            const GrCachedLayer* layer = atlased[i].fLayer;
             const SkPicture* pict = atlased[i].fPicture;
             const SkIPoint offset = atlased[i].fOffset;
+            SkDEBUGCODE(const SkPaint* layerPaint = layer->paint();)
+
+            SkASSERT(!layerPaint || !layerPaint->getImageFilter());
 
             atlasCanvas->save();
 
@@ -204,7 +211,7 @@
 
             // 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);
+            atlasCanvas->drawRect(bound, clearPaint);
 
             // info.fCTM maps the layer's top/left to the origin.
             // Since this layer is atlased, the top/left corner needs
@@ -213,11 +220,13 @@
             initialCTM.setTranslate(SkIntToScalar(-offset.fX), 
                                     SkIntToScalar(-offset.fY));
             initialCTM.postTranslate(bound.fLeft, bound.fTop);
-            
+            initialCTM.postConcat(atlased[i].fPreMat);
+
             atlasCanvas->translate(SkIntToScalar(-offset.fX), 
                                    SkIntToScalar(-offset.fY));
             atlasCanvas->translate(bound.fLeft, bound.fTop);
-            atlasCanvas->concat(atlased[i].fCTM);
+            atlasCanvas->concat(atlased[i].fPreMat);
+            atlasCanvas->concat(atlased[i].fLocalMat);
 
             SkRecordPartialDraw(*pict->fRecord.get(), atlasCanvas, bound,
                                 layer->start()+1, layer->stop(), initialCTM);
@@ -232,7 +241,7 @@
     for (int i = 0; i < nonAtlased.count(); ++i) {
         GrCachedLayer* layer = nonAtlased[i].fLayer;
         const SkPicture* pict = nonAtlased[i].fPicture;
-        const SkIPoint offset = nonAtlased[i].fOffset;
+        const SkIPoint& offset = nonAtlased[i].fOffset;
 
         // Each non-atlased layer has its own GrTexture
         SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTargetDirect(
@@ -240,6 +249,8 @@
 
         SkCanvas* layerCanvas = surface->getCanvas();
 
+        SkASSERT(0 == layer->rect().fLeft && 0 == layer->rect().fTop);
+
         // 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),
@@ -252,12 +263,12 @@
         layerCanvas->clear(SK_ColorTRANSPARENT);
 
         SkMatrix initialCTM;
-        initialCTM.setTranslate(SkIntToScalar(-offset.fX), 
-                                SkIntToScalar(-offset.fY));
+        initialCTM.setTranslate(SkIntToScalar(-offset.fX), SkIntToScalar(-offset.fY));
+        initialCTM.postConcat(nonAtlased[i].fPreMat);
 
-        layerCanvas->translate(SkIntToScalar(-offset.fX), 
-                               SkIntToScalar(-offset.fY));
-        layerCanvas->concat(nonAtlased[i].fCTM);
+        layerCanvas->translate(SkIntToScalar(-offset.fX), SkIntToScalar(-offset.fY));
+        layerCanvas->concat(nonAtlased[i].fPreMat);
+        layerCanvas->concat(nonAtlased[i].fLocalMat);
 
         SkRecordPartialDraw(*pict->fRecord.get(), layerCanvas, bound,
                             layer->start()+1, layer->stop(), initialCTM);
diff --git a/src/gpu/GrLayerHoister.h b/src/gpu/GrLayerHoister.h
index 7f49881..2b2eb09 100644
--- a/src/gpu/GrLayerHoister.h
+++ b/src/gpu/GrLayerHoister.h
@@ -21,7 +21,8 @@
     const SkPicture* fPicture;
     GrCachedLayer*   fLayer;
     SkIPoint         fOffset;
-    SkMatrix         fCTM;
+    SkMatrix         fPreMat;
+    SkMatrix         fLocalMat;
 };
 
 // This class collects the layer hoisting functionality in one place.
@@ -50,13 +51,15 @@
                                   SkTDArray<GrHoistedLayer>* recycled);
 
     /** Draw the specified layers into either the atlas or free floating textures.
+        @param context      Owner of the layer cache (and thus the layers)
         @param atlased      The layers to be drawn into the atlas
         @param nonAtlased   The layers to be drawn into their own textures
         @param recycled     Layers that don't need rendering but do need to go into the 
                             replacements object
         @param replacements The replacement structure to fill in with the rendered layer info
     */
-    static void DrawLayers(const SkTDArray<GrHoistedLayer>& atlased,
+    static void DrawLayers(GrContext* context,
+                           const SkTDArray<GrHoistedLayer>& atlased,
                            const SkTDArray<GrHoistedLayer>& nonAtlased,
                            const SkTDArray<GrHoistedLayer>& recycled,
                            GrReplacements* replacements);
diff --git a/src/gpu/GrPictureUtils.cpp b/src/gpu/GrPictureUtils.cpp
index a215a0e..e91980f 100644
--- a/src/gpu/GrPictureUtils.cpp
+++ b/src/gpu/GrPictureUtils.cpp
@@ -124,8 +124,9 @@
             dst.fPicture = src.fPicture ? src.fPicture : static_cast<const SkPicture*>(dp.picture);
             dst.fPicture->ref();
             dst.fBounds = newClip;
-            dst.fOriginXform = src.fOriginXform;
-            dst.fOriginXform.postConcat(*fCTM);
+            dst.fLocalMat = src.fLocalMat;
+            dst.fPreMat = src.fPreMat;
+            dst.fPreMat.preConcat(*fCTM);
             if (src.fPaint) {
                 dst.fPaint = SkNEW_ARGS(SkPaint, (*src.fPaint));
             }
@@ -180,7 +181,8 @@
 
         SkASSERT(NULL == slInfo.fPicture);  // This layer is in the top-most picture
         slInfo.fBounds = si.fBounds;
-        slInfo.fOriginXform = *fCTM;
+        slInfo.fLocalMat = *fCTM;
+        slInfo.fPreMat = SkMatrix::I();
         if (si.fPaint) {
             slInfo.fPaint = SkNEW_ARGS(SkPaint, (*si.fPaint));
         }
diff --git a/src/gpu/GrPictureUtils.h b/src/gpu/GrPictureUtils.h
index 7bcf632..2018b0d 100644
--- a/src/gpu/GrPictureUtils.h
+++ b/src/gpu/GrPictureUtils.h
@@ -28,9 +28,17 @@
         const SkPicture* fPicture;
         // The device space bounds of this layer.
         SkIRect fBounds;
-        // The matrix state in which this layer's draws must occur. It does not
-        // include the translation needed to map the layer's top-left point to the origin.
-        SkMatrix fOriginXform;
+        // The pre-matrix begins as the identity and accumulates the transforms
+        // of the containing SkPictures (if any). This matrix state has to be
+        // part of the initial matrix during replay so that it will be 
+        // preserved across setMatrix calls.
+        SkMatrix fPreMat;
+        // The matrix state (in the leaf picture) in which this layer's draws 
+        // must occur. It will/can be overridden by setMatrix calls in the
+        // layer itself. It does not include the translation needed to map the 
+        // layer's top-left point to the origin (which must be part of the
+        // initial matrix).
+        SkMatrix fLocalMat;
         // The paint to use on restore. Can be NULL since it is optional.
         const SkPaint* fPaint;
         // The ID of this saveLayer in the picture. 0 is an invalid ID.
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 83cdef7..02766b6 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1814,7 +1814,7 @@
 
     GrReplacements replacements;
 
-    GrLayerHoister::DrawLayers(atlased, nonAtlased, recycled, &replacements);
+    GrLayerHoister::DrawLayers(fContext, atlased, nonAtlased, recycled, &replacements);
 
     // Render the entire picture using new layers
     const SkMatrix initialMatrix = mainCanvas->getTotalMatrix();
diff --git a/tests/PictureTest.cpp b/tests/PictureTest.cpp
index d10d2b2..7546a5d 100644
--- a/tests/PictureTest.cpp
+++ b/tests/PictureTest.cpp
@@ -983,7 +983,8 @@
             REPORTER_ASSERT(reporter, NULL == info0.fPicture);
             REPORTER_ASSERT(reporter, kWidth == info0.fBounds.width() &&
                                       kHeight == info0.fBounds.height());
-            REPORTER_ASSERT(reporter, info0.fOriginXform.isIdentity());
+            REPORTER_ASSERT(reporter, info0.fLocalMat.isIdentity());
+            REPORTER_ASSERT(reporter, info0.fPreMat.isIdentity());
             REPORTER_ASSERT(reporter, 0 == info0.fBounds.fLeft && 0 == info0.fBounds.fTop);
             REPORTER_ASSERT(reporter, NULL == info0.fPaint);
             REPORTER_ASSERT(reporter, !info0.fIsNested && !info0.fHasNestedLayers);
@@ -991,7 +992,8 @@
             REPORTER_ASSERT(reporter, NULL == info1.fPicture);
             REPORTER_ASSERT(reporter, kWidth == info1.fBounds.width() &&
                                       kHeight == info1.fBounds.height());
-            REPORTER_ASSERT(reporter, info1.fOriginXform.isIdentity());
+            REPORTER_ASSERT(reporter, info1.fLocalMat.isIdentity());
+            REPORTER_ASSERT(reporter, info1.fPreMat.isIdentity());
             REPORTER_ASSERT(reporter, 0 == info1.fBounds.fLeft && 0 == info1.fBounds.fTop);
             REPORTER_ASSERT(reporter, NULL == info1.fPaint);
             REPORTER_ASSERT(reporter, !info1.fIsNested &&
@@ -1000,7 +1002,8 @@
             REPORTER_ASSERT(reporter, NULL == info2.fPicture);
             REPORTER_ASSERT(reporter, kWidth / 2 == info2.fBounds.width() &&
                                       kHeight / 2 == info2.fBounds.height()); // bound reduces size
-            REPORTER_ASSERT(reporter, !info2.fOriginXform.isIdentity());
+            REPORTER_ASSERT(reporter, !info2.fLocalMat.isIdentity());
+            REPORTER_ASSERT(reporter, info2.fPreMat.isIdentity());
             REPORTER_ASSERT(reporter, kWidth / 2 == info2.fBounds.fLeft &&   // translated
                                       kHeight / 2 == info2.fBounds.fTop);
             REPORTER_ASSERT(reporter, NULL == info1.fPaint);
@@ -1009,7 +1012,8 @@
             REPORTER_ASSERT(reporter, NULL == info3.fPicture);
             REPORTER_ASSERT(reporter, kWidth == info3.fBounds.width() &&
                                       kHeight == info3.fBounds.height());
-            REPORTER_ASSERT(reporter, info3.fOriginXform.isIdentity());
+            REPORTER_ASSERT(reporter, info3.fLocalMat.isIdentity());
+            REPORTER_ASSERT(reporter, info3.fPreMat.isIdentity());
             REPORTER_ASSERT(reporter, 0 == info3.fBounds.fLeft && 0 == info3.fBounds.fTop);
             REPORTER_ASSERT(reporter, info3.fPaint);
             REPORTER_ASSERT(reporter, !info3.fIsNested && !info3.fHasNestedLayers);
@@ -1018,7 +1022,8 @@
             REPORTER_ASSERT(reporter, kWidth == info4.fBounds.width() &&
                                       kHeight == info4.fBounds.height());
             REPORTER_ASSERT(reporter, 0 == info4.fBounds.fLeft && 0 == info4.fBounds.fTop);
-            REPORTER_ASSERT(reporter, info4.fOriginXform.isIdentity());
+            REPORTER_ASSERT(reporter, info4.fLocalMat.isIdentity());
+            REPORTER_ASSERT(reporter, info4.fPreMat.isIdentity());
             REPORTER_ASSERT(reporter, info4.fPaint);
             REPORTER_ASSERT(reporter, !info4.fIsNested &&
                                       info4.fHasNestedLayers); // has a nested SL
@@ -1027,7 +1032,8 @@
             REPORTER_ASSERT(reporter, kWidth == info5.fBounds.width() &&
                                       kHeight == info5.fBounds.height());
             REPORTER_ASSERT(reporter, 0 == info5.fBounds.fLeft && 0 == info5.fBounds.fTop);
-            REPORTER_ASSERT(reporter, info5.fOriginXform.isIdentity());
+            REPORTER_ASSERT(reporter, info5.fLocalMat.isIdentity());
+            REPORTER_ASSERT(reporter, info5.fPreMat.isIdentity());
             REPORTER_ASSERT(reporter, NULL == info5.fPaint);
             REPORTER_ASSERT(reporter, info5.fIsNested && !info5.fHasNestedLayers); // is nested
 
@@ -1035,7 +1041,8 @@
             REPORTER_ASSERT(reporter, kWidth == info6.fBounds.width() &&
                                       kHeight == info6.fBounds.height());
             REPORTER_ASSERT(reporter, 0 == info6.fBounds.fLeft && 0 == info6.fBounds.fTop);
-            REPORTER_ASSERT(reporter, info6.fOriginXform.isIdentity());
+            REPORTER_ASSERT(reporter, info6.fLocalMat.isIdentity());
+            REPORTER_ASSERT(reporter, info6.fPreMat.isIdentity());
             REPORTER_ASSERT(reporter, info6.fPaint);
             REPORTER_ASSERT(reporter, !info6.fIsNested &&
                                       info6.fHasNestedLayers); // has a nested SL
@@ -1044,7 +1051,8 @@
             REPORTER_ASSERT(reporter, kWidth == info7.fBounds.width() &&
                                       kHeight == info7.fBounds.height());
             REPORTER_ASSERT(reporter, 0 == info7.fBounds.fLeft && 0 == info7.fBounds.fTop);
-            REPORTER_ASSERT(reporter, info7.fOriginXform.isIdentity());
+            REPORTER_ASSERT(reporter, info7.fLocalMat.isIdentity());
+            REPORTER_ASSERT(reporter, info7.fPreMat.isIdentity());
             REPORTER_ASSERT(reporter, NULL == info7.fPaint);
             REPORTER_ASSERT(reporter, info7.fIsNested && !info7.fHasNestedLayers); // is nested
         }