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
This commit is contained in:
commit-bot@chromium.org 2014-04-01 16:24:06 +00:00
parent d48ad8e333
commit e3ff558a4b
12 changed files with 875 additions and 8 deletions

View File

@ -1,6 +1,7 @@
#include "DMCpuGMTask.h" #include "DMCpuGMTask.h"
#include "DMExpectationsTask.h" #include "DMExpectationsTask.h"
#include "DMPipeTask.h" #include "DMPipeTask.h"
#include "DMRecordTask.h"
#include "DMReplayTask.h" #include "DMReplayTask.h"
#include "DMSerializeTask.h" #include "DMSerializeTask.h"
#include "DMTileGridTask.h" #include "DMTileGridTask.h"
@ -38,6 +39,7 @@ void CpuGMTask::draw() {
SPAWN(PipeTask, fGMFactory(NULL), bitmap, false, false); SPAWN(PipeTask, fGMFactory(NULL), bitmap, false, false);
SPAWN(PipeTask, fGMFactory(NULL), bitmap, true, false); SPAWN(PipeTask, fGMFactory(NULL), bitmap, true, false);
SPAWN(PipeTask, fGMFactory(NULL), bitmap, true, true); SPAWN(PipeTask, fGMFactory(NULL), bitmap, true, true);
SPAWN(RecordTask, fGMFactory(NULL), bitmap);
SPAWN(ReplayTask, fGMFactory(NULL), bitmap, false); SPAWN(ReplayTask, fGMFactory(NULL), bitmap, false);
SPAWN(ReplayTask, fGMFactory(NULL), bitmap, true); SPAWN(ReplayTask, fGMFactory(NULL), bitmap, true);
SPAWN(SerializeTask, fGMFactory(NULL), bitmap); SPAWN(SerializeTask, fGMFactory(NULL), bitmap);

42
dm/DMRecordTask.cpp Normal file
View File

@ -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

31
dm/DMRecordTask.h Normal file
View File

@ -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

View File

@ -16,6 +16,7 @@
'../src/core', '../src/core',
'../src/effects', '../src/effects',
'../src/pipe/utils/', '../src/pipe/utils/',
'../src/record',
'../src/utils', '../src/utils',
'../src/utils/debugger', '../src/utils/debugger',
'../tools', '../tools',
@ -33,6 +34,7 @@
'../dm/DMExpectationsTask.cpp', '../dm/DMExpectationsTask.cpp',
'../dm/DMGpuGMTask.cpp', '../dm/DMGpuGMTask.cpp',
'../dm/DMPipeTask.cpp', '../dm/DMPipeTask.cpp',
'../dm/DMRecordTask.cpp',
'../dm/DMReplayTask.cpp', '../dm/DMReplayTask.cpp',
'../dm/DMReporter.cpp', '../dm/DMReporter.cpp',
'../dm/DMSerializeTask.cpp', '../dm/DMSerializeTask.cpp',
@ -55,6 +57,7 @@
'flags.gyp:flags', 'flags.gyp:flags',
'jsoncpp.gyp:jsoncpp', 'jsoncpp.gyp:jsoncpp',
'gputest.gyp:skgputest', 'gputest.gyp:skgputest',
'record.gyp:*',
], ],
}] }]
} }

14
gyp/record.gyp Normal file
View File

@ -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',
],
}]
}

View File

@ -300,10 +300,12 @@
'../src/core/', '../src/core/',
'../src/images', '../src/images',
'../src/lazy', '../src/lazy',
'../src/record',
], ],
'dependencies': [ 'dependencies': [
'flags.gyp:flags', 'flags.gyp:flags',
'skia_lib.gyp:skia_lib', 'skia_lib.gyp:skia_lib',
'record.gyp:*',
], ],
}, },
{ {

198
src/record/SkRecord.h Normal file
View File

@ -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

62
src/record/SkRecordDraw.h Normal file
View File

@ -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

224
src/record/SkRecorder.cpp Normal file
View File

@ -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);
}

70
src/record/SkRecorder.h Normal file
View File

@ -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

209
src/record/SkRecords.h Normal file
View File

@ -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

View File

@ -11,6 +11,7 @@
#include "SkOSFile.h" #include "SkOSFile.h"
#include "SkPicture.h" #include "SkPicture.h"
#include "SkQuadTreePicture.h" #include "SkQuadTreePicture.h"
#include "SkRecorder.h"
#include "SkStream.h" #include "SkStream.h"
#include "SkString.h" #include "SkString.h"
#include "SkTileGridPicture.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(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_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_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); 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; const int height = src ? src->height() : FLAGS_nullSize;
for (int i = 0; i < FLAGS_loops; i++) { for (int i = 0; i < FLAGS_loops; i++) {
int recordingFlags = FLAGS_flags; if (FLAGS_skr) {
SkAutoTUnref<SkPicture> dst(pictureFactory(width, height, &recordingFlags)); SkRecord record;
SkCanvas* canvas = dst->beginRecording(width, height, recordingFlags); SkRecorder canvas(&record, width, height);
if (NULL != src) { if (NULL != src) {
src->draw(canvas); src->draw(&canvas);
} }
if (FLAGS_endRecording) { } else {
dst->endRecording(); 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();
}
} }
} }