ccpr: Add a GrOctoBounds class
Encapsulates all the various code dealing with 45-degree bounding
boxes into a central GrOctoBounds class.
Bug: skia:
Change-Id: Ibc8ac4371af8f310e711579c3c77e0b3a53aff0f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/212563
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index 698ae4e..be5290e 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -14,6 +14,7 @@
#include "src/gpu/ccpr/GrCCPathCache.h"
#include "src/gpu/ccpr/GrCCPerFlushResources.h"
#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h"
+#include "src/gpu/ccpr/GrOctoBounds.h"
static bool has_coord_transforms(const GrPaint& paint) {
GrFragmentProcessor::Iter iter(paint);
@@ -361,22 +362,22 @@
// bounding boxes: One in device space, as well as a second one rotated an additional 45
// degrees. The path vertex shader uses these two bounding boxes to generate an octagon that
// circumscribes the path.
- SkRect devBounds, devBounds45;
+ GrOctoBounds octoBounds;
SkIRect devIBounds;
SkIVector devToAtlasOffset;
if (auto atlas = resources->renderShapeInAtlas(
- fMaskDevIBounds, fMatrix, fShape, fStrokeDevWidth, &devBounds, &devBounds45,
- &devIBounds, &devToAtlasOffset)) {
+ fMaskDevIBounds, fMatrix, fShape, fStrokeDevWidth, &octoBounds, &devIBounds,
+ &devToAtlasOffset)) {
op->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
- resources->appendDrawPathInstance().set(devBounds, devBounds45, devToAtlasOffset,
- SkPMColor4f_toFP16(fColor), doEvenOddFill);
+ resources->appendDrawPathInstance().set(
+ octoBounds, devToAtlasOffset, SkPMColor4f_toFP16(fColor), doEvenOddFill);
if (fDoCachePathMask) {
SkASSERT(fCacheEntry);
SkASSERT(!fCacheEntry->cachedAtlas());
SkASSERT(fShapeConservativeIBounds == fMaskDevIBounds);
- fCacheEntry->setCoverageCountAtlas(onFlushRP, atlas, devToAtlasOffset, devBounds,
- devBounds45, devIBounds, fCachedMaskShift);
+ fCacheEntry->setCoverageCountAtlas(
+ onFlushRP, atlas, devToAtlasOffset, octoBounds, devIBounds, fCachedMaskShift);
}
}
}
diff --git a/src/gpu/ccpr/GrCCPathCache.cpp b/src/gpu/ccpr/GrCCPathCache.cpp
index 4ca0845..3bc26c9 100644
--- a/src/gpu/ccpr/GrCCPathCache.cpp
+++ b/src/gpu/ccpr/GrCCPathCache.cpp
@@ -352,8 +352,7 @@
void GrCCPathCacheEntry::setCoverageCountAtlas(
GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas, const SkIVector& atlasOffset,
- const SkRect& devBounds, const SkRect& devBounds45, const SkIRect& devIBounds,
- const SkIVector& maskShift) {
+ const GrOctoBounds& octoBounds, const SkIRect& devIBounds, const SkIVector& maskShift) {
SkASSERT(fOnFlushRefCnt > 0);
SkASSERT(!fCachedAtlas); // Otherwise we would need to call releaseCachedAtlas().
@@ -369,9 +368,7 @@
fAtlasOffset = atlasOffset + maskShift;
- float dx = (float)maskShift.fX, dy = (float)maskShift.fY;
- fDevBounds = devBounds.makeOffset(-dx, -dy);
- fDevBounds45 = GrCCPathProcessor::MakeOffset45(devBounds45, -dx, -dy);
+ fOctoBounds.setOffset(octoBounds, -maskShift.fX, -maskShift.fY);
fDevIBounds = devIBounds.makeOffset(-maskShift.fX, -maskShift.fY);
}
diff --git a/src/gpu/ccpr/GrCCPathCache.h b/src/gpu/ccpr/GrCCPathCache.h
index c674d62..17e11b4 100644
--- a/src/gpu/ccpr/GrCCPathCache.h
+++ b/src/gpu/ccpr/GrCCPathCache.h
@@ -229,9 +229,9 @@
// Called once our path has been rendered into the mainline CCPR (fp16, coverage count) atlas.
// The caller will stash this atlas texture away after drawing, and during the next flush,
// recover it and attempt to copy any paths that got reused into permanent 8-bit atlases.
- void setCoverageCountAtlas(GrOnFlushResourceProvider*, GrCCAtlas*, const SkIVector& atlasOffset,
- const SkRect& devBounds, const SkRect& devBounds45,
- const SkIRect& devIBounds, const SkIVector& maskShift);
+ void setCoverageCountAtlas(
+ GrOnFlushResourceProvider*, GrCCAtlas*, const SkIVector& atlasOffset,
+ const GrOctoBounds& octoBounds, const SkIRect& devIBounds, const SkIVector& maskShift);
// Called once our path mask has been copied into a permanent, 8-bit atlas. This method points
// the entry at the new atlas and updates the GrCCCCachedAtlas data.
@@ -260,8 +260,7 @@
SkIVector fAtlasOffset;
MaskTransform fMaskTransform;
- SkRect fDevBounds;
- SkRect fDevBounds45;
+ GrOctoBounds fOctoBounds;
SkIRect fDevIBounds;
int fOnFlushRefCnt = 0;
@@ -363,8 +362,8 @@
const SkIVector& shift, uint64_t color,
DoEvenOddFill doEvenOddFill) {
float dx = (float)shift.fX, dy = (float)shift.fY;
- this->set(entry.fDevBounds.makeOffset(dx, dy), MakeOffset45(entry.fDevBounds45, dx, dy),
- entry.fAtlasOffset - shift, color, doEvenOddFill);
+ this->set(
+ entry.fOctoBounds.makeOffset(dx, dy), entry.fAtlasOffset - shift, color, doEvenOddFill);
}
#endif
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 642cbdc..416cdb1 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -13,6 +13,7 @@
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrGeometryProcessor.h"
#include "src/gpu/GrPipeline.h"
+#include "src/gpu/ccpr/GrOctoBounds.h"
class GrCCPathCacheEntry;
class GrCCPerFlushResources;
@@ -31,13 +32,6 @@
*/
class GrCCPathProcessor : public GrGeometryProcessor {
public:
- // Helper to offset the 45-degree bounding box returned by GrCCPathParser::parsePath().
- static SkRect MakeOffset45(const SkRect& devBounds45, float dx, float dy) {
- // devBounds45 is in "| 1 -1 | * devCoords" space.
- // | 1 1 |
- return devBounds45.makeOffset(dx - dy, dx + dy);
- }
-
enum class DoEvenOddFill : bool {
kNo = false,
kYes = true
@@ -45,13 +39,13 @@
struct Instance {
SkRect fDevBounds; // "right < left" indicates even-odd fill type.
- SkRect fDevBounds45; // Bounding box in "| 1 -1 | * devCoords" space.
+ SkRect fDevBounds45; // Bounding box in "| 1 -1 | * devCoords" space. See GrOctoBounds.
// | 1 1 |
SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas.
uint64_t fColor; // Color always stored as 4 x fp16
- void set(const SkRect& devBounds, const SkRect& devBounds45,
- const SkIVector& devToAtlasOffset, uint64_t, DoEvenOddFill = DoEvenOddFill::kNo);
+ void set(const GrOctoBounds&, const SkIVector& devToAtlasOffset, uint64_t,
+ DoEvenOddFill = DoEvenOddFill::kNo);
void set(const GrCCPathCacheEntry&, const SkIVector& shift, uint64_t,
DoEvenOddFill = DoEvenOddFill::kNo);
};
@@ -96,19 +90,21 @@
};
inline void GrCCPathProcessor::Instance::set(
- const SkRect& devBounds, const SkRect& devBounds45, const SkIVector& devToAtlasOffset,
- uint64_t color, DoEvenOddFill doEvenOddFill) {
+ const GrOctoBounds& octoBounds, const SkIVector& devToAtlasOffset, uint64_t color,
+ DoEvenOddFill doEvenOddFill) {
if (DoEvenOddFill::kNo == doEvenOddFill) {
// We cover "nonzero" paths with clockwise triangles, which is the default result from
- // normal bounding boxes.
- fDevBounds = devBounds;
- fDevBounds45 = devBounds45;
+ // normal octo bounds.
+ fDevBounds = octoBounds.bounds();
+ fDevBounds45 = octoBounds.bounds45();
} else {
- // We cover "even/odd" paths with counterclockwise triangles. Reorder the bounding box
- // vertices so the output is flipped horizontally.
- fDevBounds.setLTRB(devBounds.fRight, devBounds.fTop, devBounds.fLeft, devBounds.fBottom);
+ // We cover "even/odd" paths with counterclockwise triangles. Here we reorder the bounding
+ // box vertices so the output is flipped horizontally.
+ fDevBounds.setLTRB(
+ octoBounds.right(), octoBounds.top(), octoBounds.left(), octoBounds.bottom());
fDevBounds45.setLTRB(
- devBounds45.fBottom, devBounds45.fRight, devBounds45.fTop, devBounds45.fLeft);
+ octoBounds.bottom45(), octoBounds.right45(), octoBounds.top45(),
+ octoBounds.left45());
}
fDevToAtlasOffset = devToAtlasOffset;
fColor = color;
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index 567a778..ae001ce 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -282,9 +282,9 @@
emplace_at_memcpy(&fCopyPathRanges, fCurrCopyAtlasRangesIdx, std::move(srcProxy), 1);
}
-static bool transform_path_pts(const SkMatrix& m, const SkPath& path,
- const SkAutoSTArray<32, SkPoint>& outDevPts, SkRect* devBounds,
- SkRect* devBounds45) {
+static bool transform_path_pts(
+ const SkMatrix& m, const SkPath& path, const SkAutoSTArray<32, SkPoint>& outDevPts,
+ GrOctoBounds* octoBounds) {
const SkPoint* pts = SkPathPriv::PointData(path);
int numPts = path.countPoints();
SkASSERT(numPts + 1 <= outDevPts.count());
@@ -331,16 +331,19 @@
SkPoint topLeftPts[2], bottomRightPts[2];
topLeft.store(topLeftPts);
bottomRight.store(bottomRightPts);
- devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(),
- bottomRightPts[0].y());
- devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(),
- bottomRightPts[1].y());
+
+ const SkRect& devBounds = SkRect::MakeLTRB(
+ topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(), bottomRightPts[0].y());
+ const SkRect& devBounds45 = SkRect::MakeLTRB(
+ topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(), bottomRightPts[1].y());
+
+ octoBounds->set(devBounds, devBounds45);
return true;
}
GrCCAtlas* GrCCPerFlushResources::renderShapeInAtlas(
const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
- SkRect* devBounds, SkRect* devBounds45, SkIRect* devIBounds, SkIVector* devToAtlasOffset) {
+ GrOctoBounds* octoBounds, SkIRect* devIBounds, SkIVector* devToAtlasOffset) {
SkASSERT(this->isMapped());
SkASSERT(fNextPathInstanceIdx < fEndPathInstance);
@@ -350,7 +353,7 @@
SkDEBUGCODE(--fEndPathInstance);
return nullptr;
}
- if (!transform_path_pts(m, path, fLocalDevPtsBuffer, devBounds, devBounds45)) {
+ if (!transform_path_pts(m, path, fLocalDevPtsBuffer, octoBounds)) {
// The transformed path had infinite or NaN bounds.
SkDEBUGCODE(--fEndPathInstance);
return nullptr;
@@ -358,14 +361,11 @@
const SkStrokeRec& stroke = shape.style().strokeRec();
if (!stroke.isFillStyle()) {
- float r = SkStrokeRec::GetInflationRadius(stroke.getJoin(), stroke.getMiter(),
- stroke.getCap(), strokeDevWidth);
- devBounds->outset(r, r);
- // devBounds45 is in (| 1 -1 | * devCoords) space.
- // | 1 1 |
- devBounds45->outset(r*SK_ScalarSqrt2, r*SK_ScalarSqrt2);
+ float r = SkStrokeRec::GetInflationRadius(
+ stroke.getJoin(), stroke.getMiter(), stroke.getCap(), strokeDevWidth);
+ octoBounds->outset(r);
}
- devBounds->roundOut(devIBounds);
+ octoBounds->roundOut(devIBounds);
GrScissorTest scissorTest;
SkIRect clippedPathIBounds;
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index b005a3b..4fcb114 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -16,6 +16,7 @@
class GrCCPathCache;
class GrCCPathCacheEntry;
+class GrOctoBounds;
class GrOnFlushResourceProvider;
class GrShape;
@@ -79,9 +80,9 @@
//
// strokeDevWidth must be 0 for fills, 1 for hairlines, or the stroke width in device-space
// pixels for non-hairline strokes (implicitly requiring a rigid-body transform).
- GrCCAtlas* renderShapeInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const GrShape&,
- float strokeDevWidth, SkRect* devBounds, SkRect* devBounds45,
- SkIRect* devIBounds, SkIVector* devToAtlasOffset);
+ GrCCAtlas* renderShapeInAtlas(
+ const SkIRect& clipIBounds, const SkMatrix&, const GrShape&, float strokeDevWidth,
+ GrOctoBounds*, SkIRect* devIBounds, SkIVector* devToAtlasOffset);
const GrCCAtlas* renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds, const SkPath& devPath,
const SkIRect& devPathIBounds,
SkIVector* devToAtlasOffset);
diff --git a/src/gpu/ccpr/GrOctoBounds.cpp b/src/gpu/ccpr/GrOctoBounds.cpp
new file mode 100644
index 0000000..facf1aa
--- /dev/null
+++ b/src/gpu/ccpr/GrOctoBounds.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/ccpr/GrOctoBounds.h"
+
+#ifdef SK_DEBUG
+void GrOctoBounds::validateBoundsAreTight() const {
+ this->validateBoundsAreTight([](bool cond, const char* file, int line, const char* code) {
+ SkASSERTF(cond, "%s(%d): assertion failure: \"assert(%s)\"", file, line, code);
+ });
+}
+
+void GrOctoBounds::validateBoundsAreTight(const std::function<void(
+ bool cond, const char* file, int line, const char* code)>& validateFn) const {
+ constexpr static float epsilon = 1e-3;
+
+ float l=fBounds.left(), l45=fBounds45.left();
+ float t=fBounds.top(), t45=fBounds45.top();
+ float r=fBounds.right(), r45=fBounds45.right();
+ float b=fBounds.bottom(), b45=fBounds45.bottom();
+
+#define VALIDATE(CODE) validateFn(CODE, __FILE__, __LINE__, #CODE)
+ // Verify diagonals are inside far corners of the dev bounds.
+ VALIDATE(l45 >= Get_x45(l,b) - epsilon);
+ VALIDATE(t45 >= Get_y45(l,t) - epsilon);
+ VALIDATE(r45 <= Get_x45(r,t) + epsilon);
+ VALIDATE(b45 <= Get_y45(r,b) + epsilon);
+ // Verify verticals and horizontals are inside far corners of the 45-degree dev bounds.
+ VALIDATE(l >= Get_x(l45,t45) - epsilon);
+ VALIDATE(t >= Get_y(r45,t45) - epsilon);
+ VALIDATE(r <= Get_x(r45,b45) + epsilon);
+ VALIDATE(b <= Get_y(l45,b45) + epsilon);
+ // Verify diagonals are outside middle corners of the dev bounds.
+ VALIDATE(l45 <= Get_x45(r,b) + epsilon);
+ VALIDATE(l45 <= Get_x45(l,t) + epsilon);
+ VALIDATE(t45 <= Get_y45(l,b) + epsilon);
+ VALIDATE(t45 <= Get_y45(r,t) + epsilon);
+ VALIDATE(r45 >= Get_x45(l,t) - epsilon);
+ VALIDATE(r45 >= Get_x45(r,b) - epsilon);
+ VALIDATE(b45 >= Get_y45(r,t) - epsilon);
+ VALIDATE(b45 >= Get_y45(l,b) - epsilon);
+ // Verify verticals and horizontals are outside middle corners of the 45-degree dev bounds.
+ VALIDATE(l <= Get_x(l45,b45) + epsilon);
+ VALIDATE(l <= Get_x(r45,t45) + epsilon);
+ VALIDATE(t <= Get_y(r45,b45) + epsilon);
+ VALIDATE(t <= Get_y(l45,t45) + epsilon);
+ VALIDATE(r >= Get_x(r45,t45) - epsilon);
+ VALIDATE(r >= Get_x(l45,b45) - epsilon);
+ VALIDATE(b >= Get_y(l45,t45) - epsilon);
+ VALIDATE(b >= Get_y(r45,b45) - epsilon);
+#undef VALIDATE
+}
+#endif
diff --git a/src/gpu/ccpr/GrOctoBounds.h b/src/gpu/ccpr/GrOctoBounds.h
new file mode 100644
index 0000000..7630618
--- /dev/null
+++ b/src/gpu/ccpr/GrOctoBounds.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrOctoBounds_DEFINED
+#define GrOctoBounds_DEFINED
+
+#include "include/core/SkRect.h"
+#include <functional>
+
+/**
+ * This class is composed of two bounding boxes: one in device space, and one in a 45-degree rotated
+ * space.
+ *
+ * The 45-degree bounding box resides in "| 1 -1 | * coords" space.
+ * | 1 1 |
+ *
+ * The intersection of these two boxes defines the bounding octagon of a shape.
+ *
+ * Furthermore, both bounding boxes are fully tightened. This means we can blindly find the
+ * intersections between each diagonal and its vertical and horizontal neighbors, and be left with
+ * 8 points that define a convex (possibly degenerate) octagon.
+ */
+class GrOctoBounds {
+public:
+ void set(const SkRect& bounds, const SkRect& bounds45) {
+ fBounds = bounds;
+ fBounds45 = bounds45;
+ SkDEBUGCODE(this->validateBoundsAreTight());
+ }
+
+ const SkRect& bounds() const { return fBounds; }
+ float left() const { return fBounds.left(); }
+ float top() const { return fBounds.top(); }
+ float right() const { return fBounds.right(); }
+ float bottom() const { return fBounds.bottom(); }
+
+
+ // The 45-degree bounding box resides in "| 1 -1 | * coords" space.
+ // | 1 1 |
+ const SkRect& bounds45() const { return fBounds45; }
+ float left45() const { return fBounds45.left(); }
+ float top45() const { return fBounds45.top(); }
+ float right45() const { return fBounds45.right(); }
+ float bottom45() const { return fBounds45.bottom(); }
+
+ void roundOut(SkIRect* out) const {
+ // The octagon is the intersection of fBounds and fBounds45 (see the comment at the start of
+ // the class). The octagon's bounding box is therefore just fBounds. And the integer
+ // bounding box can be found by simply rounding out fBounds.
+ fBounds.roundOut(out);
+ }
+
+ GrOctoBounds makeOffset(float dx, float dy) const {
+ GrOctoBounds offset;
+ offset.setOffset(*this, dx, dy);
+ return offset;
+ }
+
+ void setOffset(const GrOctoBounds& octoBounds, float dx, float dy) {
+ fBounds = octoBounds.fBounds.makeOffset(dx, dy);
+ fBounds45 = octoBounds.fBounds45.makeOffset(dx - dy, dx + dy);
+ SkDEBUGCODE(this->validateBoundsAreTight());
+ }
+
+ void outset(float radius) {
+ fBounds.outset(radius, radius);
+ fBounds45.outset(radius*SK_ScalarSqrt2, radius*SK_ScalarSqrt2);
+ SkDEBUGCODE(this->validateBoundsAreTight());
+ }
+
+ // The 45-degree bounding box resides in "| 1 -1 | * coords" space.
+ // | 1 1 |
+ //
+ // i.e., | x45 | = | x - y |
+ // | y45 | = | x + y |
+ //
+ // These methods transform points between device space and 45-degree space.
+ constexpr static float Get_x45(float x, float y) { return x - y; }
+ constexpr static float Get_y45(float x, float y) { return x + y; }
+ constexpr static float Get_x(float x45, float y45) { return (x45 + y45) * .5f; }
+ constexpr static float Get_y(float x45, float y45) { return (y45 - x45) * .5f; }
+
+#ifdef SK_DEBUG
+ void validateBoundsAreTight() const;
+ void validateBoundsAreTight(const std::function<void(
+ bool cond, const char* file, int line, const char* code)>& validateFn) const;
+#endif
+
+private:
+ SkRect fBounds;
+ SkRect fBounds45;
+};
+
+#endif