Begin atlasing

This CL makes it possible for pulled-forward-layers to be atlased. It currently has a couple glaring limitations (which is why it is disabled):

1) the atlased layers cannot be purged nor aged out
2) the texture backing the atlas is not pulled from (or returned to) the resource cache

#1 is on hold until we have a recycling rectanizer

A separate major limitation (the non-atlased layers aren't cached) is blocked until we can transmute entries in the resource cache from scratch to non-scratch while potentially preserving their contents.

R=bsalomon@google.com

Author: robertphillips@google.com

Review URL: https://codereview.chromium.org/354533004
diff --git a/gyp/tests.gypi b/gyp/tests.gypi
index a94ad98..3b67ebd 100644
--- a/gyp/tests.gypi
+++ b/gyp/tests.gypi
@@ -87,6 +87,7 @@
     '../tests/GifTest.cpp',
     '../tests/GpuColorFilterTest.cpp',
     '../tests/GpuDrawPathTest.cpp',
+    '../tests/GpuLayerCacheTest.cpp',
     '../tests/GpuRectanizerTest.cpp',
     '../tests/GrBinHashKeyTest.cpp',
     '../tests/GrContextFactoryTest.cpp',
diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h
index 68d4702..cb5133a 100644
--- a/include/core/SkSurface.h
+++ b/include/core/SkSurface.h
@@ -77,18 +77,29 @@
         kDistanceField_TextRenderMode,
     };
 
+    enum RenderTargetFlags {
+        kNone_RenderTargetFlag      = 0x0,
+        /*
+         * By default a RenderTarget-based surface will be cleared on creation.
+         * Pass in this flag to prevent the clear from happening.
+         */
+        kDontClear_RenderTargetFlag = 0x01,
+    };
+
     /**
      *  Return a new surface using the specified render target.
      */
     static SkSurface* NewRenderTargetDirect(GrRenderTarget*,
-                                            TextRenderMode trm = kStandard_TextRenderMode);
+                                            TextRenderMode trm = kStandard_TextRenderMode,
+                                            RenderTargetFlags flags = kNone_RenderTargetFlag);
 
     /**
      *  Return a new surface whose contents will be drawn to an offscreen
      *  render target, allocated by the surface.
      */
     static SkSurface* NewRenderTarget(GrContext*, const SkImageInfo&, int sampleCount = 0,
-                                      TextRenderMode trm = kStandard_TextRenderMode);
+                                      TextRenderMode trm = kStandard_TextRenderMode,
+                                      RenderTargetFlags flags = kNone_RenderTargetFlag);
 
     /**
      *  Return a new surface whose contents will be drawn to an offscreen
@@ -103,7 +114,8 @@
      *  budget.
      */
     static SkSurface* NewScratchRenderTarget(GrContext*, const SkImageInfo&, int sampleCount = 0,
-                                             TextRenderMode trm = kStandard_TextRenderMode);
+                                             TextRenderMode trm = kStandard_TextRenderMode,
+                                             RenderTargetFlags flags = kNone_RenderTargetFlag);
 
     int width() const { return fWidth; }
     int height() const { return fHeight; }
diff --git a/include/gpu/GrRect.h b/include/gpu/GrRect.h
index ddb23b5..14130f8 100644
--- a/include/gpu/GrRect.h
+++ b/include/gpu/GrRect.h
@@ -20,6 +20,18 @@
         return r;
     }
 
+    static GrIRect16 SK_WARN_UNUSED_RESULT MakeWH(int16_t w, int16_t h) {
+        GrIRect16 r;
+        r.set(0, 0, w, h);
+        return r;
+    }
+
+    static GrIRect16 SK_WARN_UNUSED_RESULT MakeXYWH(int16_t x, int16_t y, int16_t w, int16_t h) {
+        GrIRect16 r;
+        r.set(x, y, x + w, y + h);
+        return r;
+    }
+
     int width() const { return fRight - fLeft; }
     int height() const { return fBottom - fTop; }
     int area() const { return this->width() * this->height(); }
@@ -27,6 +39,13 @@
 
     void setEmpty() { memset(this, 0, sizeof(*this)); }
 
+    void set(int16_t left, int16_t top, int16_t right, int16_t bottom) {
+        fLeft = left;
+        fTop = top;
+        fRight = right;
+        fBottom = bottom;
+    }
+
     void set(const SkIRect& r) {
         fLeft   = SkToS16(r.fLeft);
         fTop    = SkToS16(r.fTop);
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index 148237a..21658f7 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -929,7 +929,11 @@
                 SkASSERT(NULL != temp->fPaint);
                 canvas.save();
                 canvas.setMatrix(initialMatrix);
-                canvas.drawBitmap(*temp->fBM, temp->fPos.fX, temp->fPos.fY, temp->fPaint);
+                SkRect src = SkRect::Make(temp->fSrcRect);
+                SkRect dst = SkRect::MakeXYWH(temp->fPos.fX, temp->fPos.fY,
+                                              temp->fSrcRect.width(),
+                                              temp->fSrcRect.height());
+                canvas.drawBitmapRectToRect(*temp->fBM, &src, dst, temp->fPaint);
                 canvas.restore();
 
                 if (it.isValid()) {
diff --git a/src/core/SkPicturePlayback.h b/src/core/SkPicturePlayback.h
index b929a73..f5260f8 100644
--- a/src/core/SkPicturePlayback.h
+++ b/src/core/SkPicturePlayback.h
@@ -353,6 +353,8 @@
             SkIPoint        fPos;
             SkBitmap*       fBM;     // fBM is allocated so ReplacementInfo can remain POD
             const SkPaint*  fPaint;  // Note: this object doesn't own the paint
+
+            SkIRect         fSrcRect;
         };
 
         ~PlaybackReplacements() { this->freeAll(); }
diff --git a/src/gpu/GrAtlas.cpp b/src/gpu/GrAtlas.cpp
index 30b4bac..1bff42a 100644
--- a/src/gpu/GrAtlas.cpp
+++ b/src/gpu/GrAtlas.cpp
@@ -54,8 +54,7 @@
     loc->fY += offset.fY;
 }
 
-bool GrPlot::addSubImage(int width, int height, const void* image,
-                         SkIPoint16* loc) {
+bool GrPlot::addSubImage(int width, int height, const void* image, SkIPoint16* loc) {
     float percentFull = fRects->percentFull();
     if (!fRects->addRect(width, height, loc)) {
         return false;
@@ -88,7 +87,7 @@
         adjust_for_offset(loc, fOffset);
         fDirty = true;
     // otherwise, just upload the image directly
-    } else {
+    } else if (NULL != image) {
         adjust_for_offset(loc, fOffset);
         GrContext* context = fTexture->getContext();
         TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("skia.gpu"), "GrPlot::uploadToTexture");
@@ -96,6 +95,8 @@
                                     loc->fX, loc->fY, width, height,
                                     fTexture->config(), image, 0,
                                     GrContext::kDontFlush_PixelOpsFlag);
+    } else {
+        adjust_for_offset(loc, fOffset);
     }
 
 #if FONT_CACHE_STATS
@@ -146,11 +147,12 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrAtlas::GrAtlas(GrGpu* gpu, GrPixelConfig config,
+GrAtlas::GrAtlas(GrGpu* gpu, GrPixelConfig config, GrTextureFlags flags,
                  const SkISize& backingTextureSize,
                  int numPlotsX, int numPlotsY, bool batchUploads) {
     fGpu = SkRef(gpu);
     fPixelConfig = config;
+    fFlags = flags;
     fBackingTextureSize = backingTextureSize;
     fNumPlotsX = numPlotsX;
     fNumPlotsY = numPlotsY;
@@ -221,7 +223,7 @@
     if (NULL == fTexture) {
         // TODO: Update this to use the cache rather than directly creating a texture.
         GrTextureDesc desc;
-        desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
+        desc.fFlags = fFlags | kDynamicUpdate_GrTextureFlagBit;
         desc.fWidth = fBackingTextureSize.width();
         desc.fHeight = fBackingTextureSize.height();
         desc.fConfig = fPixelConfig;
diff --git a/src/gpu/GrAtlas.h b/src/gpu/GrAtlas.h
index 49a7bac..d63c8b9 100644
--- a/src/gpu/GrAtlas.h
+++ b/src/gpu/GrAtlas.h
@@ -81,13 +81,17 @@
         friend class GrAtlas;
     };
 
-    GrAtlas(GrGpu*, GrPixelConfig, const SkISize& backingTextureSize,
+    GrAtlas(GrGpu*, GrPixelConfig, GrTextureFlags flags, 
+            const SkISize& backingTextureSize,
             int numPlotsX, int numPlotsY, bool batchUploads);
     ~GrAtlas();
 
-    // add subimage of width, height dimensions to atlas
-    // returns the containing GrPlot and location relative to the backing texture
-    GrPlot* addToAtlas(ClientPlotUsage*, int width, int height, const void*, SkIPoint16*);
+    // Adds a width x height subimage to the atlas. Upon success it returns 
+    // the containing GrPlot and absolute location in the backing texture. 
+    // NULL is returned if the subimage cannot fit in the atlas.
+    // If provided, the image data will either be immediately uploaded or
+    // written to the CPU-side backing bitmap.
+    GrPlot* addToAtlas(ClientPlotUsage*, int width, int height, const void* image, SkIPoint16* loc);
 
     // remove reference to this plot
     void removePlot(ClientPlotUsage* usage, const GrPlot* plot);
@@ -105,13 +109,14 @@
 private:
     void makeMRU(GrPlot* plot);
 
-    GrGpu*        fGpu;
-    GrPixelConfig fPixelConfig;
-    GrTexture*    fTexture;
-    SkISize       fBackingTextureSize;
-    int           fNumPlotsX;
-    int           fNumPlotsY;
-    bool          fBatchUploads;
+    GrGpu*         fGpu;
+    GrPixelConfig  fPixelConfig;
+    GrTextureFlags fFlags;
+    GrTexture*     fTexture;
+    SkISize        fBackingTextureSize;
+    int            fNumPlotsX;
+    int            fNumPlotsY;
+    bool           fBatchUploads;
 
     // allocated array of GrPlots
     GrPlot*       fPlotArray;
diff --git a/src/gpu/GrLayerCache.cpp b/src/gpu/GrLayerCache.cpp
index c20d809..5f657c7 100644
--- a/src/gpu/GrLayerCache.cpp
+++ b/src/gpu/GrLayerCache.cpp
@@ -42,14 +42,14 @@
 };
 
 GrLayerCache::GrLayerCache(GrContext* context)
-    : fContext(context)
-    , fLayerPool(16) {      // TODO: may need to increase this later
+    : fContext(context) {
+    this->initAtlas();
 }
 
 GrLayerCache::~GrLayerCache() {
 }
 
-void GrLayerCache::init() {
+void GrLayerCache::initAtlas() {
     static const int kAtlasTextureWidth = 1024;
     static const int kAtlasTextureHeight = 1024;
 
@@ -58,19 +58,31 @@
     // The layer cache only gets 1 plot
     SkISize textureSize = SkISize::Make(kAtlasTextureWidth, kAtlasTextureHeight);
     fAtlas.reset(SkNEW_ARGS(GrAtlas, (fContext->getGpu(), kSkia8888_GrPixelConfig,
+                                      kRenderTarget_GrTextureFlagBit,
                                       textureSize, 1, 1, false)));
 }
 
 void GrLayerCache::freeAll() {
+    SkTDArray<GrCachedLayer*>& layerArray = fLayerHash.getArray();
+    for (int i = 0; i < fLayerHash.count(); ++i) {
+        this->unlock(layerArray[i]);
+    }
+
     fLayerHash.deleteAll();
+
+    // The atlas only lets go of its texture when the atlas is deleted. 
     fAtlas.free();
+    // GrLayerCache always assumes an atlas exists so recreate it. The atlas 
+    // lazily allocates a replacement texture so reallocating a new 
+    // atlas here won't disrupt a GrContext::contextDestroyed or freeGpuResources.
+    // TODO: Make GrLayerCache lazily allocate the atlas manager?
+    this->initAtlas();
 }
 
 GrCachedLayer* GrLayerCache::createLayer(const SkPicture* picture, int layerID) {
-    GrCachedLayer* layer = fLayerPool.alloc();
-
     SkASSERT(picture->uniqueID() != SK_InvalidGenID);
-    layer->init(picture->uniqueID(), layerID);
+
+    GrCachedLayer* layer = SkNEW_ARGS(GrCachedLayer, (picture->uniqueID(), layerID));
     fLayerHash.insert(PictureLayerKey(picture->uniqueID(), layerID), layer);
     return layer;
 }
@@ -91,19 +103,70 @@
 }
 
 bool GrLayerCache::lock(GrCachedLayer* layer, const GrTextureDesc& desc) {
-    SkASSERT(NULL == layer->getTexture());
 
-    // This just uses scratch textures and doesn't cache the texture.
+    if (NULL != layer->texture()) {
+        // This layer is already locked
+#ifdef SK_DEBUG
+        if (!layer->rect().isEmpty()) {
+            // It claims to be atlased
+            SkASSERT(layer->rect().width() == desc.fWidth);
+            SkASSERT(layer->rect().height() == desc.fHeight);
+        }
+#endif
+        return true;
+    }
+
+#if USE_ATLAS
+    SkIPoint16 loc;
+    GrPlot* plot = fAtlas->addToAtlas(&fPlotUsage, desc.fWidth, desc.fHeight, NULL, &loc);
+    if (NULL != plot) {
+        GrIRect16 bounds = GrIRect16::MakeXYWH(loc.fX, loc.fY, 
+                                               SkToS16(desc.fWidth), SkToS16(desc.fHeight));
+        layer->setTexture(fAtlas->getTexture(), bounds);
+        return false;
+    }
+#endif
+
+    // This path always uses a new scratch texture and (thus) doesn't cache anything.
     // This can yield a lot of re-rendering
-    layer->setTexture(fContext->lockAndRefScratchTexture(desc, GrContext::kApprox_ScratchTexMatch));
+    layer->setTexture(fContext->lockAndRefScratchTexture(desc, GrContext::kApprox_ScratchTexMatch),
+                      GrIRect16::MakeEmpty());
     return false;
 }
 
 void GrLayerCache::unlock(GrCachedLayer* layer) {
-    if (NULL == layer || NULL == layer->getTexture()) {
+    if (NULL == layer || NULL == layer->texture()) {
         return;
     }
 
-    fContext->unlockScratchTexture(layer->getTexture());
-    layer->setTexture(NULL);
+    // The atlas doesn't currently use a scratch texture (and we would have
+    // to free up space differently anyways)
+    // TODO: unlock atlas space when a recycling rectanizer is available
+    if (layer->texture() != fAtlas->getTexture()) {
+        fContext->unlockScratchTexture(layer->texture());
+        layer->setTexture(NULL, GrIRect16::MakeEmpty());
+    }
+}
+
+void GrLayerCache::purge(const SkPicture* picture) {
+    // This is somewhat of an abuse of GrTHashTable. We need to find all the
+    // layers associated with 'picture' but the usual hash calls only look for
+    // exact key matches. This code peeks into the hash table's innards to
+    // find all the 'picture'-related layers.
+    // TODO: use a different data structure for the layer hash?
+    SkTDArray<GrCachedLayer*> toBeRemoved;
+
+    const SkTDArray<GrCachedLayer*>& layerArray = fLayerHash.getArray();
+    for (int i = 0; i < fLayerHash.count(); ++i) {
+        if (picture->uniqueID() == layerArray[i]->pictureID()) {
+            *toBeRemoved.append() = layerArray[i];
+        }
+    }
+
+    for (int i = 0; i < toBeRemoved.count(); ++i) {
+        this->unlock(toBeRemoved[i]);
+
+        PictureLayerKey key(picture->uniqueID(), toBeRemoved[i]->layerID());
+        fLayerHash.remove(key, toBeRemoved[i]);
+    }
 }
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index a7ba2af..2747919 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -8,6 +8,8 @@
 #ifndef GrLayerCache_DEFINED
 #define GrLayerCache_DEFINED
 
+#define USE_ATLAS 0
+
 #include "GrAllocPool.h"
 #include "GrAtlas.h"
 #include "GrTHashTable.h"
@@ -17,61 +19,38 @@
 class GrGpu;
 class SkPicture;
 
-// GrAtlasLocation captures an atlased item's position in the atlas. This
-// means the plot in which it resides and its bounds inside the plot.
-// TODO: Make GrGlyph use one of these?
-class GrAtlasLocation {
-public:
-    GrAtlasLocation() : fPlot(NULL) {}
-
-    void set(GrPlot* plot, const GrIRect16& bounds) {
-        fPlot = plot;
-        fBounds = bounds;
-    }
-
-    const GrPlot* plot() const {
-        return fPlot;
-    }
-
-    const GrIRect16& bounds() const {
-        return fBounds;
-    }
-
-private:
-    GrPlot*   fPlot;
-    GrIRect16 fBounds;  // only valid is fPlot != NULL
-};
-
 // GrCachedLayer encapsulates the caching information for a single saveLayer.
 //
-// Atlased layers get a ref to their atlas GrTexture and their GrAtlasLocation
-// is filled in.
-// In this case GrCachedLayer is roughly equivalent to a GrGlyph in the font
-// caching system.
+// Atlased layers get a ref to their atlas GrTexture and 'fRect' contains
+// their absolute location in the backing texture.
 //
-// Non-atlased layers get a ref to the GrTexture in which they reside.
+// Non-atlased layers get a ref to the GrTexture in which they reside. Their
+// 'fRect' will be empty.
+//
 // TODO: can we easily reuse the empty space in the non-atlased GrTexture's?
 struct GrCachedLayer {
 public:
+    GrCachedLayer(uint32_t pictureID, int layerID) {
+        fPictureID = pictureID;
+        fLayerID = layerID;
+        fTexture = NULL;
+        fRect = GrIRect16::MakeEmpty();
+    }
+
     uint32_t pictureID() const { return fPictureID; }
     int layerID() const { return fLayerID; }
 
-    void init(uint32_t pictureID, int layerID) {
-        fPictureID = pictureID;
-        fLayerID   = layerID;
-        fTexture   = NULL;
-        fLocation.set(NULL, GrIRect16::MakeEmpty());
-    }
-
     // This call takes over the caller's ref
-    void setTexture(GrTexture* texture) {
+    void setTexture(GrTexture* texture, const GrIRect16& rect) {
         if (NULL != fTexture) {
             fTexture->unref();
         }
 
         fTexture = texture; // just take over caller's ref
+        fRect = rect;
     }
-    GrTexture* getTexture() { return fTexture; }
+    GrTexture* texture() { return fTexture; }
+    const GrIRect16& rect() const { return fRect; }
 
 private:
     uint32_t        fPictureID;
@@ -84,7 +63,9 @@
     // non-NULL, that means that the texture is locked in the texture cache.
     GrTexture*      fTexture;
 
-    GrAtlasLocation fLocation;       // only valid if the layer is atlased
+    // For non-atlased layers 'fRect' is empty otherwise it is the bound of
+    // the layer in the atlas.
+    GrIRect16       fRect;
 };
 
 // The GrLayerCache caches pre-computed saveLayers for later rendering.
@@ -115,6 +96,9 @@
     // Inform the cache that layer's cached image is not currently required
     void unlock(GrCachedLayer* layer);
 
+    // Remove all the layers (and unlock any resources) associated with 'picture'
+    void purge(const SkPicture* picture);
+
 private:
     GrContext*                fContext;  // pointer back to owning context
     SkAutoTDelete<GrAtlas>    fAtlas;    // TODO: could lazily allocate
@@ -122,10 +106,13 @@
 
     class PictureLayerKey;
     GrTHashTable<GrCachedLayer, PictureLayerKey, 7> fLayerHash;
-    GrTAllocPool<GrCachedLayer> fLayerPool;
 
-    void init();
+    void initAtlas();
     GrCachedLayer* createLayer(const SkPicture* picture, int layerID);
+
+    // for testing
+    friend class GetNumLayers;
+    int numLayers() const { return fLayerHash.count(); }
 };
 
 #endif
diff --git a/src/gpu/GrTextStrike.cpp b/src/gpu/GrTextStrike.cpp
index fb9d631..a9405ca 100644
--- a/src/gpu/GrTextStrike.cpp
+++ b/src/gpu/GrTextStrike.cpp
@@ -82,7 +82,7 @@
     if (NULL == fAtlases[atlasIndex]) {
         SkISize textureSize = SkISize::Make(GR_ATLAS_TEXTURE_WIDTH,
                                             GR_ATLAS_TEXTURE_HEIGHT);
-        fAtlases[atlasIndex] = SkNEW_ARGS(GrAtlas, (fGpu, config,
+        fAtlases[atlasIndex] = SkNEW_ARGS(GrAtlas, (fGpu, config, kNone_GrTextureFlags,
                                                     textureSize,
                                                     GR_NUM_PLOTS_X,
                                                     GR_NUM_PLOTS_Y,
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 3b82a33..318de8d 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1825,7 +1825,7 @@
 }
 
 void SkGpuDevice::EXPERIMENTAL_purge(const SkPicture* picture) {
-
+    fContext->getLayerCache()->purge(picture);
 }
 
 bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, const SkPicture* picture) {
@@ -1951,28 +1951,68 @@
             // TODO: need to deal with sample count
 
             bool needsRendering = !fContext->getLayerCache()->lock(layer, desc);
-            if (NULL == layer->getTexture()) {
+            if (NULL == layer->texture()) {
                 continue;
             }
 
             layerInfo->fBM = SkNEW(SkBitmap);  // fBM is allocated so ReplacementInfo can be POD
-            wrap_texture(layer->getTexture(), desc.fWidth, desc.fHeight, layerInfo->fBM);
+            wrap_texture(layer->texture(), 
+                         layer->rect().isEmpty() ? desc.fWidth : layer->texture()->width(),
+                         layer->rect().isEmpty() ? desc.fHeight : layer->texture()->height(),
+                         layerInfo->fBM);
 
             SkASSERT(info.fPaint);
             layerInfo->fPaint = info.fPaint;
 
+            if (layer->rect().isEmpty()) {
+                layerInfo->fSrcRect = SkIRect::MakeWH(desc.fWidth, desc.fHeight);
+            } else {
+                layerInfo->fSrcRect = SkIRect::MakeXYWH(layer->rect().fLeft,
+                                                        layer->rect().fTop,
+                                                        layer->rect().width(),
+                                                        layer->rect().height());
+            }
+
             if (needsRendering) {
                 SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTargetDirect(
-                                                    layer->getTexture()->asRenderTarget()));
+                                                    layer->texture()->asRenderTarget(),
+                                                    SkSurface::kStandard_TextRenderMode,
+                                                    SkSurface::kDontClear_RenderTargetFlag));
 
                 SkCanvas* canvas = surface->getCanvas();
 
+                if (!layer->rect().isEmpty()) {
+                    // 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()));
+                    canvas->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!
+                    SkPaint paint;
+                    paint.setColor(SK_ColorTRANSPARENT);
+                    canvas->drawRect(bound, paint);
+                } else {
+                    canvas->clear(SK_ColorTRANSPARENT);
+                }
+
                 canvas->setMatrix(info.fCTM);
-                canvas->clear(SK_ColorTRANSPARENT);
+
+                if (!layer->rect().isEmpty()) {
+                    // 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 some arbitrary location in the backing 
+                    // texture.
+                    canvas->translate(SkIntToScalar(layer->rect().fLeft),
+                                      SkIntToScalar(layer->rect().fTop));
+                } 
 
                 picture->fPlayback->setDrawLimits(info.fSaveLayerOpID, info.fRestoreOpID);
                 picture->fPlayback->draw(*canvas, NULL);
                 picture->fPlayback->setDrawLimits(0, 0);
+
                 canvas->flush();
             }
         }
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index a34b774..fab130c 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -14,7 +14,8 @@
 public:
     SK_DECLARE_INST_COUNT(SkSurface_Gpu)
 
-    SkSurface_Gpu(GrRenderTarget*, bool cached, TextRenderMode trm);
+    SkSurface_Gpu(GrRenderTarget*, bool cached, TextRenderMode trm, 
+                  SkSurface::RenderTargetFlags flags);
     virtual ~SkSurface_Gpu();
 
     virtual SkCanvas* onNewCanvas() SK_OVERRIDE;
@@ -33,14 +34,16 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkSurface_Gpu::SkSurface_Gpu(GrRenderTarget* renderTarget, bool cached, TextRenderMode trm)
+SkSurface_Gpu::SkSurface_Gpu(GrRenderTarget* renderTarget, bool cached, TextRenderMode trm,
+                             SkSurface::RenderTargetFlags flags)
         : INHERITED(renderTarget->width(), renderTarget->height()) {
-    int flags = 0;
-    flags |= cached ? SkGpuDevice::kCached_Flag : 0;
-    flags |= (kDistanceField_TextRenderMode == trm) ? SkGpuDevice::kDFFonts_Flag : 0;
-    fDevice = SkGpuDevice::Create(renderTarget, flags);
+    int deviceFlags = 0;
+    deviceFlags |= cached ? SkGpuDevice::kCached_Flag : 0;
+    deviceFlags |= (kDistanceField_TextRenderMode == trm) ? SkGpuDevice::kDFFonts_Flag : 0;
+    fDevice = SkGpuDevice::Create(renderTarget, deviceFlags);
 
-    if (kRGB_565_GrPixelConfig != renderTarget->config()) {
+    if (kRGB_565_GrPixelConfig != renderTarget->config() && 
+        !(flags & kDontClear_RenderTargetFlag)) {
         fDevice->clear(0x0);
     }
 }
@@ -101,15 +104,16 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkSurface* SkSurface::NewRenderTargetDirect(GrRenderTarget* target, TextRenderMode trm) {
+SkSurface* SkSurface::NewRenderTargetDirect(GrRenderTarget* target, TextRenderMode trm,
+                                            RenderTargetFlags flags) {
     if (NULL == target) {
         return NULL;
     }
-    return SkNEW_ARGS(SkSurface_Gpu, (target, false, trm));
+    return SkNEW_ARGS(SkSurface_Gpu, (target, false, trm, flags));
 }
 
 SkSurface* SkSurface::NewRenderTarget(GrContext* ctx, const SkImageInfo& info, int sampleCount,
-                                      TextRenderMode trm) {
+                                      TextRenderMode trm, RenderTargetFlags flags) {
     if (NULL == ctx) {
         return NULL;
     }
@@ -126,11 +130,12 @@
         return NULL;
     }
 
-    return SkNEW_ARGS(SkSurface_Gpu, (tex->asRenderTarget(), false, trm));
+    return SkNEW_ARGS(SkSurface_Gpu, (tex->asRenderTarget(), false, trm, flags));
 }
 
 SkSurface* SkSurface::NewScratchRenderTarget(GrContext* ctx, const SkImageInfo& info,
-                                             int sampleCount, TextRenderMode trm) {
+                                             int sampleCount, TextRenderMode trm,
+                                             RenderTargetFlags flags) {
     if (NULL == ctx) {
         return NULL;
     }
@@ -148,5 +153,5 @@
         return NULL;
     }
 
-    return SkNEW_ARGS(SkSurface_Gpu, (tex->asRenderTarget(), true, trm));
+    return SkNEW_ARGS(SkSurface_Gpu, (tex->asRenderTarget(), true, trm, flags));
 }
diff --git a/tests/GpuLayerCacheTest.cpp b/tests/GpuLayerCacheTest.cpp
new file mode 100644
index 0000000..96f7af4
--- /dev/null
+++ b/tests/GpuLayerCacheTest.cpp
@@ -0,0 +1,136 @@
+/*
+* 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 "GrContextFactory.h"
+#include "GrLayerCache.h"
+#include "Test.h"
+
+static const int kNumLayers = 5;
+
+class GetNumLayers {
+public:
+    static int NumLayers(GrLayerCache* cache) {
+        return cache->numLayers();
+    }
+};
+
+// Add several layers to the cache
+static void create_layers(skiatest::Reporter* reporter,
+                          GrLayerCache* cache,
+                          const SkPicture& picture) {
+    GrCachedLayer* layers[kNumLayers];
+
+    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]);
+
+        REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(cache) == 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]->rect().isEmpty());
+    }
+
+}
+
+// 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) {
+
+    GrContext* context = factory->get(GrContextFactory::kNative_GLContextType);
+    if (NULL == context) {
+        return;
+    }
+
+    SkPicture picture;
+
+    GrLayerCache cache(context);
+
+    create_layers(reporter, &cache, picture);
+
+    // 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) {
+        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);
+
+        REPORTER_ASSERT(reporter, NULL != layer->texture());
+#if USE_ATLAS
+        // The first 4 layers should be in the atlas (and thus have non-empty
+        // rects)
+        if (i < 4) {
+            REPORTER_ASSERT(reporter, !layer->rect().isEmpty());
+        } else {
+#endif
+            REPORTER_ASSERT(reporter, layer->rect().isEmpty());
+#if USE_ATLAS
+        }
+#endif
+    }
+
+    // Unlock the textures
+    for (int i = 0; i < kNumLayers; ++i) {
+        GrCachedLayer* layer = cache.findLayer(&picture, i);
+        REPORTER_ASSERT(reporter, NULL != layer);
+
+        cache.unlock(layer);
+    }
+
+    for (int i = 0; i < kNumLayers; ++i) {
+        GrCachedLayer* layer = cache.findLayer(&picture, i);
+        REPORTER_ASSERT(reporter, NULL != layer);
+
+#if USE_ATLAS
+        // The first 4 layers should be in the atlas (and thus do not 
+        // currently unlock). The final layer should be unlocked.
+        if (i < 4) {
+            REPORTER_ASSERT(reporter, NULL != layer->texture());
+            REPORTER_ASSERT(reporter, !layer->rect().isEmpty());
+        } else {
+#endif
+            REPORTER_ASSERT(reporter, NULL == layer->texture());
+            REPORTER_ASSERT(reporter, layer->rect().isEmpty());
+#if USE_ATLAS
+        }
+#endif
+    }
+
+    // Free them all SkGpuDevice-style. This will not free up the
+    // atlas' texture but will eliminate all the layers.
+    cache.purge(&picture);
+
+    REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(&cache) == 0);
+    // TODO: add VRAM/resource cache check here
+
+    // Re-create the layers
+    create_layers(reporter, &cache, picture);
+
+    // Free them again GrContext-style. This should free up everything.
+    cache.freeAll();
+
+    REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(&cache) == 0);
+    // TODO: add VRAM/resource cache check here
+}
+
+#endif
diff --git a/tools/render_pictures_main.cpp b/tools/render_pictures_main.cpp
index c2c7875..13ae6ea 100644
--- a/tools/render_pictures_main.cpp
+++ b/tools/render_pictures_main.cpp
@@ -205,6 +205,12 @@
         SkDebugf("Failed to render %s\n", inputFilename.c_str());
     }
 
+    if (FLAGS_preprocess) {
+        if (NULL != renderer.getCanvas()) {
+            renderer.getCanvas()->EXPERIMENTAL_purge(renderer.getPicture());
+        }
+    }
+
     renderer.end();
 
     SkDELETE(picture);