diff options
-rw-r--r-- | src/record/SkRecord.h | 99 | ||||
-rw-r--r-- | src/record/SkRecordDraw.cpp | 109 | ||||
-rw-r--r-- | src/record/SkRecordOpts.cpp | 233 | ||||
-rw-r--r-- | src/record/SkRecordOpts.h | 11 | ||||
-rw-r--r-- | src/record/SkRecorder.cpp | 2 | ||||
-rw-r--r-- | src/record/SkRecords.h | 97 | ||||
-rw-r--r-- | tests/RecordCullingTest.cpp | 24 | ||||
-rw-r--r-- | tests/RecordTest.cpp | 18 | ||||
-rw-r--r-- | tests/RecorderTest.cpp | 9 |
9 files changed, 402 insertions, 200 deletions
diff --git a/src/record/SkRecord.h b/src/record/SkRecord.h index 4c2f5b5ed4..ddf1b3d95f 100644 --- a/src/record/SkRecord.h +++ b/src/record/SkRecord.h @@ -31,59 +31,43 @@ public: ~SkRecord() { Destroyer destroyer; - this->mutate(destroyer); + for (unsigned i = 0; i < this->count(); i++) { + this->mutate(i, destroyer); + } } + // Returns the number of canvas commands in this SkRecord. unsigned count() const { return fCount; } - // Accepts a visitor functor with this interface: + // Visit the i-th canvas command with a functor matching 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. + // This operator() must be defined for at least all SkRecords::*. template <typename F> void visit(unsigned i, F& f) const { SkASSERT(i < this->count()); fRecords[i].visit(fTypes[i], f); } - // As above. 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++) { - this->visit(i, f); - } - } - - // Accepts a visitor functor with this interface: + // Mutate the i-th canvas command with a functor matching 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. + // This operator() must be defined for at least all SkRecords::*. template <typename F> void mutate(unsigned i, F& f) { SkASSERT(i < this->count()); fRecords[i].mutate(fTypes[i], f); } - // As above. 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++) { - this->mutate(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. + // 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); } - // 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. + // 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) { @@ -93,9 +77,26 @@ public: } fTypes[fCount] = T::kType; - return fRecords[fCount++].alloc<T>(this); + return fRecords[fCount++].set(this->alloc<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 old command remain valid for the life of the SkRecord, but + // you must destroy the old command. (It's okay to destroy it first before calling replace.) + template <typename T> + T* replace(unsigned i) { + SkASSERT(i < this->count()); + fTypes[i] = T::kType; + return fRecords[i].set(this->alloc<T>()); + } + + // A mutator that can be used with replace to destroy canvas commands. + struct Destroyer { + template <typename T> + void operator()(T* record) { record->~T(); } + }; + private: // Implementation notes! // @@ -131,23 +132,9 @@ private: // 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*) + // The cost to append a T into this structure is 1 + sizeof(void*) + sizeof(T). - // 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: @@ -159,19 +146,15 @@ 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. + // 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: - - // Allocate space for a T, perhaps using the SkRecord to allocate that space. + // Point this record to its data in fAlloc. Returns ptr for convenience. template <typename T> - T* alloc(SkRecord* record) { - if (IsLarge<T>()) { - fRecord = (uintptr_t)record->alloc<T>(); - } - return this->ptr<T>(); + T* set(T* ptr) { + fPtr = ptr; + return ptr; } // Visit this record with functor F (see public API above) assuming the record we're @@ -194,13 +177,9 @@ private: 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); } + T* ptr() const { return (T*)fPtr; } - uintptr_t fRecord; + void* fPtr; }; // fAlloc needs to be a data structure which can append variable length data in contiguous diff --git a/src/record/SkRecordDraw.cpp b/src/record/SkRecordDraw.cpp index c6e85502a7..21b2c7a82b 100644 --- a/src/record/SkRecordDraw.cpp +++ b/src/record/SkRecordDraw.cpp @@ -18,15 +18,16 @@ public: void next() { ++fIndex; } template <typename T> void operator()(const T& r) { - if (!this->canSkip(r)) { + if (!this->skip(r)) { this->draw(r); this->updateClip<T>(); } } private: - // Can we skip this command right now? - template <typename T> bool canSkip(const T&) const { + // Return true if we can skip this command, false if not. + // Update fIndex here directly to skip more than just this one command. + template <typename T> bool skip(const T&) { // We can skip most commands if the clip is empty. Exceptions are specialized below. return fClipEmpty; } @@ -44,6 +45,8 @@ private: bool fClipEmpty; }; +// TODO(mtklein): do this specialization with template traits instead of macros + // These commands may change the clip. #define UPDATE_CLIP(T) template <> void Draw::updateClip<SkRecords::T>() \ { fClipEmpty = fCanvas->isClipEmpty(); } @@ -56,75 +59,34 @@ UPDATE_CLIP(ClipRegion); #undef UPDATE_CLIP // These commands must always run. -#define CAN_SKIP(T) template <> bool Draw::canSkip(const SkRecords::T&) const { return false; } -CAN_SKIP(Restore); -CAN_SKIP(Save); -CAN_SKIP(SaveLayer); -CAN_SKIP(Clear); -CAN_SKIP(PushCull); -CAN_SKIP(PopCull); -#undef CAN_SKIP +#define SKIP(T) template <> bool Draw::skip(const SkRecords::T&) { return false; } +SKIP(Restore); +SKIP(Save); +SKIP(SaveLayer); +SKIP(Clear); +SKIP(PushCull); +SKIP(PopCull); +#undef SKIP // We can skip these commands if they're intersecting with a clip that's already empty. -#define CAN_SKIP(T) template <> bool Draw::canSkip(const SkRecords::T& r) const \ +#define SKIP(T) template <> bool Draw::skip(const SkRecords::T& r) \ { return fClipEmpty && SkRegion::kIntersect_Op == r.op; } -CAN_SKIP(ClipPath); -CAN_SKIP(ClipRRect); -CAN_SKIP(ClipRect); -CAN_SKIP(ClipRegion); -#undef CAN_SKIP - -static bool can_skip_text(const SkCanvas& c, const SkPaint& p, SkScalar minY, SkScalar maxY) { - // 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 out if that's not possible. - if (p.isVerticalText() || !p.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 = p.getTextSize() * 1.5f; - SkDEBUGCODE(SkPaint::FontMetrics metrics;) - SkDEBUGCODE(p.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, -buffer, SK_Scalar1, buffer); - SkRect adjusted = p.computeFastBounds(bounds, &bounds); - return c.quickRejectY(minY + adjusted.fTop, maxY + adjusted.fBottom); -} - -template <> bool Draw::canSkip(const SkRecords::DrawPosTextH& r) const { - return fClipEmpty || can_skip_text(*fCanvas, r.paint, r.y, r.y); -} +SKIP(ClipPath); +SKIP(ClipRRect); +SKIP(ClipRect); +SKIP(ClipRegion); +#undef SKIP -template <> bool Draw::canSkip(const SkRecords::DrawPosText& r) const { - if (fClipEmpty) { - return true; - } - - // TODO(mtklein): may want to move this minY/maxY calculation into a one-time pass - const unsigned points = r.paint.countText(r.text, r.byteLength); - if (points == 0) { - return true; - } - SkScalar minY = SK_ScalarInfinity, maxY = SK_ScalarNegativeInfinity; - for (unsigned i = 0; i < points; i++) { - minY = SkTMin(minY, r.pos[i].fY); - maxY = SkTMax(maxY, r.pos[i].fY); - } - - return can_skip_text(*fCanvas, r.paint, minY, maxY); -} +// NoOps can always be skipped and draw nothing. +template <> bool Draw::skip(const SkRecords::NoOp&) { return true; } +template <> void Draw::draw(const SkRecords::NoOp&) {} #define DRAW(T, call) template <> void Draw::draw(const SkRecords::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(r.matrix)); @@ -154,15 +116,26 @@ DRAW(DrawVertices, drawVertices(r.vmode, r.vertexCount, r.vertices, r.texs, r.co r.xmode.get(), r.indices, r.indexCount, r.paint)); #undef DRAW -// PushCull is a bit of a oddball. We might be able to just skip until just past its popCull. -template <> void Draw::draw(const SkRecords::PushCull& r) { - if (r.popOffset != SkRecords::kUnsetPopOffset && fCanvas->quickReject(r.rect)) { - fIndex += r.popOffset; - } else { - fCanvas->pushCull(r.rect); +// Added by SkRecordAnnotateCullingPairs. +template <> bool Draw::skip(const SkRecords::PairedPushCull& r) { + if (fCanvas->quickReject(r.base->rect)) { + fIndex += r.skip; + return true; } + return false; } +// Added by SkRecordBoundDrawPosTextH +template <> bool Draw::skip(const SkRecords::BoundedDrawPosTextH& r) { + return fClipEmpty || fCanvas->quickRejectY(r.minY, r.maxY); +} + +// These draw by proxying to the commands they wrap. (All the optimization is for skip().) +#define DRAW(T) template <> void Draw::draw(const SkRecords::T& r) { this->draw(*r.base); } +DRAW(PairedPushCull); +DRAW(BoundedDrawPosTextH); +#undef DRAW + } // namespace void SkRecordDraw(const SkRecord& record, SkCanvas* canvas) { diff --git a/src/record/SkRecordOpts.cpp b/src/record/SkRecordOpts.cpp index 8963c07bca..1b23acde58 100644 --- a/src/record/SkRecordOpts.cpp +++ b/src/record/SkRecordOpts.cpp @@ -11,39 +11,236 @@ #include "SkTDArray.h" void SkRecordOptimize(SkRecord* record) { + // TODO(mtklein): fuse independent optimizations to reduce number of passes? + SkRecordNoopSaveRestores(record); SkRecordAnnotateCullingPairs(record); + SkRecordReduceDrawPosTextStrength(record); // Helpful to run this before BoundDrawPosTextH. + SkRecordBoundDrawPosTextH(record); } +// Streamline replacing one command with another. +#define REPLACE(record, index, T, ...) \ + SkNEW_PLACEMENT_ARGS(record->replace<SkRecords::T>(index), SkRecords::T, (__VA_ARGS__)) + namespace { -struct Annotator { - unsigned index; - SkTDArray<SkRecords::PushCull*> pushStack; +// Convenience base class to share some common implementation code. +class Common : SkNoncopyable { +public: + explicit Common(SkRecord* record) : fRecord(record), fIndex(0) {} + + unsigned index() const { return fIndex; } + void next() { ++fIndex; } + +protected: + SkRecord* fRecord; + unsigned fIndex; +}; + +// Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops. +// TODO(mtklein): state machine diagram +class SaveRestoreNooper : public Common { +public: + explicit SaveRestoreNooper(SkRecord* record) + : Common(record), fSave(kInactive), fChanged(false) {} + + // Most drawing commands reset to inactive state without nooping anything. + template <typename T> + void operator()(T*) { fSave = kInactive; } + + bool changed() const { return fChanged; } + +private: + static const unsigned kInactive = ~0; + unsigned fSave; + bool fChanged; +}; + +// If the command doesn't draw anything, that doesn't reset the state back to inactive. +// TODO(mtklein): do this with some sort of template-based trait mechanism instead of macros +#define IGNORE(T) template <> void SaveRestoreNooper::operator()(SkRecords::T*) {} +IGNORE(NoOp) +IGNORE(Concat) +IGNORE(SetMatrix) +IGNORE(ClipRect) +IGNORE(ClipRRect) +IGNORE(ClipPath) +IGNORE(ClipRegion) +IGNORE(PairedPushCull) +IGNORE(PushCull) +IGNORE(PopCull) +#undef CLIP + +template <> +void SaveRestoreNooper::operator()(SkRecords::Save* r) { + fSave = SkCanvas::kMatrixClip_SaveFlag == r->flags ? this->index() : kInactive; +} + +template <> +void SaveRestoreNooper::operator()(SkRecords::Restore* r) { + if (fSave != kInactive) { + // Remove everything between the save and restore, inclusive on both sides. + fChanged = true; + SkRecord::Destroyer destroyer; + for (unsigned i = fSave; i <= this->index(); i++) { + fRecord->mutate(i, destroyer); + REPLACE(fRecord, i, NoOp); + } + fSave = kInactive; + } +} + + +// Tries to replace PushCull with PairedPushCull, which lets us skip to the paired PopCull +// when the canvas can quickReject the cull rect. +class CullAnnotator : public Common { +public: + explicit CullAnnotator(SkRecord* record) : Common(record) {} + + // Do nothing to most ops. + template <typename T> + void operator()(T*) {} + +private: + struct Pair { + unsigned index; + SkRecords::PushCull* command; + }; + + SkTDArray<Pair> fPushStack; +}; + +template <> +void CullAnnotator::operator()(SkRecords::PushCull* push) { + Pair pair = { this->index(), push }; + fPushStack.push(pair); +} + +template <> +void CullAnnotator::operator()(SkRecords::PopCull* pop) { + Pair push = fPushStack.top(); + fPushStack.pop(); + + SkASSERT(this->index() > push.index); + unsigned skip = this->index() - push.index; + + // PairedPushCull adopts push.command. + REPLACE(fRecord, push.index, PairedPushCull, push.command, skip); +} + +// Replaces DrawPosText with DrawPosTextH when all Y coordinates are equal. +class StrengthReducer : public Common { +public: + explicit StrengthReducer(SkRecord* record) : Common(record) {} - // Do nothing to most record types. - template <typename T> void operator()(T*) {} + // Do nothing to most ops. + template <typename T> + void operator()(T*) {} }; -template <> void Annotator::operator()(SkRecords::PushCull* push) { - // Store the push's index for now. We'll calculate the offset using this in the paired pop. - push->popOffset = index; - pushStack.push(push); +template <> +void StrengthReducer::operator()(SkRecords::DrawPosText* r) { + const unsigned points = r->paint.countText(r->text, r->byteLength); + if (points == 0) { + // No point (ha!). + return; + } + + const SkScalar firstY = r->pos[0].fY; + for (unsigned i = 1; i < points; i++) { + if (r->pos[i].fY != firstY) { + // Needs the full strength of DrawPosText. + return; + } + } + // All ys are the same. We can replace DrawPosText with DrawPosTextH. + + // r->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 = &r->pos[0].fX; + for (unsigned i = 0; i < 2*points; i += 2) { + scalars[i/2] = scalars[i]; + } + + SkRecord::Destroyer destroyer; + fRecord->mutate(this->index(), destroyer); + REPLACE(fRecord, this->index(), + DrawPosTextH, (char*)r->text, r->byteLength, scalars, firstY, r->paint); } -template <> void Annotator::operator()(SkRecords::PopCull* pop) { - SkRecords::PushCull* push = pushStack.top(); - pushStack.pop(); - SkASSERT(index > push->popOffset); // push->popOffset holds the index of the push. - push->popOffset = index - push->popOffset; // Now it's the offset between push and pop. +// Tries to replace DrawPosTextH with BoundedDrawPosTextH, which knows conservative upper and lower +// bounds to use with SkCanvas::quickRejectY. +class TextBounder : public Common { +public: + explicit TextBounder(SkRecord* record) : Common(record) {} + + // Do nothing to most ops. + template <typename T> + void operator()(T*) {} +}; + +template <> +void TextBounder::operator()(SkRecords::DrawPosTextH* r) { + // 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 (r->paint.isVerticalText() || !r->paint.canComputeFastBounds()) { + return; + } + + // 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 = r->paint.getTextSize() * 1.5f; + SkDEBUGCODE(SkPaint::FontMetrics metrics;) + SkDEBUGCODE(r->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, r->y - buffer, SK_Scalar1, r->y + buffer); + SkRect adjusted = r->paint.computeFastBounds(bounds, &bounds); + + // BoundedDrawPosTextH adopts r. + REPLACE(fRecord, this->index(), BoundedDrawPosTextH, r, adjusted.fTop, adjusted.fBottom); +} + +template <typename Pass> +static void run_pass(Pass& pass, SkRecord* record) { + for (; pass.index() < record->count(); pass.next()) { + record->mutate(pass.index(), pass); + } } } // namespace + +void SkRecordNoopSaveRestores(SkRecord* record) { + // Run SaveRestoreNooper until it doesn't make any more changes. + bool changed; + do { + SaveRestoreNooper nooper(record); + run_pass(nooper, record); + changed = nooper.changed(); + } while (changed); +} + void SkRecordAnnotateCullingPairs(SkRecord* record) { - Annotator annotator; + CullAnnotator annotator(record); + run_pass(annotator, record); +} - for (annotator.index = 0; annotator.index < record->count(); annotator.index++) { - record->mutate(annotator.index, annotator); - } +void SkRecordReduceDrawPosTextStrength(SkRecord* record) { + StrengthReducer reducer(record); + run_pass(reducer, record); } + +void SkRecordBoundDrawPosTextH(SkRecord* record) { + TextBounder bounder(record); + run_pass(bounder, record); +} + +#undef REPLACE diff --git a/src/record/SkRecordOpts.h b/src/record/SkRecordOpts.h index bc19a1fcc4..c9cbccf9de 100644 --- a/src/record/SkRecordOpts.h +++ b/src/record/SkRecordOpts.h @@ -14,7 +14,16 @@ void SkRecordOptimize(SkRecord*); -// Annotates PushCull records in record with the relative offset of their paired PopCull. +// Turns logical no-op Save-[non-drawing command]*-Restore patterns into actual no-ops. +void SkRecordNoopSaveRestores(SkRecord*); // TODO(mtklein): add unit tests + +// 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*); // TODO(mtklein): add unit tests + +// Calculate min and max Y bounds for DrawPosTextH commands, for use with SkCanvas::quickRejectY. +void SkRecordBoundDrawPosTextH(SkRecord*); // TODO(mtklein): add unit tests + #endif//SkRecordOpts_DEFINED diff --git a/src/record/SkRecorder.cpp b/src/record/SkRecorder.cpp index aff6609cf9..d5ca01f081 100644 --- a/src/record/SkRecorder.cpp +++ b/src/record/SkRecorder.cpp @@ -213,7 +213,7 @@ void SkRecorder::willRestore() { } void SkRecorder::onPushCull(const SkRect& rect) { - APPEND(PushCull, rect, SkRecords::kUnsetPopOffset); + APPEND(PushCull, rect); } void SkRecorder::onPopCull() { diff --git a/src/record/SkRecords.h b/src/record/SkRecords.h index 10806565d9..7a897406e9 100644 --- a/src/record/SkRecords.h +++ b/src/record/SkRecords.h @@ -19,36 +19,39 @@ namespace SkRecords { // // 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) \ - M(PushCull) \ - M(PopCull) +#define SK_RECORD_TYPES(M) \ + M(NoOp) \ + 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) \ + M(PushCull) \ + M(PopCull) \ + M(PairedPushCull) /*From SkRecordAnnotateCullingPairs*/ \ + M(BoundedDrawPosTextH) /*From SkRecordBoundDrawPosTextH*/ // Defines SkRecords::Type, an enum of all record types. #define ENUM(T) T##_Type, @@ -107,6 +110,12 @@ struct T { \ 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 { @@ -114,8 +123,19 @@ public: Optional(T* ptr) : fPtr(ptr) {} ~Optional() { if (fPtr) fPtr->~T(); } - operator T*() { return fPtr; } - operator const T*() const { return fPtr; } + 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() { fPtr->~T(); } + + ACT_AS_PTR(fPtr); private: T* fPtr; }; @@ -126,8 +146,7 @@ class PODArray : SkNoncopyable { public: PODArray(T* ptr) : fPtr(ptr) {} - operator T*() { return fPtr; } - operator const T*() const { return fPtr; } + ACT_AS_PTR(fPtr); private: T* fPtr; }; @@ -151,15 +170,13 @@ private: SkBitmap fBitmap; }; -// None of these records manages the lifetimes of pointers, except for DrawVertices handling its -// Xfermode specially. +RECORD0(NoOp); RECORD0(Restore); RECORD1(Save, SkCanvas::SaveFlags, flags); RECORD3(SaveLayer, Optional<SkRect>, bounds, Optional<SkPaint>, paint, SkCanvas::SaveFlags, flags); -static const unsigned kUnsetPopOffset = 0; -RECORD2(PushCull, SkRect, rect, unsigned, popOffset); +RECORD1(PushCull, SkRect, rect); RECORD0(PopCull); RECORD1(Concat, SkMatrix, matrix); @@ -247,6 +264,10 @@ struct DrawVertices { SkPaint paint; }; +// 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 diff --git a/tests/RecordCullingTest.cpp b/tests/RecordCullingTest.cpp index 982fbe7938..7cb62414c3 100644 --- a/tests/RecordCullingTest.cpp +++ b/tests/RecordCullingTest.cpp @@ -12,14 +12,20 @@ #include "SkRecorder.h" #include "SkRecords.h" -struct PushCullScanner { +struct SkipScanner { template <typename T> void operator()(const T&) {} - SkTDArray<unsigned> fPopOffsets; + void apply(const SkRecord& record) { + for (unsigned i = 0; i < record.count(); i++) { + record.visit(i, *this); + } + } + + SkTDArray<unsigned> fSkips; }; -template <> void PushCullScanner::operator()(const SkRecords::PushCull& record) { - *fPopOffsets.append() = record.popOffset; +template <> void SkipScanner::operator()(const SkRecords::PairedPushCull& r) { + *fSkips.append() = r.skip; } @@ -39,10 +45,10 @@ DEF_TEST(RecordCulling, r) { SkRecordAnnotateCullingPairs(&record); - PushCullScanner scan; - record.visit(scan); + SkipScanner scan; + scan.apply(record); - REPORTER_ASSERT(r, 2 == scan.fPopOffsets.count()); - REPORTER_ASSERT(r, 6 == scan.fPopOffsets[0]); - REPORTER_ASSERT(r, 2 == scan.fPopOffsets[1]); + REPORTER_ASSERT(r, 2 == scan.fSkips.count()); + REPORTER_ASSERT(r, 6 == scan.fSkips[0]); + REPORTER_ASSERT(r, 2 == scan.fSkips[1]); } diff --git a/tests/RecordTest.cpp b/tests/RecordTest.cpp index 315f15b700..e58ef10dda 100644 --- a/tests/RecordTest.cpp +++ b/tests/RecordTest.cpp @@ -19,6 +19,12 @@ public: int area() const { return fArea; } + void apply(const SkRecord& record) { + for (unsigned i = 0; i < record.count(); i++) { + record.visit(i, *this); + } + } + private: int fArea; }; @@ -29,6 +35,12 @@ template <> void AreaSummer::operator()(const SkRecords::DrawRect& record) { // Scales out the bottom-right corner of any DrawRect command it sees by 2x. struct Stretch { template <typename T> void operator()(T*) {} + + void apply(SkRecord* record) { + for (unsigned i = 0; i < record->count(); i++) { + record->mutate(i, *this); + } + } }; template <> void Stretch::operator()(SkRecords::DrawRect* record) { record->rect.fRight *= 2; @@ -46,14 +58,14 @@ DEF_TEST(Record, r) { // Its area should be 100. AreaSummer summer; - record.visit(summer); + summer.apply(record); REPORTER_ASSERT(r, summer.area() == 100); // Scale 2x. Stretch stretch; - record.mutate(stretch); + stretch.apply(&record); // Now its area should be 100 + 400. - record.visit(summer); + summer.apply(record); REPORTER_ASSERT(r, summer.area() == 500); } diff --git a/tests/RecorderTest.cpp b/tests/RecorderTest.cpp index 384425ddc2..407cf9add0 100644 --- a/tests/RecorderTest.cpp +++ b/tests/RecorderTest.cpp @@ -28,6 +28,12 @@ public: template <typename T> int count() const { return fHistogram[T::kType]; } + void apply(const SkRecord& record) { + for (unsigned i = 0; i < record.count(); i++) { + record.visit(i, *this); + } + } + private: int fHistogram[kRecordTypes]; }; @@ -39,8 +45,7 @@ DEF_TEST(Recorder, r) { recorder.drawRect(SkRect::MakeWH(10, 10), SkPaint()); Tally tally; - record.visit(tally); - + tally.apply(record); REPORTER_ASSERT(r, 1 == tally.count<SkRecords::DrawRect>()); } |