From 73fffeb83aab56bc8c2c5ce143ee9d132d64ac37 Mon Sep 17 00:00:00 2001 From: "commit-bot@chromium.org" Date: Mon, 5 May 2014 21:59:52 +0000 Subject: [PATCH] Add pattern matchers for SkRecord This is a mid-level library for finding patterns of commands in an SkRecord. At the API level, it's a bit regex inspired. Some examples: - Pattern1> matches a single DrawRect - Pattern1>> matches 0 or more DrawRects - Pattern2, Is> matches a single clip rect followed by a single draw rect - Pattern3, Star, Is> matches a single Save, followed by any number of Draws, followed by Restore - Pattern1, Is>> matches a DrawRect or a ClipRect - Pattern1>> matches a command that's notClipRect. Once you have a pattern, you can call .search() on it to step through ranges of matching commands. This means patterns can replace most of the custom iteration logic for optimization passes: the generic pattern searching steps through all the optimization candidates, which optimization-specific code further inspects and mutates. SkRecordTraits is now unused. Bye bye! Generated code and performance of SkRecordOpts is very similar to what it was before. (I had to use SK_ALWAYS_INLINE in a few places to make this so.) BUG=skia:2378 R=fmalita@chromium.org, bungeman@google.com, mtklein@google.com Author: mtklein@chromium.org Review URL: https://codereview.chromium.org/263063002 git-svn-id: http://skia.googlecode.com/svn/trunk@14582 2bbb7eff-a529-9590-31e7-b0007b416f81 --- gyp/tests.gypi | 1 + src/record/SkRecordOpts.cpp | 279 ++++++++++++++++------------------- src/record/SkRecordPattern.h | 219 +++++++++++++++++++++++++++ src/record/SkRecordTraits.h | 31 ---- src/utils/SkTLogic.h | 10 ++ tests/RecordPatternTest.cpp | 192 ++++++++++++++++++++++++ 6 files changed, 550 insertions(+), 182 deletions(-) create mode 100644 src/record/SkRecordPattern.h delete mode 100644 src/record/SkRecordTraits.h create mode 100644 tests/RecordPatternTest.cpp diff --git a/gyp/tests.gypi b/gyp/tests.gypi index afe256c691..68c179dfa7 100644 --- a/gyp/tests.gypi +++ b/gyp/tests.gypi @@ -142,6 +142,7 @@ '../tests/Reader32Test.cpp', '../tests/RecordDrawTest.cpp', '../tests/RecordOptsTest.cpp', + '../tests/RecordPatternTest.cpp', '../tests/RecordTest.cpp', '../tests/RecorderTest.cpp', '../tests/RecordingTest.cpp', diff --git a/src/record/SkRecordOpts.cpp b/src/record/SkRecordOpts.cpp index 5b537de040..aaa611cf39 100644 --- a/src/record/SkRecordOpts.cpp +++ b/src/record/SkRecordOpts.cpp @@ -7,10 +7,12 @@ #include "SkRecordOpts.h" -#include "SkRecordTraits.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? SkRecordNoopSaveRestores(record); @@ -19,205 +21,180 @@ void SkRecordOptimize(SkRecord* record) { SkRecordBoundDrawPosTextH(record); } -namespace { +// 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. -// Convenience base class to share some common implementation code. -class Common : SkNoncopyable { -public: - explicit Common(SkRecord* record) : fRecord(record), fIndex(0) {} +// 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 +static bool apply(Pass* pass, SkRecord* record) { + typename Pass::Pattern pattern; + bool changed = false; + unsigned begin, end = 0; - unsigned index() const { return fIndex; } - void next() { ++fIndex; } - -protected: - SkRecord* fRecord; - unsigned fIndex; -}; + while (pattern.search(record, &begin, &end)) { + changed |= pass->onMatch(record, &pattern, begin, end); + } + return changed; +} // 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) {} +struct SaveRestoreNooper { + // Star matches greedily, so we also have to exclude Save and Restore. + typedef Pattern3, + Star, + Is, + IsDraw> > >, + Is > + Pattern; - // Drawing commands reset state to inactive without nooping. - template - SK_WHEN(SkRecords::IsDraw, void) operator()(T*) { fSave = kInactive; } - - // Most non-drawing commands can be ignored. - template - SK_WHEN(!SkRecords::IsDraw, void) operator()(T*) {} - - void operator()(SkRecords::Save* r) { - fSave = SkCanvas::kMatrixClip_SaveFlag == r->flags ? this->index() : kInactive; - } - - void operator()(SkRecords::Restore* r) { - if (fSave != kInactive) { - // Remove everything between the save and restore, inclusive on both sides. - fChanged = true; - for (unsigned i = fSave; i <= this->index(); i++) { - fRecord->replace(i); - } - fSave = kInactive; + 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()->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(i); + } + return true; } - - bool changed() const { return fChanged; } - -private: - static const unsigned kInactive = ~0; - unsigned fSave; - bool fChanged; -}; - -// 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 void operator()(T*) {} - - void operator()(SkRecords::PushCull* push) { - Pair pair = { this->index(), push }; - fPushStack.push(pair); - } - - void operator()(SkRecords::PopCull* pop) { - Pair push = fPushStack.top(); - fPushStack.pop(); - - SkASSERT(this->index() > push.index); - unsigned skip = this->index() - push.index; - - SkRecords::Adopted adopted(push.command); - SkNEW_PLACEMENT_ARGS(fRecord->replace(push.index, adopted), - SkRecords::PairedPushCull, (&adopted, skip)); - } - -private: - struct Pair { - unsigned index; - SkRecords::PushCull* command; - }; - - SkTDArray fPushStack; }; +void SkRecordNoopSaveRestores(SkRecord* record) { + SaveRestoreNooper pass; + while (apply(&pass, record)); // Run until it stops changing things. +} // Replaces DrawPosText with DrawPosTextH when all Y coordinates are equal. -class StrengthReducer : public Common { -public: - explicit StrengthReducer(SkRecord* record) : Common(record) {} +struct StrengthReducer { + typedef Pattern1 > Pattern; - // Do nothing to most ops. - template void operator()(T*) {} + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + SkASSERT(end == begin + 1); + DrawPosText* draw = pattern->first(); - void operator()(SkRecords::DrawPosText* r) { - const unsigned points = r->paint.countText(r->text, r->byteLength); + const unsigned points = draw->paint.countText(draw->text, draw->byteLength); if (points == 0) { - // No point (ha!). - return; + return false; // No point (ha!). } - const SkScalar firstY = r->pos[0].fY; + const SkScalar firstY = draw->pos[0].fY; for (unsigned i = 1; i < points; i++) { - if (r->pos[i].fY != firstY) { - // Needs the full strength of DrawPosText. - return; + if (draw->pos[i].fY != firstY) { + return false; // Needs full power of DrawPosText. } } // All ys are the same. We can replace DrawPosText with DrawPosTextH. - // r->pos is points SkPoints, [(x,y),(x,y),(x,y),(x,y), ... ]. + // 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 = &r->pos[0].fX; + SkScalar* scalars = &draw->pos[0].fX; for (unsigned i = 0; i < 2*points; i += 2) { scalars[i/2] = scalars[i]; } - // Extend lifetime of r to the end of the method so we can copy its parts. - SkRecords::Adopted adopted(r); - SkNEW_PLACEMENT_ARGS(fRecord->replace(this->index(), adopted), - SkRecords::DrawPosTextH, - (r->text, r->byteLength, scalars, firstY, r->paint)); + // Extend lifetime of draw to the end of the loop so we can copy its paint. + Adopted adopted(draw); + SkNEW_PLACEMENT_ARGS(record->replace(begin, adopted), + DrawPosTextH, + (draw->text, draw->byteLength, scalars, firstY, draw->paint)); + 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. -class TextBounder : public Common { -public: - explicit TextBounder(SkRecord* record) : Common(record) {} +struct TextBounder { + typedef Pattern1 > Pattern; - // Do nothing to most ops. - template void operator()(T*) {} + bool onMatch(SkRecord* record, Pattern* pattern, unsigned begin, unsigned end) { + SkASSERT(end == begin + 1); + DrawPosTextH* draw = pattern->first(); - void 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; + 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 = r->paint.getTextSize() * 1.5f; + const SkScalar buffer = draw->paint.getTextSize() * 1.5f; SkDEBUGCODE(SkPaint::FontMetrics metrics;) - SkDEBUGCODE(r->paint.getFontMetrics(&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, r->y - buffer, SK_Scalar1, r->y + buffer); - SkRect adjusted = r->paint.computeFastBounds(bounds, &bounds); + bounds.set(0, draw->y - buffer, SK_Scalar1, draw->y + buffer); + SkRect adjusted = draw->paint.computeFastBounds(bounds, &bounds); - SkRecords::Adopted adopted(r); - SkNEW_PLACEMENT_ARGS( - fRecord->replace(this->index(), adopted), - SkRecords::BoundedDrawPosTextH, - (&adopted, adjusted.fTop, adjusted.fBottom)); + Adopted adopted(draw); + SkNEW_PLACEMENT_ARGS(record->replace(begin, adopted), + BoundedDrawPosTextH, + (&adopted, adjusted.fTop, adjusted.fBottom)); + return true; } }; - - -template -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) { - CullAnnotator annotator(record); - run_pass(annotator, record); -} - -void SkRecordReduceDrawPosTextStrength(SkRecord* record) { - StrengthReducer reducer(record); - run_pass(reducer, record); -} - void SkRecordBoundDrawPosTextH(SkRecord* record) { - TextBounder bounder(record); - run_pass(bounder, 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 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 adopted(push.command); + SkNEW_PLACEMENT_ARGS(fRecord->replace(push.index, adopted), + PairedPushCull, (&adopted, skip)); + } + + void apply(SkRecord* record) { + for (fRecord = record, fIndex = 0; fIndex < record->count(); fIndex++) { + fRecord->mutate(fIndex, *this); + } + } + +private: + struct Pair { + unsigned index; + PushCull* command; + }; + + SkTDArray fPushStack; + SkRecord* fRecord; + unsigned fIndex; +}; +void SkRecordAnnotateCullingPairs(SkRecord* record) { + CullAnnotator pass; + pass.apply(record); } diff --git a/src/record/SkRecordPattern.h b/src/record/SkRecordPattern.h new file mode 100644 index 0000000000..2023a90572 --- /dev/null +++ b/src/record/SkRecordPattern.h @@ -0,0 +1,219 @@ +#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 +class Is { +public: + Is() : fPtr(NULL) {} + + typedef T type; + type* get() { return fPtr; } + + bool match(T* ptr) { + fPtr = ptr; + return true; + } + + template + bool match(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 + SK_WHEN(HasMember_paint, bool) match(T* draw) { + fPaint = AsPtr(draw->paint); + return true; + } + + template + SK_WHEN(!HasMember_paint, bool) match(T*) { + fPaint = NULL; + return false; + } + +private: + // Abstracts away whether the paint is always part of the command or optional. + template static T* AsPtr(SkRecords::Optional& x) { return x; } + template static T* AsPtr(T& x) { return &x; } + + type* fPaint; +}; + +// Matches if Matcher doesn't. Stores nothing. +template +struct Not { + template + bool match(T* ptr) { return !Matcher().match(ptr); } +}; + +// Matches if either of A or B does. Stores nothing. +template +struct Or { + template + bool match(T* ptr) { return A().match(ptr) || B().match(ptr); } +}; + +// Matches if any of A, B or C does. Stores nothing. +template +struct Or3 : Or > {}; + +// We'll use this to choose which implementation of Star suits each Matcher. +SK_CREATE_TYPE_DETECTOR(type); + +// Star is a special matcher that matches Matcher 0 or more times _greedily_ in the SkRecord. +// This version stores nothing. It's enabled when Matcher stores nothing. +template +class Star { +public: + void reset() {} + + template + bool match(T* ptr) { return Matcher().match(ptr); } +}; + +// This version stores a list of matches. It's enabled if Matcher stores something. +template +class Star, void)> { +public: + typedef SkTDArray type; + type* get() { return &fMatches; } + + void reset() { fMatches.rewind(); } + + template + bool match(T* ptr) { + Matcher matcher; + if (matcher.match(ptr)) { + fMatches.push(matcher.get()); + return true; + } + return false; + } + +private: + type fMatches; +}; + + +// 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 +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 T* first() { return fHead.get(); } + template T* second() { return fTail.fHead.get(); } + template T* third() { return fTail.fTail.fHead.get(); } + +private: + template + void operator()(T* r) { fHeadMatched = fHead.match(r); } + + // If head isn't a Star, try to match at i once. + template + unsigned matchHead(T*, SkRecord* record, unsigned i) { + if (i < record->count()) { + fHeadMatched = false; + record->mutate(i, *this); + if (fHeadMatched) { + return i+1; + } + } + return 0; + } + + // If head is a Star, walk i until it doesn't match. + template + unsigned matchHead(Star*, SkRecord* record, unsigned i) { + fHead.reset(); + while (i < record->count()) { + fHeadMatched = false; + record->mutate(i, *this); + if (!fHeadMatched) { + return i; + } + i++; + } + return 0; + } + + Matcher fHead; + Pattern fTail; + bool fHeadMatched; + + friend class ::SkRecord; // So operator() can otherwise stay private. + + // All Cons are friends with each other. This lets first, second, and third work. + template 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 +struct Pattern1 : Cons {}; + +template +struct Pattern2 : Cons > {}; + +template +struct Pattern3 : Cons > {}; + +} // namespace SkRecords + +#endif//SkRecordPattern_DEFINED diff --git a/src/record/SkRecordTraits.h b/src/record/SkRecordTraits.h deleted file mode 100644 index 570a717e92..0000000000 --- a/src/record/SkRecordTraits.h +++ /dev/null @@ -1,31 +0,0 @@ -#include "SkRecords.h" -#include "SkTLogic.h" - -// Type traits that are useful for working with SkRecords. - -namespace SkRecords { - -namespace { - -// Abstracts away whether the T is optional or not. -template const T* as_ptr(const SkRecords::Optional& x) { return x; } -template const T* as_ptr(const T& x) { return &x; } - -} // namespace - -// Gets the paint from any command that may have one. -template const SkPaint* GetPaint(const Command& x) { return as_ptr(x.paint); } - -// Have a paint? You are a draw command! -template struct IsDraw { - SK_CREATE_MEMBER_DETECTOR(paint); - static const bool value = HasMember_paint::value; -}; - -// Have a clip op? You are a clip command. -template struct IsClip { - SK_CREATE_MEMBER_DETECTOR(op); - static const bool value = HasMember_op::value; -}; - -} // namespace SkRecords diff --git a/src/utils/SkTLogic.h b/src/utils/SkTLogic.h index 62952ad13c..925d4bdcd4 100644 --- a/src/utils/SkTLogic.h +++ b/src/utils/SkTLogic.h @@ -89,4 +89,14 @@ public: static const bool value = sizeof(func(NULL)) == sizeof(uint16_t); \ } +// Same sort of thing as SK_CREATE_MEMBER_DETECTOR, but checks for the existence of a nested type. +#define SK_CREATE_TYPE_DETECTOR(type) \ +template \ +class HasType_##type { \ + template static uint8_t func(typename U::type*); \ + template static uint16_t func(...); \ +public: \ + static const bool value = sizeof(func(NULL)) == sizeof(uint8_t); \ +} + #endif diff --git a/tests/RecordPatternTest.cpp b/tests/RecordPatternTest.cpp new file mode 100644 index 0000000000..e013150072 --- /dev/null +++ b/tests/RecordPatternTest.cpp @@ -0,0 +1,192 @@ +#include "Test.h" + +#include "SkRecord.h" +#include "SkRecordPattern.h" +#include "SkRecorder.h" +#include "SkRecords.h" + +using namespace SkRecords; +typedef Pattern3, + Is, + Is > + SaveClipRectRestore; + +DEF_TEST(RecordPattern_Simple, r) { + SaveClipRectRestore pattern; + + SkRecord record; + REPORTER_ASSERT(r, !pattern.match(&record, 0)); + + SkRecorder recorder(SkRecorder::kWriteOnly_Mode, &record, 1920, 1200); + + // Build up a save-clip-restore block. The pattern will match only it's complete. + recorder.save(); + REPORTER_ASSERT(r, !pattern.match(&record, 0)); + + recorder.clipRect(SkRect::MakeWH(300, 200)); + REPORTER_ASSERT(r, !pattern.match(&record, 0)); + + recorder.restore(); + REPORTER_ASSERT(r, pattern.match(&record, 0)); + REPORTER_ASSERT(r, pattern.first() != NULL); + REPORTER_ASSERT(r, pattern.second() != NULL); + REPORTER_ASSERT(r, pattern.third() != NULL); +} + +DEF_TEST(RecordPattern_StartingIndex, r) { + SaveClipRectRestore pattern; + + SkRecord record; + SkRecorder recorder(SkRecorder::kWriteOnly_Mode, &record, 1920, 1200); + + // There will be two save-clipRect-restore blocks [0,3) and [3,6). + for (int i = 0; i < 2; i++) { + recorder.save(); + recorder.clipRect(SkRect::MakeWH(300, 200)); + recorder.restore(); + } + + // We should match only at 0 and 3. Going over the length should fail gracefully. + for (unsigned i = 0; i < 8; i++) { + if (i == 0 || i == 3) { + REPORTER_ASSERT(r, pattern.match(&record, i) == i + 3); + } else { + REPORTER_ASSERT(r, !pattern.match(&record, i)); + } + } +} + +DEF_TEST(RecordPattern_DontMatchSubsequences, r) { + SaveClipRectRestore pattern; + + SkRecord record; + SkRecorder recorder(SkRecorder::kWriteOnly_Mode, &record, 1920, 1200); + + recorder.save(); + recorder.clipRect(SkRect::MakeWH(300, 200)); + recorder.drawRect(SkRect::MakeWH(600, 300), SkPaint()); + recorder.restore(); + + REPORTER_ASSERT(r, !pattern.match(&record, 0)); +} + +DEF_TEST(RecordPattern_Star, r) { + Pattern3, Star >, Is > pattern; + + SkRecord record; + SkRecorder recorder(SkRecorder::kWriteOnly_Mode, &record, 1920, 1200); + + recorder.save(); + recorder.restore(); + REPORTER_ASSERT(r, pattern.match(&record, 0)); + REPORTER_ASSERT(r, pattern.second >()->count() == 0); + + recorder.save(); + recorder.clipRect(SkRect::MakeWH(300, 200)); + recorder.restore(); + REPORTER_ASSERT(r, pattern.match(&record, 2)); + REPORTER_ASSERT(r, pattern.second >()->count() == 1); + + recorder.save(); + recorder.clipRect(SkRect::MakeWH(300, 200)); + recorder.clipRect(SkRect::MakeWH(100, 100)); + recorder.restore(); + REPORTER_ASSERT(r, pattern.match(&record, 5)); + REPORTER_ASSERT(r, pattern.second >()->count() == 2); +} + +DEF_TEST(RecordPattern_IsDraw, r) { + Pattern3, IsDraw, Is > pattern; + + SkRecord record; + SkRecorder recorder(SkRecorder::kWriteOnly_Mode, &record, 1920, 1200); + + recorder.save(); + recorder.clipRect(SkRect::MakeWH(300, 200)); + recorder.restore(); + + REPORTER_ASSERT(r, !pattern.match(&record, 0)); + + SkPaint paint; + + recorder.save(); + paint.setColor(0xEEAA8822); + recorder.drawRect(SkRect::MakeWH(300, 200), paint); + recorder.restore(); + + recorder.save(); + paint.setColor(0xFACEFACE); + recorder.drawPaint(paint); + recorder.restore(); + + REPORTER_ASSERT(r, pattern.match(&record, 3)); + REPORTER_ASSERT(r, pattern.first() != NULL); + REPORTER_ASSERT(r, pattern.second()->getColor() == 0xEEAA8822); + REPORTER_ASSERT(r, pattern.third() != NULL); + + REPORTER_ASSERT(r, pattern.match(&record, 6)); + REPORTER_ASSERT(r, pattern.first() != NULL); + REPORTER_ASSERT(r, pattern.second()->getColor() == 0xFACEFACE); + REPORTER_ASSERT(r, pattern.third() != NULL); +} + +DEF_TEST(RecordPattern_Complex, r) { + Pattern3, + Star, + Is, + IsDraw> > >, + Is > pattern; + + SkRecord record; + SkRecorder recorder(SkRecorder::kWriteOnly_Mode, &record, 1920, 1200); + + recorder.save(); + recorder.restore(); + REPORTER_ASSERT(r, pattern.match(&record, 0) == 2); + + recorder.save(); + recorder.save(); + recorder.restore(); + recorder.restore(); + REPORTER_ASSERT(r, !pattern.match(&record, 2)); + REPORTER_ASSERT(r, pattern.match(&record, 3) == 5); + + recorder.save(); + recorder.clipRect(SkRect::MakeWH(300, 200)); + recorder.restore(); + REPORTER_ASSERT(r, pattern.match(&record, 6) == 9); + + recorder.save(); + recorder.clipRect(SkRect::MakeWH(300, 200)); + recorder.drawRect(SkRect::MakeWH(100, 3000), SkPaint()); + recorder.restore(); + REPORTER_ASSERT(r, !pattern.match(&record, 9)); + + recorder.save(); + recorder.pushCull(SkRect::MakeWH(300, 200)); + recorder.clipRect(SkRect::MakeWH(300, 200)); + recorder.clipRect(SkRect::MakeWH(100, 400)); + recorder.popCull(); + recorder.restore(); + REPORTER_ASSERT(r, pattern.match(&record, 13) == 19); + + // Same as above, but using pattern.search to step through matches. + unsigned begin, end = 0; + REPORTER_ASSERT(r, pattern.search(&record, &begin, &end)); + REPORTER_ASSERT(r, begin == 0); + REPORTER_ASSERT(r, end == 2); + + REPORTER_ASSERT(r, pattern.search(&record, &begin, &end)); + REPORTER_ASSERT(r, begin == 3); + REPORTER_ASSERT(r, end == 5); + + REPORTER_ASSERT(r, pattern.search(&record, &begin, &end)); + REPORTER_ASSERT(r, begin == 6); + REPORTER_ASSERT(r, end == 9); + + REPORTER_ASSERT(r, pattern.search(&record, &begin, &end)); + REPORTER_ASSERT(r, begin == 13); + REPORTER_ASSERT(r, end == 19); + + REPORTER_ASSERT(r, !pattern.search(&record, &begin, &end)); +}