Use an image generator to back SkPictureShader tiles.
authorfmalita <fmalita@chromium.org>
Tue, 24 Feb 2015 21:02:57 +0000 (13:02 -0800)
committerCommit bot <commit-bot@chromium.org>
Tue, 24 Feb 2015 21:02:57 +0000 (13:02 -0800)
To avoid lifetime issues for tiles backed by discardable memory, use an
image generator to re-generate them on the fly.

With this CL, we are now caching bitmap shaders wrapping discardable
pixel ref bitmaps backed by picture image generators.

(the CL also includes some minor/unrelated SkPictureShader cleanup)

BUG=skia:3220
R=reed@google.com,halcanary@google.com

Review URL: https://codereview.chromium.org/866773002

src/core/SkPictureShader.cpp
src/core/SkPictureShader.h

index cf0157c..1453aee 100644 (file)
@@ -10,6 +10,7 @@
 #include "SkBitmap.h"
 #include "SkBitmapProcShader.h"
 #include "SkCanvas.h"
+#include "SkImageGenerator.h"
 #include "SkMatrixUtils.h"
 #include "SkPicture.h"
 #include "SkReadBuffer.h"
 namespace {
 static unsigned gBitmapSkaderKeyNamespaceLabel;
 
+class PictureImageGenerator : public SkImageGenerator {
+public:
+    PictureImageGenerator(const SkPicture* picture,
+                          const SkRect& pictureTile,
+                          const SkISize& tileSize)
+        : fPicture(SkRef(picture))
+        , fPictureTile(pictureTile)
+        , fRasterTileInfo(SkImageInfo::MakeN32Premul(tileSize)) {}
+
+protected:
+    virtual bool onGetInfo(SkImageInfo *info) SK_OVERRIDE {
+        *info = fRasterTileInfo;
+        return true;
+    }
+
+    virtual Result onGetPixels(const SkImageInfo& info, void *pixels, size_t rowBytes,
+                             SkPMColor ctable[], int *ctableCount) SK_OVERRIDE {
+        if (info != fRasterTileInfo || SkToBool(ctable) || SkToBool(ctableCount)) {
+            return kInvalidConversion;
+        }
+
+        SkSize tileScale = SkSize::Make(SkIntToScalar(info.width()) / fPictureTile.width(),
+                                        SkIntToScalar(info.height()) / fPictureTile.height());
+        SkBitmap tileBitmap;
+        if (!tileBitmap.installPixels(info, pixels, rowBytes)) {
+            return kInvalidParameters;
+        }
+        tileBitmap.eraseColor(SK_ColorTRANSPARENT);
+
+        // Always disable LCD text, since we can't assume our image will be opaque.
+        SkCanvas tileCanvas(tileBitmap, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
+        tileCanvas.scale(tileScale.width(), tileScale.height());
+        tileCanvas.translate(-fPictureTile.x(), -fPictureTile.y());
+        tileCanvas.drawPicture(fPicture);
+
+        return kSuccess;
+    }
+
+private:
+    SkAutoTUnref<const SkPicture> fPicture;
+    const SkRect                  fPictureTile;
+    const SkImageInfo             fRasterTileInfo;
+};
+
 struct BitmapShaderKey : public SkResourceCache::Key {
 public:
     BitmapShaderKey(uint32_t pictureID,
@@ -81,24 +126,12 @@ struct BitmapShaderRec : public SkResourceCache::Rec {
 
         result->reset(SkRef(rec.fShader.get()));
 
-        SkBitmap tile;
-        rec.fShader.get()->asABitmap(&tile, NULL, NULL);
-        // FIXME: this doesn't protect the pixels from being discarded as soon as we unlock.
-        // Should be handled via a pixel ref generator instead
-        // (https://code.google.com/p/skia/issues/detail?id=3220).
-        SkAutoLockPixels alp(tile, true);
-        return tile.getPixels() != NULL;
+        // The bitmap shader is backed by an image generator, thus it can always re-generate its
+        // pixels if discarded.
+        return true;
     }
 };
 
-static bool cache_try_alloc_pixels(SkBitmap* bitmap) {
-    SkBitmap::Allocator* allocator = SkResourceCache::GetAllocator();
-
-    return NULL != allocator
-        ? allocator->allocPixelRef(bitmap, NULL)
-        : bitmap->tryAllocPixels();
-}
-
 } // namespace
 
 SkPictureShader::SkPictureShader(const SkPicture* picture, TileMode tmx, TileMode tmy,
@@ -110,10 +143,6 @@ SkPictureShader::SkPictureShader(const SkPicture* picture, TileMode tmx, TileMod
     , fTmy(tmy) {
 }
 
-SkPictureShader::~SkPictureShader() {
-    fPicture->unref();
-}
-
 SkPictureShader* SkPictureShader::Create(const SkPicture* picture, TileMode tmx, TileMode tmy,
                                          const SkMatrix* localMatrix, const SkRect* tile) {
     if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) {
@@ -188,18 +217,10 @@ SkShader* SkPictureShader::refBitmapShader(const SkMatrix& matrix, const SkMatri
 
     if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) {
         SkBitmap bm;
-        bm.setInfo(SkImageInfo::MakeN32Premul(tileSize));
-        if (!cache_try_alloc_pixels(&bm)) {
+        if (!SkInstallDiscardablePixelRef(SkNEW_ARGS(PictureImageGenerator,
+                                                     (fPicture, fTile, tileSize)), &bm)) {
             return NULL;
         }
-        bm.eraseColor(SK_ColorTRANSPARENT);
-
-        // Always disable LCD text, since we can't assume our image will be opaque.
-        SkCanvas canvas(bm, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
-
-        canvas.scale(tileScale.width(), tileScale.height());
-        canvas.translate(-fTile.x(), -fTile.y());
-        canvas.drawPicture(fPicture);
 
         SkMatrix shaderMatrix = this->getLocalMatrix();
         shaderMatrix.preScale(1 / tileScale.width(), 1 / tileScale.height());
@@ -280,10 +301,10 @@ void SkPictureShader::toString(SkString* str) const {
     };
 
     str->appendf("PictureShader: [%f:%f:%f:%f] ",
-                 fPicture ? fPicture->cullRect().fLeft : 0,
-                 fPicture ? fPicture->cullRect().fTop : 0,
-                 fPicture ? fPicture->cullRect().fRight : 0,
-                 fPicture ? fPicture->cullRect().fBottom : 0);
+                 fPicture->cullRect().fLeft,
+                 fPicture->cullRect().fTop,
+                 fPicture->cullRect().fRight,
+                 fPicture->cullRect().fBottom);
 
     str->appendf("(%s, %s)", gTileModeName[fTmx], gTileModeName[fTmy]);
 
index 8df9f53..3afe58f 100644 (file)
@@ -23,7 +23,6 @@ class SkPictureShader : public SkShader {
 public:
     static SkPictureShader* Create(const SkPicture*, TileMode, TileMode, const SkMatrix*,
                                    const SkRect*);
-    virtual ~SkPictureShader();
 
     size_t contextSize() const SK_OVERRIDE;
 
@@ -43,9 +42,9 @@ private:
 
     SkShader* refBitmapShader(const SkMatrix&, const SkMatrix* localMatrix) const;
 
-    const SkPicture* fPicture;
-    SkRect           fTile;
-    TileMode         fTmx, fTmy;
+    SkAutoTUnref<const SkPicture> fPicture;
+    SkRect                        fTile;
+    TileMode                      fTmx, fTmy;
 
     class PictureShaderContext : public SkShader::Context {
     public: