Simplify conservative clip tracking for SkNoPixelsDevice
Deletes SkConservativeClip as a type, it was only used by SkNoPixelsDevice at this point. Simplifies the clip tracking to only rely on intersect/difference ops (but also expands it to support updating overall bounds if the diff op is a sufficiently large rectangle). Adds SkRect::roundIn() -> SkIRect to match the exposed API for round() and roundOut(). Bug: skia:10205 Change-Id: I1337a51a8a4e51f94fe2b5f9ab29a0b5058b8094 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/437737 Reviewed-by: Mike Reed <reed@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com> Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
parent
f8a550491e
commit
8c75b2d30e
@ -1297,6 +1297,18 @@ public:
|
||||
this->roundOut(&ir);
|
||||
return ir;
|
||||
}
|
||||
/** Sets SkIRect by rounding up fLeft and fTop; and discarding the fractional portion
|
||||
of fRight and fBottom, using
|
||||
(SkScalarCeilToInt(fLeft), SkScalarCeilToInt(fTop),
|
||||
SkScalarFloorToInt(fRight), SkScalarFloorToInt(fBottom)).
|
||||
|
||||
@return rounded SkIRect
|
||||
*/
|
||||
SkIRect roundIn() const {
|
||||
SkIRect ir;
|
||||
this->roundIn(&ir);
|
||||
return ir;
|
||||
}
|
||||
|
||||
/** Swaps fLeft and fRight if fLeft is greater than fRight; and swaps
|
||||
fTop and fBottom if fTop is greater than fBottom. Result may be empty;
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "src/core/SkMatrixPriv.h"
|
||||
#include "src/core/SkPathPriv.h"
|
||||
#include "src/core/SkRasterClip.h"
|
||||
#include "src/core/SkRectPriv.h"
|
||||
#include "src/core/SkSpecialImage.h"
|
||||
#include "src/core/SkTLazy.h"
|
||||
#include "src/core/SkTextBlobPriv.h"
|
||||
@ -509,48 +510,47 @@ void SkNoPixelsDevice::onRestore() {
|
||||
}
|
||||
}
|
||||
|
||||
SkConservativeClip& SkNoPixelsDevice::writableClip() {
|
||||
SkNoPixelsDevice::ClipState& SkNoPixelsDevice::writableClip() {
|
||||
SkASSERT(!fClipStack.empty());
|
||||
ClipState& current = fClipStack.back();
|
||||
if (current.fDeferredSaveCount > 0) {
|
||||
current.fDeferredSaveCount--;
|
||||
return fClipStack.push_back(ClipState(current.fClip)).fClip;
|
||||
// Stash current state in case 'current' moves during a resize
|
||||
SkIRect bounds = current.fClipBounds;
|
||||
bool aa = current.fIsAA;
|
||||
bool rect = current.fIsRect;
|
||||
return fClipStack.emplace_back(bounds, aa, rect);
|
||||
} else {
|
||||
return current.fClip;
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
void SkNoPixelsDevice::onClipRect(const SkRect& rect, SkClipOp op, bool aa) {
|
||||
this->writableClip().opRect(rect, this->localToDevice(), this->bounds(), (SkRegion::Op) op, aa);
|
||||
this->writableClip().op(op, this->localToDevice44(), rect,
|
||||
aa, /*fillsBounds=*/true);
|
||||
}
|
||||
|
||||
void SkNoPixelsDevice::onClipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
|
||||
this->writableClip().opRRect(rrect, this->localToDevice(), this->bounds(),
|
||||
(SkRegion::Op) op, aa);
|
||||
this->writableClip().op(op, this->localToDevice44(), rrect.getBounds(),
|
||||
aa, /*fillsBounds=*/rrect.isRect());
|
||||
}
|
||||
|
||||
void SkNoPixelsDevice::onClipPath(const SkPath& path, SkClipOp op, bool aa) {
|
||||
this->writableClip().opPath(path, this->localToDevice(), this->bounds(),
|
||||
(SkRegion::Op) op, aa);
|
||||
// Toggle op if the path is inverse filled
|
||||
if (path.isInverseFillType()) {
|
||||
op = (op == SkClipOp::kDifference ? SkClipOp::kIntersect : SkClipOp::kDifference);
|
||||
}
|
||||
this->writableClip().op(op, this->localToDevice44(), path.getBounds(),
|
||||
aa, /*fillsBounds=*/false);
|
||||
}
|
||||
|
||||
void SkNoPixelsDevice::onClipRegion(const SkRegion& globalRgn, SkClipOp op) {
|
||||
if (globalRgn.isEmpty()) {
|
||||
this->writableClip().setEmpty();
|
||||
} else if (this->isPixelAlignedToGlobal()) {
|
||||
SkIPoint origin = this->getOrigin();
|
||||
SkRegion deviceRgn(globalRgn);
|
||||
deviceRgn.translate(-origin.fX, -origin.fY);
|
||||
this->writableClip().opRegion(deviceRgn, (SkRegion::Op) op);
|
||||
} else {
|
||||
this->writableClip().opRect(SkRect::Make(globalRgn.getBounds()),
|
||||
this->globalToDevice().asM33(), this->bounds(),
|
||||
(SkRegion::Op) op, false);
|
||||
}
|
||||
this->writableClip().op(op, this->globalToDevice(), SkRect::Make(globalRgn.getBounds()),
|
||||
/*isAA=*/false, /*fillsBounds=*/globalRgn.isRect());
|
||||
}
|
||||
|
||||
void SkNoPixelsDevice::onClipShader(sk_sp<SkShader> shader) {
|
||||
this->writableClip().opShader(std::move(shader));
|
||||
this->writableClip().fIsRect = false;
|
||||
}
|
||||
|
||||
void SkNoPixelsDevice::onReplaceClip(const SkIRect& rect) {
|
||||
@ -558,16 +558,51 @@ void SkNoPixelsDevice::onReplaceClip(const SkIRect& rect) {
|
||||
if (!deviceRect.intersect(this->bounds())) {
|
||||
deviceRect.setEmpty();
|
||||
}
|
||||
this->writableClip().setRect(deviceRect);
|
||||
auto& clip = this->writableClip();
|
||||
clip.fClipBounds = deviceRect;
|
||||
clip.fIsRect = true;
|
||||
clip.fIsAA = false;
|
||||
}
|
||||
|
||||
SkBaseDevice::ClipType SkNoPixelsDevice::onGetClipType() const {
|
||||
const auto& clip = this->clip();
|
||||
if (clip.isEmpty()) {
|
||||
if (clip.fClipBounds.isEmpty()) {
|
||||
return ClipType::kEmpty;
|
||||
} else if (clip.isRect()) {
|
||||
} else if (clip.fIsRect) {
|
||||
return ClipType::kRect;
|
||||
} else {
|
||||
return ClipType::kComplex;
|
||||
}
|
||||
}
|
||||
|
||||
void SkNoPixelsDevice::ClipState::op(SkClipOp op, const SkM44& transform, const SkRect& bounds,
|
||||
bool isAA, bool fillsBounds) {
|
||||
const bool isRect = fillsBounds && SkMatrixPriv::IsScaleTranslateAsM33(transform);
|
||||
fIsAA |= isAA;
|
||||
|
||||
SkRect devBounds = bounds.isEmpty() ? SkRect::MakeEmpty()
|
||||
: SkMatrixPriv::MapRect(transform, bounds);
|
||||
if (op == SkClipOp::kIntersect) {
|
||||
if (!fClipBounds.intersect(isAA ? devBounds.roundOut() : devBounds.round())) {
|
||||
fClipBounds.setEmpty();
|
||||
}
|
||||
// A rectangular clip remains rectangular if the intersection is a rect
|
||||
fIsRect &= isRect;
|
||||
} else if (isRect) {
|
||||
// Conservatively, we can leave the clip bounds unchanged and respect the difference op.
|
||||
// But, if we're subtracting out an axis-aligned rectangle that fully spans our existing
|
||||
// clip on an axis, we can shrink the clip bounds.
|
||||
SkASSERT(op == SkClipOp::kDifference);
|
||||
SkIRect difference;
|
||||
if (SkRectPriv::Subtract(fClipBounds, isAA ? devBounds.roundIn() : devBounds.round(),
|
||||
&difference)) {
|
||||
fClipBounds = difference;
|
||||
} else {
|
||||
// The difference couldn't be represented as a rect
|
||||
fIsRect = false;
|
||||
}
|
||||
} else {
|
||||
// A non-rect shape was applied
|
||||
fIsRect = false;
|
||||
}
|
||||
}
|
||||
|
@ -496,16 +496,16 @@ protected:
|
||||
void onClipRegion(const SkRegion& globalRgn, SkClipOp op) override;
|
||||
void onClipShader(sk_sp<SkShader> shader) override;
|
||||
void onReplaceClip(const SkIRect& rect) override;
|
||||
bool onClipIsAA() const override { return this->clip().isAA(); }
|
||||
bool onClipIsAA() const override { return this->clip().fIsAA; }
|
||||
bool onClipIsWideOpen() const override {
|
||||
return this->clip().isRect() &&
|
||||
return this->clip().fIsRect &&
|
||||
this->onDevClipBounds() == this->bounds();
|
||||
}
|
||||
void onAsRgnClip(SkRegion* rgn) const override {
|
||||
rgn->setRect(this->onDevClipBounds());
|
||||
}
|
||||
ClipType onGetClipType() const override;
|
||||
SkIRect onDevClipBounds() const override { return this->clip().getBounds(); }
|
||||
SkIRect onDevClipBounds() const override { return this->clip().fClipBounds; }
|
||||
|
||||
void drawPaint(const SkPaint& paint) override {}
|
||||
void drawPoints(SkCanvas::PointMode, size_t, const SkPoint[], const SkPaint&) override {}
|
||||
@ -529,20 +529,27 @@ protected:
|
||||
|
||||
private:
|
||||
struct ClipState {
|
||||
SkConservativeClip fClip;
|
||||
int fDeferredSaveCount = 0;
|
||||
SkIRect fClipBounds;
|
||||
int fDeferredSaveCount;
|
||||
bool fIsAA;
|
||||
bool fIsRect;
|
||||
|
||||
ClipState() = default;
|
||||
explicit ClipState(const SkConservativeClip& clip) : fClip(clip) {}
|
||||
ClipState(const SkIRect& bounds, bool isAA, bool isRect)
|
||||
: fClipBounds(bounds)
|
||||
, fDeferredSaveCount(0)
|
||||
, fIsAA(isAA)
|
||||
, fIsRect(isRect) {}
|
||||
|
||||
void op(SkClipOp op, const SkM44& transform, const SkRect& bounds,
|
||||
bool isAA, bool fillsBounds);
|
||||
};
|
||||
|
||||
const SkConservativeClip& clip() const { return fClipStack.back().fClip; }
|
||||
SkConservativeClip& writableClip();
|
||||
const ClipState& clip() const { return fClipStack.back(); }
|
||||
ClipState& writableClip();
|
||||
|
||||
void resetClipStack() {
|
||||
fClipStack.reset();
|
||||
ClipState& state = fClipStack.push_back();
|
||||
state.fClip.setRect(this->bounds());
|
||||
fClipStack.emplace_back(this->bounds(), /*isAA=*/false, /*isRect=*/true);
|
||||
}
|
||||
|
||||
SkSTArray<4, ClipState> fClipStack;
|
||||
|
@ -9,129 +9,6 @@
|
||||
#include "src/core/SkRasterClip.h"
|
||||
#include "src/core/SkRegionPriv.h"
|
||||
|
||||
enum MutateResult {
|
||||
kDoNothing_MutateResult,
|
||||
kReplaceClippedAgainstGlobalBounds_MutateResult,
|
||||
kContinue_MutateResult,
|
||||
};
|
||||
|
||||
static MutateResult mutate_conservative_op(SkRegion::Op* op, bool inverseFilled) {
|
||||
if (inverseFilled) {
|
||||
switch (*op) {
|
||||
case SkRegion::kIntersect_Op:
|
||||
case SkRegion::kDifference_Op:
|
||||
// These ops can only shrink the current clip. So leaving
|
||||
// the clip unchanged conservatively respects the contract.
|
||||
return kDoNothing_MutateResult;
|
||||
case SkRegion::kUnion_Op:
|
||||
case SkRegion::kReplace_Op:
|
||||
case SkRegion::kReverseDifference_Op:
|
||||
case SkRegion::kXOR_Op: {
|
||||
// These ops can grow the current clip up to the extents of
|
||||
// the input clip, which is inverse filled, so we just set
|
||||
// the current clip to the device bounds.
|
||||
*op = SkRegion::kReplace_Op;
|
||||
return kReplaceClippedAgainstGlobalBounds_MutateResult;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not inverse filled
|
||||
switch (*op) {
|
||||
case SkRegion::kIntersect_Op:
|
||||
case SkRegion::kUnion_Op:
|
||||
case SkRegion::kReplace_Op:
|
||||
return kContinue_MutateResult;
|
||||
case SkRegion::kDifference_Op:
|
||||
// Difference can only shrink the current clip.
|
||||
// Leaving clip unchanged conservatively fullfills the contract.
|
||||
return kDoNothing_MutateResult;
|
||||
case SkRegion::kReverseDifference_Op:
|
||||
// To reverse, we swap in the bounds with a replace op.
|
||||
// As with difference, leave it unchanged.
|
||||
*op = SkRegion::kReplace_Op;
|
||||
return kContinue_MutateResult;
|
||||
case SkRegion::kXOR_Op:
|
||||
// Be conservative, based on (A XOR B) always included in (A union B),
|
||||
// which is always included in (bounds(A) union bounds(B))
|
||||
*op = SkRegion::kUnion_Op;
|
||||
return kContinue_MutateResult;
|
||||
}
|
||||
}
|
||||
SkASSERT(false); // unknown op
|
||||
return kDoNothing_MutateResult;
|
||||
}
|
||||
|
||||
void SkConservativeClip::opRect(const SkRect& localRect, const SkMatrix& ctm,
|
||||
const SkIRect& devBounds, SkRegion::Op op, bool doAA) {
|
||||
this->applyOpParams(op, doAA ? ClipAA::kYes : ClipAA::kNo,
|
||||
ctm.isScaleTranslate() ? IsRect::kYes : IsRect::kNo);
|
||||
SkIRect ir;
|
||||
switch (mutate_conservative_op(&op, false)) {
|
||||
case kDoNothing_MutateResult:
|
||||
return;
|
||||
case kReplaceClippedAgainstGlobalBounds_MutateResult:
|
||||
ir = devBounds;
|
||||
break;
|
||||
case kContinue_MutateResult: {
|
||||
SkRect devRect;
|
||||
ctm.mapRect(&devRect, localRect);
|
||||
ir = doAA ? devRect.roundOut() : devRect.round();
|
||||
} break;
|
||||
}
|
||||
this->opIRect(ir, op);
|
||||
}
|
||||
|
||||
void SkConservativeClip::opRRect(const SkRRect& rrect, const SkMatrix& ctm,
|
||||
const SkIRect& devBounds, SkRegion::Op op, bool doAA) {
|
||||
this->applyOpParams(op, doAA ? ClipAA::kYes : ClipAA::kNo,
|
||||
(rrect.isRect() && ctm.isScaleTranslate()) ? IsRect::kYes : IsRect:: kNo);
|
||||
this->opRect(rrect.getBounds(), ctm, devBounds, op, doAA);
|
||||
}
|
||||
|
||||
void SkConservativeClip::opPath(const SkPath& path, const SkMatrix& ctm, const SkIRect& devBounds,
|
||||
SkRegion::Op op, bool doAA) {
|
||||
this->applyOpParams(op, doAA ? ClipAA::kYes : ClipAA::kNo, IsRect::kNo);
|
||||
SkIRect ir;
|
||||
switch (mutate_conservative_op(&op, path.isInverseFillType())) {
|
||||
case kDoNothing_MutateResult:
|
||||
return;
|
||||
case kReplaceClippedAgainstGlobalBounds_MutateResult:
|
||||
ir = devBounds;
|
||||
break;
|
||||
case kContinue_MutateResult: {
|
||||
SkRect bounds = path.getBounds();
|
||||
ctm.mapRect(&bounds);
|
||||
ir = bounds.roundOut();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this->opIRect(ir, op);
|
||||
}
|
||||
|
||||
void SkConservativeClip::opRegion(const SkRegion& rgn, SkRegion::Op op) {
|
||||
this->applyOpParams(op, ClipAA::kNo, rgn.isRect() ? IsRect::kYes : IsRect::kNo);
|
||||
this->opIRect(rgn.getBounds(), op);
|
||||
}
|
||||
|
||||
void SkConservativeClip::opIRect(const SkIRect& devRect, SkRegion::Op op) {
|
||||
this->applyOpParams(op, ClipAA::kNo, IsRect::kYes);
|
||||
|
||||
if (SkRegion::kIntersect_Op == op) {
|
||||
if (!fBounds.intersect(devRect)) {
|
||||
fBounds.setEmpty();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// This may still create a complex region (which we would then take the bounds
|
||||
// Perhaps we should inline the op-logic directly to never create the rgn...
|
||||
SkRegion result;
|
||||
result.op(SkRegion(fBounds), SkRegion(devRect), op);
|
||||
fBounds = result.getBounds();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkRasterClip::SkRasterClip(const SkRasterClip& that)
|
||||
: fIsBW(that.fIsBW)
|
||||
, fIsEmpty(that.fIsEmpty)
|
||||
|
@ -15,42 +15,6 @@
|
||||
|
||||
class SkRRect;
|
||||
|
||||
class SkConservativeClip {
|
||||
SkIRect fBounds = SkIRect::MakeEmpty();
|
||||
bool fIsRect = true;
|
||||
bool fAA = false;
|
||||
|
||||
enum class ClipAA : bool { kNo = false, kYes = true };
|
||||
enum class IsRect : bool { kNo = false, kYes = true };
|
||||
|
||||
inline void applyOpParams(SkRegion::Op op, ClipAA aa, IsRect rect) {
|
||||
fAA |= (bool) aa;
|
||||
fIsRect &= (op == SkRegion::kIntersect_Op && (bool) rect);
|
||||
}
|
||||
|
||||
public:
|
||||
bool isEmpty() const { return fBounds.isEmpty(); }
|
||||
bool isRect() const { return fIsRect; }
|
||||
bool isAA() const { return fAA; }
|
||||
const SkIRect& getBounds() const { return fBounds; }
|
||||
|
||||
void setEmpty() { this->setRect(SkIRect::MakeEmpty()); }
|
||||
void setRect(const SkIRect& r) {
|
||||
fBounds = r;
|
||||
fIsRect = true;
|
||||
fAA = false;
|
||||
}
|
||||
|
||||
void opShader(sk_sp<SkShader>) {
|
||||
fIsRect = false;
|
||||
}
|
||||
void opRect(const SkRect&, const SkMatrix&, const SkIRect& limit, SkRegion::Op, bool isAA);
|
||||
void opRRect(const SkRRect&, const SkMatrix&, const SkIRect& limit, SkRegion::Op, bool isAA);
|
||||
void opPath(const SkPath&, const SkMatrix&, const SkIRect& limit, SkRegion::Op, bool isAA);
|
||||
void opRegion(const SkRegion&, SkRegion::Op);
|
||||
void opIRect(const SkIRect&, SkRegion::Op);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps a SkRegion and SkAAClip, so we have a single object that can represent either our
|
||||
* BW or antialiased clips.
|
||||
|
Loading…
Reference in New Issue
Block a user