[WIP] Add Context to SkDrawLooper.

SkDrawLooper carries some state during draws. This CL extracts this state into
a separate class Context, which is then passed by the users of SkDrawLooper
into the appropriate methods.
This is a step towards making SkDrawLooper immutable.

BUG=skia:2141
R=scroggo@google.com, reed@google.com, sugoi@google.com

Author: dominikg@chromium.org

Review URL: https://codereview.chromium.org/155513012

git-svn-id: http://skia.googlecode.com/svn/trunk@13760 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2014-03-12 09:42:01 +00:00
parent ad07e69d4c
commit 79fbb40bca
10 changed files with 168 additions and 88 deletions

View File

@ -30,24 +30,49 @@ public:
SK_DECLARE_INST_COUNT(SkDrawLooper)
/**
* Called right before something is being drawn. This will be followed by
* calls to next() until next() returns false.
* Holds state during a draw. Users call next() until it returns false.
*
* Subclasses of SkDrawLooper should create a subclass of this object to
* hold state specific to their subclass.
*/
virtual void init(SkCanvas*) = 0;
class SK_API Context : public SkNoncopyable {
public:
Context() {}
virtual ~Context() {}
/**
* Called in a loop (after init()). Each time true is returned, the object
* is drawn (possibly with a modified canvas and/or paint). When false is
* finally returned, drawing for the object stops.
* Called in a loop on objects returned by SkDrawLooper::createContext().
* Each time true is returned, the object is drawn (possibly with a modified
* canvas and/or paint). When false is finally returned, drawing for the object
* stops.
*
* On each call, the paint will be in its original state, but the canvas
* will be as it was following the previous call to next() or init().
* On each call, the paint will be in its original state, but the
* canvas will be as it was following the previous call to next() or
* createContext().
*
* The implementation must ensure that, when next() finally returns false,
* that the canvas has been restored to the state it was initially, before
* init() was first called.
* The implementation must ensure that, when next() finally returns
* false, the canvas has been restored to the state it was
* initially, before createContext() was first called.
*/
virtual bool next(SkCanvas*, SkPaint* paint) = 0;
virtual bool next(SkCanvas* canvas, SkPaint* paint) = 0;
};
/**
* Called right before something is being drawn. Returns a Context
* whose next() method should be called until it returns false.
* The caller has to ensure that the storage pointer provides enough
* memory for the Context. The required size can be queried by calling
* contextSize(). It is also the caller's responsibility to destroy the
* object after use.
*/
virtual Context* createContext(SkCanvas*, void* storage) const = 0;
/**
* Returns the number of bytes needed to store subclasses of Context (belonging to the
* corresponding SkDrawLooper subclass).
*/
virtual size_t contextSize() const = 0;
/**
* The fast bounds functions are used to enable the paint to be culled early
@ -59,9 +84,9 @@ public:
* storage rect, where the storage rect is with the union of the src rect
* and the looper's bounding rect.
*/
virtual bool canComputeFastBounds(const SkPaint& paint);
virtual bool canComputeFastBounds(const SkPaint& paint) const;
virtual void computeFastBounds(const SkPaint& paint,
const SkRect& src, SkRect* dst);
const SkRect& src, SkRect* dst) const;
SkDEVCODE(virtual void toString(SkString* str) const = 0;)
SK_DEFINE_FLATTENABLE_TYPE(SkDrawLooper)

View File

@ -43,9 +43,9 @@ public:
uint32_t flags = kNone_BlurFlag);
virtual ~SkBlurDrawLooper();
// overrides from SkDrawLooper
virtual void init(SkCanvas*);
virtual bool next(SkCanvas*, SkPaint* paint);
virtual SkDrawLooper::Context* createContext(SkCanvas*, void* storage) const SK_OVERRIDE;
virtual size_t contextSize() const SK_OVERRIDE { return sizeof(BlurDrawLooperContext); }
SK_DEVELOPER_TO_STRING()
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurDrawLooper)
@ -66,7 +66,17 @@ private:
kAfterEdge,
kDone
};
class BlurDrawLooperContext : public SkDrawLooper::Context {
public:
explicit BlurDrawLooperContext(const SkBlurDrawLooper* looper);
virtual bool next(SkCanvas* canvas, SkPaint* paint) SK_OVERRIDE;
private:
const SkBlurDrawLooper* fLooper;
State fState;
};
void init(SkScalar sigma, SkScalar dx, SkScalar dy, SkColor color, uint32_t flags);

View File

@ -94,9 +94,9 @@ public:
/// Similar to addLayer, but adds a layer to the top.
SkPaint* addLayerOnTop(const LayerInfo&);
// overrides from SkDrawLooper
virtual void init(SkCanvas*);
virtual bool next(SkCanvas*, SkPaint* paint);
virtual SkDrawLooper::Context* createContext(SkCanvas*, void* storage) const SK_OVERRIDE;
virtual size_t contextSize() const SK_OVERRIDE { return sizeof(LayerDrawLooperContext); }
SK_DEVELOPER_TO_STRING()
@ -118,9 +118,18 @@ private:
int fCount;
// state-machine during the init/next cycle
class LayerDrawLooperContext : public SkDrawLooper::Context {
public:
explicit LayerDrawLooperContext(const SkLayerDrawLooper* looper);
protected:
virtual bool next(SkCanvas*, SkPaint* paint) SK_OVERRIDE;
private:
Rec* fCurrRec;
static void ApplyInfo(SkPaint* dst, const SkPaint& src, const LayerInfo&);
};
class MyRegistrar : public SkFlattenable::Registrar {
public:

View File

@ -19,6 +19,7 @@
#include "SkPicture.h"
#include "SkRasterClip.h"
#include "SkRRect.h"
#include "SkSmallAllocator.h"
#include "SkSurface_Base.h"
#include "SkTemplates.h"
#include "SkTextFormatParams.h"
@ -337,7 +338,6 @@ public:
bool skipLayerForImageFilter = false,
const SkRect* bounds = NULL) : fOrigPaint(paint) {
fCanvas = canvas;
fLooper = paint.getLooper();
fFilter = canvas->getDrawFilter();
fPaint = NULL;
fSaveCount = canvas->getSaveCount();
@ -354,10 +354,13 @@ public:
fDoClearImageFilter = true;
}
if (fLooper) {
fLooper->init(canvas);
if (SkDrawLooper* looper = paint.getLooper()) {
void* buffer = fLooperContextAllocator.reserveT<SkDrawLooper::Context>(
looper->contextSize());
fLooperContext = looper->createContext(canvas, buffer);
fIsSimple = false;
} else {
fLooperContext = NULL;
// can we be marked as simple?
fIsSimple = !fFilter && !fDoClearImageFilter;
}
@ -391,13 +394,14 @@ private:
SkLazyPaint fLazyPaint;
SkCanvas* fCanvas;
const SkPaint& fOrigPaint;
SkDrawLooper* fLooper;
SkDrawFilter* fFilter;
const SkPaint* fPaint;
int fSaveCount;
bool fDoClearImageFilter;
bool fDone;
bool fIsSimple;
SkDrawLooper::Context* fLooperContext;
SkSmallAllocator<1, 32> fLooperContextAllocator;
bool doNext(SkDrawFilter::Type drawType);
};
@ -405,7 +409,7 @@ private:
bool AutoDrawLooper::doNext(SkDrawFilter::Type drawType) {
fPaint = NULL;
SkASSERT(!fIsSimple);
SkASSERT(fLooper || fFilter || fDoClearImageFilter);
SkASSERT(fLooperContext || fFilter || fDoClearImageFilter);
SkPaint* paint = fLazyPaint.set(fOrigPaint);
@ -413,7 +417,7 @@ bool AutoDrawLooper::doNext(SkDrawFilter::Type drawType) {
paint->setImageFilter(NULL);
}
if (fLooper && !fLooper->next(fCanvas, paint)) {
if (fLooperContext && !fLooperContext->next(fCanvas, paint)) {
fDone = true;
return false;
}
@ -422,7 +426,7 @@ bool AutoDrawLooper::doNext(SkDrawFilter::Type drawType) {
fDone = true;
return false;
}
if (NULL == fLooper) {
if (NULL == fLooperContext) {
// no looper means we only draw once
fDone = true;
}
@ -430,7 +434,7 @@ bool AutoDrawLooper::doNext(SkDrawFilter::Type drawType) {
fPaint = paint;
// if we only came in here for the imagefilter, mark us as done
if (!fLooper && !fFilter) {
if (!fLooperContext && !fFilter) {
fDone = true;
}

View File

@ -10,14 +10,17 @@
#include "SkMatrix.h"
#include "SkPaint.h"
#include "SkRect.h"
#include "SkSmallAllocator.h"
bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) {
bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) const {
SkCanvas canvas;
SkSmallAllocator<1, 32> allocator;
void* buffer = allocator.reserveT<SkDrawLooper::Context>(this->contextSize());
this->init(&canvas);
SkDrawLooper::Context* context = this->createContext(&canvas, buffer);
for (;;) {
SkPaint p(paint);
if (this->next(&canvas, &p)) {
if (context->next(&canvas, &p)) {
p.setLooper(NULL);
if (!p.canComputeFastBounds()) {
return false;
@ -30,14 +33,16 @@ bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) {
}
void SkDrawLooper::computeFastBounds(const SkPaint& paint, const SkRect& src,
SkRect* dst) {
SkRect* dst) const {
SkCanvas canvas;
SkSmallAllocator<1, 32> allocator;
void* buffer = allocator.reserveT<SkDrawLooper::Context>(this->contextSize());
*dst = src; // catch case where there are no loops
this->init(&canvas);
SkDrawLooper::Context* context = this->createContext(&canvas, buffer);
for (bool firstTime = true;; firstTime = false) {
SkPaint p(paint);
if (this->next(&canvas, &p)) {
if (context->next(&canvas, &p)) {
SkRect r(src);
p.setLooper(NULL);

View File

@ -96,20 +96,21 @@ public:
return static_cast<T*>(buf);
}
private:
/*
* Helper function to provide space for one T. The space will be in
* fStorage if there is room, or on the heap otherwise. Either way, this
* class will call ~T() in its destructor and free the heap allocation if
* necessary.
* Reserve a specified amount of space (must be enough space for one T).
* The space will be in fStorage if there is room, or on the heap otherwise.
* Either way, this class will call ~T() in its destructor and free the heap
* allocation if necessary.
* Unlike createT(), this method will not call the constructor of T.
*/
template<typename T> void* reserveT() {
template<typename T> void* reserveT(size_t storageRequired = sizeof(T)) {
SkASSERT(fNumObjects < kMaxObjects);
SkASSERT(storageRequired >= sizeof(T));
if (kMaxObjects == fNumObjects) {
return NULL;
}
const size_t storageRemaining = SkAlign4(kTotalBytes) - fStorageUsed;
const size_t storageRequired = SkAlign4(sizeof(T));
storageRequired = SkAlign4(storageRequired);
Rec* rec = &fRecs[fNumObjects];
if (storageRequired > storageRemaining) {
// Allocate on the heap. Ideally we want to avoid this situation,

View File

@ -33,7 +33,6 @@ void SkBlurDrawLooper::init(SkScalar sigma, SkScalar dx, SkScalar dy,
fDy = dy;
fBlurColor = color;
fBlurFlags = flags;
fState = kDone;
SkASSERT(flags <= kAll_BlurFlag);
if (sigma > 0) {
@ -90,11 +89,16 @@ void SkBlurDrawLooper::flatten(SkWriteBuffer& buffer) const {
buffer.writeUInt(fBlurFlags);
}
void SkBlurDrawLooper::init(SkCanvas*) {
fState = kBeforeEdge;
SkDrawLooper::Context* SkBlurDrawLooper::createContext(SkCanvas*, void* storage) const {
return SkNEW_PLACEMENT_ARGS(storage, BlurDrawLooperContext, (this));
}
bool SkBlurDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
SkBlurDrawLooper::BlurDrawLooperContext::BlurDrawLooperContext(
const SkBlurDrawLooper* looper)
: fLooper(looper), fState(SkBlurDrawLooper::kBeforeEdge) {}
bool SkBlurDrawLooper::BlurDrawLooperContext::next(SkCanvas* canvas,
SkPaint* paint) {
switch (fState) {
case kBeforeEdge:
// we do nothing if a maskfilter is already installed
@ -104,23 +108,23 @@ bool SkBlurDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
}
#ifdef SK_BUILD_FOR_ANDROID
SkColor blurColor;
blurColor = fBlurColor;
blurColor = fLooper->fBlurColor;
if (SkColorGetA(blurColor) == 255) {
blurColor = SkColorSetA(blurColor, paint->getAlpha());
}
paint->setColor(blurColor);
#else
paint->setColor(fBlurColor);
paint->setColor(fLooper->fBlurColor);
#endif
paint->setMaskFilter(fBlur);
paint->setColorFilter(fColorFilter);
paint->setMaskFilter(fLooper->fBlur);
paint->setColorFilter(fLooper->fColorFilter);
canvas->save(SkCanvas::kMatrix_SaveFlag);
if (fBlurFlags & kIgnoreTransform_BlurFlag) {
if (fLooper->fBlurFlags & kIgnoreTransform_BlurFlag) {
SkMatrix transform(canvas->getTotalMatrix());
transform.postTranslate(fDx, fDy);
transform.postTranslate(fLooper->fDx, fLooper->fDy);
canvas->setMatrix(transform);
} else {
canvas->translate(fDx, fDy);
canvas->translate(fLooper->fDx, fLooper->fDy);
}
fState = kAfterEdge;
return true;

View File

@ -24,8 +24,7 @@ SkLayerDrawLooper::LayerInfo::LayerInfo() {
SkLayerDrawLooper::SkLayerDrawLooper()
: fRecs(NULL),
fTopRec(NULL),
fCount(0),
fCurrRec(NULL) {
fCount(0) {
}
SkLayerDrawLooper::~SkLayerDrawLooper() {
@ -75,9 +74,9 @@ SkPaint* SkLayerDrawLooper::addLayerOnTop(const LayerInfo& info) {
return &rec->fPaint;
}
void SkLayerDrawLooper::init(SkCanvas* canvas) {
fCurrRec = fRecs;
SkLayerDrawLooper::Context* SkLayerDrawLooper::createContext(SkCanvas* canvas, void* storage) const {
canvas->save(SkCanvas::kMatrix_SaveFlag);
return SkNEW_PLACEMENT_ARGS(storage, LayerDrawLooperContext, (this));
}
static SkColor xferColor(SkColor src, SkColor dst, SkXfermode::Mode mode) {
@ -98,8 +97,8 @@ static SkColor xferColor(SkColor src, SkColor dst, SkXfermode::Mode mode) {
// Even with kEntirePaint_Bits, we always ensure that the master paint's
// text-encoding is respected, since that controls how we interpret the
// text/length parameters of a draw[Pos]Text call.
void SkLayerDrawLooper::ApplyInfo(SkPaint* dst, const SkPaint& src,
const LayerInfo& info) {
void SkLayerDrawLooper::LayerDrawLooperContext::ApplyInfo(
SkPaint* dst, const SkPaint& src, const LayerInfo& info) {
dst->setColor(xferColor(src.getColor(), dst->getColor(), info.fColorMode));
@ -167,7 +166,11 @@ static void postTranslate(SkCanvas* canvas, SkScalar dx, SkScalar dy) {
canvas->setMatrix(m);
}
bool SkLayerDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
SkLayerDrawLooper::LayerDrawLooperContext::LayerDrawLooperContext(
const SkLayerDrawLooper* looper) : fCurrRec(looper->fRecs) {}
bool SkLayerDrawLooper::LayerDrawLooperContext::next(SkCanvas* canvas,
SkPaint* paint) {
canvas->restore();
if (NULL == fCurrRec) {
return false;
@ -180,7 +183,8 @@ bool SkLayerDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
postTranslate(canvas, fCurrRec->fInfo.fOffset.fX,
fCurrRec->fInfo.fOffset.fY);
} else {
canvas->translate(fCurrRec->fInfo.fOffset.fX, fCurrRec->fInfo.fOffset.fY);
canvas->translate(fCurrRec->fInfo.fOffset.fX,
fCurrRec->fInfo.fOffset.fY);
}
fCurrRec = fCurrRec->fNext;

View File

@ -15,6 +15,7 @@
#include "SkRect.h"
#include "SkRefCnt.h"
#include "SkScalar.h"
#include "SkSmallAllocator.h"
#include "SkXfermode.h"
#include "Test.h"
@ -57,10 +58,12 @@ static void test_frontToBack(skiatest::Reporter* reporter) {
SkCanvas canvas(&device);
SkPaint paint;
SkAutoTUnref<SkLayerDrawLooper> looper(looperBuilder.detachLooper());
looper->init(&canvas);
SkSmallAllocator<1, 32> allocator;
void* buffer = allocator.reserveT<SkDrawLooper::Context>(looper->contextSize());
SkDrawLooper::Context* context = looper->createContext(&canvas, buffer);
// The back layer should come first.
REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrc_Mode));
canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
REPORTER_ASSERT(reporter, 10.0f == device.fLastMatrix.getTranslateX());
@ -68,14 +71,14 @@ static void test_frontToBack(skiatest::Reporter* reporter) {
paint.reset();
// Then the front layer.
REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode));
canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
REPORTER_ASSERT(reporter, 0.0f == device.fLastMatrix.getTranslateX());
REPORTER_ASSERT(reporter, 0.0f == device.fLastMatrix.getTranslateY());
// Only two layers were added, so that should be the end.
REPORTER_ASSERT(reporter, !looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, !context->next(&canvas, &paint));
}
static void test_backToFront(skiatest::Reporter* reporter) {
@ -95,10 +98,12 @@ static void test_backToFront(skiatest::Reporter* reporter) {
SkCanvas canvas(&device);
SkPaint paint;
SkAutoTUnref<SkLayerDrawLooper> looper(looperBuilder.detachLooper());
looper->init(&canvas);
SkSmallAllocator<1, 32> allocator;
void* buffer = allocator.reserveT<SkDrawLooper::Context>(looper->contextSize());
SkDrawLooper::Context* context = looper->createContext(&canvas, buffer);
// The back layer should come first.
REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode));
canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
REPORTER_ASSERT(reporter, 0.0f == device.fLastMatrix.getTranslateX());
@ -106,14 +111,14 @@ static void test_backToFront(skiatest::Reporter* reporter) {
paint.reset();
// Then the front layer.
REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrc_Mode));
canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
REPORTER_ASSERT(reporter, 10.0f == device.fLastMatrix.getTranslateX());
REPORTER_ASSERT(reporter, 20.0f == device.fLastMatrix.getTranslateY());
// Only two layers were added, so that should be the end.
REPORTER_ASSERT(reporter, !looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, !context->next(&canvas, &paint));
}
static void test_mixed(skiatest::Reporter* reporter) {
@ -133,10 +138,12 @@ static void test_mixed(skiatest::Reporter* reporter) {
SkCanvas canvas(&device);
SkPaint paint;
SkAutoTUnref<SkLayerDrawLooper> looper(looperBuilder.detachLooper());
looper->init(&canvas);
SkSmallAllocator<1, 32> allocator;
void* buffer = allocator.reserveT<SkDrawLooper::Context>(looper->contextSize());
SkDrawLooper::Context* context = looper->createContext(&canvas, buffer);
// The back layer should come first.
REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode));
canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
REPORTER_ASSERT(reporter, 0.0f == device.fLastMatrix.getTranslateX());
@ -144,14 +151,14 @@ static void test_mixed(skiatest::Reporter* reporter) {
paint.reset();
// Then the front layer.
REPORTER_ASSERT(reporter, looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, context->next(&canvas, &paint));
REPORTER_ASSERT(reporter, SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrc_Mode));
canvas.drawRect(SkRect::MakeWH(50.0f, 50.0f), paint);
REPORTER_ASSERT(reporter, 10.0f == device.fLastMatrix.getTranslateX());
REPORTER_ASSERT(reporter, 20.0f == device.fLastMatrix.getTranslateY());
// Only two layers were added, so that should be the end.
REPORTER_ASSERT(reporter, !looper->next(&canvas, &paint));
REPORTER_ASSERT(reporter, !context->next(&canvas, &paint));
}
DEF_TEST(LayerDrawLooper, reporter) {

View File

@ -7,6 +7,7 @@
#include "SkCanvas.h"
#include "SkDrawLooper.h"
#include "SkTypes.h"
#include "Test.h"
/*
@ -14,12 +15,25 @@
*/
class TestLooper : public SkDrawLooper {
public:
bool fOnce;
virtual void init(SkCanvas*) SK_OVERRIDE {
fOnce = true;
virtual SkDrawLooper::Context* createContext(SkCanvas*, void* storage) const SK_OVERRIDE {
return SkNEW_PLACEMENT(storage, TestDrawLooperContext);
}
virtual size_t contextSize() const SK_OVERRIDE { return sizeof(TestDrawLooperContext); }
#ifdef SK_DEVELOPER
virtual void toString(SkString* str) const SK_OVERRIDE {
str->append("TestLooper:");
}
#endif
private:
class TestDrawLooperContext : public SkDrawLooper::Context {
public:
TestDrawLooperContext() : fOnce(true) {}
virtual ~TestDrawLooperContext() {}
virtual bool next(SkCanvas* canvas, SkPaint*) SK_OVERRIDE {
if (fOnce) {
fOnce = false;
@ -28,12 +42,9 @@ public:
}
return false;
}
#ifdef SK_DEVELOPER
virtual void toString(SkString* str) const SK_OVERRIDE {
str->append("TestLooper:");
}
#endif
private:
bool fOnce;
};
SK_DECLARE_UNFLATTENABLE_OBJECT()
};