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:
Michael Ludwig 2021-08-23 20:13:23 -04:00 committed by SkCQ
parent f8a550491e
commit 8c75b2d30e
5 changed files with 89 additions and 194 deletions

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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.