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:
parent
d48ad8e333
commit
e3ff558a4b
@ -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);
|
||||
|
42
dm/DMRecordTask.cpp
Normal file
42
dm/DMRecordTask.cpp
Normal 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
31
dm/DMRecordTask.h
Normal 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
|
@ -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:*',
|
||||
],
|
||||
}]
|
||||
}
|
||||
|
14
gyp/record.gyp
Normal file
14
gyp/record.gyp
Normal 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',
|
||||
],
|
||||
}]
|
||||
}
|
@ -300,10 +300,12 @@
|
||||
'../src/core/',
|
||||
'../src/images',
|
||||
'../src/lazy',
|
||||
'../src/record',
|
||||
],
|
||||
'dependencies': [
|
||||
'flags.gyp:flags',
|
||||
'skia_lib.gyp:skia_lib',
|
||||
'record.gyp:*',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
198
src/record/SkRecord.h
Normal file
198
src/record/SkRecord.h
Normal 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
62
src/record/SkRecordDraw.h
Normal 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
224
src/record/SkRecorder.cpp
Normal 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
70
src/record/SkRecorder.h
Normal 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
209
src/record/SkRecords.h
Normal 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
|
@ -11,6 +11,7 @@
|
||||
#include "SkOSFile.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkQuadTreePicture.h"
|
||||
#include "SkRecorder.h"
|
||||
#include "SkStream.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTileGridPicture.h"
|
||||
@ -30,6 +31,7 @@ DEFINE_bool(endRecording, true, "If false, don't time SkPicture::endRecording()"
|
||||
DEFINE_int32(nullSize, 1000, "Pretend dimension of null source picture.");
|
||||
DEFINE_int32(tileGridSize, 512, "Set the tile grid size. Has no effect if bbh is not set to tilegrid.");
|
||||
DEFINE_string(bbh, "", "Turn on the bbh and select the type, one of rtree, tilegrid, quadtree");
|
||||
DEFINE_bool(skr, false, "Record SKR instead of SKP.");
|
||||
|
||||
typedef SkPicture* (*PictureFactory)(const int width, const int height, int* recordingFlags);
|
||||
|
||||
@ -75,14 +77,22 @@ static void bench_record(SkPicture* src, const char* name, PictureFactory pictur
|
||||
const int height = src ? src->height() : FLAGS_nullSize;
|
||||
|
||||
for (int i = 0; i < FLAGS_loops; i++) {
|
||||
int recordingFlags = FLAGS_flags;
|
||||
SkAutoTUnref<SkPicture> dst(pictureFactory(width, height, &recordingFlags));
|
||||
SkCanvas* canvas = dst->beginRecording(width, height, recordingFlags);
|
||||
if (NULL != src) {
|
||||
src->draw(canvas);
|
||||
}
|
||||
if (FLAGS_endRecording) {
|
||||
dst->endRecording();
|
||||
if (FLAGS_skr) {
|
||||
SkRecord record;
|
||||
SkRecorder canvas(&record, width, height);
|
||||
if (NULL != src) {
|
||||
src->draw(&canvas);
|
||||
}
|
||||
} else {
|
||||
int recordingFlags = FLAGS_flags;
|
||||
SkAutoTUnref<SkPicture> dst(pictureFactory(width, height, &recordingFlags));
|
||||
SkCanvas* canvas = dst->beginRecording(width, height, recordingFlags);
|
||||
if (NULL != src) {
|
||||
src->draw(canvas);
|
||||
}
|
||||
if (FLAGS_endRecording) {
|
||||
dst->endRecording();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user