SkRecord strawman
authorcommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 1 Apr 2014 16:24:06 +0000 (16:24 +0000)
committercommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 1 Apr 2014 16:24:06 +0000 (16:24 +0000)
Record performance as measured by bench_record (out/Release/bench_record --skr) improves by at least 1.9x, at most 6.7x, arithmetic mean 2.6x, geometric mean 3.0x.  So, good.

Correctness as measured by DM (out/Debug/dm --skr) is ~ok.  One GM (shadertext2) fails because we're assuming all paint effects are immutable, but SkShaders are still mutable.

To do after this CL:
  - measure playback speed
  - catch up feature-wise to SkPicture
  - match today's playback speed

BUG=skia:
R=robertphillips@google.com, bsalomon@google.com, reed@google.com, mtklein@google.com

Author: mtklein@chromium.org

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

git-svn-id: http://skia.googlecode.com/svn/trunk@14010 2bbb7eff-a529-9590-31e7-b0007b416f81

12 files changed:
dm/DMCpuGMTask.cpp
dm/DMRecordTask.cpp [new file with mode: 0644]
dm/DMRecordTask.h [new file with mode: 0644]
gyp/dm.gyp
gyp/record.gyp [new file with mode: 0644]
gyp/tools.gyp
src/record/SkRecord.h [new file with mode: 0644]
src/record/SkRecordDraw.h [new file with mode: 0644]
src/record/SkRecorder.cpp [new file with mode: 0644]
src/record/SkRecorder.h [new file with mode: 0644]
src/record/SkRecords.h [new file with mode: 0644]
tools/bench_record.cpp

index 6ab0014..7ab1d44 100644 (file)
@@ -1,6 +1,7 @@
 #include "DMCpuGMTask.h"
 #include "DMExpectationsTask.h"
 #include "DMPipeTask.h"
+#include "DMRecordTask.h"
 #include "DMReplayTask.h"
 #include "DMSerializeTask.h"
 #include "DMTileGridTask.h"
@@ -38,6 +39,7 @@ void CpuGMTask::draw() {
     SPAWN(PipeTask, fGMFactory(NULL), bitmap, false, false);
     SPAWN(PipeTask, fGMFactory(NULL), bitmap, true, false);
     SPAWN(PipeTask, fGMFactory(NULL), bitmap, true, true);
+    SPAWN(RecordTask, fGMFactory(NULL), bitmap);
     SPAWN(ReplayTask, fGMFactory(NULL), bitmap, false);
     SPAWN(ReplayTask, fGMFactory(NULL), bitmap, true);
     SPAWN(SerializeTask, fGMFactory(NULL), bitmap);
diff --git a/dm/DMRecordTask.cpp b/dm/DMRecordTask.cpp
new file mode 100644 (file)
index 0000000..0109a41
--- /dev/null
@@ -0,0 +1,42 @@
+#include "DMRecordTask.h"
+#include "DMUtil.h"
+#include "DMWriteTask.h"
+#include "SkCommandLineFlags.h"
+#include "SkRecordDraw.h"
+#include "SkRecorder.h"
+
+DEFINE_bool(skr, false, "If true, run SKR tests.");
+
+namespace DM {
+
+RecordTask::RecordTask(const Task& parent, skiagm::GM* gm, SkBitmap reference)
+    : CpuTask(parent)
+    , fName(UnderJoin(parent.name().c_str(), "skr"))
+    , fGM(gm)
+    , fReference(reference)
+    {}
+
+void RecordTask::draw() {
+    // Record the GM into an SkRecord.
+    SkRecord record;
+    SkRecorder canvas(&record, fReference.width(), fReference.height());
+    canvas.concat(fGM->getInitialTransform());
+    fGM->draw(&canvas);
+
+    // Draw the SkRecord back into a bitmap.
+    SkBitmap bitmap;
+    SetupBitmap(fReference.colorType(), fGM.get(), &bitmap);
+    SkCanvas target(bitmap);
+    record.visit(SkRecordDraw(&target));
+
+    if (!BitmapsEqual(bitmap, fReference)) {
+        this->fail();
+        this->spawnChild(SkNEW_ARGS(WriteTask, (*this, bitmap)));
+    }
+}
+
+bool RecordTask::shouldSkip() const {
+    return !FLAGS_skr;
+}
+
+}  // namespace DM
diff --git a/dm/DMRecordTask.h b/dm/DMRecordTask.h
new file mode 100644 (file)
index 0000000..7efd951
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef DMRecordTask_DEFINED
+#define DMRecordTask_DEFINED
+
+#include "DMTask.h"
+#include "SkBitmap.h"
+#include "SkString.h"
+#include "SkTemplates.h"
+#include "gm.h"
+
+// Records a GM through an SkRecord, draws it, and compares against the reference bitmap.
+
+namespace DM {
+
+class RecordTask : public CpuTask {
+
+public:
+    RecordTask(const Task& parent, skiagm::GM*, SkBitmap reference);
+
+    virtual void draw() SK_OVERRIDE;
+    virtual bool shouldSkip() const SK_OVERRIDE;
+    virtual SkString name() const SK_OVERRIDE { return fName; }
+
+private:
+    const SkString fName;
+    SkAutoTDelete<skiagm::GM> fGM;
+    const SkBitmap fReference;
+};
+
+}  // namespace DM
+
+#endif  // DMRecordTask_DEFINED
index c96281a..6bce65a 100644 (file)
@@ -16,6 +16,7 @@
             '../src/core',
             '../src/effects',
             '../src/pipe/utils/',
+            '../src/record',
             '../src/utils',
             '../src/utils/debugger',
             '../tools',
@@ -33,6 +34,7 @@
             '../dm/DMExpectationsTask.cpp',
             '../dm/DMGpuGMTask.cpp',
             '../dm/DMPipeTask.cpp',
+            '../dm/DMRecordTask.cpp',
             '../dm/DMReplayTask.cpp',
             '../dm/DMReporter.cpp',
             '../dm/DMSerializeTask.cpp',
@@ -55,6 +57,7 @@
             'flags.gyp:flags',
             'jsoncpp.gyp:jsoncpp',
             'gputest.gyp:skgputest',
+            'record.gyp:*',
         ],
     }]
 }
diff --git a/gyp/record.gyp b/gyp/record.gyp
new file mode 100644 (file)
index 0000000..2e4a560
--- /dev/null
@@ -0,0 +1,14 @@
+# An experimental library for faster recording of SkCanvas commands.
+{
+    'targets': [{
+        'target_name': 'record',
+        'type': 'static_library',
+        'include_dirs': [
+            '../include/config',
+            '../include/core',
+        ],
+        'sources': [
+            '../src/record/SkRecorder.cpp',
+        ],
+    }]
+}
index fe84a97..461cc6b 100644 (file)
         '../src/core/',
         '../src/images',
         '../src/lazy',
+        '../src/record',
       ],
       'dependencies': [
         'flags.gyp:flags',
         'skia_lib.gyp:skia_lib',
+        'record.gyp:*',
       ],
     },
     {
diff --git a/src/record/SkRecord.h b/src/record/SkRecord.h
new file mode 100644 (file)
index 0000000..4013874
--- /dev/null
@@ -0,0 +1,198 @@
+#ifndef SkRecord_DEFINED
+#define SkRecord_DEFINED
+
+#include "SkChunkAlloc.h"
+#include "SkRecords.h"
+#include "SkTemplates.h"
+
+// SkRecord (REC-ord) represents a sequence of SkCanvas calls, saved for future use.
+// These future uses may include: replay, optimization, serialization, or combinations of those.
+//
+// Though an enterprising user may find calling alloc(), append(), visit(), and mutate() enough to
+// work with SkRecord, you probably want to look at SkRecorder which presents an SkCanvas interface
+// for creating an SkRecord, and SkRecordDraw which plays an SkRecord back into another SkCanvas.
+//
+// SkRecord often looks like it's compatible with any type T, but really it's compatible with any
+// type T which has a static const SkRecords::Type kType.  That is to say, SkRecord is compatible
+// only with SkRecords::* structs defined in SkRecords.h.  Your compiler will helpfully yell if you
+// get this wrong.
+
+class SkRecord : SkNoncopyable {
+public:
+    SkRecord(size_t chunkBytes = 4096, unsigned firstReserveCount = 64 / sizeof(void*))
+        : fAlloc(chunkBytes), fCount(0), fReserved(0), kFirstReserveCount(firstReserveCount) {}
+    ~SkRecord() { this->mutate(Destroyer()); }
+
+    // Accepts a visitor functor with this interface:
+    //   template <typename T>
+    //   void operator()()(const T& record) { ... }
+    // This operator() must be defined for at least all SkRecords::*; your compiler will help you
+    // get this right.
+    //
+    // f will be called on each recorded canvas call in the order they were append()ed.
+    template <typename F>
+    void visit(F f) const {
+        for (unsigned i = 0; i < fCount; i++) {
+            fRecords[i].visit(fTypes[i], f);
+        }
+    }
+
+    // Accepts a visitor functor with this interface:
+    //   template <typename T>
+    //   void operator()()(T* record) { ... }
+    // This operator() must be defined for at least all SkRecords::*; again, your compiler will help
+    // you get this right.
+    //
+    // f will be called on each recorded canvas call in the order they were append()ed.
+    template <typename F>
+    void mutate(F f) {
+        for (unsigned i = 0; i < fCount; i++) {
+            fRecords[i].mutate(fTypes[i], f);
+        }
+    }
+
+    // Allocate contiguous space for count Ts, to be destroyed (not just freed) when the SkRecord is
+    // destroyed.  For classes with constructors, placement new into this array.  Throws on failure.
+    // Here T can really be any class, not just those from SkRecords.
+    template <typename T>
+    T* alloc(unsigned count = 1) {
+        return (T*)fAlloc.allocThrow(sizeof(T) * count);
+    }
+
+    // Allocate space to record a canvas call of type T at the end of this SkRecord.  You are
+    // expected to placement new an object of type T onto this pointer.
+    template <typename T>
+    T* append() {
+        if (fCount == fReserved) {
+            fReserved = SkTMax(kFirstReserveCount, fReserved*2);
+            fRecords.realloc(fReserved);
+            fTypes.realloc(fReserved);
+        }
+
+        fTypes[fCount] = T::kType;
+        return fRecords[fCount++].alloc<T>(this);
+    }
+
+private:
+    // Implementation notes!
+    //
+    // Logically an SkRecord is structured as an array of pointers into a big chunk of memory where
+    // records representing each canvas draw call are stored:
+    //
+    // fRecords:  [*][*][*]...
+    //             |  |  |
+    //             |  |  |
+    //             |  |  +---------------------------------------+
+    //             |  +-----------------+                        |
+    //             |                    |                        |
+    //             v                    v                        v
+    //   fAlloc:  [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::DrawRect]...
+    //
+    // In the scheme above, the pointers in fRecords are void*: they have no type.  The type is not
+    // stored in fAlloc either; we just write raw data there.  But we need that type information.
+    // Here are some options:
+    //   1) use inheritance, virtuals, and vtables to make the fRecords pointers smarter
+    //   2) store the type data manually in fAlloc at the start of each record
+    //   3) store the type data manually somewhere with fRecords
+    //
+    // This code uses approach 3).  The implementation feels very similar to 1), but it's
+    // devirtualized instead of using the language's polymorphism mechanisms.  This lets us work
+    // with the types themselves (as SkRecords::Type), a sort of limited free RTTI; it lets us pay
+    // only 1 byte to store the type instead of a full pointer (4-8 bytes); and it leads to better
+    // decoupling between the SkRecords::* record types and the operations performed on them in
+    // visit() or mutate().  The recorded canvas calls don't have to have any idea about the
+    // operations performed on them.
+    //
+    // We store the types in a parallel fTypes array, mainly so that they can be tightly packed as
+    // single bytes.  This has the side effect of allowing very fast analysis passes over an
+    // SkRecord looking for just patterns of draw commands (or using this as a quick reject
+    // mechanism) though there's admittedly not a very good API exposed publically for this.
+    //
+    // We pull one final sneaky trick in the implementation.  When recording canvas calls that need
+    // to store less than a pointer of data, we don't go through the usual path of allocating the
+    // draw command in fAlloc and a pointer to it in fRecords; instead, we ignore fAlloc and
+    // directly allocate the object in the space we would have put the pointer in fRecords.  This is
+    // why you'll see uintptr_t instead of void* in Record below.
+    //
+    // The cost of appending a single record into this structure is then:
+    //   - 1 + sizeof(void*) + sizeof(T) if sizeof(T) >  sizeof(void*)
+    //   - 1 + sizeof(void*)             if sizeof(T) <= sizeof(void*)
+
+
+    // A mutator that calls destructors of all the canvas calls we've recorded.
+    struct Destroyer {
+        template <typename T>
+        void operator()(T* record) { record->~T(); }
+    };
+
+    // Logically the same as SkRecords::Type, but packed into 8 bits.
+    struct Type8 {
+    public:
+        // This intentionally converts implicitly back and forth.
+        Type8(SkRecords::Type type) : fType(type) { SkASSERT(*this == type); }
+        operator SkRecords::Type () { return (SkRecords::Type)fType; }
+
+    private:
+        uint8_t fType;
+    };
+
+    // Logically a void* to some bytes in fAlloc, but maybe has the bytes stored immediately
+    // instead.  This is also the main interface for devirtualized polymorphic dispatch: see visit()
+    // and mutate(), which essentially do the work of the missing vtable.
+    struct Record {
+    public:
+
+        // Allocate space for a T, perhaps using the SkRecord to allocate that space.
+        template <typename T>
+        T* alloc(SkRecord* record) {
+            if (IsLarge<T>()) {
+                fRecord = (uintptr_t)record->alloc<T>();
+            }
+            return this->ptr<T>();
+        }
+
+        // Visit this record with functor F (see public API above) assuming the record we're
+        // pointing to has this type.
+        template <typename F>
+        void visit(Type8 type, F f) const {
+        #define CASE(T) case SkRecords::T##_Type: return f(*this->ptr<SkRecords::T>());
+            switch(type) { SK_RECORD_TYPES(CASE) }
+        #undef CASE
+        }
+
+        // Mutate this record with functor F (see public API above) assuming the record we're
+        // pointing to has this type.
+        template <typename F>
+        void mutate(Type8 type, F f) {
+        #define CASE(T) case SkRecords::T##_Type: return f(this->ptr<SkRecords::T>());
+            switch(type) { SK_RECORD_TYPES(CASE) }
+        #undef CASE
+        }
+
+    private:
+        template <typename T>
+        T* ptr() const { return (T*)(IsLarge<T>() ? (void*)fRecord : &fRecord); }
+
+        // Is T too big to fit directly into a uintptr_t, neededing external allocation?
+        template <typename T>
+        static bool IsLarge() { return sizeof(T) > sizeof(uintptr_t); }
+
+        uintptr_t fRecord;
+    };
+
+    // fAlloc needs to be a data structure which can append variable length data in contiguous
+    // chunks, returning a stable handle to that data for later retrieval.
+    //
+    // fRecords and fTypes need to be data structures that can append fixed length data, and need to
+    // support efficient forward iteration.  (They don't need to be contiguous or indexable.)
+
+    SkChunkAlloc fAlloc;
+    SkAutoTMalloc<Record> fRecords;
+    SkAutoTMalloc<Type8> fTypes;
+    // fCount and fReserved measure both fRecords and fTypes, which always grow in lock step.
+    unsigned fCount;
+    unsigned fReserved;
+    const unsigned kFirstReserveCount;
+};
+
+#endif//SkRecord_DEFINED
diff --git a/src/record/SkRecordDraw.h b/src/record/SkRecordDraw.h
new file mode 100644 (file)
index 0000000..744dfc5
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef SkRecordDraw_DEFINED
+#define SkRecordDraw_DEFINED
+
+#include "SkRecord.h"
+#include "SkRecords.h"
+#include "SkCanvas.h"
+
+// This is an SkRecord visitor that will draw that SkRecord to an SkCanvas.
+
+struct SkRecordDraw {
+    explicit SkRecordDraw(SkCanvas* canvas) : canvas(canvas) {}
+
+    // No base case, so we'll be compile-time checked that we implemented all possibilities below.
+    template <typename T> void operator()(const T& record);
+
+    SkCanvas* canvas;
+};
+
+// Nothing fancy here.
+// The structs in SkRecord are completely isomorphic to their corresponding SkCanvas calls.
+
+#define CASE(T) template <> void SkRecordDraw::operator()(const SkRecords::T& r)
+
+CASE(Restore) { canvas->restore(); }
+CASE(Save) { canvas->save(r.flags); }
+CASE(SaveLayer) { canvas->saveLayer(r.bounds, r.paint, r.flags); }
+
+CASE(Concat) { canvas->concat(r.matrix); }
+CASE(SetMatrix) { canvas->setMatrix(r.matrix); }
+
+CASE(ClipPath) { canvas->clipPath(r.path, r.op, r.doAA); }
+CASE(ClipRRect) { canvas->clipRRect(r.rrect, r.op, r.doAA); }
+CASE(ClipRect) { canvas->clipRect(r.rect, r.op, r.doAA); }
+CASE(ClipRegion) { canvas->clipRegion(r.region, r.op); }
+
+CASE(Clear) { canvas->clear(r.color); }
+CASE(DrawBitmap) { canvas->drawBitmap(r.bitmap, r.left, r.top, r.paint); }
+CASE(DrawBitmapMatrix) { canvas->drawBitmapMatrix(r.bitmap, r.matrix, r.paint); }
+CASE(DrawBitmapNine) { canvas->drawBitmapNine(r.bitmap, r.center, r.dst, r.paint); }
+CASE(DrawBitmapRectToRect) {
+    canvas->drawBitmapRectToRect(r.bitmap, r.src, r.dst, r.paint, r.flags);
+}
+CASE(DrawDRRect) { canvas->drawDRRect(r.outer, r.inner, r.paint); }
+CASE(DrawOval) { canvas->drawOval(r.oval, r.paint); }
+CASE(DrawPaint) { canvas->drawPaint(r.paint); }
+CASE(DrawPath) { canvas->drawPath(r.path, r.paint); }
+CASE(DrawPoints) { canvas->drawPoints(r.mode, r.count, r.pts, r.paint); }
+CASE(DrawPosText) { canvas->drawPosText(r.text, r.byteLength, r.pos, r.paint); }
+CASE(DrawPosTextH) { canvas->drawPosTextH(r.text, r.byteLength, r.xpos, r.y, r.paint); }
+CASE(DrawRRect) { canvas->drawRRect(r.rrect, r.paint); }
+CASE(DrawRect) { canvas->drawRect(r.rect, r.paint); }
+CASE(DrawSprite) { canvas->drawSprite(r.bitmap, r.left, r.top, r.paint); }
+CASE(DrawText) { canvas->drawText(r.text, r.byteLength, r.x, r.y, r.paint); }
+CASE(DrawTextOnPath) { canvas->drawTextOnPath(r.text, r.byteLength, r.path, r.matrix, r.paint); }
+CASE(DrawVertices) {
+    canvas->drawVertices(r.vmode, r.vertexCount, r.vertices, r.texs, r.colors,
+                         r.xmode.get(), r.indices, r.indexCount, r.paint);
+}
+
+#undef CASE
+
+#endif//SkRecordDraw_DEFINED
diff --git a/src/record/SkRecorder.cpp b/src/record/SkRecorder.cpp
new file mode 100644 (file)
index 0000000..1edcc52
--- /dev/null
@@ -0,0 +1,224 @@
+#include "SkRecorder.h"
+#include "SkPicture.h"
+
+// SkCanvas will fail in mysterious ways if it doesn't know the real width and height.
+SkRecorder::SkRecorder(SkRecord* record, int width, int height)
+    : SkCanvas(width, height), fRecord(record) {}
+
+// To make appending to fRecord a little less verbose.
+#define APPEND(T, ...) \
+        SkNEW_PLACEMENT_ARGS(fRecord->append<SkRecords::T>(), SkRecords::T, (__VA_ARGS__))
+
+// The structs we're creating all copy their constructor arguments.  Given the way the SkRecords
+// framework works, sometimes they happen to technically be copied twice, which is fine and elided
+// into a single copy unless the class has a non-trivial copy constructor.  For classes with
+// non-trivial copy constructors, we skip the first copy (and its destruction) by wrapping the value
+// with delay_copy(), forcing the argument to be passed by const&.
+//
+// This is used below for SkBitmap, SkPaint, SkPath, and SkRegion, which all have non-trivial copy
+// constructors and destructors.  You'll know you've got a good candidate T if you see ~T() show up
+// unexpectedly on a profile of record time.  Otherwise don't bother.
+template <typename T>
+class Reference {
+public:
+    Reference(const T& x) : fX(x) {}
+    operator const T&() const { return fX; }
+private:
+    const T& fX;
+};
+
+template <typename T>
+static Reference<T> delay_copy(const T& x) { return Reference<T>(x); }
+
+// Use copy() only for optional arguments, to be copied if present or skipped if not.
+// (For most types we just pass by value and let copy constructors do their thing.)
+template <typename T>
+T* SkRecorder::copy(const T* src) {
+    if (NULL == src) {
+        return NULL;
+    }
+    return SkNEW_PLACEMENT_ARGS(fRecord->alloc<T>(), T, (*src));
+}
+
+// This copy() is for arrays.
+// It will work with POD or non-POD, though currently we only use it for POD.
+template <typename T>
+T* SkRecorder::copy(const T src[], unsigned count) {
+    if (NULL == src) {
+        return NULL;
+    }
+    T* dst = fRecord->alloc<T>(count);
+    for (unsigned i = 0; i < count; i++) {
+        SkNEW_PLACEMENT_ARGS(dst + i, T, (src[i]));
+    }
+    return dst;
+}
+
+// Specialization for copying strings, using memcpy.
+// This measured around 2x faster for copying code points,
+// but I found no corresponding speedup for other arrays.
+template <>
+char* SkRecorder::copy(const char src[], unsigned count) {
+    if (NULL == src) {
+        return NULL;
+    }
+    char* dst = fRecord->alloc<char>(count);
+    memcpy(dst, src, count);
+    return dst;
+}
+
+void SkRecorder::clear(SkColor color) {
+    APPEND(Clear, color);
+}
+
+void SkRecorder::drawPaint(const SkPaint& paint) {
+    APPEND(DrawPaint, delay_copy(paint));
+}
+
+void SkRecorder::drawPoints(PointMode mode,
+                            size_t count,
+                            const SkPoint pts[],
+                            const SkPaint& paint) {
+    APPEND(DrawPoints, mode, count, this->copy(pts, count), delay_copy(paint));
+}
+
+void SkRecorder::drawRect(const SkRect& rect, const SkPaint& paint) {
+    APPEND(DrawRect, rect, delay_copy(paint));
+}
+
+void SkRecorder::drawOval(const SkRect& oval, const SkPaint& paint) {
+    APPEND(DrawOval, oval, delay_copy(paint));
+}
+
+void SkRecorder::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
+    APPEND(DrawRRect, rrect, delay_copy(paint));
+}
+
+void SkRecorder::drawPath(const SkPath& path, const SkPaint& paint) {
+    APPEND(DrawPath, delay_copy(path), delay_copy(paint));
+}
+
+void SkRecorder::drawBitmap(const SkBitmap& bitmap,
+                            SkScalar left,
+                            SkScalar top,
+                            const SkPaint* paint) {
+    APPEND(DrawBitmap, delay_copy(bitmap), left, top, this->copy(paint));
+}
+
+void SkRecorder::drawBitmapRectToRect(const SkBitmap& bitmap,
+                                      const SkRect* src,
+                                      const SkRect& dst,
+                                      const SkPaint* paint,
+                                      DrawBitmapRectFlags flags) {
+    APPEND(DrawBitmapRectToRect,
+           delay_copy(bitmap), this->copy(src), dst, this->copy(paint), flags);
+}
+
+void SkRecorder::drawBitmapMatrix(const SkBitmap& bitmap,
+                                  const SkMatrix& matrix,
+                                  const SkPaint* paint) {
+    APPEND(DrawBitmapMatrix, delay_copy(bitmap), matrix, this->copy(paint));
+}
+
+void SkRecorder::drawBitmapNine(const SkBitmap& bitmap,
+                                const SkIRect& center,
+                                const SkRect& dst,
+                                const SkPaint* paint) {
+    APPEND(DrawBitmapNine, delay_copy(bitmap), center, dst, this->copy(paint));
+}
+
+void SkRecorder::drawSprite(const SkBitmap& bitmap, int left, int top, const SkPaint* paint) {
+    APPEND(DrawSprite, delay_copy(bitmap), left, top, this->copy(paint));
+}
+
+void SkRecorder::drawText(const void* text, size_t byteLength,
+                          SkScalar x, SkScalar y, const SkPaint& paint) {
+    APPEND(DrawText,
+           this->copy((const char*)text, byteLength), byteLength, x, y, delay_copy(paint));
+}
+
+void SkRecorder::drawPosText(const void* text, size_t byteLength,
+                             const SkPoint pos[], const SkPaint& paint) {
+    const unsigned points = paint.countText(text, byteLength);
+    APPEND(DrawPosText,
+           this->copy((const char*)text, byteLength), byteLength,
+           this->copy(pos, points), delay_copy(paint));
+}
+
+void SkRecorder::drawPosTextH(const void* text, size_t byteLength,
+                              const SkScalar xpos[], SkScalar constY, const SkPaint& paint) {
+    const unsigned points = paint.countText(text, byteLength);
+    APPEND(DrawPosTextH,
+           this->copy((const char*)text, byteLength), byteLength,
+           this->copy(xpos, points), constY, delay_copy(paint));
+}
+
+void SkRecorder::drawTextOnPath(const void* text, size_t byteLength,
+                                const SkPath& path, const SkMatrix* matrix, const SkPaint& paint) {
+    APPEND(DrawTextOnPath,
+           this->copy((const char*)text, byteLength), byteLength,
+           delay_copy(path), this->copy(matrix), delay_copy(paint));
+}
+
+void SkRecorder::drawPicture(SkPicture& picture) {
+    picture.draw(this);
+}
+
+void SkRecorder::drawVertices(VertexMode vmode,
+                              int vertexCount, const SkPoint vertices[],
+                              const SkPoint texs[], const SkColor colors[],
+                              SkXfermode* xmode,
+                              const uint16_t indices[], int indexCount, const SkPaint& paint) {
+    APPEND(DrawVertices, vmode,
+                         vertexCount,
+                         this->copy(vertices, vertexCount),
+                         texs ? this->copy(texs, vertexCount) : NULL,
+                         colors ? this->copy(colors, vertexCount) : NULL,
+                         xmode,
+                         this->copy(indices, indexCount),
+                         indexCount,
+                         delay_copy(paint));
+}
+
+void SkRecorder::willSave(SkCanvas::SaveFlags flags) {
+    APPEND(Save, flags);
+}
+
+SkCanvas::SaveLayerStrategy SkRecorder::willSaveLayer(const SkRect* bounds,
+                                                      const SkPaint* paint,
+                                                      SkCanvas::SaveFlags flags) {
+    APPEND(SaveLayer, this->copy(bounds), this->copy(paint), flags);
+    return SkCanvas::kNoLayer_SaveLayerStrategy;
+}
+
+void SkRecorder::willRestore() {
+    APPEND(Restore);
+}
+
+void SkRecorder::didConcat(const SkMatrix& matrix) {
+    APPEND(Concat, matrix);
+}
+
+void SkRecorder::didSetMatrix(const SkMatrix& matrix) {
+    APPEND(SetMatrix, matrix);
+}
+
+void SkRecorder::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) {
+    APPEND(DrawDRRect, outer, inner, delay_copy(paint));
+}
+
+void SkRecorder::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
+    APPEND(ClipRect, rect, op, edgeStyle == kSoft_ClipEdgeStyle);
+}
+
+void SkRecorder::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
+    APPEND(ClipRRect, rrect, op, edgeStyle == kSoft_ClipEdgeStyle);
+}
+
+void SkRecorder::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
+    APPEND(ClipPath, delay_copy(path), op, edgeStyle == kSoft_ClipEdgeStyle);
+}
+
+void SkRecorder::onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op) {
+    APPEND(ClipRegion, delay_copy(deviceRgn), op);
+}
diff --git a/src/record/SkRecorder.h b/src/record/SkRecorder.h
new file mode 100644 (file)
index 0000000..9d722b4
--- /dev/null
@@ -0,0 +1,70 @@
+#ifndef SkRecorder_DEFINED
+#define SkRecorder_DEFINED
+
+#include "SkCanvas.h"
+#include "SkRecord.h"
+#include "SkRecords.h"
+
+// SkRecorder provides an SkCanvas interface for recording into an SkRecord.
+
+class SkRecorder : public SkCanvas {
+public:
+    // Does not take ownership of the SkRecord.
+    SkRecorder(SkRecord*, int width, int height);
+
+    void clear(SkColor);
+    void drawPaint(const SkPaint& paint);
+    void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint);
+    void drawRect(const SkRect& rect, const SkPaint& paint);
+    void drawOval(const SkRect& oval, const SkPaint&);
+    void drawRRect(const SkRRect& rrect, const SkPaint& paint);
+    void drawPath(const SkPath& path, const SkPaint& paint);
+    void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
+                    const SkPaint* paint = NULL);
+    void drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst,
+                              const SkPaint* paint = NULL,
+                              DrawBitmapRectFlags flags = kNone_DrawBitmapRectFlag);
+    void drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& m, const SkPaint* paint = NULL);
+    void drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst,
+                        const SkPaint* paint = NULL);
+    void drawSprite(const SkBitmap& bitmap, int left, int top, const SkPaint* paint = NULL);
+    void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
+                  const SkPaint& paint);
+    void drawPosText(const void* text, size_t byteLength, const SkPoint pos[],
+                     const SkPaint& paint);
+    void drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY,
+                      const SkPaint& paint);
+    void drawTextOnPath(const void* text, size_t byteLength,
+                        const SkPath& path, const SkMatrix* matrix, const SkPaint& paint);
+    void drawPicture(SkPicture& picture);
+    void drawVertices(VertexMode vmode,
+                      int vertexCount, const SkPoint vertices[],
+                      const SkPoint texs[], const SkColor colors[],
+                      SkXfermode* xmode,
+                      const uint16_t indices[], int indexCount,
+                      const SkPaint& paint);
+
+    void willSave(SkCanvas::SaveFlags);
+    SaveLayerStrategy willSaveLayer(const SkRect*, const SkPaint*, SkCanvas::SaveFlags);
+    void willRestore();
+
+    void didConcat(const SkMatrix&);
+    void didSetMatrix(const SkMatrix&);
+
+    void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&);
+    void onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle);
+    void onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle);
+    void onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle);
+    void onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op);
+
+private:
+    template <typename T>
+    T* copy(const T*);
+
+    template <typename T>
+    T* copy(const T[], unsigned count);
+
+    SkRecord* fRecord;
+};
+
+#endif//SkRecorder_DEFINED
diff --git a/src/record/SkRecords.h b/src/record/SkRecords.h
new file mode 100644 (file)
index 0000000..67cfb20
--- /dev/null
@@ -0,0 +1,209 @@
+#ifndef SkRecords_DEFINED
+#define SkRecords_DEFINED
+
+#include "SkCanvas.h"
+
+namespace SkRecords {
+
+// A list of all the types of canvas calls we can record.
+// Each of these is reified into a struct below.
+//
+// (We're using the macro-of-macro trick here to do several different things with the same list.)
+//
+// We leave this SK_RECORD_TYPES macro defined for use by code that wants to operate on SkRecords
+// types polymorphically.  (See SkRecord::Record::{visit,mutate} for an example.)
+#define SK_RECORD_TYPES(M)  \
+    M(Restore)              \
+    M(Save)                 \
+    M(SaveLayer)            \
+    M(Concat)               \
+    M(SetMatrix)            \
+    M(ClipPath)             \
+    M(ClipRRect)            \
+    M(ClipRect)             \
+    M(ClipRegion)           \
+    M(Clear)                \
+    M(DrawBitmap)           \
+    M(DrawBitmapMatrix)     \
+    M(DrawBitmapNine)       \
+    M(DrawBitmapRectToRect) \
+    M(DrawDRRect)           \
+    M(DrawOval)             \
+    M(DrawPaint)            \
+    M(DrawPath)             \
+    M(DrawPoints)           \
+    M(DrawPosText)          \
+    M(DrawPosTextH)         \
+    M(DrawRRect)            \
+    M(DrawRect)             \
+    M(DrawSprite)           \
+    M(DrawText)             \
+    M(DrawTextOnPath)       \
+    M(DrawVertices)
+
+// Defines SkRecords::Type, an enum of all record types.
+#define ENUM(T) T##_Type,
+enum Type { SK_RECORD_TYPES(ENUM) };
+#undef ENUM
+
+// Macros to make it easier to define a record for a draw call with 0 args, 1 args, 2 args, etc.
+// These should be clearer when you look at their use below.
+#define RECORD0(T)                      \
+struct T {                              \
+    static const Type kType = T##_Type; \
+    T() {}                              \
+};
+
+// We try to be flexible about the types the constructors take.  Instead of requring the exact type
+// A here, we take any type Z which implicitly casts to A.  This allows the delay_copy() trick to
+// work, allowing the caller to decide whether to pass by value or by const&.
+
+#define RECORD1(T, A, a)                \
+struct T {                              \
+    static const Type kType = T##_Type; \
+    template <typename Z>               \
+    T(Z a) : a(a) {}                    \
+    A a;                                \
+};
+
+#define RECORD2(T, A, a, B, b)          \
+struct T {                              \
+    static const Type kType = T##_Type; \
+    template <typename Z, typename Y>   \
+    T(Z a, Y b) : a(a), b(b) {}         \
+    A a; B b;                           \
+};
+
+#define RECORD3(T, A, a, B, b, C, c)              \
+struct T {                                        \
+    static const Type kType = T##_Type;           \
+    template <typename Z, typename Y, typename X> \
+    T(Z a, Y b, X c) : a(a), b(b), c(c) {}        \
+    A a; B b; C c;                                \
+};
+
+#define RECORD4(T, A, a, B, b, C, c, D, d)                    \
+struct T {                                                    \
+    static const Type kType = T##_Type;                       \
+    template <typename Z, typename Y, typename X, typename W> \
+    T(Z a, Y b, X c, W d) : a(a), b(b), c(c), d(d) {}         \
+    A a; B b; C c; D d;                                       \
+};
+
+#define RECORD5(T, A, a, B, b, C, c, D, d, E, e)                          \
+struct T {                                                                \
+    static const Type kType = T##_Type;                                   \
+    template <typename Z, typename Y, typename X, typename W, typename V> \
+    T(Z a, Y b, X c, W d, V e) : a(a), b(b), c(c), d(d), e(e) {}          \
+    A a; B b; C c; D d; E e;                                              \
+};
+
+// Like SkBitmap, but deep copies pixels if they're not immutable.
+// Using this, we guarantee the immutability of all bitmaps we record.
+class ImmutableBitmap {
+public:
+    explicit ImmutableBitmap(const SkBitmap& bitmap) {
+        if (bitmap.isImmutable()) {
+            fBitmap = bitmap;
+        } else {
+            bitmap.copyTo(&fBitmap);
+        }
+        fBitmap.setImmutable();
+    }
+
+    operator const SkBitmap& () const { return fBitmap; }
+
+private:
+    SkBitmap fBitmap;
+};
+
+// Pointers here represent either an optional value or an array if accompanied by a count.
+// None of these records manages the lifetimes of pointers, except for DrawVertices handling its
+// Xfermode specially.
+
+RECORD0(Restore);
+RECORD1(Save, SkCanvas::SaveFlags, flags);
+RECORD3(SaveLayer, SkRect*, bounds, SkPaint*, paint, SkCanvas::SaveFlags, flags);
+
+RECORD1(Concat, SkMatrix, matrix);
+RECORD1(SetMatrix, SkMatrix, matrix);
+
+RECORD3(ClipPath, SkPath, path, SkRegion::Op, op, bool, doAA);
+RECORD3(ClipRRect, SkRRect, rrect, SkRegion::Op, op, bool, doAA);
+RECORD3(ClipRect, SkRect, rect, SkRegion::Op, op, bool, doAA);
+RECORD2(ClipRegion, SkRegion, region, SkRegion::Op, op);
+
+RECORD1(Clear, SkColor, color);
+RECORD4(DrawBitmap, ImmutableBitmap, bitmap, SkScalar, left, SkScalar, top, SkPaint*, paint);
+RECORD3(DrawBitmapMatrix, ImmutableBitmap, bitmap, SkMatrix, matrix, SkPaint*, paint);
+RECORD4(DrawBitmapNine, ImmutableBitmap, bitmap, SkIRect, center, SkRect, dst, SkPaint*, paint);
+RECORD5(DrawBitmapRectToRect, ImmutableBitmap, bitmap,
+                              SkRect*, src,
+                              SkRect, dst,
+                              SkPaint*, paint,
+                              SkCanvas::DrawBitmapRectFlags, flags);
+RECORD3(DrawDRRect, SkRRect, outer, SkRRect, inner, SkPaint, paint);
+RECORD2(DrawOval, SkRect, oval, SkPaint, paint);
+RECORD1(DrawPaint, SkPaint, paint);
+RECORD2(DrawPath, SkPath, path, SkPaint, paint);
+RECORD4(DrawPoints, SkCanvas::PointMode, mode, size_t, count, SkPoint*, pts, SkPaint, paint);
+RECORD4(DrawPosText, char*, text, size_t, byteLength, SkPoint*, pos, SkPaint, paint);
+RECORD5(DrawPosTextH, char*, text,
+                      size_t, byteLength,
+                      SkScalar*, xpos,
+                      SkScalar, y,
+                      SkPaint, paint);
+RECORD2(DrawRRect, SkRRect, rrect, SkPaint, paint);
+RECORD2(DrawRect, SkRect, rect, SkPaint, paint);
+RECORD4(DrawSprite, ImmutableBitmap, bitmap, int, left, int, top, SkPaint*, paint);
+RECORD5(DrawText, char*, text, size_t, byteLength, SkScalar, x, SkScalar, y, SkPaint, paint);
+RECORD5(DrawTextOnPath, char*, text,
+                        size_t, byteLength,
+                        SkPath, path,
+                        SkMatrix*, matrix,
+                        SkPaint, paint);
+
+// This guy is so ugly we just write it manually.
+struct DrawVertices {
+    static const Type kType = DrawVertices_Type;
+
+    DrawVertices(SkCanvas::VertexMode vmode,
+                 int vertexCount,
+                 SkPoint* vertices,
+                 SkPoint* texs,
+                 SkColor* colors,
+                 SkXfermode* xmode,
+                 uint16_t* indices,
+                 int indexCount,
+                 const SkPaint& paint)
+        : vmode(vmode)
+        , vertexCount(vertexCount)
+        , vertices(vertices)
+        , texs(texs)
+        , colors(colors)
+        , xmode(SkSafeRef(xmode))
+        , indices(indices)
+        , indexCount(indexCount)
+        , paint(paint) {}
+
+    SkCanvas::VertexMode vmode;
+    int vertexCount;
+    SkPoint* vertices;
+    SkPoint* texs;
+    SkColor* colors;
+    SkAutoTUnref<SkXfermode> xmode;
+    uint16_t* indices;
+    int indexCount;
+    SkPaint paint;
+};
+
+#undef RECORD0
+#undef RECORD1
+#undef RECORD2
+#undef RECORD3
+#undef RECORD4
+#undef RECORD5
+
+}  // namespace SkRecords
+
+#endif//SkRecords_DEFINED
index cc8cc0e..db0ea9b 100644 (file)
@@ -11,6 +11,7 @@
 #include "SkOSFile.h"
 #include "SkPicture.h"
 #include "SkQuadTreePicture.h"
+#include "SkRecorder.h"
 #include "SkStream.h"
 #include "SkString.h"
 #include "SkTileGridPicture.h"
@@ -30,6 +31,7 @@ DEFINE_bool(endRecording, true, "If false, don't time SkPicture::endRecording()"
 DEFINE_int32(nullSize, 1000, "Pretend dimension of null source picture.");
 DEFINE_int32(tileGridSize, 512, "Set the tile grid size. Has no effect if bbh is not set to tilegrid.");
 DEFINE_string(bbh, "", "Turn on the bbh and select the type, one of rtree, tilegrid, quadtree");
+DEFINE_bool(skr, false, "Record SKR instead of SKP.");
 
 typedef SkPicture* (*PictureFactory)(const int width, const int height, int* recordingFlags);
 
@@ -75,14 +77,22 @@ static void bench_record(SkPicture* src, const char* name, PictureFactory pictur
     const int height = src ? src->height() : FLAGS_nullSize;
 
     for (int i = 0; i < FLAGS_loops; i++) {
-        int recordingFlags = FLAGS_flags;
-        SkAutoTUnref<SkPicture> dst(pictureFactory(width, height, &recordingFlags));
-        SkCanvas* canvas = dst->beginRecording(width, height, recordingFlags);
-        if (NULL != src) {
-            src->draw(canvas);
-        }
-        if (FLAGS_endRecording) {
-            dst->endRecording();
+        if (FLAGS_skr) {
+            SkRecord record;
+            SkRecorder canvas(&record, width, height);
+            if (NULL != src) {
+                src->draw(&canvas);
+            }
+        } else {
+            int recordingFlags = FLAGS_flags;
+            SkAutoTUnref<SkPicture> dst(pictureFactory(width, height, &recordingFlags));
+            SkCanvas* canvas = dst->beginRecording(width, height, recordingFlags);
+            if (NULL != src) {
+                src->draw(canvas);
+            }
+            if (FLAGS_endRecording) {
+                dst->endRecording();
+            }
         }
     }