#include "DMCpuGMTask.h"
#include "DMExpectationsTask.h"
#include "DMPipeTask.h"
+#include "DMRecordTask.h"
#include "DMReplayTask.h"
#include "DMSerializeTask.h"
#include "DMTileGridTask.h"
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);
--- /dev/null
+#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
--- /dev/null
+#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
'../src/core',
'../src/effects',
'../src/pipe/utils/',
+ '../src/record',
'../src/utils',
'../src/utils/debugger',
'../tools',
'../dm/DMExpectationsTask.cpp',
'../dm/DMGpuGMTask.cpp',
'../dm/DMPipeTask.cpp',
+ '../dm/DMRecordTask.cpp',
'../dm/DMReplayTask.cpp',
'../dm/DMReporter.cpp',
'../dm/DMSerializeTask.cpp',
'flags.gyp:flags',
'jsoncpp.gyp:jsoncpp',
'gputest.gyp:skgputest',
+ 'record.gyp:*',
],
}]
}
--- /dev/null
+# 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',
+ ],
+ }]
+}
'../src/core/',
'../src/images',
'../src/lazy',
+ '../src/record',
],
'dependencies': [
'flags.gyp:flags',
'skia_lib.gyp:skia_lib',
+ 'record.gyp:*',
],
},
{
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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
--- /dev/null
+#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
#include "SkOSFile.h"
#include "SkPicture.h"
#include "SkQuadTreePicture.h"
+#include "SkRecorder.h"
#include "SkStream.h"
#include "SkString.h"
#include "SkTileGridPicture.h"
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);
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();
+ }
}
}