Skip to content

Commit 51b74af

Browse files
lhkbobSkia Commit-Bot
authored andcommitted
Reland "Move conservative bounds tracking from SkCanvas to SkNoPixelsDevice"
This reverts commit 8636e13. Reason for revert: recording canvases with really big float bounds could produce a non-empty integer rect bounds that became empty after mapping it to (0,0,w,h) for the device. This meant resetForNextPictures logic of updating QR bounds directly from the input bounds allowed state to become inconsistent with computeDevClipBounds(). PS1->PS4 shows the 1-liner to just compute bounds from the device. This means that, for now, we preserve the behavior of setting the QR bounds to be actually empty. skbug.com/10997 is added to fix the underlying issue with recorders and excessively large float bounds. If that change landed first, I'd be able to reland this w/o any modifications, but have decided that it's better to have all locations that modify fQuickRejectBounds use the exact same expression. Original change's description: > Revert "Move conservative bounds tracking from SkCanvas to SkNoPixelsDevice" > > This reverts commit 11a3947. > > Reason for revert: assert during google3 tests. > > Original change's description: > > Move conservative bounds tracking from SkCanvas to SkNoPixelsDevice > > > > Change-Id: I56670b4a4159e21eaa1a58a9a3ee439298d5aa8e > > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/335863 > > Reviewed-by: Mike Reed <[email protected]> > > Commit-Queue: Michael Ludwig <[email protected]> > > [email protected],[email protected],[email protected],[email protected] > > Change-Id: I7c3a8797460113d9a8ef18d82bbbd64aba2f439c > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/338316 > Reviewed-by: Michael Ludwig <[email protected]> > Commit-Queue: Michael Ludwig <[email protected]> [email protected],[email protected],[email protected],[email protected] # Not skipping CQ checks because this is a reland. Change-Id: I1b33e128b4fb4e06b8c7a6ee9b9dcc67202674d8 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/338322 Commit-Queue: Michael Ludwig <[email protected]> Reviewed-by: Michael Ludwig <[email protected]>
1 parent ecc9108 commit 51b74af

File tree

4 files changed

+196
-72
lines changed

4 files changed

+196
-72
lines changed

include/core/SkCanvas.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2636,9 +2636,9 @@ class SK_API SkCanvas {
26362636
sk_sp<SkMarkerStack> fMarkerStack;
26372637

26382638
// the first N recs that can fit here mean we won't call malloc
2639-
static constexpr int kMCRecSize = 128; // most recent measurement
2640-
static constexpr int kMCRecCount = 32; // common depth for save/restores
2641-
static constexpr int kDeviceCMSize = 64; // most recent measurement
2639+
static constexpr int kMCRecSize = 96; // most recent measurement
2640+
static constexpr int kMCRecCount = 32; // common depth for save/restores
2641+
static constexpr int kDeviceCMSize = 64; // most recent measurement
26422642

26432643
intptr_t fMCRecStorage[kMCRecSize * kMCRecCount / sizeof(intptr_t)];
26442644
intptr_t fDeviceCMStorage[kDeviceCMSize / sizeof(intptr_t)];
@@ -2758,6 +2758,10 @@ class SK_API SkCanvas {
27582758
bool fIsScaleTranslate;
27592759
SkRect fQuickRejectBounds;
27602760

2761+
// Compute the clip's bounds based on all clipped SkDevice's reported device bounds transformed
2762+
// into the canvas' global space.
2763+
SkRect computeDeviceClipBounds() const;
2764+
27612765
class AutoValidateClip;
27622766
void validateClip() const;
27632767

src/core/SkCanvas.cpp

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,6 @@ class SkCanvas::MCRec {
230230
*/
231231
DeviceCM* fTopLayer;
232232
std::unique_ptr<BackImage> fBackImage;
233-
SkConservativeClip fRasterClip;
234233
SkM44 fMatrix;
235234
int fDeferredSaveCount;
236235

@@ -243,7 +242,7 @@ class SkCanvas::MCRec {
243242
// don't bother initializing fNext
244243
inc_rec();
245244
}
246-
MCRec(const MCRec& prev) : fRasterClip(prev.fRasterClip), fMatrix(prev.fMatrix) {
245+
MCRec(const MCRec& prev) : fMatrix(prev.fMatrix) {
247246
fLayer = nullptr;
248247
fTopLayer = prev.fTopLayer;
249248
fDeferredSaveCount = 0;
@@ -256,13 +255,12 @@ class SkCanvas::MCRec {
256255
dec_rec();
257256
}
258257

259-
void reset(const SkIRect& bounds) {
258+
void reset() {
260259
SkASSERT(fLayer);
261260
SkASSERT(fDeferredSaveCount == 0);
262261
fLayer->validate();
263262

264263
fMatrix.setIdentity();
265-
fRasterClip.setRect(bounds);
266264
}
267265
};
268266

@@ -471,37 +469,35 @@ class AutoLayerForImageFilter {
471469

472470
#define DRAW_END }
473471

474-
// TODO: conservativeUpdate is temporary to stage moving bounds tracking into SkNoPixelsDevice
475-
#define UPDATE_DEVICE_CLIP(code, conservativeUpdate) \
472+
#define UPDATE_DEVICE_CLIP(code) \
476473
do { \
477474
AutoValidateClip avc(this); \
478475
FOR_EACH_TOP_DEVICE(code); \
479-
fMCRec->fRasterClip.conservativeUpdate; \
480-
fQuickRejectBounds = qr_clip_bounds(fMCRec->fRasterClip.getBounds()); \
476+
fQuickRejectBounds = qr_clip_bounds(this->computeDeviceClipBounds()); \
481477
} while(0)
482478

483479
////////////////////////////////////////////////////////////////////////////
484480

485-
static inline SkRect qr_clip_bounds(const SkIRect& bounds) {
481+
static inline SkRect qr_clip_bounds(const SkRect& bounds) {
486482
if (bounds.isEmpty()) {
487483
return SkRect::MakeEmpty();
488484
}
489485

490486
// Expand bounds out by 1 in case we are anti-aliasing. We store the
491487
// bounds as floats to enable a faster quick reject implementation.
492488
SkRect dst;
493-
SkNx_cast<float>(Sk4i::Load(&bounds.fLeft) + Sk4i(-1,-1,1,1)).store(&dst.fLeft);
489+
(Sk4f::Load(&bounds.fLeft) + Sk4f(-1.f, -1.f, 1.f, 1.f)).store(&dst.fLeft);
494490
return dst;
495491
}
496492

497493
void SkCanvas::resetForNextPicture(const SkIRect& bounds) {
498494
this->restoreToCount(1);
499-
fMCRec->reset(bounds);
495+
fMCRec->reset();
500496

501497
// We're peering through a lot of structs here. Only at this scope do we
502498
// know that the device is a SkNoPixelsDevice.
503499
static_cast<SkNoPixelsDevice*>(fMCRec->fLayer->fDevice.get())->resetForNextPicture(bounds);
504-
fQuickRejectBounds = qr_clip_bounds(bounds);
500+
fQuickRejectBounds = qr_clip_bounds(this->computeDeviceClipBounds());
505501
fIsScaleTranslate = true;
506502
}
507503

@@ -519,7 +515,6 @@ void SkCanvas::init(sk_sp<SkBaseDevice> device) {
519515

520516
fMCRec = (MCRec*)fMCStack.push_back();
521517
new (fMCRec) MCRec;
522-
fMCRec->fRasterClip.setDeviceClipRestriction(&fClipRestrictionRect);
523518
fIsScaleTranslate = true;
524519

525520
fMCRec->fLayer = (DeviceCM*)fDeviceCMStorage;
@@ -528,19 +523,15 @@ void SkCanvas::init(sk_sp<SkBaseDevice> device) {
528523
fMCRec->fTopLayer = fMCRec->fLayer;
529524

530525
fSurfaceBase = nullptr;
531-
fQuickRejectBounds = {0, 0, 0, 0};
532526

533527
if (device) {
534528
// The root device and the canvas should always have the same pixel geometry
535529
SkASSERT(fProps.pixelGeometry() == device->surfaceProps().pixelGeometry());
536-
fMCRec->fRasterClip.setRect(device->getGlobalBounds());
537-
fQuickRejectBounds = qr_clip_bounds(device->getGlobalBounds());
538-
539530
device->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect);
540-
541531
device->setMarkerStack(fMarkerStack.get());
542532
}
543533

534+
fQuickRejectBounds = qr_clip_bounds(this->computeDeviceClipBounds());
544535
fScratchGlyphRunBuilder = std::make_unique<SkGlyphRunBuilder>();
545536
}
546537

@@ -1167,15 +1158,13 @@ void SkCanvas::internalSaveLayer(const SaveLayerRec& rec, SaveLayerStrategy stra
11671158
// replaceClip() simulates what a new top-level device's canvas would be in
11681159
// the non-recording scenario. This allows the canvas to report the expanding
11691160
// effects of image filters on the temporary clip bounds.
1170-
UPDATE_DEVICE_CLIP(device->replaceClip(ir),
1171-
setRect(ir));
1161+
UPDATE_DEVICE_CLIP(device->replaceClip(ir));
11721162
} else {
11731163
// else the layer device failed to be created, so the saveLayer() effectively
11741164
// becomes just a save(). The clipRegion() explicitly applies the bounds of the
11751165
// failed layer, without resetting the clip of the prior device that all subsequent
11761166
// nested draw calls need to respect.
1177-
UPDATE_DEVICE_CLIP(device->clipRegion(SkRegion(ir), SkClipOp::kIntersect),
1178-
opRegion(SkRegion(ir), SkRegion::kIntersect_Op));
1167+
UPDATE_DEVICE_CLIP(device->clipRegion(SkRegion(ir), SkClipOp::kIntersect));
11791168
}
11801169
}
11811170
return;
@@ -1196,10 +1185,6 @@ void SkCanvas::internalSaveLayer(const SaveLayerRec& rec, SaveLayerStrategy stra
11961185

11971186
newDevice->setOrigin(fMCRec->fMatrix, ir.fLeft, ir.fTop);
11981187
newDevice->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect);
1199-
if (boundsAffectsClip) {
1200-
fMCRec->fRasterClip.setRect(ir);
1201-
fQuickRejectBounds = qr_clip_bounds(ir);
1202-
}
12031188

12041189
if (layer->fNext) {
12051190
// need to punch a hole in the previous device, so we don't draw there, given that
@@ -1210,6 +1195,8 @@ void SkCanvas::internalSaveLayer(const SaveLayerRec& rec, SaveLayerStrategy stra
12101195
layer->fDevice->clipRegion(hole, SkClipOp::kDifference);
12111196
} while (layer->fNext);
12121197
}
1198+
1199+
fQuickRejectBounds = qr_clip_bounds(this->computeDeviceClipBounds());
12131200
}
12141201

12151202
int SkCanvas::saveLayerAlpha(const SkRect* bounds, U8CPU alpha) {
@@ -1317,7 +1304,10 @@ void SkCanvas::internalRestore() {
13171304

13181305
if (fMCRec) {
13191306
fIsScaleTranslate = SkMatrixPriv::IsScaleTranslateAsM33(fMCRec->fMatrix);
1320-
fQuickRejectBounds = qr_clip_bounds(fMCRec->fRasterClip.getBounds());
1307+
// Update the quick-reject bounds in case the restore changed the top device or the
1308+
// removed save record had included modifications to the clip stack.
1309+
fQuickRejectBounds = qr_clip_bounds(this->computeDeviceClipBounds());
1310+
this->validateClip();
13211311
}
13221312
}
13231313

@@ -1586,8 +1576,7 @@ void SkCanvas::clipRect(const SkRect& rect, SkClipOp op, bool doAA) {
15861576
void SkCanvas::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle) {
15871577
SkASSERT(rect.isSorted());
15881578
const bool isAA = kSoft_ClipEdgeStyle == edgeStyle;
1589-
UPDATE_DEVICE_CLIP(device->clipRect(rect, op, isAA), \
1590-
opRect(rect, fMCRec->fMatrix.asM33(), this->getTopLayerBounds(), (SkRegion::Op) op, isAA));
1579+
UPDATE_DEVICE_CLIP(device->clipRect(rect, op, isAA));
15911580
}
15921581

15931582
void SkCanvas::androidFramework_setDeviceClipRestriction(const SkIRect& rect) {
@@ -1597,14 +1586,12 @@ void SkCanvas::androidFramework_setDeviceClipRestriction(const SkIRect& rect) {
15971586
// removing it (i.e. rect is empty).
15981587
this->checkForDeferredSave();
15991588
}
1600-
UPDATE_DEVICE_CLIP(device->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect),\
1601-
opIRect(fClipRestrictionRect, SkRegion::kIntersect_Op));
1589+
UPDATE_DEVICE_CLIP(device->androidFramework_setDeviceClipRestriction(&fClipRestrictionRect));
16021590
}
16031591

16041592
void SkCanvas::androidFramework_replaceClip(const SkIRect& rect) {
16051593
this->checkForDeferredSave();
1606-
UPDATE_DEVICE_CLIP(device->replaceClip(rect), \
1607-
setRect(rect));
1594+
UPDATE_DEVICE_CLIP(device->replaceClip(rect));
16081595
}
16091596

16101597
void SkCanvas::clipRRect(const SkRRect& rrect, SkClipOp op, bool doAA) {
@@ -1619,8 +1606,7 @@ void SkCanvas::clipRRect(const SkRRect& rrect, SkClipOp op, bool doAA) {
16191606

16201607
void SkCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle) {
16211608
bool isAA = kSoft_ClipEdgeStyle == edgeStyle;
1622-
UPDATE_DEVICE_CLIP(device->clipRRect(rrect, op, isAA), \
1623-
opRRect(rrect, fMCRec->fMatrix.asM33(), this->getTopLayerBounds(), (SkRegion::Op)op, isAA));
1609+
UPDATE_DEVICE_CLIP(device->clipRRect(rrect, op, isAA));
16241610
}
16251611

16261612
void SkCanvas::clipPath(const SkPath& path, SkClipOp op, bool doAA) {
@@ -1650,8 +1636,7 @@ void SkCanvas::clipPath(const SkPath& path, SkClipOp op, bool doAA) {
16501636

16511637
void SkCanvas::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) {
16521638
bool isAA = kSoft_ClipEdgeStyle == edgeStyle;
1653-
UPDATE_DEVICE_CLIP(device->clipPath(path, op, isAA), \
1654-
opPath(path, fMCRec->fMatrix.asM33(), this->getTopLayerBounds(), (SkRegion::Op)op, isAA));
1639+
UPDATE_DEVICE_CLIP(device->clipPath(path, op, isAA));
16551640
}
16561641

16571642
void SkCanvas::clipShader(sk_sp<SkShader> sh, SkClipOp op) {
@@ -1672,8 +1657,7 @@ void SkCanvas::clipShader(sk_sp<SkShader> sh, SkClipOp op) {
16721657
}
16731658

16741659
void SkCanvas::onClipShader(sk_sp<SkShader> sh, SkClipOp op) {
1675-
UPDATE_DEVICE_CLIP(device->clipShader(sh, op),
1676-
opShader(sh));
1660+
UPDATE_DEVICE_CLIP(device->clipShader(sh, op));
16771661
}
16781662

16791663
void SkCanvas::clipRegion(const SkRegion& rgn, SkClipOp op) {
@@ -1682,13 +1666,12 @@ void SkCanvas::clipRegion(const SkRegion& rgn, SkClipOp op) {
16821666
}
16831667

16841668
void SkCanvas::onClipRegion(const SkRegion& rgn, SkClipOp op) {
1685-
UPDATE_DEVICE_CLIP(device->clipRegion(rgn, op),
1686-
opRegion(rgn, (SkRegion::Op) op));
1669+
UPDATE_DEVICE_CLIP(device->clipRegion(rgn, op));
16871670
}
16881671

16891672
void SkCanvas::validateClip() const {
16901673
#ifdef SK_DEBUG
1691-
SkRect tmp = qr_clip_bounds(this->fMCRec->fRasterClip.getBounds());
1674+
SkRect tmp = qr_clip_bounds(this->computeDeviceClipBounds());
16921675
if (this->isClipEmpty()) {
16931676
SkASSERT(fQuickRejectBounds.isEmpty());
16941677
} else {
@@ -1735,14 +1718,9 @@ void SkCanvas::temporary_internal_getRgnClip(SkRegion* rgn) {
17351718
///////////////////////////////////////////////////////////////////////////////
17361719

17371720
bool SkCanvas::isClipEmpty() const {
1738-
return fMCRec->fRasterClip.isEmpty();
1739-
1740-
// TODO: should we only use the conservative answer in a recording canvas?
1741-
#if 0
17421721
SkBaseDevice* dev = this->getTopDevice();
17431722
// if no device we return true
1744-
return !dev || dev->onGetClipType() == SkBaseDevice::kEmpty_ClipType;
1745-
#endif
1723+
return !dev || dev->onGetClipType() == SkBaseDevice::ClipType::kEmpty;
17461724
}
17471725

17481726
bool SkCanvas::isClipRect() const {
@@ -1841,7 +1819,28 @@ SkRect SkCanvas::getLocalClipBounds() const {
18411819
}
18421820

18431821
SkIRect SkCanvas::getDeviceClipBounds() const {
1844-
return fMCRec->fRasterClip.getBounds();
1822+
return this->computeDeviceClipBounds().roundOut();
1823+
}
1824+
1825+
SkRect SkCanvas::computeDeviceClipBounds() const {
1826+
if (this->isClipEmpty()) {
1827+
return SkRect::MakeEmpty();
1828+
}
1829+
1830+
// Compute the union of all top devices' device bounds, mapped into the global coordinate system
1831+
DeviceCM* layer = fMCRec->fTopLayer;
1832+
SkRect totalBounds = SkRect::MakeEmpty();
1833+
while (layer) {
1834+
if (layer->fDevice) {
1835+
SkIRect devClipBounds = layer->fDevice->devClipBounds();
1836+
SkRect layerBounds =
1837+
layer->fDevice->deviceToGlobal().mapRect(SkRect::Make(devClipBounds));
1838+
totalBounds.join(layerBounds);
1839+
}
1840+
layer = layer->fNext;
1841+
}
1842+
1843+
return totalBounds;
18451844
}
18461845

18471846
///////////////////////////////////////////////////////////////////////
@@ -2453,7 +2452,10 @@ bool SkCanvas::canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h, const
24532452
SkPoint pt;
24542453
ctm.mapXY(x, y, &pt);
24552454
SkIRect ir = SkIRect::MakeXYWH(SkScalarRoundToInt(pt.x()), SkScalarRoundToInt(pt.y()), w, h);
2456-
return ir.contains(fMCRec->fRasterClip.getBounds());
2455+
// quick bounds have been outset by 1px compared to overall device bounds, so this makes the
2456+
// contains check equivalent to between ir and device bounds
2457+
ir.outset(1, 1);
2458+
return ir.contains(fQuickRejectBounds);
24572459
}
24582460

24592461
// Given storage for a real paint, and an optional paint parameter, clean-up the param (if non-null)

0 commit comments

Comments
 (0)