From e3ff558a4baf4cb924e7513a81c8073ddae385fc Mon Sep 17 00:00:00 2001 From: "commit-bot@chromium.org" Date: Tue, 1 Apr 2014 16:24:06 +0000 Subject: [PATCH] SkRecord strawman 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 --- dm/DMCpuGMTask.cpp | 2 + dm/DMRecordTask.cpp | 42 +++++++ dm/DMRecordTask.h | 31 ++++++ gyp/dm.gyp | 3 + gyp/record.gyp | 14 +++ gyp/tools.gyp | 2 + src/record/SkRecord.h | 198 +++++++++++++++++++++++++++++++++ src/record/SkRecordDraw.h | 62 +++++++++++ src/record/SkRecorder.cpp | 224 ++++++++++++++++++++++++++++++++++++++ src/record/SkRecorder.h | 70 ++++++++++++ src/record/SkRecords.h | 209 +++++++++++++++++++++++++++++++++++ tools/bench_record.cpp | 26 +++-- 12 files changed, 875 insertions(+), 8 deletions(-) create mode 100644 dm/DMRecordTask.cpp create mode 100644 dm/DMRecordTask.h create mode 100644 gyp/record.gyp create mode 100644 src/record/SkRecord.h create mode 100644 src/record/SkRecordDraw.h create mode 100644 src/record/SkRecorder.cpp create mode 100644 src/record/SkRecorder.h create mode 100644 src/record/SkRecords.h diff --git a/dm/DMCpuGMTask.cpp b/dm/DMCpuGMTask.cpp index 6ab0014fd5..7ab1d44ee5 100644 --- a/dm/DMCpuGMTask.cpp +++ b/dm/DMCpuGMTask.cpp @@ -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 index 0000000000..0109a41d8f --- /dev/null +++ b/dm/DMRecordTask.cpp @@ -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 index 0000000000..7efd951e0f --- /dev/null +++ b/dm/DMRecordTask.h @@ -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 fGM; + const SkBitmap fReference; +}; + +} // namespace DM + +#endif // DMRecordTask_DEFINED diff --git a/gyp/dm.gyp b/gyp/dm.gyp index c96281aef6..6bce65afb3 100644 --- a/gyp/dm.gyp +++ b/gyp/dm.gyp @@ -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 index 0000000000..2e4a560ec7 --- /dev/null +++ b/gyp/record.gyp @@ -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', + ], + }] +} diff --git a/gyp/tools.gyp b/gyp/tools.gyp index fe84a97813..461cc6b64b 100644 --- a/gyp/tools.gyp +++ b/gyp/tools.gyp @@ -300,10 +300,12 @@ '../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 index 0000000000..4013874677 --- /dev/null +++ b/src/record/SkRecord.h @@ -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 + // 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 + 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 + // 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 + 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 + 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 + T* append() { + if (fCount == fReserved) { + fReserved = SkTMax(kFirstReserveCount, fReserved*2); + fRecords.realloc(fReserved); + fTypes.realloc(fReserved); + } + + fTypes[fCount] = T::kType; + return fRecords[fCount++].alloc(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 + 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 + T* alloc(SkRecord* record) { + if (IsLarge()) { + fRecord = (uintptr_t)record->alloc(); + } + return this->ptr(); + } + + // Visit this record with functor F (see public API above) assuming the record we're + // pointing to has this type. + template + void visit(Type8 type, F f) const { + #define CASE(T) case SkRecords::T##_Type: return f(*this->ptr()); + 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 + void mutate(Type8 type, F f) { + #define CASE(T) case SkRecords::T##_Type: return f(this->ptr()); + switch(type) { SK_RECORD_TYPES(CASE) } + #undef CASE + } + + private: + template + T* ptr() const { return (T*)(IsLarge() ? (void*)fRecord : &fRecord); } + + // Is T too big to fit directly into a uintptr_t, neededing external allocation? + template + 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 fRecords; + SkAutoTMalloc 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 index 0000000000..744dfc5b33 --- /dev/null +++ b/src/record/SkRecordDraw.h @@ -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 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 index 0000000000..1edcc52239 --- /dev/null +++ b/src/record/SkRecorder.cpp @@ -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, (__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 +class Reference { +public: + Reference(const T& x) : fX(x) {} + operator const T&() const { return fX; } +private: + const T& fX; +}; + +template +static Reference delay_copy(const T& x) { return Reference(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 +T* SkRecorder::copy(const T* src) { + if (NULL == src) { + return NULL; + } + return SkNEW_PLACEMENT_ARGS(fRecord->alloc(), T, (*src)); +} + +// This copy() is for arrays. +// It will work with POD or non-POD, though currently we only use it for POD. +template +T* SkRecorder::copy(const T src[], unsigned count) { + if (NULL == src) { + return NULL; + } + T* dst = fRecord->alloc(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(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 index 0000000000..9d722b4b2b --- /dev/null +++ b/src/record/SkRecorder.h @@ -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 + T* copy(const T*); + + template + 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 index 0000000000..67cfb20c4c --- /dev/null +++ b/src/record/SkRecords.h @@ -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 \ + T(Z a) : a(a) {} \ + A a; \ +}; + +#define RECORD2(T, A, a, B, b) \ +struct T { \ + static const Type kType = T##_Type; \ + template \ + 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 \ + 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 \ + 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 \ + 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 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 diff --git a/tools/bench_record.cpp b/tools/bench_record.cpp index cc8cc0e39d..db0ea9b71f 100644 --- a/tools/bench_record.cpp +++ b/tools/bench_record.cpp @@ -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 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 dst(pictureFactory(width, height, &recordingFlags)); + SkCanvas* canvas = dst->beginRecording(width, height, recordingFlags); + if (NULL != src) { + src->draw(canvas); + } + if (FLAGS_endRecording) { + dst->endRecording(); + } } }