Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / skia / src / core / SkPicture.cpp
index 6843430..01b315a 100644 (file)
 
 
 #include "SkPictureFlat.h"
+#include "SkPictureData.h"
 #include "SkPicturePlayback.h"
 #include "SkPictureRecord.h"
+#include "SkPictureRecorder.h"
 
-#include "SkBBHFactory.h"
 #include "SkBitmapDevice.h"
 #include "SkCanvas.h"
 #include "SkChunkAlloc.h"
+#include "SkDrawPictureCallback.h"
 #include "SkPaintPriv.h"
+#include "SkPathEffect.h"
 #include "SkPicture.h"
 #include "SkRegion.h"
+#include "SkShader.h"
 #include "SkStream.h"
 #include "SkTDArray.h"
+#include "SkTLogic.h"
 #include "SkTSearch.h"
 #include "SkTime.h"
 
 #include "SkReader32.h"
 #include "SkWriter32.h"
 #include "SkRTree.h"
-#include "SkBBoxHierarchyRecord.h"
 
 #if SK_SUPPORT_GPU
 #include "GrContext.h"
 #endif
 
+#include "SkRecord.h"
+#include "SkRecordDraw.h"
+#include "SkRecordOpts.h"
+#include "SkRecorder.h"
+
 template <typename T> int SafeCount(const T* obj) {
     return obj ? obj->count() : 0;
 }
 
-#define DUMP_BUFFER_SIZE 65536
+///////////////////////////////////////////////////////////////////////////////
 
-//#define ENABLE_TIME_DRAW    // dumps milliseconds for each draw
+namespace {
 
+// Some commands 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; }
 
-#ifdef SK_DEBUG
-// enable SK_DEBUG_TRACE to trace DrawType elements when
-//     recorded and played back
-// #define SK_DEBUG_TRACE
-// enable SK_DEBUG_SIZE to see the size of picture components
-// #define SK_DEBUG_SIZE
-// enable SK_DEBUG_DUMP to see the contents of recorded elements
-// #define SK_DEBUG_DUMP
-// enable SK_DEBUG_VALIDATE to check internal structures for consistency
-// #define SK_DEBUG_VALIDATE
-#endif
+/** SkRecords visitor to determine whether an instance may require an
+    "external" bitmap to rasterize. May return false positives.
+    Does not return true for bitmap text.
 
-#if defined SK_DEBUG_TRACE || defined SK_DEBUG_DUMP
-const char* DrawTypeToString(DrawType drawType) {
-    switch (drawType) {
-        case UNUSED: SkDebugf("DrawType UNUSED\n"); SkASSERT(0); break;
-        case CLIP_PATH: return "CLIP_PATH";
-        case CLIP_REGION: return "CLIP_REGION";
-        case CLIP_RECT: return "CLIP_RECT";
-        case CLIP_RRECT: return "CLIP_RRECT";
-        case CONCAT: return "CONCAT";
-        case DRAW_BITMAP: return "DRAW_BITMAP";
-        case DRAW_BITMAP_MATRIX: return "DRAW_BITMAP_MATRIX";
-        case DRAW_BITMAP_NINE: return "DRAW_BITMAP_NINE";
-        case DRAW_BITMAP_RECT_TO_RECT: return "DRAW_BITMAP_RECT_TO_RECT";
-        case DRAW_CLEAR: return "DRAW_CLEAR";
-        case DRAW_DATA: return "DRAW_DATA";
-        case DRAW_OVAL: return "DRAW_OVAL";
-        case DRAW_PAINT: return "DRAW_PAINT";
-        case DRAW_PATH: return "DRAW_PATH";
-        case DRAW_PICTURE: return "DRAW_PICTURE";
-        case DRAW_POINTS: return "DRAW_POINTS";
-        case DRAW_POS_TEXT: return "DRAW_POS_TEXT";
-        case DRAW_POS_TEXT_TOP_BOTTOM: return "DRAW_POS_TEXT_TOP_BOTTOM";
-        case DRAW_POS_TEXT_H: return "DRAW_POS_TEXT_H";
-        case DRAW_POS_TEXT_H_TOP_BOTTOM: return "DRAW_POS_TEXT_H_TOP_BOTTOM";
-        case DRAW_RECT: return "DRAW_RECT";
-        case DRAW_RRECT: return "DRAW_RRECT";
-        case DRAW_SPRITE: return "DRAW_SPRITE";
-        case DRAW_TEXT: return "DRAW_TEXT";
-        case DRAW_TEXT_ON_PATH: return "DRAW_TEXT_ON_PATH";
-        case DRAW_TEXT_TOP_BOTTOM: return "DRAW_TEXT_TOP_BOTTOM";
-        case DRAW_VERTICES: return "DRAW_VERTICES";
-        case RESTORE: return "RESTORE";
-        case ROTATE: return "ROTATE";
-        case SAVE: return "SAVE";
-        case SAVE_LAYER: return "SAVE_LAYER";
-        case SCALE: return "SCALE";
-        case SET_MATRIX: return "SET_MATRIX";
-        case SKEW: return "SKEW";
-        case TRANSLATE: return "TRANSLATE";
-        case NOOP: return "NOOP";
-        default:
-            SkDebugf("DrawType error 0x%08x\n", drawType);
-            SkASSERT(0);
-            break;
+    Expected use is to determine whether images need to be decoded before
+    rasterizing a particular SkRecord.
+ */
+struct BitmapTester {
+    // Helpers.  These create HasMember_bitmap and HasMember_paint.
+    SK_CREATE_MEMBER_DETECTOR(bitmap);
+    SK_CREATE_MEMBER_DETECTOR(paint);
+
+
+    // Main entry for visitor:
+    // If the command is a DrawPicture, recurse.
+    // If the command has a bitmap directly, return true.
+    // If the command has a paint and the paint has a bitmap, return true.
+    // Otherwise, return false.
+    bool operator()(const SkRecords::DrawPicture& op) { return op.picture->willPlayBackBitmaps(); }
+
+    template <typename T>
+    bool operator()(const T& r) { return CheckBitmap(r); }
+
+
+    // If the command has a bitmap, of course we're going to play back bitmaps.
+    template <typename T>
+    static SK_WHEN(HasMember_bitmap<T>, bool) CheckBitmap(const T&) { return true; }
+
+    // If not, look for one in its paint (if it has a paint).
+    template <typename T>
+    static SK_WHEN(!HasMember_bitmap<T>, bool) CheckBitmap(const T& r) { return CheckPaint(r); }
+
+    // If we have a paint, dig down into the effects looking for a bitmap.
+    template <typename T>
+    static SK_WHEN(HasMember_paint<T>, bool) CheckPaint(const T& r) {
+        const SkPaint* paint = AsPtr(r.paint);
+        if (paint) {
+            const SkShader* shader = paint->getShader();
+            if (shader &&
+                shader->asABitmap(NULL, NULL, NULL) == SkShader::kDefault_BitmapType) {
+                return true;
+            }
+        }
+        return false;
     }
-    SkASSERT(0);
-    return NULL;
-}
-#endif
 
-#ifdef SK_DEBUG_VALIDATE
-static void validateMatrix(const SkMatrix* matrix) {
-    SkScalar scaleX = matrix->getScaleX();
-    SkScalar scaleY = matrix->getScaleY();
-    SkScalar skewX = matrix->getSkewX();
-    SkScalar skewY = matrix->getSkewY();
-    SkScalar perspX = matrix->getPerspX();
-    SkScalar perspY = matrix->getPerspY();
-    if (scaleX != 0 && skewX != 0)
-        SkDebugf("scaleX != 0 && skewX != 0\n");
-    SkASSERT(scaleX == 0 || skewX == 0);
-    SkASSERT(scaleY == 0 || skewY == 0);
-    SkASSERT(perspX == 0);
-    SkASSERT(perspY == 0);
-}
-#endif
-
-
-///////////////////////////////////////////////////////////////////////////////
+    // If we don't have a paint, that non-paint has no bitmap.
+    template <typename T>
+    static SK_WHEN(!HasMember_paint<T>, bool) CheckPaint(const T&) { return false; }
+};
 
-SkPicture::SkPicture()
-    : fAccelData(NULL) {
-    this->needsNewGenID();
-    fRecord = NULL;
-    fPlayback = NULL;
-    fWidth = fHeight = 0;
-}
-
-SkPicture::SkPicture(const SkPicture& src)
-    : INHERITED()
-    , fAccelData(NULL)
-    , fContentInfo(src.fContentInfo) {
-    this->needsNewGenID();
-    fWidth = src.fWidth;
-    fHeight = src.fHeight;
-    fRecord = NULL;
-
-    /*  We want to copy the src's playback. However, if that hasn't been built
-        yet, we need to fake a call to endRecording() without actually calling
-        it (since it is destructive, and we don't want to change src).
-     */
-    if (src.fPlayback) {
-        fPlayback = SkNEW_ARGS(SkPicturePlayback, (this, *src.fPlayback));
-        SkASSERT(NULL == src.fRecord);
-        fUniqueID = src.uniqueID();     // need to call method to ensure != 0
-    } else if (src.fRecord) {
-        SkPictInfo info;
-        this->createHeader(&info);
-        // here we do a fake src.endRecording()
-        fPlayback = SkNEW_ARGS(SkPicturePlayback, (this, *src.fRecord, info));
-    } else {
-        fPlayback = NULL;
+bool WillPlaybackBitmaps(const SkRecord& record) {
+    BitmapTester tester;
+    for (unsigned i = 0; i < record.count(); i++) {
+        if (record.visit<bool>(i, tester)) {
+            return true;
+        }
     }
-
-    fPathHeap.reset(SkSafeRef(src.fPathHeap.get()));
+    return false;
 }
 
-const SkPath& SkPicture::getPath(int index) const {
-    return (*fPathHeap.get())[index];
-}
+// SkRecord visitor to find recorded text.
+struct TextHunter {
+    // All ops with text have that text as a char array member named "text".
+    SK_CREATE_MEMBER_DETECTOR(text);
+    bool operator()(const SkRecords::DrawPicture& op) { return op.picture->hasText(); }
+    template <typename T> SK_WHEN(HasMember_text<T>,  bool) operator()(const T&) { return true;  }
+    template <typename T> SK_WHEN(!HasMember_text<T>, bool) operator()(const T&) { return false; }
+};
 
-int SkPicture::addPathToHeap(const SkPath& path) {
-    if (NULL == fPathHeap) {
-        fPathHeap.reset(SkNEW(SkPathHeap));
-    }
-#ifdef SK_DEDUP_PICTURE_PATHS
-    return fPathHeap->insert(path);
-#else
-    return fPathHeap->append(path);
-#endif
-}
+} // namespace
 
-void SkPicture::initForPlayback() const {
-    // ensure that the paths bounds are pre-computed
-    if (NULL != fPathHeap.get()) {
-        for (int i = 0; i < fPathHeap->count(); i++) {
-            (*fPathHeap.get())[i].updateBoundsCache();
+/** SkRecords visitor to determine heuristically whether or not a SkPicture
+    will be performant when rasterized on the GPU.
+ */
+struct SkPicture::PathCounter {
+    SK_CREATE_MEMBER_DETECTOR(paint);
+
+    PathCounter()
+        : numPaintWithPathEffectUses (0)
+        , numFastPathDashEffects (0)
+        , numAAConcavePaths (0)
+        , numAAHairlineConcavePaths (0)
+        , numAADFEligibleConcavePaths(0) {
+    }
+
+    // Recurse into nested pictures.
+    void operator()(const SkRecords::DrawPicture& op) {
+        const SkPicture::Analysis& analysis = op.picture->fAnalysis;
+        numPaintWithPathEffectUses += analysis.fNumPaintWithPathEffectUses;
+        numFastPathDashEffects     += analysis.fNumFastPathDashEffects;
+        numAAConcavePaths          += analysis.fNumAAConcavePaths;
+        numAAHairlineConcavePaths  += analysis.fNumAAHairlineConcavePaths;
+        numAADFEligibleConcavePaths  += analysis.fNumAADFEligibleConcavePaths;
+    }
+
+    void checkPaint(const SkPaint* paint) {
+        if (paint && paint->getPathEffect()) {
+            numPaintWithPathEffectUses++;
         }
     }
-}
 
-void SkPicture::dumpSize() const {
-    SkDebugf("--- picture size: paths=%d\n",
-             SafeCount(fPathHeap.get()));
-}
-
-SkPicture::~SkPicture() {
-    SkSafeUnref(fRecord);
-    SkDELETE(fPlayback);
-    SkSafeUnref(fAccelData);
-}
-
-void SkPicture::internalOnly_EnableOpts(bool enableOpts) {
-    if (NULL != fRecord) {
-        fRecord->internalOnly_EnableOpts(enableOpts);
+    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) {
+                numFastPathDashEffects++;
+            }
+        }
     }
-}
-
-void SkPicture::swap(SkPicture& other) {
-    SkTSwap(fUniqueID, other.fUniqueID);
-    SkTSwap(fRecord, other.fRecord);
-    SkTSwap(fPlayback, other.fPlayback);
-    SkTSwap(fAccelData, other.fAccelData);
-    SkTSwap(fWidth, other.fWidth);
-    SkTSwap(fHeight, other.fHeight);
-    fPathHeap.swap(&other.fPathHeap);
-    fContentInfo.swap(&other.fContentInfo);
-}
 
-SkPicture* SkPicture::clone() const {
-    SkPicture* clonedPicture = SkNEW(SkPicture);
-    this->clone(clonedPicture, 1);
-    return clonedPicture;
-}
+    void operator()(const SkRecords::DrawPath& op) {
+        this->checkPaint(&op.paint);
+        if (op.paint.isAntiAlias() && !op.path.isConvex()) {
+            numAAConcavePaths++;
 
-void SkPicture::clone(SkPicture* pictures, int count) const {
-    SkPictCopyInfo copyInfo;
-    SkPictInfo info;
-    this->createHeader(&info);
-
-    for (int i = 0; i < count; i++) {
-        SkPicture* clone = &pictures[i];
-
-        clone->needsNewGenID();
-        clone->fWidth = fWidth;
-        clone->fHeight = fHeight;
-        SkSafeSetNull(clone->fRecord);
-        SkDELETE(clone->fPlayback);
-        clone->fContentInfo.set(fContentInfo);
-
-        /*  We want to copy the src's playback. However, if that hasn't been built
-            yet, we need to fake a call to endRecording() without actually calling
-            it (since it is destructive, and we don't want to change src).
-         */
-        if (fPlayback) {
-            if (!copyInfo.initialized) {
-                int paintCount = SafeCount(fPlayback->fPaints);
-
-                /* The alternative to doing this is to have a clone method on the paint and have it
-                 * make the deep copy of its internal structures as needed. The holdup to doing
-                 * that is at this point we would need to pass the SkBitmapHeap so that we don't
-                 * unnecessarily flatten the pixels in a bitmap shader.
-                 */
-                copyInfo.paintData.setCount(paintCount);
-
-                /* Use an SkBitmapHeap to avoid flattening bitmaps in shaders. If there already is
-                 * one, use it. If this SkPicturePlayback was created from a stream, fBitmapHeap
-                 * will be NULL, so create a new one.
-                 */
-                if (fPlayback->fBitmapHeap.get() == NULL) {
-                    // FIXME: Put this on the stack inside SkPicture::clone.
-                    SkBitmapHeap* heap = SkNEW(SkBitmapHeap);
-                    copyInfo.controller.setBitmapStorage(heap);
-                    heap->unref();
-                } else {
-                    copyInfo.controller.setBitmapStorage(fPlayback->fBitmapHeap);
-                }
-
-                SkDEBUGCODE(int heapSize = SafeCount(fPlayback->fBitmapHeap.get());)
-                for (int i = 0; i < paintCount; i++) {
-                    if (NeedsDeepCopy(fPlayback->fPaints->at(i))) {
-                        copyInfo.paintData[i] =
-                            SkFlatData::Create<SkPaint::FlatteningTraits>(&copyInfo.controller,
-                                                              fPlayback->fPaints->at(i), 0);
-
-                    } else {
-                        // this is our sentinel, which we use in the unflatten loop
-                        copyInfo.paintData[i] = NULL;
-                    }
-                }
-                SkASSERT(SafeCount(fPlayback->fBitmapHeap.get()) == heapSize);
-
-                // needed to create typeface playback
-                copyInfo.controller.setupPlaybacks();
-                copyInfo.initialized = true;
+            SkPaint::Style paintStyle = op.paint.getStyle();
+            const SkRect& pathBounds = op.path.getBounds();
+            if (SkPaint::kStroke_Style == paintStyle &&
+                0 == op.paint.getStrokeWidth()) {
+                numAAHairlineConcavePaths++;
+            } else if (SkPaint::kFill_Style == paintStyle && pathBounds.width() < 64.f &&
+                       pathBounds.height() < 64.f && !op.path.isVolatile()) {
+                numAADFEligibleConcavePaths++;
             }
-
-            clone->fPlayback = SkNEW_ARGS(SkPicturePlayback, (clone, *fPlayback, &copyInfo));
-            SkASSERT(NULL == fRecord);
-            clone->fUniqueID = this->uniqueID(); // need to call method to ensure != 0
-        } else if (fRecord) {
-            // here we do a fake src.endRecording()
-            clone->fPlayback = SkNEW_ARGS(SkPicturePlayback, (clone, *fRecord, info, true));
-        } else {
-            clone->fPlayback = NULL;
         }
-
-        clone->fPathHeap.reset(SkSafeRef(fPathHeap.get()));
     }
-}
 
-SkPicture::AccelData::Domain SkPicture::AccelData::GenerateDomain() {
-    static int32_t gNextID = 0;
-
-    int32_t id = sk_atomic_inc(&gNextID);
-    if (id >= 1 << (8 * sizeof(Domain))) {
-        SK_CRASH();
+    template <typename T>
+    SK_WHEN(HasMember_paint<T>, void) operator()(const T& op) {
+        this->checkPaint(AsPtr(op.paint));
     }
 
-    return static_cast<Domain>(id);
-}
+    template <typename T>
+    SK_WHEN(!HasMember_paint<T>, void) operator()(const T& op) { /* do nothing */ }
 
-///////////////////////////////////////////////////////////////////////////////
+    int numPaintWithPathEffectUses;
+    int numFastPathDashEffects;
+    int numAAConcavePaths;
+    int numAAHairlineConcavePaths;
+    int numAADFEligibleConcavePaths;
+};
 
-#ifdef SK_SUPPORT_LEGACY_DERIVED_PICTURE_CLASSES
+SkPicture::Analysis::Analysis(const SkRecord& record) {
+    fWillPlaybackBitmaps = WillPlaybackBitmaps(record);
 
-SkCanvas* SkPicture::beginRecording(int width, int height,
-                                    uint32_t recordingFlags) {
-    if (fPlayback) {
-        SkDELETE(fPlayback);
-        fPlayback = NULL;
+    PathCounter counter;
+    for (unsigned i = 0; i < record.count(); i++) {
+        record.visit<void>(i, counter);
     }
-    SkSafeUnref(fAccelData);
-    SkSafeSetNull(fRecord);
-    fContentInfo.reset();
-
-    this->needsNewGenID();
-
-    // Must be set before calling createBBoxHierarchy
-    fWidth = width;
-    fHeight = height;
+    fNumPaintWithPathEffectUses = counter.numPaintWithPathEffectUses;
+    fNumFastPathDashEffects     = counter.numFastPathDashEffects;
+    fNumAAConcavePaths          = counter.numAAConcavePaths;
+    fNumAAHairlineConcavePaths  = counter.numAAHairlineConcavePaths;
+    fNumAADFEligibleConcavePaths  = counter.numAADFEligibleConcavePaths;
 
-    const SkISize size = SkISize::Make(width, height);
-
-    if (recordingFlags & kOptimizeForClippedPlayback_RecordingFlag) {
-        SkBBoxHierarchy* tree = this->createBBoxHierarchy();
-        SkASSERT(NULL != tree);
-        fRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (this, size, recordingFlags, tree));
-        tree->unref();
-    } else {
-        fRecord = SkNEW_ARGS(SkPictureRecord, (this, size, recordingFlags));
+    fHasText = false;
+    TextHunter text;
+    for (unsigned i = 0; i < record.count(); i++) {
+        if (record.visit<bool>(i, text)) {
+            fHasText = true;
+            break;
+        }
     }
-    fRecord->beginRecording();
-
-    return fRecord;
 }
 
-#endif
-
-SkCanvas* SkPicture::beginRecording(int width, int height,
-                                    SkBBHFactory* bbhFactory,
-                                    uint32_t recordingFlags) {
-    if (fPlayback) {
-        SkDELETE(fPlayback);
-        fPlayback = NULL;
-    }
-    SkSafeUnref(fAccelData);
-    SkSafeSetNull(fRecord);
-    SkASSERT(NULL == fPathHeap);
-    fContentInfo.reset();
-
-    this->needsNewGenID();
-
-    fWidth = width;
-    fHeight = height;
-
-    const SkISize size = SkISize::Make(width, height);
-
-    if (NULL != bbhFactory) {
-        SkAutoTUnref<SkBBoxHierarchy> tree((*bbhFactory)(width, height));
-        SkASSERT(NULL != tree);
-        fRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (this, size,
-                                                     recordingFlags|
-                                                     kOptimizeForClippedPlayback_RecordingFlag,
-                                                     tree.get()));
-    } else {
-        fRecord = SkNEW_ARGS(SkPictureRecord, (this, size, recordingFlags));
+bool SkPicture::Analysis::suitableForGpuRasterization(const char** reason,
+                                                      int sampleCount) const {
+    // TODO: the heuristic used here needs to be refined
+    static const int kNumPaintWithPathEffectsUsesTol = 1;
+    static const int kNumAAConcavePathsTol = 5;
+
+    int numNonDashedPathEffects = fNumPaintWithPathEffectUses -
+                                  fNumFastPathDashEffects;
+    bool suitableForDash = (0 == fNumPaintWithPathEffectUses) ||
+                           (numNonDashedPathEffects < kNumPaintWithPathEffectsUsesTol
+                               && 0 == sampleCount);
+
+    bool ret = suitableForDash &&
+               (fNumAAConcavePaths - fNumAAHairlineConcavePaths - fNumAADFEligibleConcavePaths)
+                   < kNumAAConcavePathsTol;
+
+    if (!ret && reason) {
+        if (!suitableForDash) {
+            if (0 != sampleCount) {
+                *reason = "Can't use multisample on dash effect.";
+            } else {
+                *reason = "Too many non dashed path effects.";
+            }
+        } else if ((fNumAAConcavePaths - fNumAAHairlineConcavePaths - fNumAADFEligibleConcavePaths)
+                    >= kNumAAConcavePathsTol)
+            *reason = "Too many anti-aliased concave paths.";
+        else
+            *reason = "Unknown reason for GPU unsuitability.";
     }
-    fRecord->beginRecording();
-
-    return fRecord;
+    return ret;
 }
 
+///////////////////////////////////////////////////////////////////////////////
 
-#ifdef SK_SUPPORT_LEGACY_DERIVED_PICTURE_CLASSES
-
-SkBBoxHierarchy* SkPicture::createBBoxHierarchy() const {
-    // TODO: this code is now replicated in SkRTreePicture. Once all external
-    // clients have been weaned off of kOptimizeForClippedPlayback_RecordingFlag,
-    // this code can be removed.
-
-    // These values were empirically determined to produce reasonable
-    // performance in most cases.
-    static const int kRTreeMinChildren = 6;
-    static const int kRTreeMaxChildren = 11;
+// fRecord OK
+SkPicture::SkPicture(SkScalar width, SkScalar height,
+                     const SkPictureRecord& record,
+                     bool deepCopyOps)
+    : fCullWidth(width)
+    , fCullHeight(height)
+    , fAnalysis() {
+    this->needsNewGenID();
 
-    SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(fWidth),
-                                       SkIntToScalar(fHeight));
-    bool sortDraws = false;  // Do not sort draw calls when bulk loading.
+    SkPictInfo info;
+    this->createHeader(&info);
+    fData.reset(SkNEW_ARGS(SkPictureData, (record, info, deepCopyOps)));
+}
 
-    return SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren,
-                           aspectRatio, sortDraws);
+// Create an SkPictureData-backed SkPicture from an SkRecord.
+// This for compatibility with serialization code only.  This is not cheap.
+SkPicture* SkPicture::Backport(const SkRecord& src, const SkRect& cullRect) {
+    SkPictureRecord rec(SkISize::Make(cullRect.width(), cullRect.height()), 0/*flags*/);
+    rec.beginRecording();
+        SkRecordDraw(src, &rec, NULL/*bbh*/, NULL/*callback*/);
+    rec.endRecording();
+    return SkNEW_ARGS(SkPicture, (cullRect.width(), cullRect.height(), rec, false/*deepCopyOps*/));
 }
 
-#endif
+// fRecord OK
+SkPicture::~SkPicture() {
+    this->callDeletionListeners();
+}
 
-SkCanvas* SkPicture::getRecordingCanvas() const {
-    // will be null if we are not recording
-    return fRecord;
+// fRecord OK
+void SkPicture::EXPERIMENTAL_addAccelData(const SkPicture::AccelData* data) const {
+    fAccelData.reset(SkRef(data));
 }
 
-void SkPicture::endRecording() {
-    if (NULL == fPlayback) {
-        if (NULL != fRecord) {
-            fRecord->endRecording();
-            SkPictInfo info;
-            this->createHeader(&info);
-            fPlayback = SkNEW_ARGS(SkPicturePlayback, (this, *fRecord, info));
-            SkSafeSetNull(fRecord);
-        }
+// fRecord OK
+const SkPicture::AccelData* SkPicture::EXPERIMENTAL_getAccelData(
+        SkPicture::AccelData::Key key) const {
+    if (fAccelData.get() && fAccelData->getKey() == key) {
+        return fAccelData.get();
     }
-    SkASSERT(NULL == fRecord);
+    return NULL;
 }
 
-const SkPicture::OperationList& SkPicture::OperationList::InvalidList() {
-    static OperationList gInvalid;
-    return gInvalid;
-}
+// fRecord OK
+SkPicture::AccelData::Domain SkPicture::AccelData::GenerateDomain() {
+    static int32_t gNextID = 0;
 
-const SkPicture::OperationList& SkPicture::EXPERIMENTAL_getActiveOps(const SkIRect& queryRect) {
-    this->endRecording();  // TODO: remove eventually
-    if (NULL != fPlayback) {
-        return fPlayback->getActiveOps(queryRect);
+    int32_t id = sk_atomic_inc(&gNextID);
+    if (id >= 1 << (8 * sizeof(Domain))) {
+        SK_CRASH();
     }
-    return OperationList::InvalidList();
+
+    return static_cast<Domain>(id);
 }
 
-size_t SkPicture::EXPERIMENTAL_curOpID() const {
-    if (NULL != fPlayback) {
-        return fPlayback->curOpID();
+///////////////////////////////////////////////////////////////////////////////
+
+// fRecord OK
+void SkPicture::playback(SkCanvas* canvas, SkDrawPictureCallback* callback) const {
+    SkASSERT(canvas);
+    SkASSERT(fData.get() || fRecord.get());
+
+    if (fData.get()) {
+        SkPicturePlayback playback(this);
+        playback.draw(canvas, callback);
     }
-    return 0;
-}
+    if (fRecord.get()) {
+        // If the query contains the whole picture, don't bother with the BBH.
+        SkRect clipBounds = { 0, 0, 0, 0 };
+        (void)canvas->getClipBounds(&clipBounds);
+        const bool useBBH = !clipBounds.contains(this->cullRect());
 
-void SkPicture::draw(SkCanvas* surface, SkDrawPictureCallback* callback) {
-    this->endRecording(); // TODO: remove eventually
-    if (NULL != fPlayback) {
-        fPlayback->draw(*surface, callback);
+        SkRecordDraw(*fRecord, canvas, useBBH ? fBBH.get() : NULL, callback);
     }
 }
 
@@ -450,6 +339,7 @@ void SkPicture::draw(SkCanvas* surface, SkDrawPictureCallback* callback) {
 
 static const char kMagic[] = { 's', 'k', 'i', 'a', 'p', 'i', 'c', 't' };
 
+// fRecord OK
 bool SkPicture::IsValidPictInfo(const SkPictInfo& info) {
     if (0 != memcmp(info.fMagic, kMagic, sizeof(kMagic))) {
         return false;
@@ -463,6 +353,7 @@ bool SkPicture::IsValidPictInfo(const SkPictInfo& info) {
     return true;
 }
 
+// fRecord OK
 bool SkPicture::InternalOnly_StreamIsSKP(SkStream* stream, SkPictInfo* pInfo) {
     if (NULL == stream) {
         return false;
@@ -471,7 +362,32 @@ bool SkPicture::InternalOnly_StreamIsSKP(SkStream* stream, SkPictInfo* pInfo) {
     // Check magic bytes.
     SkPictInfo info;
     SkASSERT(sizeof(kMagic) == sizeof(info.fMagic));
-    if (!stream->read(&info, sizeof(info)) || !IsValidPictInfo(info)) {
+
+    if (!stream->read(&info.fMagic, sizeof(kMagic))) {
+        return false;
+    }
+
+    info.fVersion = stream->readU32();
+
+#ifndef V35_COMPATIBILITY_CODE
+    if (info.fVersion < 35) {
+        info.fCullRect.fLeft = 0;
+        info.fCullRect.fTop = 0;
+        info.fCullRect.fRight = SkIntToScalar(stream->readU32());
+        info.fCullRect.fBottom = SkIntToScalar(stream->readU32());
+    } else {
+#endif
+        info.fCullRect.fLeft = stream->readScalar();
+        info.fCullRect.fTop = stream->readScalar();
+        info.fCullRect.fRight = stream->readScalar();
+        info.fCullRect.fBottom = stream->readScalar();
+#ifndef V35_COMPATIBILITY_CODE
+    }
+#endif
+
+    info.fFlags = stream->readU32();
+
+    if (!IsValidPictInfo(info)) {
         return false;
     }
 
@@ -481,11 +397,34 @@ bool SkPicture::InternalOnly_StreamIsSKP(SkStream* stream, SkPictInfo* pInfo) {
     return true;
 }
 
-bool SkPicture::InternalOnly_BufferIsSKP(SkReadBuffer& buffer, SkPictInfo* pInfo) {
+// fRecord OK
+bool SkPicture::InternalOnly_BufferIsSKP(SkReadBuffer* buffer, SkPictInfo* pInfo) {
     // Check magic bytes.
     SkPictInfo info;
     SkASSERT(sizeof(kMagic) == sizeof(info.fMagic));
-    if (!buffer.readByteArray(&info, sizeof(info)) || !IsValidPictInfo(info)) {
+
+    if (!buffer->readByteArray(&info.fMagic, sizeof(kMagic))) {
+        return false;
+    }
+
+    info.fVersion = buffer->readUInt();
+
+#ifndef V35_COMPATIBILITY_CODE
+    if (info.fVersion < 35) {
+        info.fCullRect.fLeft = 0;
+        info.fCullRect.fTop = 0;
+        info.fCullRect.fRight = SkIntToScalar(buffer->readUInt());
+        info.fCullRect.fBottom = SkIntToScalar(buffer->readUInt());
+    } else {
+#endif
+        buffer->readRect(&info.fCullRect);
+#ifndef V35_COMPATIBILITY_CODE
+    }
+#endif
+
+    info.fFlags = buffer->readUInt();
+
+    if (!IsValidPictInfo(info)) {
         return false;
     }
 
@@ -495,15 +434,24 @@ bool SkPicture::InternalOnly_BufferIsSKP(SkReadBuffer& buffer, SkPictInfo* pInfo
     return true;
 }
 
-SkPicture::SkPicture(SkPicturePlayback* playback, int width, int height)
-    : fPlayback(playback)
-    , fRecord(NULL)
-    , fWidth(width)
-    , fHeight(height)
-    , fAccelData(NULL) {
+// fRecord OK
+SkPicture::SkPicture(SkPictureData* data, SkScalar width, SkScalar height)
+    : fData(data)
+    , fCullWidth(width)
+    , fCullHeight(height)
+    , fAnalysis() {
     this->needsNewGenID();
 }
 
+SkPicture* SkPicture::Forwardport(const SkPicture& src) {
+    SkAutoTDelete<SkRecord> record(SkNEW(SkRecord));
+    SkRecorder canvas(record.get(), src.cullRect().width(), src.cullRect().height());
+    src.playback(&canvas);
+    return SkNEW_ARGS(SkPicture, (src.cullRect().width(), src.cullRect().height(),
+                                  record.detach(), NULL/*bbh*/));
+}
+
+// fRecord OK
 SkPicture* SkPicture::CreateFromStream(SkStream* stream, InstallPixelRefProc proc) {
     SkPictInfo info;
 
@@ -511,44 +459,41 @@ SkPicture* SkPicture::CreateFromStream(SkStream* stream, InstallPixelRefProc pro
         return NULL;
     }
 
-    SkPicture* newPict = SkNEW_ARGS(SkPicture, (NULL, info.fWidth, info.fHeight));
-
     // Check to see if there is a playback to recreate.
     if (stream->readBool()) {
-        SkPicturePlayback* playback = SkPicturePlayback::CreateFromStream(newPict, stream,
-                                                                          info, proc);
-        if (NULL == playback) {
-            SkDELETE(newPict);
+        SkPictureData* data = SkPictureData::CreateFromStream(stream, info, proc);
+        if (NULL == data) {
             return NULL;
         }
-        newPict->fPlayback = playback;
+        const SkPicture src(data, info.fCullRect.width(), info.fCullRect.height());
+        return Forwardport(src);
     }
 
-    return newPict;
+    return NULL;
 }
 
+// fRecord OK
 SkPicture* SkPicture::CreateFromBuffer(SkReadBuffer& buffer) {
     SkPictInfo info;
 
-    if (!InternalOnly_BufferIsSKP(buffer, &info)) {
+    if (!InternalOnly_BufferIsSKP(&buffer, &info)) {
         return NULL;
     }
 
-    SkPicture* newPict = SkNEW_ARGS(SkPicture, (NULL, info.fWidth, info.fHeight));
-
     // Check to see if there is a playback to recreate.
     if (buffer.readBool()) {
-        SkPicturePlayback* playback = SkPicturePlayback::CreateFromBuffer(newPict, buffer, info);
-        if (NULL == playback) {
-            SkDELETE(newPict);
+        SkPictureData* data = SkPictureData::CreateFromBuffer(buffer, info);
+        if (NULL == data) {
             return NULL;
         }
-        newPict->fPlayback = playback;
+        const SkPicture src(data, info.fCullRect.width(), info.fCullRect.height());
+        return Forwardport(src);
     }
 
-    return newPict;
+    return NULL;
 }
 
+// fRecord OK
 void SkPicture::createHeader(SkPictInfo* info) const {
     // Copy magic bytes at the beginning of the header
     SkASSERT(sizeof(kMagic) == 8);
@@ -557,8 +502,7 @@ void SkPicture::createHeader(SkPictInfo* info) const {
 
     // Set picture info after magic bytes in the header
     info->fVersion = CURRENT_PICTURE_VERSION;
-    info->fWidth = fWidth;
-    info->fHeight = fHeight;
+    info->fCullRect = this->cullRect();
     info->fFlags = SkPictInfo::kCrossProcess_Flag;
     // TODO: remove this flag, since we're always float (now)
     info->fFlags |= SkPictInfo::kScalarIsFloat_Flag;
@@ -568,115 +512,100 @@ void SkPicture::createHeader(SkPictInfo* info) const {
     }
 }
 
+// fRecord OK
 void SkPicture::serialize(SkWStream* stream, EncodeBitmap encoder) const {
-    SkPicturePlayback* playback = fPlayback;
+    const SkPictureData* data = fData.get();
 
-    SkPictInfo info;
-    this->createHeader(&info);
-    if (NULL == playback && fRecord) {
-        playback = SkNEW_ARGS(SkPicturePlayback, (this, *fRecord, info));
+    // If we're a new-format picture, backport to old format for serialization.
+    SkAutoTDelete<SkPicture> oldFormat;
+    if (NULL == data && fRecord.get()) {
+        oldFormat.reset(Backport(*fRecord, this->cullRect()));
+        data = oldFormat->fData.get();
+        SkASSERT(data);
     }
 
+    SkPictInfo info;
+    this->createHeader(&info);
+    SkASSERT(sizeof(SkPictInfo) == 32);
     stream->write(&info, sizeof(info));
-    if (playback) {
+
+    if (data) {
         stream->writeBool(true);
-        playback->serialize(stream, encoder);
-        // delete playback if it is a local version (i.e. cons'd up just now)
-        if (playback != fPlayback) {
-            SkDELETE(playback);
-        }
+        data->serialize(stream, encoder);
     } else {
         stream->writeBool(false);
     }
 }
 
-void SkPicture::WriteTagSize(SkWriteBuffer& buffer, uint32_t tag, size_t size) {
-    buffer.writeUInt(tag);
-    buffer.writeUInt(SkToU32(size));
-}
-
-void SkPicture::WriteTagSize(SkWStream* stream, uint32_t tag,  size_t size) {
-    stream->write32(tag);
-    stream->write32(SkToU32(size));
-}
-
-bool SkPicture::parseBufferTag(SkReadBuffer& buffer,
-                               uint32_t tag,
-                               uint32_t size) {
-    switch (tag) {
-        case SK_PICT_PATH_BUFFER_TAG:
-            if (size > 0) {
-                fPathHeap.reset(SkNEW_ARGS(SkPathHeap, (buffer)));
-            }
-            break;
-        default:
-            // The tag was invalid.
-            return false;
-    }
-
-    return true;    // success
-}
-
-void SkPicture::flattenToBuffer(SkWriteBuffer& buffer) const {
-    int n;
+// fRecord OK
+void SkPicture::flatten(SkWriteBuffer& buffer) const {
+    const SkPictureData* data = fData.get();
 
-    if ((n = SafeCount(fPathHeap.get())) > 0) {
-        WriteTagSize(buffer, SK_PICT_PATH_BUFFER_TAG, n);
-        fPathHeap->flatten(buffer);
+    // If we're a new-format picture, backport to old format for serialization.
+    SkAutoTDelete<SkPicture> oldFormat;
+    if (NULL == data && fRecord.get()) {
+        oldFormat.reset(Backport(*fRecord, this->cullRect()));
+        data = oldFormat->fData.get();
+        SkASSERT(data);
     }
-}
-
-void SkPicture::flatten(SkWriteBuffer& buffer) const {
-    SkPicturePlayback* playback = fPlayback;
 
     SkPictInfo info;
     this->createHeader(&info);
-    if (NULL == playback && fRecord) {
-        playback = SkNEW_ARGS(SkPicturePlayback, (this, *fRecord, info));
-    }
+    buffer.writeByteArray(&info.fMagic, sizeof(info.fMagic));
+    buffer.writeUInt(info.fVersion);
+    buffer.writeRect(info.fCullRect);
+    buffer.writeUInt(info.fFlags);
 
-    buffer.writeByteArray(&info, sizeof(info));
-    if (playback) {
+    if (data) {
         buffer.writeBool(true);
-        playback->flatten(buffer);
-        // delete playback if it is a local version (i.e. cons'd up just now)
-        if (playback != fPlayback) {
-            SkDELETE(playback);
-        }
+        data->flatten(buffer);
     } else {
         buffer.writeBool(false);
     }
 }
 
 #if SK_SUPPORT_GPU
-bool SkPicture::suitableForGpuRasterization(GrContext* context) const {
-    // TODO: the heuristic used here needs to be refined
-    static const int kNumPaintWithPathEffectUsesTol = 1;
-    static const int kNumAAConcavePaths = 5;
-
-    SkASSERT(this->numAAHairlineConcavePaths() <= this->numAAConcavePaths());
+// fRecord OK
+bool SkPicture::suitableForGpuRasterization(GrContext* context, const char **reason) const {
+    if (fRecord.get()) {
+        return fAnalysis.suitableForGpuRasterization(reason, 0);
+    }
+    if (NULL == fData.get()) {
+        if (reason) {
+            *reason = "Missing internal data.";
+        }
+        return false;
+    }
 
-    return this->numPaintWithPathEffectUses() < kNumPaintWithPathEffectUsesTol &&
-           (this->numAAConcavePaths()-this->numAAHairlineConcavePaths()) < kNumAAConcavePaths;
+    return fData->suitableForGpuRasterization(context, reason);
 }
 #endif
 
-bool SkPicture::willPlayBackBitmaps() const {
-    if (!fPlayback) {
-        return false;
+// fRecord OK
+bool SkPicture::hasText() const {
+    if (fRecord.get()) {
+        return fAnalysis.fHasText;
+    }
+    if (fData.get()) {
+        return fData->hasText();
     }
-    return fPlayback->containsBitmaps();
+    SkFAIL("Unreachable");
+    return false;
 }
 
-#ifdef SK_BUILD_FOR_ANDROID
-void SkPicture::abortPlayback() {
-    if (NULL == fPlayback) {
-        return;
+// fRecord OK
+bool SkPicture::willPlayBackBitmaps() const {
+    if (fRecord.get()) {
+        return fAnalysis.fWillPlaybackBitmaps;
+    }
+    if (fData.get()) {
+        return fData->containsBitmaps();
     }
-    fPlayback->abort();
+    SkFAIL("Unreachable");
+    return false;
 }
-#endif
 
+// fRecord OK
 static int32_t next_picture_generation_id() {
     static int32_t  gPictureGenerationID = 0;
     // do a loop in case our global wraps around, as we never want to
@@ -688,14 +617,60 @@ static int32_t next_picture_generation_id() {
     return genID;
 }
 
+// fRecord OK
 uint32_t SkPicture::uniqueID() const {
-    if (NULL != fRecord) {
-        SkASSERT(NULL == fPlayback);
-        return SK_InvalidGenID;
-    }
-
     if (SK_InvalidGenID == fUniqueID) {
         fUniqueID = next_picture_generation_id();
     }
     return fUniqueID;
 }
+
+
+static SkRecord* optimized(SkRecord* r) {
+    SkRecordOptimize(r);
+    return r;
+}
+
+// fRecord OK
+SkPicture::SkPicture(SkScalar width, SkScalar height, SkRecord* record, SkBBoxHierarchy* bbh)
+    : fCullWidth(width)
+    , fCullHeight(height)
+    , fRecord(optimized(record))
+    , fBBH(SkSafeRef(bbh))
+    , fAnalysis(*fRecord) {
+    // TODO: delay as much of this work until just before first playback?
+    if (fBBH.get()) {
+        SkRecordFillBounds(this->cullRect(), *fRecord, fBBH.get());
+    }
+    this->needsNewGenID();
+}
+
+// Note that we are assuming that this entry point will only be called from
+// one thread. Currently the only client of this method is
+// SkGpuDevice::EXPERIMENTAL_optimize which should be only called from a single
+// thread.
+void SkPicture::addDeletionListener(DeletionListener* listener) const {
+    SkASSERT(listener);
+
+    *fDeletionListeners.append() = SkRef(listener);
+}
+
+void SkPicture::callDeletionListeners() {
+    for (int i = 0; i < fDeletionListeners.count(); ++i) {
+        fDeletionListeners[i]->onDeletion(this->uniqueID());
+    }
+
+    fDeletionListeners.unrefAll();
+}
+
+// fRecord OK
+int SkPicture::approximateOpCount() const {
+    SkASSERT(fRecord.get() || fData.get());
+    if (fRecord.get()) {
+        return fRecord->count();
+    }
+    if (fData.get()) {
+        return fData->opCount();
+    }
+    return 0;
+}