diff options
author | mtklein <mtklein@chromium.org> | 2014-06-17 12:08:15 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-06-17 12:08:16 -0700 |
commit | 887f3979f0c717e69a8b7d169169bc27eb46d3b5 (patch) | |
tree | 01f767e11b31e57df3a4f8a9de033e677731c1ff /src/core | |
parent | 24ad0965404c0bc978f27db27e2f00ebac95239c (diff) |
Add EXPERIMENTAL_beginRecording() for SkRecord-based recording.
The interesting stuff is in SkPictureRecorder.{h,cpp}. The rest is mostly moving SkRecord from its own directories into core to avoid circular dependencies in GYP.
After plumbing SkRecord all the way through in Picture, I'll delete its old entry point include/record/SkRecording.h. For now it and record.gypi need to stay where they are to keep Chrome building.
BUG=skia:
R=reed@google.com, mtklein@google.com
Author: mtklein@chromium.org
Review URL: https://codereview.chromium.org/331573004
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/SkPictureRecorder.cpp | 82 | ||||
-rw-r--r-- | src/core/SkRecord.h | 234 | ||||
-rw-r--r-- | src/core/SkRecordDraw.cpp | 71 | ||||
-rw-r--r-- | src/core/SkRecordDraw.h | 55 | ||||
-rw-r--r-- | src/core/SkRecordOpts.cpp | 303 | ||||
-rw-r--r-- | src/core/SkRecordOpts.h | 35 | ||||
-rw-r--r-- | src/core/SkRecordPattern.h | 183 | ||||
-rw-r--r-- | src/core/SkRecorder.cpp | 263 | ||||
-rw-r--r-- | src/core/SkRecorder.h | 112 | ||||
-rw-r--r-- | src/core/SkRecording.cpp | 44 | ||||
-rw-r--r-- | src/core/SkRecords.h | 291 |
11 files changed, 1647 insertions, 26 deletions
diff --git a/src/core/SkPictureRecorder.cpp b/src/core/SkPictureRecorder.cpp index 3abd08e1b0..f1423e3992 100644 --- a/src/core/SkPictureRecorder.cpp +++ b/src/core/SkPictureRecorder.cpp @@ -9,17 +9,26 @@ #include "SkPicturePlayback.h" #include "SkPictureRecord.h" #include "SkPictureRecorder.h" -#include "SkTypes.h" +#include "SkRecord.h" +#include "SkRecordDraw.h" +#include "SkRecorder.h" +#include "SkTypes.h" SkPictureRecorder::~SkPictureRecorder() { - SkSafeSetNull(fCanvas); + this->reset(); +} + +void SkPictureRecorder::reset() { + SkSafeSetNull(fPictureRecord); + SkSafeSetNull(fRecorder); + SkDELETE(fRecord); + fRecord = NULL; } SkCanvas* SkPictureRecorder::beginRecording(int width, int height, SkBBHFactory* bbhFactory /* = NULL */, uint32_t recordFlags /* = 0 */) { - SkSafeSetNull(fCanvas); // terminate any prior recording(s) - + this->reset(); // terminate any prior recording(s) fWidth = width; fHeight = height; @@ -28,49 +37,70 @@ SkCanvas* SkPictureRecorder::beginRecording(int width, int height, if (NULL != bbhFactory) { SkAutoTUnref<SkBBoxHierarchy> tree((*bbhFactory)(width, height)); SkASSERT(NULL != tree); - fCanvas = SkNEW_ARGS(SkBBoxHierarchyRecord, (size, recordFlags, tree.get())); + fPictureRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (size, recordFlags, tree.get())); } else { - fCanvas = SkNEW_ARGS(SkPictureRecord, (size, recordFlags)); + fPictureRecord = SkNEW_ARGS(SkPictureRecord, (size, recordFlags)); } - fCanvas->beginRecording(); + fPictureRecord->beginRecording(); + return this->getRecordingCanvas(); +} + +SkCanvas* SkPictureRecorder::EXPERIMENTAL_beginRecording(int width, int height, + SkBBHFactory* bbhFactory /* = NULL */) { + this->reset(); + fWidth = width; + fHeight = height; - return fCanvas; + // TODO: plumb bbhFactory through + fRecord = SkNEW(SkRecord); + fRecorder = SkNEW_ARGS(SkRecorder, (fRecord, width, height)); + return this->getRecordingCanvas(); } SkCanvas* SkPictureRecorder::getRecordingCanvas() { - return fCanvas; + if (NULL != fRecorder) { + return fRecorder; + } + return fPictureRecord; } SkPicture* SkPictureRecorder::endRecording() { - if (NULL == fCanvas) { - return NULL; - } + SkPicture* picture = NULL; - fCanvas->endRecording(); + if (NULL != fRecorder) { + // TODO: picture = SkNEW_ARGS(SkPicture, (fWidth, fHeight, fRecord)); + // fRecord = NULL; + } - const bool deepCopyOps = false; - SkAutoTUnref<SkPicture> picture(SkNEW_ARGS(SkPicture, (fWidth, fHeight, - *fCanvas, deepCopyOps))); - SkSafeSetNull(fCanvas); + if (NULL != fPictureRecord) { + fPictureRecord->endRecording(); + const bool deepCopyOps = false; + picture = SkNEW_ARGS(SkPicture, (fWidth, fHeight, *fPictureRecord, deepCopyOps)); + } - return picture.detach(); + this->reset(); + return picture; } void SkPictureRecorder::internalOnly_EnableOpts(bool enableOpts) { - if (NULL != fCanvas) { - fCanvas->internalOnly_EnableOpts(enableOpts); + if (NULL != fPictureRecord) { + fPictureRecord->internalOnly_EnableOpts(enableOpts); } } void SkPictureRecorder::partialReplay(SkCanvas* canvas) const { - if (NULL == fCanvas || NULL == canvas) { - // Not recording or nothing to replay into + if (NULL == canvas) { return; } - const bool deepCopyOps = true; - SkAutoTUnref<SkPicture> picture(SkNEW_ARGS(SkPicture, (fWidth, fHeight, - *fCanvas, deepCopyOps))); - picture->draw(canvas); + if (NULL != fRecorder) { + SkRecordDraw(*fRecord, canvas); + } + + if (NULL != fPictureRecord) { + const bool deepCopyOps = true; + SkPicture picture(fWidth, fHeight, *fPictureRecord, deepCopyOps); + picture.draw(canvas); + } } diff --git a/src/core/SkRecord.h b/src/core/SkRecord.h new file mode 100644 index 0000000000..6c5177eefb --- /dev/null +++ b/src/core/SkRecord.h @@ -0,0 +1,234 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecord_DEFINED +#define SkRecord_DEFINED + +#include "SkChunkAlloc.h" +#include "SkRecords.h" +#include "SkTLogic.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() { + Destroyer destroyer; + for (unsigned i = 0; i < this->count(); i++) { + this->mutate<void>(i, destroyer); + } + } + + // Returns the number of canvas commands in this SkRecord. + unsigned count() const { return fCount; } + + // Visit the i-th canvas command with a functor matching this interface: + // template <typename T> + // R operator()(const T& record) { ... } + // This operator() must be defined for at least all SkRecords::*. + template <typename R, typename F> + R visit(unsigned i, F& f) const { + SkASSERT(i < this->count()); + return fRecords[i].visit<R>(fTypes[i], f); + } + + // Mutate the i-th canvas command with a functor matching this interface: + // template <typename T> + // R operator()(T* record) { ... } + // This operator() must be defined for at least all SkRecords::*. + template <typename R, typename F> + R mutate(unsigned i, F& f) { + SkASSERT(i < this->count()); + return fRecords[i].mutate<R>(fTypes[i], f); + } + // TODO: It'd be nice to infer R from F for visit and mutate if we ever get std::result_of. + + // Allocate contiguous space for count Ts, to be freed when the SkRecord is destroyed. + // Here T can be any class, not just those from SkRecords. Throws on failure. + template <typename T> + T* alloc(unsigned count = 1) { + return (T*)fAlloc.allocThrow(sizeof(T) * count); + } + + // Add a new command of type T to 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++].set(this->allocCommand<T>()); + } + + // Replace the i-th command with a new command of type T. + // You are expected to placement new an object of type T onto this pointer. + // References to the original command are invalidated. + template <typename T> + T* replace(unsigned i) { + SkASSERT(i < this->count()); + + Destroyer destroyer; + this->mutate<void>(i, destroyer); + + fTypes[i] = T::kType; + return fRecords[i].set(this->allocCommand<T>()); + } + + // Replace the i-th command with a new command of type T. + // You are expected to placement new an object of type T onto this pointer. + // You must show proof that you've already adopted the existing command. + template <typename T, typename Existing> + T* replace(unsigned i, const SkRecords::Adopted<Existing>& proofOfAdoption) { + SkASSERT(i < this->count()); + + SkASSERT(Existing::kType == fTypes[i]); + SkASSERT(proofOfAdoption == fRecords[i].ptr<Existing>()); + + fTypes[i] = T::kType; + return fRecords[i].set(this->allocCommand<T>()); + } + +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. + // + // The cost to append a T into this structure is 1 + sizeof(void*) + sizeof(T). + + // A mutator that can be used with replace to destroy canvas commands. + 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; + }; + + // No point in allocating any more than one of an empty struct. + // We could just return NULL but it's sort of confusing to return NULL on success. + template <typename T> + SK_WHEN(SkTIsEmpty<T>, T*) allocCommand() { + static T singleton = {}; + return &singleton; + } + + template <typename T> + SK_WHEN(!SkTIsEmpty<T>, T*) allocCommand() { return this->alloc<T>(); } + + // An untyped pointer to some bytes in fAlloc. This is the interface for polymorphic dispatch: + // visit() and mutate() work with the parallel fTypes array to do the work of a vtable. + struct Record { + public: + // Point this record to its data in fAlloc. Returns ptr for convenience. + template <typename T> + T* set(T* ptr) { + fPtr = ptr; + return ptr; + } + + // Get the data in fAlloc, assuming it's of type T. + template <typename T> + T* ptr() const { return (T*)fPtr; } + + // Visit this record with functor F (see public API above) assuming the record we're + // pointing to has this type. + template <typename R, typename F> + R 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 + SkDEBUGFAIL("Unreachable"); + return R(); + } + + // Mutate this record with functor F (see public API above) assuming the record we're + // pointing to has this type. + template <typename R, typename F> + R 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 + SkDEBUGFAIL("Unreachable"); + return R(); + } + + private: + void* fPtr; + }; + + // fAlloc needs to be a data structure which can append variable length data in contiguous + // chunks, returning a stable handle to that data for later retrieval. + // + // fRecords and fTypes need to be data structures that can append fixed length data, and need to + // support efficient forward iteration. (They don't need to be contiguous or indexable.) + + SkChunkAlloc fAlloc; + SkAutoTMalloc<Record> fRecords; + SkAutoTMalloc<Type8> fTypes; + // fCount and fReserved measure both fRecords and fTypes, which always grow in lock step. + unsigned fCount; + unsigned fReserved; + const unsigned kFirstReserveCount; +}; + +#endif//SkRecord_DEFINED diff --git a/src/core/SkRecordDraw.cpp b/src/core/SkRecordDraw.cpp new file mode 100644 index 0000000000..2bf7076bb0 --- /dev/null +++ b/src/core/SkRecordDraw.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkRecordDraw.h" + +void SkRecordDraw(const SkRecord& record, SkCanvas* canvas) { + for (SkRecords::Draw draw(canvas); draw.index() < record.count(); draw.next()) { + record.visit<void>(draw.index(), draw); + } +} + +namespace SkRecords { + +bool Draw::skip(const PairedPushCull& r) { + if (fCanvas->quickReject(r.base->rect)) { + fIndex += r.skip; + return true; + } + return false; +} + +bool Draw::skip(const BoundedDrawPosTextH& r) { + return fCanvas->quickRejectY(r.minY, r.maxY); +} + +// NoOps draw nothing. +template <> void Draw::draw(const NoOp&) {} + +#define DRAW(T, call) template <> void Draw::draw(const T& r) { fCanvas->call; } +DRAW(Restore, restore()); +DRAW(Save, save(r.flags)); +DRAW(SaveLayer, saveLayer(r.bounds, r.paint, r.flags)); +DRAW(PopCull, popCull()); +DRAW(PushCull, pushCull(r.rect)); +DRAW(Clear, clear(r.color)); +DRAW(Concat, concat(r.matrix)); +DRAW(SetMatrix, setMatrix(SkMatrix::Concat(fInitialCTM, r.matrix))); + +DRAW(ClipPath, clipPath(r.path, r.op, r.doAA)); +DRAW(ClipRRect, clipRRect(r.rrect, r.op, r.doAA)); +DRAW(ClipRect, clipRect(r.rect, r.op, r.doAA)); +DRAW(ClipRegion, clipRegion(r.region, r.op)); + +DRAW(DrawBitmap, drawBitmap(r.bitmap, r.left, r.top, r.paint)); +DRAW(DrawBitmapMatrix, drawBitmapMatrix(r.bitmap, r.matrix, r.paint)); +DRAW(DrawBitmapNine, drawBitmapNine(r.bitmap, r.center, r.dst, r.paint)); +DRAW(DrawBitmapRectToRect, drawBitmapRectToRect(r.bitmap, r.src, r.dst, r.paint, r.flags)); +DRAW(DrawDRRect, drawDRRect(r.outer, r.inner, r.paint)); +DRAW(DrawOval, drawOval(r.oval, r.paint)); +DRAW(DrawPaint, drawPaint(r.paint)); +DRAW(DrawPath, drawPath(r.path, r.paint)); +DRAW(DrawPoints, drawPoints(r.mode, r.count, r.pts, r.paint)); +DRAW(DrawPosText, drawPosText(r.text, r.byteLength, r.pos, r.paint)); +DRAW(DrawPosTextH, drawPosTextH(r.text, r.byteLength, r.xpos, r.y, r.paint)); +DRAW(DrawRRect, drawRRect(r.rrect, r.paint)); +DRAW(DrawRect, drawRect(r.rect, r.paint)); +DRAW(DrawSprite, drawSprite(r.bitmap, r.left, r.top, r.paint)); +DRAW(DrawText, drawText(r.text, r.byteLength, r.x, r.y, r.paint)); +DRAW(DrawTextOnPath, drawTextOnPath(r.text, r.byteLength, r.path, r.matrix, r.paint)); +DRAW(DrawVertices, drawVertices(r.vmode, r.vertexCount, r.vertices, r.texs, r.colors, + r.xmode.get(), r.indices, r.indexCount, r.paint)); +#undef DRAW + +template <> void Draw::draw(const PairedPushCull& r) { this->draw(*r.base); } +template <> void Draw::draw(const BoundedDrawPosTextH& r) { this->draw(*r.base); } + +} // namespace SkRecords diff --git a/src/core/SkRecordDraw.h b/src/core/SkRecordDraw.h new file mode 100644 index 0000000000..359679a6d7 --- /dev/null +++ b/src/core/SkRecordDraw.h @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecordDraw_DEFINED +#define SkRecordDraw_DEFINED + +#include "SkRecord.h" +#include "SkCanvas.h" + +// Draw an SkRecord into an SkCanvas. A convenience wrapper around SkRecords::Draw. +void SkRecordDraw(const SkRecord&, SkCanvas*); + +namespace SkRecords { + +// This is an SkRecord visitor that will draw that SkRecord to an SkCanvas. +class Draw : SkNoncopyable { +public: + explicit Draw(SkCanvas* canvas) + : fInitialCTM(canvas->getTotalMatrix()), fCanvas(canvas), fIndex(0) {} + + unsigned index() const { return fIndex; } + void next() { ++fIndex; } + + template <typename T> void operator()(const T& r) { + if (!this->skip(r)) { + this->draw(r); + } + } + +private: + // No base case, so we'll be compile-time checked that we implement all possibilities. + template <typename T> void draw(const T&); + + // skip() should return true if we can skip this command, false if not. + // It may update fIndex directly to skip more than just this one command. + + // Mostly we just blindly call fCanvas and let it handle quick rejects itself. + template <typename T> bool skip(const T&) { return false; } + + // We add our own quick rejects for commands added by optimizations. + bool skip(const PairedPushCull&); + bool skip(const BoundedDrawPosTextH&); + + const SkMatrix fInitialCTM; + SkCanvas* fCanvas; + unsigned fIndex; +}; + +} // namespace SkRecords + +#endif//SkRecordDraw_DEFINED diff --git a/src/core/SkRecordOpts.cpp b/src/core/SkRecordOpts.cpp new file mode 100644 index 0000000000..75f7c62605 --- /dev/null +++ b/src/core/SkRecordOpts.cpp @@ -0,0 +1,303 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkRecordOpts.h" + +#include "SkRecordPattern.h" +#include "SkRecords.h" +#include "SkTDArray.h" + +using namespace SkRecords; + +void SkRecordOptimize(SkRecord* record) { + // TODO(mtklein): fuse independent optimizations to reduce number of passes? + SkRecordNoopCulls(record); + SkRecordNoopSaveRestores(record); + // TODO(mtklein): figure out why we draw differently and reenable + //SkRecordNoopSaveLayerDrawRestores(record); + + SkRecordAnnotateCullingPairs(record); + SkRecordReduceDrawPosTextStrength(record); // Helpful to run this before BoundDrawPosTextH. + SkRecordBoundDrawPosTextH(record); +} + +// Most of the optimizations in this file are pattern-based. These are all defined as structs with: +// - a Pattern typedef +// - a bool onMatch(SkRceord*, Pattern*, unsigned begin, unsigned end) method, +// which returns true if it made changes and false if not. + +// Run a pattern-based optimization once across the SkRecord, returning true if it made any changes. +// It looks for spans which match Pass::Pattern, and when found calls onMatch() with the pattern, +// record, and [begin,end) span of the commands that matched. +template <typename Pass> +static bool apply(Pass* pass, SkRecord* record) { + typename Pass::Pattern pattern; + bool changed = false; + unsigned begin, end = 0; + + while (pattern.search(record, &begin, &end)) { + changed |= pass->onMatch(record, &pattern, begin, end); + } + return changed; +} + +struct CullNooper { + typedef Pattern3<Is<PushCull>, Star<Is<NoOp> >, Is<PopCull> > Pattern; + + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + record->replace<NoOp>(begin); // PushCull + record->replace<NoOp>(end-1); // PopCull + return true; + } +}; + +void SkRecordNoopCulls(SkRecord* record) { + CullNooper pass; + while (apply(&pass, record)); +} + +// Turns the logical NoOp Save and Restore in Save-Draw*-Restore patterns into actual NoOps. +struct SaveOnlyDrawsRestoreNooper { + typedef Pattern3<Is<Save>, + Star<Or<Is<NoOp>, IsDraw> >, + Is<Restore> > + Pattern; + + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + record->replace<NoOp>(begin); // Save + record->replace<NoOp>(end-1); // Restore + return true; + } +}; +// Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops. +struct SaveNoDrawsRestoreNooper { + // Star matches greedily, so we also have to exclude Save and Restore. + typedef Pattern3<Is<Save>, + Star<Not<Or3<Is<Save>, + Is<Restore>, + IsDraw> > >, + Is<Restore> > + Pattern; + + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + // If restore doesn't revert both matrix and clip, this isn't safe to noop away. + if (pattern->first<Save>()->flags != SkCanvas::kMatrixClip_SaveFlag) { + return false; + } + + // The entire span between Save and Restore (inclusively) does nothing. + for (unsigned i = begin; i < end; i++) { + record->replace<NoOp>(i); + } + return true; + } +}; +void SkRecordNoopSaveRestores(SkRecord* record) { + SaveOnlyDrawsRestoreNooper onlyDraws; + SaveNoDrawsRestoreNooper noDraws; + + // Run until they stop changing things. + while (apply(&onlyDraws, record) || apply(&noDraws, record)); +} + +// For some SaveLayer-[drawing command]-Restore patterns, merge the SaveLayer's alpha into the +// draw, and no-op the SaveLayer and Restore. +struct SaveLayerDrawRestoreNooper { + typedef Pattern3<Is<SaveLayer>, IsDraw, Is<Restore> > Pattern; + + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + SaveLayer* saveLayer = pattern->first<SaveLayer>(); + if (saveLayer->bounds != NULL) { + // SaveLayer with bounds is too tricky for us. + return false; + } + + SkPaint* layerPaint = saveLayer->paint; + if (NULL == layerPaint) { + // There wasn't really any point to this SaveLayer at all. + return KillSaveLayerAndRestore(record, begin); + } + + SkPaint* drawPaint = pattern->second<SkPaint>(); + if (drawPaint == NULL) { + // We can just give the draw the SaveLayer's paint. + // TODO(mtklein): figure out how to do this clearly + return false; + } + + const uint32_t layerColor = layerPaint->getColor(); + const uint32_t drawColor = drawPaint->getColor(); + if (!IsOnlyAlpha(layerColor) || !IsOpaque(drawColor) || + HasAnyEffect(*layerPaint) || HasAnyEffect(*drawPaint)) { + // Too fancy for us. Actually, as long as layerColor is just an alpha + // we can blend it into drawColor's alpha; drawColor doesn't strictly have to be opaque. + return false; + } + + drawPaint->setColor(SkColorSetA(drawColor, SkColorGetA(layerColor))); + return KillSaveLayerAndRestore(record, begin); + } + + static bool KillSaveLayerAndRestore(SkRecord* record, unsigned saveLayerIndex) { + record->replace<NoOp>(saveLayerIndex); // SaveLayer + record->replace<NoOp>(saveLayerIndex+2); // Restore + return true; + } + + static bool HasAnyEffect(const SkPaint& paint) { + return paint.getPathEffect() || + paint.getShader() || + paint.getXfermode() || + paint.getMaskFilter() || + paint.getColorFilter() || + paint.getRasterizer() || + paint.getLooper() || + paint.getImageFilter(); + } + + static bool IsOpaque(SkColor color) { + return SkColorGetA(color) == SK_AlphaOPAQUE; + } + static bool IsOnlyAlpha(SkColor color) { + return SK_ColorTRANSPARENT == SkColorSetA(color, SK_AlphaTRANSPARENT); + } +}; +void SkRecordNoopSaveLayerDrawRestores(SkRecord* record) { + SaveLayerDrawRestoreNooper pass; + apply(&pass, record); +} + + +// Replaces DrawPosText with DrawPosTextH when all Y coordinates are equal. +struct StrengthReducer { + typedef Pattern1<Is<DrawPosText> > Pattern; + + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + SkASSERT(end == begin + 1); + DrawPosText* draw = pattern->first<DrawPosText>(); + + const unsigned points = draw->paint.countText(draw->text, draw->byteLength); + if (points == 0) { + return false; // No point (ha!). + } + + const SkScalar firstY = draw->pos[0].fY; + for (unsigned i = 1; i < points; i++) { + if (draw->pos[i].fY != firstY) { + return false; // Needs full power of DrawPosText. + } + } + // All ys are the same. We can replace DrawPosText with DrawPosTextH. + + // draw->pos is points SkPoints, [(x,y),(x,y),(x,y),(x,y), ... ]. + // We're going to squint and look at that as 2*points SkScalars, [x,y,x,y,x,y,x,y, ...]. + // Then we'll rearrange things so all the xs are in order up front, clobbering the ys. + SK_COMPILE_ASSERT(sizeof(SkPoint) == 2 * sizeof(SkScalar), SquintingIsNotSafe); + SkScalar* scalars = &draw->pos[0].fX; + for (unsigned i = 0; i < 2*points; i += 2) { + scalars[i/2] = scalars[i]; + } + + // Extend lifetime of draw to the end of the loop so we can copy its paint. + Adopted<DrawPosText> adopted(draw); + SkNEW_PLACEMENT_ARGS(record->replace<DrawPosTextH>(begin, adopted), + DrawPosTextH, + (draw->paint, draw->text, draw->byteLength, scalars, firstY)); + return true; + } +}; +void SkRecordReduceDrawPosTextStrength(SkRecord* record) { + StrengthReducer pass; + apply(&pass, record); +} + +// Tries to replace DrawPosTextH with BoundedDrawPosTextH, which knows conservative upper and lower +// bounds to use with SkCanvas::quickRejectY. +struct TextBounder { + typedef Pattern1<Is<DrawPosTextH> > Pattern; + + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + SkASSERT(end == begin + 1); + DrawPosTextH* draw = pattern->first<DrawPosTextH>(); + + // If we're drawing vertical text, none of the checks we're about to do make any sense. + // We'll need to call SkPaint::computeFastBounds() later, so bail if that's not possible. + if (draw->paint.isVerticalText() || !draw->paint.canComputeFastBounds()) { + return false; + } + + // Rather than checking the top and bottom font metrics, we guess. Actually looking up the + // top and bottom metrics is slow, and this overapproximation should be good enough. + const SkScalar buffer = draw->paint.getTextSize() * 1.5f; + SkDEBUGCODE(SkPaint::FontMetrics metrics;) + SkDEBUGCODE(draw->paint.getFontMetrics(&metrics);) + SkASSERT(-buffer <= metrics.fTop); + SkASSERT(+buffer >= metrics.fBottom); + + // Let the paint adjust the text bounds. We don't care about left and right here, so we use + // 0 and 1 respectively just so the bounds rectangle isn't empty. + SkRect bounds; + bounds.set(0, draw->y - buffer, SK_Scalar1, draw->y + buffer); + SkRect adjusted = draw->paint.computeFastBounds(bounds, &bounds); + + Adopted<DrawPosTextH> adopted(draw); + SkNEW_PLACEMENT_ARGS(record->replace<BoundedDrawPosTextH>(begin, adopted), + BoundedDrawPosTextH, + (&adopted, adjusted.fTop, adjusted.fBottom)); + return true; + } +}; +void SkRecordBoundDrawPosTextH(SkRecord* record) { + TextBounder pass; + apply(&pass, record); +} + +// Replaces PushCull with PairedPushCull, which lets us skip to the paired PopCull when the canvas +// can quickReject the cull rect. +// There's no efficient way (yet?) to express this one as a pattern, so we write a custom pass. +class CullAnnotator { +public: + // Do nothing to most ops. + template <typename T> void operator()(T*) {} + + void operator()(PushCull* push) { + Pair pair = { fIndex, push }; + fPushStack.push(pair); + } + + void operator()(PopCull* pop) { + Pair push = fPushStack.top(); + fPushStack.pop(); + + SkASSERT(fIndex > push.index); + unsigned skip = fIndex - push.index; + + Adopted<PushCull> adopted(push.command); + SkNEW_PLACEMENT_ARGS(fRecord->replace<PairedPushCull>(push.index, adopted), + PairedPushCull, (&adopted, skip)); + } + + void apply(SkRecord* record) { + for (fRecord = record, fIndex = 0; fIndex < record->count(); fIndex++) { + fRecord->mutate<void>(fIndex, *this); + } + } + +private: + struct Pair { + unsigned index; + PushCull* command; + }; + + SkTDArray<Pair> fPushStack; + SkRecord* fRecord; + unsigned fIndex; +}; +void SkRecordAnnotateCullingPairs(SkRecord* record) { + CullAnnotator pass; + pass.apply(record); +} diff --git a/src/core/SkRecordOpts.h b/src/core/SkRecordOpts.h new file mode 100644 index 0000000000..b535ec958a --- /dev/null +++ b/src/core/SkRecordOpts.h @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRecordOpts_DEFINED +#define SkRecordOpts_DEFINED + +#include "SkRecord.h" + +// Run all optimizations in recommended order. +void SkRecordOptimize(SkRecord*); + +// NoOp away pointless PushCull/PopCull pairs with nothing between them. +void SkRecordNoopCulls(SkRecord*); + +// Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops. +void SkRecordNoopSaveRestores(SkRecord*); + +// For some SaveLayer-[drawing command]-Restore patterns, merge the SaveLayer's alpha into the +// draw, and no-op the SaveLayer and Restore. +void SkRecordNoopSaveLayerDrawRestores(SkRecord*); + +// Annotates PushCull commands with the relative offset of their paired PopCull. +void SkRecordAnnotateCullingPairs(SkRecord*); + +// Convert DrawPosText to DrawPosTextH when all the Y coordinates are equal. +void SkRecordReduceDrawPosTextStrength(SkRecord*); + +// Calculate min and max Y bounds for DrawPosTextH commands, for use with SkCanvas::quickRejectY. +void SkRecordBoundDrawPosTextH(SkRecord*); + +#endif//SkRecordOpts_DEFINED diff --git a/src/core/SkRecordPattern.h b/src/core/SkRecordPattern.h new file mode 100644 index 0000000000..57779ffd76 --- /dev/null +++ b/src/core/SkRecordPattern.h @@ -0,0 +1,183 @@ +#ifndef SkRecordPattern_DEFINED +#define SkRecordPattern_DEFINED + +#include "SkTLogic.h" + +namespace SkRecords { + +// First, some matchers. These match a single command in the SkRecord, +// and may hang onto some data from it. If so, you can get the data by calling .get(). + +// Matches a command of type T, and stores that command. +template <typename T> +class Is { +public: + Is() : fPtr(NULL) {} + + typedef T type; + type* get() { return fPtr; } + + bool operator()(T* ptr) { + fPtr = ptr; + return true; + } + + template <typename U> + bool operator()(U*) { + fPtr = NULL; + return false; + } + +private: + type* fPtr; +}; + +// Matches any command that draws, and stores its paint. +class IsDraw { + SK_CREATE_MEMBER_DETECTOR(paint); +public: + IsDraw() : fPaint(NULL) {} + + typedef SkPaint type; + type* get() { return fPaint; } + + template <typename T> + SK_WHEN(HasMember_paint<T>, bool) operator()(T* draw) { + fPaint = AsPtr(draw->paint); + return true; + } + + template <typename T> + SK_WHEN(!HasMember_paint<T>, bool) operator()(T*) { + fPaint = NULL; + return false; + } + + // SaveLayer has an SkPaint named paint, but it's not a draw. + bool operator()(SaveLayer*) { + fPaint = NULL; + return false; + } + +private: + // Abstracts away whether the paint is always part of the command or optional. + template <typename T> static T* AsPtr(SkRecords::Optional<T>& x) { return x; } + template <typename T> static T* AsPtr(T& x) { return &x; } + + type* fPaint; +}; + +// Matches if Matcher doesn't. Stores nothing. +template <typename Matcher> +struct Not { + template <typename T> + bool operator()(T* ptr) { return !Matcher()(ptr); } +}; + +// Matches if either of A or B does. Stores nothing. +template <typename A, typename B> +struct Or { + template <typename T> + bool operator()(T* ptr) { return A()(ptr) || B()(ptr); } +}; + +// Matches if any of A, B or C does. Stores nothing. +template <typename A, typename B, typename C> +struct Or3 : Or<A, Or<B, C> > {}; + +// Star is a special matcher that greedily matches Matcher 0 or more times. Stores nothing. +template <typename Matcher> +struct Star { + template <typename T> + bool operator()(T* ptr) { return Matcher()(ptr); } +}; + +// Cons builds a list of Matchers. +// It first matches Matcher (something from above), then Pattern (another Cons or Nil). +// +// This is the main entry point to pattern matching, and so provides a couple of extra API bits: +// - search scans through the record to look for matches; +// - first, second, and third return the data stored by their respective matchers in the pattern. +// +// These Cons build lists analogously to Lisp's "cons". See Pattern# for the "list" equivalent. +template <typename Matcher, typename Pattern> +class Cons { +public: + // If this pattern matches the SkRecord starting at i, + // return the index just past the end of the pattern, otherwise return 0. + SK_ALWAYS_INLINE unsigned match(SkRecord* record, unsigned i) { + i = this->matchHead(&fHead, record, i); + return i == 0 ? 0 : fTail.match(record, i); + } + + // Starting from *end, walk through the SkRecord to find the first span matching this pattern. + // If there is no such span, return false. If there is, return true and set [*begin, *end). + SK_ALWAYS_INLINE bool search(SkRecord* record, unsigned* begin, unsigned* end) { + for (*begin = *end; *begin < record->count(); ++(*begin)) { + *end = this->match(record, *begin); + if (*end != 0) { + return true; + } + } + return false; + } + + // Once either match or search has succeeded, access the stored data of the first, second, + // or third matcher in this pattern. Add as needed for longer patterns. + // T is checked statically at compile time; no casting is involved. It's just an API wart. + template <typename T> T* first() { return fHead.get(); } + template <typename T> T* second() { return fTail.fHead.get(); } + template <typename T> T* third() { return fTail.fTail.fHead.get(); } + +private: + // If head isn't a Star, try to match at i once. + template <typename T> + unsigned matchHead(T*, SkRecord* record, unsigned i) { + if (i < record->count()) { + if (record->mutate<bool>(i, fHead)) { + return i+1; + } + } + return 0; + } + + // If head is a Star, walk i until it doesn't match. + template <typename T> + unsigned matchHead(Star<T>*, SkRecord* record, unsigned i) { + while (i < record->count()) { + if (!record->mutate<bool>(i, fHead)) { + return i; + } + i++; + } + return 0; + } + + Matcher fHead; + Pattern fTail; + + // All Cons are friends with each other. This lets first, second, and third work. + template <typename, typename> friend class Cons; +}; + +// Nil is the end of every pattern Cons chain. +struct Nil { + // Bottoms out recursion down the fTail chain. Just return whatever i the front decided on. + unsigned match(SkRecord*, unsigned i) { return i; } +}; + +// These Pattern# types are syntax sugar over Cons and Nil, just to help eliminate some of the +// template noise. Use these if you can. Feel free to add more for longer patterns. +// All types A, B, C, ... are Matchers. +template <typename A> +struct Pattern1 : Cons<A, Nil> {}; + +template <typename A, typename B> +struct Pattern2 : Cons<A, Pattern1<B> > {}; + +template <typename A, typename B, typename C> +struct Pattern3 : Cons<A, Pattern2<B, C> > {}; + +} // namespace SkRecords + +#endif//SkRecordPattern_DEFINED diff --git a/src/core/SkRecorder.cpp b/src/core/SkRecorder.cpp new file mode 100644 index 0000000000..8581257c4a --- /dev/null +++ b/src/core/SkRecorder.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#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) {} + +void SkRecorder::forgetRecord() { + fRecord = NULL; +} + +// To make appending to fRecord a little less verbose. +#define APPEND(T, ...) \ + SkNEW_PLACEMENT_ARGS(fRecord->append<SkRecords::T>(), SkRecords::T, (__VA_ARGS__)) + +// For methods which must call back into SkCanvas. +#define INHERITED(method, ...) this->SkCanvas::method(__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, delay_copy(paint), mode, count, this->copy(pts, count)); +} + +void SkRecorder::drawRect(const SkRect& rect, const SkPaint& paint) { + APPEND(DrawRect, delay_copy(paint), rect); +} + +void SkRecorder::drawOval(const SkRect& oval, const SkPaint& paint) { + APPEND(DrawOval, delay_copy(paint), oval); +} + +void SkRecorder::drawRRect(const SkRRect& rrect, const SkPaint& paint) { + APPEND(DrawRRect, delay_copy(paint), rrect); +} + +void SkRecorder::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { + APPEND(DrawDRRect, delay_copy(paint), outer, inner); +} + +void SkRecorder::drawPath(const SkPath& path, const SkPaint& paint) { + APPEND(DrawPath, delay_copy(paint), delay_copy(path)); +} + +void SkRecorder::drawBitmap(const SkBitmap& bitmap, + SkScalar left, + SkScalar top, + const SkPaint* paint) { + APPEND(DrawBitmap, this->copy(paint), delay_copy(bitmap), left, top); +} + +void SkRecorder::drawBitmapRectToRect(const SkBitmap& bitmap, + const SkRect* src, + const SkRect& dst, + const SkPaint* paint, + DrawBitmapRectFlags flags) { + APPEND(DrawBitmapRectToRect, + this->copy(paint), delay_copy(bitmap), this->copy(src), dst, flags); +} + +void SkRecorder::drawBitmapMatrix(const SkBitmap& bitmap, + const SkMatrix& matrix, + const SkPaint* paint) { + APPEND(DrawBitmapMatrix, this->copy(paint), delay_copy(bitmap), matrix); +} + +void SkRecorder::drawBitmapNine(const SkBitmap& bitmap, + const SkIRect& center, + const SkRect& dst, + const SkPaint* paint) { + APPEND(DrawBitmapNine, this->copy(paint), delay_copy(bitmap), center, dst); +} + +void SkRecorder::drawSprite(const SkBitmap& bitmap, int left, int top, const SkPaint* paint) { + APPEND(DrawSprite, this->copy(paint), delay_copy(bitmap), left, top); +} + +void SkRecorder::onDrawText(const void* text, size_t byteLength, + SkScalar x, SkScalar y, const SkPaint& paint) { + APPEND(DrawText, + delay_copy(paint), this->copy((const char*)text, byteLength), byteLength, x, y); +} + +void SkRecorder::onDrawPosText(const void* text, size_t byteLength, + const SkPoint pos[], const SkPaint& paint) { + const unsigned points = paint.countText(text, byteLength); + APPEND(DrawPosText, + delay_copy(paint), + this->copy((const char*)text, byteLength), + byteLength, + this->copy(pos, points)); +} + +void SkRecorder::onDrawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, const SkPaint& paint) { + const unsigned points = paint.countText(text, byteLength); + APPEND(DrawPosTextH, + delay_copy(paint), + this->copy((const char*)text, byteLength), + byteLength, + this->copy(xpos, points), + constY); +} + +void SkRecorder::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, + const SkMatrix* matrix, const SkPaint& paint) { + APPEND(DrawTextOnPath, + delay_copy(paint), + this->copy((const char*)text, byteLength), + byteLength, + delay_copy(path), + this->copy(matrix)); +} + +void SkRecorder::onDrawPicture(const 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, delay_copy(paint), + vmode, + vertexCount, + this->copy(vertices, vertexCount), + texs ? this->copy(texs, vertexCount) : NULL, + colors ? this->copy(colors, vertexCount) : NULL, + xmode, + this->copy(indices, indexCount), + indexCount); +} + +void SkRecorder::willSave(SkCanvas::SaveFlags flags) { + APPEND(Save, flags); + INHERITED(willSave, flags); +} + +SkCanvas::SaveLayerStrategy SkRecorder::willSaveLayer(const SkRect* bounds, + const SkPaint* paint, + SkCanvas::SaveFlags flags) { + APPEND(SaveLayer, this->copy(bounds), this->copy(paint), flags); + INHERITED(willSaveLayer, bounds, paint, flags); + return SkCanvas::kNoLayer_SaveLayerStrategy; +} + +void SkRecorder::willRestore() { + APPEND(Restore); + INHERITED(willRestore); +} + +void SkRecorder::onPushCull(const SkRect& rect) { + APPEND(PushCull, rect); +} + +void SkRecorder::onPopCull() { + APPEND(PopCull); +} + +void SkRecorder::didConcat(const SkMatrix& matrix) { + APPEND(Concat, matrix); + INHERITED(didConcat, matrix); +} + +void SkRecorder::didSetMatrix(const SkMatrix& matrix) { + APPEND(SetMatrix, matrix); + INHERITED(didSetMatrix, matrix); +} + +void SkRecorder::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) { + APPEND(ClipRect, rect, op, edgeStyle == kSoft_ClipEdgeStyle); + INHERITED(onClipRect, rect, op, edgeStyle); +} + +void SkRecorder::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) { + APPEND(ClipRRect, rrect, op, edgeStyle == kSoft_ClipEdgeStyle); + INHERITED(updateClipConservativelyUsingBounds, rrect.getBounds(), op, false); +} + +void SkRecorder::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) { + APPEND(ClipPath, delay_copy(path), op, edgeStyle == kSoft_ClipEdgeStyle); + INHERITED(updateClipConservativelyUsingBounds, path.getBounds(), op, path.isInverseFillType()); +} + +void SkRecorder::onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op) { + APPEND(ClipRegion, delay_copy(deviceRgn), op); + INHERITED(onClipRegion, deviceRgn, op); +} diff --git a/src/core/SkRecorder.h b/src/core/SkRecorder.h new file mode 100644 index 0000000000..3e2932d42e --- /dev/null +++ b/src/core/SkRecorder.h @@ -0,0 +1,112 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#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); + + // Make SkRecorder forget entirely about its SkRecord*; all calls to SkRecorder will fail. + void forgetRecord(); + + void clear(SkColor) SK_OVERRIDE; + void drawPaint(const SkPaint& paint) SK_OVERRIDE; + void drawPoints(PointMode mode, + size_t count, + const SkPoint pts[], + const SkPaint& paint) SK_OVERRIDE; + void drawRect(const SkRect& rect, const SkPaint& paint) SK_OVERRIDE; + void drawOval(const SkRect& oval, const SkPaint&) SK_OVERRIDE; + void drawRRect(const SkRRect& rrect, const SkPaint& paint) SK_OVERRIDE; + void drawPath(const SkPath& path, const SkPaint& paint) SK_OVERRIDE; + void drawBitmap(const SkBitmap& bitmap, + SkScalar left, + SkScalar top, + const SkPaint* paint = NULL) SK_OVERRIDE; + void drawBitmapRectToRect(const SkBitmap& bitmap, + const SkRect* src, + const SkRect& dst, + const SkPaint* paint = NULL, + DrawBitmapRectFlags flags = kNone_DrawBitmapRectFlag) SK_OVERRIDE; + void drawBitmapMatrix(const SkBitmap& bitmap, + const SkMatrix& m, + const SkPaint* paint = NULL) SK_OVERRIDE; + void drawBitmapNine(const SkBitmap& bitmap, + const SkIRect& center, + const SkRect& dst, + const SkPaint* paint = NULL) SK_OVERRIDE; + void drawSprite(const SkBitmap& bitmap, + int left, + int top, + const SkPaint* paint = NULL) SK_OVERRIDE; + 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) SK_OVERRIDE; + + void willSave(SkCanvas::SaveFlags) SK_OVERRIDE; + SaveLayerStrategy willSaveLayer(const SkRect*, const SkPaint*, SkCanvas::SaveFlags) SK_OVERRIDE; + void willRestore() SK_OVERRIDE; + + void didConcat(const SkMatrix&) SK_OVERRIDE; + void didSetMatrix(const SkMatrix&) SK_OVERRIDE; + + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) SK_OVERRIDE; + void onDrawText(const void* text, + size_t byteLength, + SkScalar x, + SkScalar y, + const SkPaint& paint) SK_OVERRIDE; + void onDrawPosText(const void* text, + size_t byteLength, + const SkPoint pos[], + const SkPaint& paint) SK_OVERRIDE; + void onDrawPosTextH(const void* text, + size_t byteLength, + const SkScalar xpos[], + SkScalar constY, + const SkPaint& paint) SK_OVERRIDE; + void onDrawTextOnPath(const void* text, + size_t byteLength, + const SkPath& path, + const SkMatrix* matrix, + const SkPaint& paint) SK_OVERRIDE; + void onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) SK_OVERRIDE; + void onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) SK_OVERRIDE; + void onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) SK_OVERRIDE; + void onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op) SK_OVERRIDE; + + void onDrawPicture(const SkPicture* picture) SK_OVERRIDE; + + void onPushCull(const SkRect& cullRect) SK_OVERRIDE; + void onPopCull() SK_OVERRIDE; + +private: + template <typename T> + T* copy(const T*); + + template <typename T> + T* copy(const T[], unsigned count); + + SkRecord* fRecord; +}; + +#endif//SkRecorder_DEFINED diff --git a/src/core/SkRecording.cpp b/src/core/SkRecording.cpp new file mode 100644 index 0000000000..94fabce912 --- /dev/null +++ b/src/core/SkRecording.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "../../include/record/SkRecording.h" + +#include "SkRecord.h" +#include "SkRecordOpts.h" +#include "SkRecordDraw.h" +#include "SkRecorder.h" + +namespace EXPERIMENTAL { + +SkPlayback::SkPlayback(const SkRecord* record) : fRecord(record) {} + +SkPlayback::~SkPlayback() {} + +void SkPlayback::draw(SkCanvas* canvas) const { + SkASSERT(fRecord.get() != NULL); + SkRecordDraw(*fRecord, canvas); +} + +SkRecording::SkRecording(int width, int height) + : fRecord(SkNEW(SkRecord)) + , fRecorder(SkNEW_ARGS(SkRecorder, (fRecord.get(), width, height))) + {} + +SkPlayback* SkRecording::releasePlayback() { + SkASSERT(fRecorder->unique()); + fRecorder->forgetRecord(); + SkRecordOptimize(fRecord.get()); + return SkNEW_ARGS(SkPlayback, (fRecord.detach())); +} + +SkRecording::~SkRecording() {} + +SkCanvas* SkRecording::canvas() { + return fRecord.get() ? fRecorder.get() : NULL; +} + +} // namespace EXPERIMENTAL diff --git a/src/core/SkRecords.h b/src/core/SkRecords.h new file mode 100644 index 0000000000..581ae21e05 --- /dev/null +++ b/src/core/SkRecords.h @@ -0,0 +1,291 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#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.) +// +// Order doesn't technically matter here, but the compiler can generally generate better code if +// you keep them semantically grouped, especially the Draws. It's also nice to leave NoOp at 0. +#define SK_RECORD_TYPES(M) \ + M(NoOp) \ + M(Restore) \ + M(Save) \ + M(SaveLayer) \ + M(PushCull) \ + M(PopCull) \ + M(PairedPushCull) /*From SkRecordAnnotateCullingPairs*/ \ + 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) \ + M(BoundedDrawPosTextH) /*From SkRecordBoundDrawPosTextH*/ + +// 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; \ +}; + +// 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; \ +}; + +#define ACT_AS_PTR(ptr) \ + operator T*() { return ptr; } \ + operator const T*() const { return ptr; } \ + T* operator->() { return ptr; } \ + const T* operator->() const { return ptr; } + +// An Optional doesn't own the pointer's memory, but may need to destroy non-POD data. +template <typename T> +class Optional : SkNoncopyable { +public: + Optional(T* ptr) : fPtr(ptr) {} + ~Optional() { if (fPtr) fPtr->~T(); } + + ACT_AS_PTR(fPtr); +private: + T* fPtr; +}; + +// Like Optional, but ptr must not be NULL. +template <typename T> +class Adopted : SkNoncopyable { +public: + Adopted(T* ptr) : fPtr(ptr) { SkASSERT(fPtr); } + Adopted(Adopted* source) { + // Transfer ownership from source to this. + fPtr = source->fPtr; + source->fPtr = NULL; + } + ~Adopted() { if (fPtr) fPtr->~T(); } + + ACT_AS_PTR(fPtr); +private: + T* fPtr; +}; + +// PODArray doesn't own the pointer's memory, and we assume the data is POD. +template <typename T> +class PODArray { +public: + PODArray(T* ptr) : fPtr(ptr) {} + // Default copy and assign. + + ACT_AS_PTR(fPtr); +private: + T* fPtr; +}; + +#undef ACT_AS_PTR + +// 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; +}; + +RECORD0(NoOp); + +RECORD0(Restore); +RECORD1(Save, SkCanvas::SaveFlags, flags); +RECORD3(SaveLayer, Optional<SkRect>, bounds, Optional<SkPaint>, paint, SkCanvas::SaveFlags, flags); + +RECORD1(PushCull, SkRect, rect); +RECORD0(PopCull); + +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); +// While not strictly required, if you have an SkPaint, it's fastest to put it first. +RECORD4(DrawBitmap, Optional<SkPaint>, paint, + ImmutableBitmap, bitmap, + SkScalar, left, + SkScalar, top); +RECORD3(DrawBitmapMatrix, Optional<SkPaint>, paint, ImmutableBitmap, bitmap, SkMatrix, matrix); +RECORD4(DrawBitmapNine, Optional<SkPaint>, paint, + ImmutableBitmap, bitmap, + SkIRect, center, + SkRect, dst); +RECORD5(DrawBitmapRectToRect, Optional<SkPaint>, paint, + ImmutableBitmap, bitmap, + Optional<SkRect>, src, + SkRect, dst, + SkCanvas::DrawBitmapRectFlags, flags); +RECORD3(DrawDRRect, SkPaint, paint, SkRRect, outer, SkRRect, inner); +RECORD2(DrawOval, SkPaint, paint, SkRect, oval); +RECORD1(DrawPaint, SkPaint, paint); +RECORD2(DrawPath, SkPaint, paint, SkPath, path); +RECORD4(DrawPoints, SkPaint, paint, SkCanvas::PointMode, mode, size_t, count, SkPoint*, pts); +RECORD4(DrawPosText, SkPaint, paint, + PODArray<char>, text, + size_t, byteLength, + PODArray<SkPoint>, pos); +RECORD5(DrawPosTextH, SkPaint, paint, + PODArray<char>, text, + size_t, byteLength, + PODArray<SkScalar>, xpos, + SkScalar, y); +RECORD2(DrawRRect, SkPaint, paint, SkRRect, rrect); +RECORD2(DrawRect, SkPaint, paint, SkRect, rect); +RECORD4(DrawSprite, Optional<SkPaint>, paint, ImmutableBitmap, bitmap, int, left, int, top); +RECORD5(DrawText, SkPaint, paint, + PODArray<char>, text, + size_t, byteLength, + SkScalar, x, + SkScalar, y); +RECORD5(DrawTextOnPath, SkPaint, paint, + PODArray<char>, text, + size_t, byteLength, + SkPath, path, + Optional<SkMatrix>, matrix); + +// This guy is so ugly we just write it manually. +struct DrawVertices { + static const Type kType = DrawVertices_Type; + + DrawVertices(const SkPaint& paint, + SkCanvas::VertexMode vmode, + int vertexCount, + SkPoint* vertices, + SkPoint* texs, + SkColor* colors, + SkXfermode* xmode, + uint16_t* indices, + int indexCount) + : paint(paint) + , vmode(vmode) + , vertexCount(vertexCount) + , vertices(vertices) + , texs(texs) + , colors(colors) + , xmode(SkSafeRef(xmode)) + , indices(indices) + , indexCount(indexCount) {} + + SkPaint paint; + SkCanvas::VertexMode vmode; + int vertexCount; + PODArray<SkPoint> vertices; + PODArray<SkPoint> texs; + PODArray<SkColor> colors; + SkAutoTUnref<SkXfermode> xmode; + PODArray<uint16_t> indices; + int indexCount; +}; + +// Records added by optimizations. +RECORD2(PairedPushCull, Adopted<PushCull>, base, unsigned, skip); +RECORD3(BoundedDrawPosTextH, Adopted<DrawPosTextH>, base, SkScalar, minY, SkScalar, maxY); + +#undef RECORD0 +#undef RECORD1 +#undef RECORD2 +#undef RECORD3 +#undef RECORD4 +#undef RECORD5 + +} // namespace SkRecords + +#endif//SkRecords_DEFINED |