remove SkMiniPicture and co.
This was an optimization for Chromium that I believe is no longer
relevant in a world of PaintOpBuffers.
Change-Id: Ic7526715a0ef1c3cec387a44189b7d56d5107af5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/213680
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Mike Klein <mtklein@google.com>
diff --git a/gn/core.gni b/gn/core.gni
index 85cc224..8b98306 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -254,8 +254,6 @@
"$_src/core/SkMatrixUtils.h",
"$_src/core/SkMipMap.cpp",
"$_src/core/SkMipMap.h",
- "$_src/core/SkMiniRecorder.cpp",
- "$_src/core/SkMiniRecorder.h",
"$_src/core/SkModeColorFilter.cpp",
"$_src/core/SkNextID.h",
"$_src/core/SkLatticeIter.cpp",
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 6aa17e2..6df0bdf 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -219,7 +219,6 @@
friend class SkBigPicture;
friend class SkEmptyPicture;
friend class SkPicturePriv;
- template <typename> friend class SkMiniPicture;
void serialize(SkWStream*, const SkSerialProcs*, class SkRefCntSet* typefaces) const;
static sk_sp<SkPicture> MakeFromStream(SkStream*, const SkDeserialProcs*,
@@ -241,8 +240,6 @@
// Returns NULL if this is not an SkBigPicture.
virtual const class SkBigPicture* asSkBigPicture() const { return nullptr; }
- friend struct SkPathCounter;
-
// V35: Store SkRect (rather then width & height) in header
// V36: Remove (obsolete) alphatype from SkColorTable
// V37: Added shadow only option to SkDropShadowImageFilter (last version to record CLEAR)
diff --git a/include/core/SkPictureRecorder.h b/include/core/SkPictureRecorder.h
index 9fc0f3b..ba158d4 100644
--- a/include/core/SkPictureRecorder.h
+++ b/include/core/SkPictureRecorder.h
@@ -22,7 +22,6 @@
class GrContext;
class SkCanvas;
class SkDrawable;
-class SkMiniRecorder;
class SkPictureRecord;
class SkRecord;
class SkRecorder;
@@ -117,7 +116,6 @@
sk_sp<SkBBoxHierarchy> fBBH;
std::unique_ptr<SkRecorder> fRecorder;
sk_sp<SkRecord> fRecord;
- std::unique_ptr<SkMiniRecorder> fMiniRecorder;
typedef SkNoncopyable INHERITED;
};
diff --git a/src/core/SkMiniRecorder.cpp b/src/core/SkMiniRecorder.cpp
deleted file mode 100644
index e75ecd0..0000000
--- a/src/core/SkMiniRecorder.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "include/core/SkCanvas.h"
-#include "include/core/SkPicture.h"
-#include "include/core/SkTextBlob.h"
-#include "include/private/SkOnce.h"
-#include "src/core/SkMiniRecorder.h"
-#include "src/core/SkPictureCommon.h"
-#include "src/core/SkRecordDraw.h"
-#include "src/core/SkRectPriv.h"
-#include "src/core/SkTLazy.h"
-#include <new>
-
-using namespace SkRecords;
-
-class SkEmptyPicture final : public SkPicture {
-public:
- void playback(SkCanvas*, AbortCallback*) const override { }
-
- size_t approximateBytesUsed() const override { return sizeof(*this); }
- int approximateOpCount() const override { return 0; }
- SkRect cullRect() const override { return SkRect::MakeEmpty(); }
-};
-
-// Calculate conservative bounds for each type of draw op that can be its own mini picture.
-// These are fairly easy because we know they can't be affected by any matrix or saveLayers.
-static SkRect adjust_for_paint(SkRect bounds, const SkPaint& paint) {
- return paint.canComputeFastBounds() ? paint.computeFastBounds(bounds, &bounds)
- : SkRectPriv::MakeLargest();
-}
-static SkRect bounds(const DrawRect& op) {
- return adjust_for_paint(op.rect, op.paint);
-}
-static SkRect bounds(const DrawPath& op) {
- return op.path.isInverseFillType() ? SkRectPriv::MakeLargest()
- : adjust_for_paint(op.path.getBounds(), op.paint);
-}
-static SkRect bounds(const DrawTextBlob& op) {
- return adjust_for_paint(op.blob->bounds().makeOffset(op.x, op.y), op.paint);
-}
-
-template <typename T>
-class SkMiniPicture final : public SkPicture {
-public:
- SkMiniPicture(const SkRect* cull, T* op) : fCull(cull ? *cull : bounds(*op)) {
- memcpy(&fOp, op, sizeof(fOp)); // We take ownership of op's guts.
- }
-
- void playback(SkCanvas* c, AbortCallback*) const override {
- SkRecords::Draw(c, nullptr, nullptr, 0, nullptr)(fOp);
- }
-
- size_t approximateBytesUsed() const override { return sizeof(*this); }
- int approximateOpCount() const override { return 1; }
- SkRect cullRect() const override { return fCull; }
-
-private:
- SkRect fCull;
- T fOp;
-};
-
-
-SkMiniRecorder::SkMiniRecorder() : fState(State::kEmpty) {}
-SkMiniRecorder::~SkMiniRecorder() {
- if (fState != State::kEmpty) {
- // We have internal state pending.
- // Detaching then deleting a picture is an easy way to clean up.
- (void)this->detachAsPicture(nullptr);
- }
- SkASSERT(fState == State::kEmpty);
-}
-
-#define TRY_TO_STORE(Type, ...) \
- if (fState != State::kEmpty) { return false; } \
- fState = State::k##Type; \
- new (fBuffer.get()) Type{__VA_ARGS__}; \
- return true
-
-bool SkMiniRecorder::drawRect(const SkRect& rect, const SkPaint& paint) {
- TRY_TO_STORE(DrawRect, paint, rect);
-}
-
-bool SkMiniRecorder::drawPath(const SkPath& path, const SkPaint& paint) {
- TRY_TO_STORE(DrawPath, paint, path);
-}
-
-bool SkMiniRecorder::drawTextBlob(const SkTextBlob* b, SkScalar x, SkScalar y, const SkPaint& p) {
- TRY_TO_STORE(DrawTextBlob, p, sk_ref_sp(b), x, y);
-}
-#undef TRY_TO_STORE
-
-
-sk_sp<SkPicture> SkMiniRecorder::detachAsPicture(const SkRect* cull) {
-#define CASE(Type) \
- case State::k##Type: \
- fState = State::kEmpty; \
- return sk_make_sp<SkMiniPicture<Type>>(cull, reinterpret_cast<Type*>(fBuffer.get()))
-
- static SkOnce once;
- static SkPicture* empty;
-
- switch (fState) {
- case State::kEmpty:
- once([]{ empty = new SkEmptyPicture; });
- return sk_ref_sp(empty);
- CASE(DrawPath);
- CASE(DrawRect);
- CASE(DrawTextBlob);
- }
- SkASSERT(false);
- return nullptr;
-#undef CASE
-}
-
-void SkMiniRecorder::flushAndReset(SkCanvas* canvas) {
-#define CASE(Type) \
- case State::k##Type: { \
- fState = State::kEmpty; \
- Type* op = reinterpret_cast<Type*>(fBuffer.get()); \
- SkRecords::Draw(canvas, nullptr, nullptr, 0, nullptr)(*op); \
- op->~Type(); \
- } return
-
- switch (fState) {
- case State::kEmpty: return;
- CASE(DrawPath);
- CASE(DrawRect);
- CASE(DrawTextBlob);
- }
- SkASSERT(false);
-#undef CASE
-}
diff --git a/src/core/SkMiniRecorder.h b/src/core/SkMiniRecorder.h
deleted file mode 100644
index ac62491..0000000
--- a/src/core/SkMiniRecorder.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkMiniRecorder_DEFINED
-#define SkMiniRecorder_DEFINED
-
-#include "include/core/SkScalar.h"
-#include "include/core/SkTypes.h"
-#include "src/core/SkRecords.h"
-class SkCanvas;
-
-// Records small pictures, but only a limited subset of the canvas API, and may fail.
-class SkMiniRecorder : SkNoncopyable {
-public:
- SkMiniRecorder();
- ~SkMiniRecorder();
-
- // Try to record an op. Returns false on failure.
- bool drawPath(const SkPath&, const SkPaint&);
- bool drawRect(const SkRect&, const SkPaint&);
- bool drawTextBlob(const SkTextBlob*, SkScalar x, SkScalar y, const SkPaint&);
-
- // Detach anything we've recorded as a picture, resetting this SkMiniRecorder.
- // If cull is nullptr we'll calculate it.
- sk_sp<SkPicture> detachAsPicture(const SkRect* cull);
-
- // Flush anything we've recorded to the canvas, resetting this SkMiniRecorder.
- // This is logically the same as but rather more efficient than:
- // sk_sp<SkPicture> pic(this->detachAsPicture(nullptr));
- // pic->playback(canvas);
- void flushAndReset(SkCanvas*);
-
-private:
- enum class State {
- kEmpty,
- kDrawPath,
- kDrawRect,
- kDrawTextBlob,
- };
-
- State fState;
-
- template <size_t A, size_t B>
- struct Max { static const size_t val = A > B ? A : B; };
-
- static const size_t kInlineStorage =
- Max<sizeof(SkRecords::DrawPath),
- Max<sizeof(SkRecords::DrawRect),
- sizeof(SkRecords::DrawTextBlob)>::val>::val;
- SkAlignedSStorage<kInlineStorage> fBuffer;
-};
-
-#endif//SkMiniRecorder_DEFINED
diff --git a/src/core/SkPictureCommon.h b/src/core/SkPictureCommon.h
index 2a10699..0dcb633 100644
--- a/src/core/SkPictureCommon.h
+++ b/src/core/SkPictureCommon.h
@@ -8,84 +8,8 @@
#ifndef SkPictureCommon_DEFINED
#define SkPictureCommon_DEFINED
-// Some shared code used by both SkBigPicture and SkMiniPicture.
-// SkTextHunter -- SkRecord visitor that returns true when the op draws text.
-// SkPathCounter -- SkRecord visitor that counts paths that draw slowly on the GPU.
-
-#include "include/core/SkPathEffect.h"
-#include "include/core/SkShader.h"
-#include "include/private/SkTLogic.h"
-#include "src/core/SkRecords.h"
-
-// TODO: might be nicer to have operator() return an int (the number of slow paths) ?
-struct SkPathCounter {
- // Some ops have a paint, some have an optional paint. Either way, get back a pointer.
- static const SkPaint* AsPtr(const SkPaint& p) { return &p; }
- static const SkPaint* AsPtr(const SkRecords::Optional<SkPaint>& p) { return p; }
-
- SkPathCounter() : fNumSlowPathsAndDashEffects(0) {}
-
- void checkPaint(const SkPaint* paint) {
- if (paint && paint->getPathEffect()) {
- // Initially assume it's slow.
- fNumSlowPathsAndDashEffects++;
- }
- }
-
- void operator()(const SkRecords::DrawPoints& op) {
- this->checkPaint(&op.paint);
- const SkPathEffect* effect = op.paint.getPathEffect();
- if (effect) {
- SkPathEffect::DashInfo info;
- SkPathEffect::DashType dashType = effect->asADash(&info);
- if (2 == op.count && SkPaint::kRound_Cap != op.paint.getStrokeCap() &&
- SkPathEffect::kDash_DashType == dashType && 2 == info.fCount) {
- fNumSlowPathsAndDashEffects--;
- }
- }
- }
-
- void operator()(const SkRecords::DrawPath& op) {
- this->checkPaint(&op.paint);
- if (op.paint.isAntiAlias() && !op.path.isConvex()) {
- SkPaint::Style paintStyle = op.paint.getStyle();
- const SkRect& pathBounds = op.path.getBounds();
- if (SkPaint::kStroke_Style == paintStyle &&
- 0 == op.paint.getStrokeWidth()) {
- // AA hairline concave path is not slow.
- } else if (SkPaint::kFill_Style == paintStyle && pathBounds.width() < 64.f &&
- pathBounds.height() < 64.f && !op.path.isVolatile()) {
- // AADF eligible concave path is not slow.
- } else {
- fNumSlowPathsAndDashEffects++;
- }
- }
- }
-
- void operator()(const SkRecords::ClipPath& op) {
- // TODO: does the SkRegion op matter?
- if (op.opAA.aa() && !op.path.isConvex()) {
- fNumSlowPathsAndDashEffects++;
- }
- }
-
- void operator()(const SkRecords::SaveLayer& op) {
- this->checkPaint(AsPtr(op.paint));
- }
-
- template <typename T>
- SK_WHEN(T::kTags & SkRecords::kHasPaint_Tag, void) operator()(const T& op) {
- this->checkPaint(AsPtr(op.paint));
- }
-
- template <typename T>
- SK_WHEN(!(T::kTags & SkRecords::kHasPaint_Tag), void)
- operator()(const T& op) { /* do nothing */ }
-
- int fNumSlowPathsAndDashEffects;
-};
-
-sk_sp<SkImage> ImageDeserializer_SkDeserialImageProc(const void*, size_t, void* imagedeserializer);
+class SkStream;
+struct SkPictInfo;
bool SkPicture_StreamIsSKP(SkStream*, SkPictInfo*);
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index dbc0817..11bb248 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -12,6 +12,7 @@
#include "include/core/SkCanvasVirtualEnforcer.h"
#include "include/core/SkFlattenable.h"
#include "include/core/SkPicture.h"
+#include "include/core/SkTextBlob.h"
#include "include/core/SkVertices.h"
#include "include/private/SkTArray.h"
#include "include/private/SkTDArray.h"
diff --git a/src/core/SkPictureRecorder.cpp b/src/core/SkPictureRecorder.cpp
index eab9517..6391792 100644
--- a/src/core/SkPictureRecorder.cpp
+++ b/src/core/SkPictureRecorder.cpp
@@ -10,7 +10,6 @@
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkTypes.h"
#include "src/core/SkBigPicture.h"
-#include "src/core/SkMiniRecorder.h"
#include "src/core/SkRecord.h"
#include "src/core/SkRecordDraw.h"
#include "src/core/SkRecordOpts.h"
@@ -19,8 +18,7 @@
SkPictureRecorder::SkPictureRecorder() {
fActivelyRecording = false;
- fMiniRecorder.reset(new SkMiniRecorder);
- fRecorder.reset(new SkRecorder(nullptr, SkRect::MakeEmpty(), fMiniRecorder.get()));
+ fRecorder.reset(new SkRecorder(nullptr, SkRect::MakeEmpty()));
}
SkPictureRecorder::~SkPictureRecorder() {}
@@ -44,7 +42,7 @@
SkRecorder::DrawPictureMode dpm = (recordFlags & kPlaybackDrawPicture_RecordFlag)
? SkRecorder::Playback_DrawPictureMode
: SkRecorder::Record_DrawPictureMode;
- fRecorder->reset(fRecord.get(), cullRect, dpm, fMiniRecorder.get());
+ fRecorder->reset(fRecord.get(), cullRect, dpm);
fActivelyRecording = true;
return this->getRecordingCanvas();
}
@@ -53,14 +51,21 @@
return fActivelyRecording ? fRecorder.get() : nullptr;
}
+class SkEmptyPicture final : public SkPicture {
+public:
+ void playback(SkCanvas*, AbortCallback*) const override { }
+
+ size_t approximateBytesUsed() const override { return sizeof(*this); }
+ int approximateOpCount() const override { return 0; }
+ SkRect cullRect() const override { return SkRect::MakeEmpty(); }
+};
+
sk_sp<SkPicture> SkPictureRecorder::finishRecordingAsPicture(uint32_t finishFlags) {
fActivelyRecording = false;
fRecorder->restoreToCount(1); // If we were missing any restores, add them now.
if (fRecord->count() == 0) {
- auto pic = fMiniRecorder->detachAsPicture(fBBH ? nullptr : &fCullRect);
- fBBH.reset(nullptr);
- return pic;
+ return sk_make_sp<SkEmptyPicture>();
}
// TODO: delay as much of this work until just before first playback?
@@ -115,7 +120,6 @@
sk_sp<SkDrawable> SkPictureRecorder::finishRecordingAsDrawable(uint32_t finishFlags) {
fActivelyRecording = false;
- fRecorder->flushMiniRecorder();
fRecorder->restoreToCount(1); // If we were missing any restores, add them now.
SkRecordOptimize(fRecord.get());
diff --git a/src/core/SkRecorder.cpp b/src/core/SkRecorder.cpp
index afd8a35..70f2407 100644
--- a/src/core/SkRecorder.cpp
+++ b/src/core/SkRecorder.cpp
@@ -39,28 +39,25 @@
///////////////////////////////////////////////////////////////////////////////////////////////
-SkRecorder::SkRecorder(SkRecord* record, int width, int height, SkMiniRecorder* mr)
+SkRecorder::SkRecorder(SkRecord* record, int width, int height)
: SkCanvasVirtualEnforcer<SkNoDrawCanvas>(width, height)
, fDrawPictureMode(Record_DrawPictureMode)
, fApproxBytesUsedBySubPictures(0)
- , fRecord(record)
- , fMiniRecorder(mr) {}
+ , fRecord(record) {}
-SkRecorder::SkRecorder(SkRecord* record, const SkRect& bounds, SkMiniRecorder* mr)
+SkRecorder::SkRecorder(SkRecord* record, const SkRect& bounds)
: SkCanvasVirtualEnforcer<SkNoDrawCanvas>(bounds.roundOut())
, fDrawPictureMode(Record_DrawPictureMode)
, fApproxBytesUsedBySubPictures(0)
- , fRecord(record)
- , fMiniRecorder(mr) {}
+ , fRecord(record) {}
void SkRecorder::reset(SkRecord* record, const SkRect& bounds,
- DrawPictureMode dpm, SkMiniRecorder* mr) {
+ DrawPictureMode dpm) {
this->forgetRecord();
fDrawPictureMode = dpm;
fRecord = record;
SkIRect rounded = bounds.roundOut();
this->resetCanvas(rounded.right(), rounded.bottom());
- fMiniRecorder = mr;
}
void SkRecorder::forgetRecord() {
@@ -72,15 +69,9 @@
// To make appending to fRecord a little less verbose.
template<typename T, typename... Args>
void SkRecorder::append(Args&&... args) {
- if (fMiniRecorder) {
- this->flushMiniRecorder();
- }
new (fRecord->append<T>()) T{std::forward<Args>(args)...};
}
-#define TRY_MINIRECORDER(method, ...) \
- if (fMiniRecorder && fMiniRecorder->method(__VA_ARGS__)) return
-
// For methods which must call back into SkNoDrawCanvas.
#define INHERITED(method, ...) this->SkNoDrawCanvas::method(__VA_ARGS__)
@@ -127,14 +118,6 @@
return this->copy(src, strlen(src)+1);
}
-void SkRecorder::flushMiniRecorder() {
- if (fMiniRecorder) {
- SkMiniRecorder* mr = fMiniRecorder;
- fMiniRecorder = nullptr; // Needs to happen before flushAndReset() or we recurse forever.
- mr->flushAndReset(this);
- }
-}
-
void SkRecorder::onDrawPaint(const SkPaint& paint) {
this->append<SkRecords::DrawPaint>(paint);
}
@@ -151,7 +134,6 @@
}
void SkRecorder::onDrawRect(const SkRect& rect, const SkPaint& paint) {
- TRY_MINIRECORDER(drawRect, rect, paint);
this->append<SkRecords::DrawRect>(paint, rect);
}
@@ -190,7 +172,6 @@
}
void SkRecorder::onDrawPath(const SkPath& path, const SkPaint& paint) {
- TRY_MINIRECORDER(drawPath, path, paint);
this->append<SkRecords::DrawPath>(paint, path);
}
@@ -259,7 +240,6 @@
void SkRecorder::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
const SkPaint& paint) {
- TRY_MINIRECORDER(drawTextBlob, blob, x, y, paint);
this->append<SkRecords::DrawTextBlob>(paint, sk_ref_sp(blob), x, y);
}
diff --git a/src/core/SkRecorder.h b/src/core/SkRecorder.h
index cea6dff..93bb317 100644
--- a/src/core/SkRecorder.h
+++ b/src/core/SkRecorder.h
@@ -12,7 +12,6 @@
#include "include/private/SkTDArray.h"
#include "include/utils/SkNoDrawCanvas.h"
#include "src/core/SkBigPicture.h"
-#include "src/core/SkMiniRecorder.h"
#include "src/core/SkRecord.h"
#include "src/core/SkRecords.h"
@@ -40,11 +39,11 @@
class SkRecorder final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
public:
// Does not take ownership of the SkRecord.
- SkRecorder(SkRecord*, int width, int height, SkMiniRecorder* = nullptr); // legacy version
- SkRecorder(SkRecord*, const SkRect& bounds, SkMiniRecorder* = nullptr);
+ SkRecorder(SkRecord*, int width, int height); // legacy version
+ SkRecorder(SkRecord*, const SkRect& bounds);
enum DrawPictureMode { Record_DrawPictureMode, Playback_DrawPictureMode };
- void reset(SkRecord*, const SkRect& bounds, DrawPictureMode, SkMiniRecorder* = nullptr);
+ void reset(SkRecord*, const SkRect& bounds, DrawPictureMode);
size_t approxBytesUsedBySubPictures() const { return fApproxBytesUsedBySubPictures; }
@@ -121,8 +120,6 @@
sk_sp<SkSurface> onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override;
- void flushMiniRecorder();
-
private:
template <typename T>
T* copy(const T*);
@@ -137,8 +134,6 @@
size_t fApproxBytesUsedBySubPictures;
SkRecord* fRecord;
std::unique_ptr<SkDrawableList> fDrawableList;
-
- SkMiniRecorder* fMiniRecorder;
};
#endif//SkRecorder_DEFINED
diff --git a/tests/PictureTest.cpp b/tests/PictureTest.cpp
index e851cfb..44da230 100644
--- a/tests/PictureTest.cpp
+++ b/tests/PictureTest.cpp
@@ -11,6 +11,7 @@
#include "include/core/SkClipOp.h"
#include "include/core/SkColor.h"
#include "include/core/SkData.h"
+#include "include/core/SkFont.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMatrix.h"
@@ -29,7 +30,6 @@
#include "src/core/SkBBoxHierarchy.h"
#include "src/core/SkBigPicture.h"
#include "src/core/SkClipOpPriv.h"
-#include "src/core/SkMiniRecorder.h"
#include "src/core/SkPicturePriv.h"
#include "src/core/SkRectPriv.h"
#include "tests/Test.h"
@@ -758,16 +758,6 @@
}
}
-DEF_TEST(MiniRecorderLeftHanging, r) {
- // Any shader or other ref-counted effect will do just fine here.
- SkPaint paint;
- paint.setShader(SkShaders::Color(SK_ColorRED));
-
- SkMiniRecorder rec;
- REPORTER_ASSERT(r, rec.drawRect(SkRect::MakeWH(20,30), paint));
- // Don't call rec.detachPicture(). Test succeeds by not asserting or leaking the shader.
-}
-
DEF_TEST(Picture_preserveCullRect, r) {
SkPictureRecorder recorder;
@@ -793,7 +783,7 @@
// bounds of those ops, we should trim down the picture cull to the ops' bounds.
// If we're not using an SkBBH, we shouldn't change it.
DEF_TEST(Picture_UpdatedCull_1, r) {
- // Testing 1 draw exercises SkMiniPicture.
+ // Testing 1 draw exercised SkMiniPicture, which no longer exists, but still a fine test.
SkRTreeFactory factory;
SkPictureRecorder recorder;