Cache tessellation metadata between inset/outset calls
Change-Id: I1fb4807ab80dad9a02e9e236d43b3f2be7511412
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/251769
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/geometry/GrQuadUtils.cpp b/src/gpu/geometry/GrQuadUtils.cpp
index cc44663..b4be529 100644
--- a/src/gpu/geometry/GrQuadUtils.cpp
+++ b/src/gpu/geometry/GrQuadUtils.cpp
@@ -385,109 +385,133 @@
// TessellationHelper implementation
///////////////////////////////////////////////////////////////////////////////////////////////////
-TessellationHelper::EdgeVectors TessellationHelper::getEdgeVectors() const {
- EdgeVectors v;
+TessellationHelper::TessellationHelper(const GrQuad& deviceQuad, const GrQuad* localQuad)
+ : fAAFlags(GrQuadAAFlags::kNone)
+ , fDeviceType(deviceQuad.quadType())
+ , fLocalType(localQuad ? localQuad->quadType() : GrQuad::Type::kAxisAligned) {
+ fOriginal.fX = deviceQuad.x4f();
+ fOriginal.fY = deviceQuad.y4f();
+ fOriginal.fW = deviceQuad.w4f();
+
+ if (localQuad) {
+ fOriginal.fU = localQuad->x4f();
+ fOriginal.fV = localQuad->y4f();
+ fOriginal.fR = localQuad->w4f();
+ fOriginal.fUVRCount = fLocalType == GrQuad::Type::kPerspective ? 3 : 2;
+ } else {
+ fOriginal.fUVRCount = 0;
+ }
+
if (fDeviceType == GrQuad::Type::kPerspective) {
V4f iw = 1.0 / fOriginal.fW;
- v.fX2D = fOriginal.fX * iw;
- v.fY2D = fOriginal.fY * iw;
+ fEdgeVectors.fX2D = fOriginal.fX * iw;
+ fEdgeVectors.fY2D = fOriginal.fY * iw;
} else {
- v.fX2D = fOriginal.fX;
- v.fY2D = fOriginal.fY;
+ fEdgeVectors.fX2D = fOriginal.fX;
+ fEdgeVectors.fY2D = fOriginal.fY;
}
- v.fDX = next_ccw(v.fX2D) - v.fX2D;
- v.fDY = next_ccw(v.fY2D) - v.fY2D;
- v.fInvLengths = rsqrt(mad(v.fDX, v.fDX, v.fDY * v.fDY));
+ fEdgeVectors.fDX = next_ccw(fEdgeVectors.fX2D) - fEdgeVectors.fX2D;
+ fEdgeVectors.fDY = next_ccw(fEdgeVectors.fY2D) - fEdgeVectors.fY2D;
+ fEdgeVectors.fInvLengths = rsqrt(mad(fEdgeVectors.fDX, fEdgeVectors.fDX,
+ fEdgeVectors.fDY * fEdgeVectors.fDY));
// Normalize edge vectors
- v.fDX *= v.fInvLengths;
- v.fDY *= v.fInvLengths;
- return v;
+ fEdgeVectors.fDX *= fEdgeVectors.fInvLengths;
+ fEdgeVectors.fDY *= fEdgeVectors.fInvLengths;
}
-TessellationHelper::EdgeEquations TessellationHelper::getEdgeEquations(
- const EdgeVectors& edgeVectors) const {
- V4f dx = edgeVectors.fDX;
- V4f dy = edgeVectors.fDY;
- // Correct for bad edges by copying adjacent edge information into the bad component
- correct_bad_edges(edgeVectors.fInvLengths >= 1.f / kTolerance, &dx, &dy, nullptr);
+const TessellationHelper::EdgeEquations& TessellationHelper::getEdgeEquations() {
+ if (!fEdgeEquations.fValid) {
+ V4f dx = fEdgeVectors.fDX;
+ V4f dy = fEdgeVectors.fDY;
+ // Correct for bad edges by copying adjacent edge information into the bad component
+ correct_bad_edges(fEdgeVectors.fInvLengths >= 1.f / kTolerance, &dx, &dy, nullptr);
- V4f c = mad(dx, edgeVectors.fY2D, -dy * edgeVectors.fX2D);
- // Make sure normals point into the shape
- V4f test = mad(dy, next_cw(edgeVectors.fX2D), mad(-dx, next_cw(edgeVectors.fY2D), c));
- if (any(test < -kTolerance)) {
- return {-dy, dx, -c, true};
- } else {
- return {dy, -dx, c, false};
+ V4f c = mad(dx, fEdgeVectors.fY2D, -dy * fEdgeVectors.fX2D);
+ // Make sure normals point into the shape
+ V4f test = mad(dy, next_cw(fEdgeVectors.fX2D), mad(-dx, next_cw(fEdgeVectors.fY2D), c));
+ if (any(test < -kTolerance)) {
+ fEdgeEquations.fA = -dy;
+ fEdgeEquations.fB = dx;
+ fEdgeEquations.fC = -c;
+ } else {
+ fEdgeEquations.fA = dy;
+ fEdgeEquations.fB = -dx;
+ fEdgeEquations.fC = c;
+ }
+
+ fEdgeEquations.fValid = true;
}
+ return fEdgeEquations;
}
-TessellationHelper::OutsetRequest TessellationHelper::getOutsetRequest(
- const EdgeVectors& edgeVectors) const {
- V4f mask = fAAFlags == GrQuadAAFlags::kAll ? V4f(1.f) :
- V4f{(GrQuadAAFlags::kLeft & fAAFlags) ? 1.f : 0.f,
- (GrQuadAAFlags::kBottom & fAAFlags) ? 1.f : 0.f,
- (GrQuadAAFlags::kTop & fAAFlags) ? 1.f : 0.f,
- (GrQuadAAFlags::kRight & fAAFlags) ? 1.f : 0.f};
+const TessellationHelper::OutsetRequest& TessellationHelper::getOutsetRequest() {
+ if (!fOutsetRequest.fValid) {
+ V4f mask = fAAFlags == GrQuadAAFlags::kAll ? V4f(1.f) :
+ V4f{(GrQuadAAFlags::kLeft & fAAFlags) ? 1.f : 0.f,
+ (GrQuadAAFlags::kBottom & fAAFlags) ? 1.f : 0.f,
+ (GrQuadAAFlags::kTop & fAAFlags) ? 1.f : 0.f,
+ (GrQuadAAFlags::kRight & fAAFlags) ? 1.f : 0.f};
- // Calculate the lengths of the outset/inset edge vectors per corner, before applying the mask
- bool degenerate;
- V4f cornerOutsetLen;
- if (fDeviceType <= GrQuad::Type::kRectilinear) {
- // Since it's rectangular, the corners move the same distance as the edge lines would
- cornerOutsetLen = 0.5f;
- // While it's still rectangular, must use the degenerate path when the quad is less
- // than a pixel along a side since the coverage must be updated. (len < 1 implies 1/len > 1)
- degenerate = any(edgeVectors.fInvLengths > 1.f);
- } else if (any(edgeVectors.fInvLengths >= 1.f / kTolerance)) {
- // Have an edge that is effectively length 0, so we're dealing with a triangle. Skip
- // computing corner outsets, since degenerate path won't use them.
- degenerate = true;
- } else {
- // Must scale corner distance by 1/2sin(theta), where theta is the angle between the two
- // edges at that corner. cos(theta) is equal to dot(dXY, next_cw(dXY)),
- // and sin(theta) = sqrt(1 - cos(theta)^2)
- V4f cosTheta = mad(edgeVectors.fDX, next_cw(edgeVectors.fDX),
- edgeVectors.fDY * next_cw(edgeVectors.fDY));
-
- // If the angle is too shallow between edges, go through the degenerate path, otherwise
- // adding and subtracting very large vectors in almost opposite directions leads to float
- // errors.
- if (any(abs(cosTheta) >= 0.9f)) {
- // Skip updating the outsets since degenerate code path doesn't rely on that
+ // Calculate the lengths of the outset/inset edge vectors per corner, before applying the
+ // mask
+ bool degenerate;
+ V4f cornerOutsetLen;
+ if (fDeviceType <= GrQuad::Type::kRectilinear) {
+ // Since it's rectangular, the corners move the same distance as the edge lines would
+ cornerOutsetLen = 0.5f;
+ // While it's still rectangular, must use the degenerate path when the quad is less than
+ // a pixel along a side since the coverage must be updated. (len < 1 implies 1/len > 1)
+ degenerate = any(fEdgeVectors.fInvLengths > 1.f);
+ } else if (any(fEdgeVectors.fInvLengths >= 1.f / kTolerance)) {
+ // Have an edge that is effectively length 0, so we're dealing with a triangle. Skip
+ // computing corner outsets, since degenerate path won't use them.
degenerate = true;
} else {
- cornerOutsetLen = 0.5f * rsqrt(1.f - cosTheta * cosTheta); // 1/2sin(theta)
+ // Must scale corner distance by 1/2sin(theta), where theta is the angle between the two
+ // edges at that corner. cos(theta) is equal to dot(dXY, next_cw(dXY)),
+ // and sin(theta) = sqrt(1 - cos(theta)^2)
+ V4f cosTheta = mad(fEdgeVectors.fDX, next_cw(fEdgeVectors.fDX),
+ fEdgeVectors.fDY * next_cw(fEdgeVectors.fDY));
- // When outsetting or insetting, the current edge's AA adds to the length:
- // cos(pi - theta)/2sin(theta) + cos(pi-ccw(theta))/2sin(ccw(theta))
- // Moving an adjacent edge updates the length by 1/2sin(theta|ccw(theta))
- V4f halfTanTheta = -cosTheta * cornerOutsetLen; // cos(pi - theta) = -cos(theta)
- V4f edgeAdjust = mask * (halfTanTheta + next_ccw(halfTanTheta)) +
- next_ccw(mask) * next_ccw(cornerOutsetLen) +
- next_cw(mask) * cornerOutsetLen;
- // If either outsetting (plus edgeAdjust) or insetting (minus edgeAdjust) make edgeLen
- // negative then it's degenerate
- V4f threshold = 0.1f - (1.f / edgeVectors.fInvLengths);
- degenerate = any(edgeAdjust < threshold) || any(edgeAdjust > -threshold);
+ // If the angle is too shallow between edges, go through the degenerate path, otherwise
+ // adding and subtracting very large vectors in almost opposite directions leads to
+ // float errors.
+ if (any(abs(cosTheta) >= 0.9f)) {
+ // Skip updating the outsets since degenerate code path doesn't rely on that
+ degenerate = true;
+ } else {
+ cornerOutsetLen = 0.5f * rsqrt(1.f - cosTheta * cosTheta); // 1/2sin(theta)
+
+ // When outsetting or insetting, the current edge's AA adds to the length:
+ // cos(pi - theta)/2sin(theta) + cos(pi-ccw(theta))/2sin(ccw(theta))
+ // Moving an adjacent edge updates the length by 1/2sin(theta|ccw(theta))
+ V4f halfTanTheta = -cosTheta * cornerOutsetLen; // cos(pi - theta) = -cos(theta)
+ V4f edgeAdjust = mask * (halfTanTheta + next_ccw(halfTanTheta)) +
+ next_ccw(mask) * next_ccw(cornerOutsetLen) +
+ next_cw(mask) * cornerOutsetLen;
+ // If either outsetting (plus edgeAdjust) or insetting (minus edgeAdjust) make
+ // edgeLen negative then it's degenerate
+ V4f threshold = 0.1f - (1.f / fEdgeVectors.fInvLengths);
+ degenerate = any(edgeAdjust < threshold) || any(edgeAdjust > -threshold);
+ }
}
- }
- OutsetRequest r;
- r.fEdgeDistances = 0.5f * mask; // Half a pixel for AA on edges that can move
- r.fDegenerate = degenerate;
- if (!degenerate) {
- // When the projected device quad is not degenerate, the vertex corners can move
- // cornerOutsetLen along their edge and their cw-rotated edge. The vertex's edge points
- // inwards and the cw-rotated edge points outwards, hence the minus-sign.
- // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on"
- // both its points need to be moved along their other edge vectors.
- r.fOutsets = -cornerOutsetLen * next_cw(mask); // scales dx, dy
- r.fOutsetsCW = cornerOutsetLen * mask; // scales next_cw(dx), next_cw(dy)
+ fOutsetRequest.fEdgeDistances = 0.5f * mask; // Half a pixel for AA on edges that can move
+ fOutsetRequest.fDegenerate = degenerate;
+ if (!degenerate) {
+ // When the projected device quad is not degenerate, the vertex corners can move
+ // cornerOutsetLen along their edge and their cw-rotated edge. The vertex's edge points
+ // inwards and the cw-rotated edge points outwards, hence the minus-sign.
+ // The mask is rotated compared to the outsets and edge vectors, since if the edge is
+ // "on" both its points need to be moved along their other edge vectors.
+ fOutsetRequest.fOutsets = -cornerOutsetLen * next_cw(mask); // scales dx, dy
+ fOutsetRequest.fOutsetsCW = cornerOutsetLen * mask; // scales next_cw(dx, dy)
+ }
+ fOutsetRequest.fValid = true;
}
-
- return r;
+ return fOutsetRequest;
}
void TessellationHelper::Vertices::moveAlong(const EdgeVectors& edgeVectors,
@@ -642,10 +666,9 @@
return w * h;
}
-int TessellationHelper::computeDegenerateQuad(const V4f& signedEdgeDistances,
- const EdgeEquations& edges,
- V4f* x2d, V4f* y2d) {
+int TessellationHelper::computeDegenerateQuad(const V4f& signedEdgeDistances, V4f* x2d, V4f* y2d) {
// Move the edge by the signed edge adjustment.
+ const EdgeEquations& edges = this->getEdgeEquations();
V4f oc = edges.fC + signedEdgeDistances;
// There are 6 points that we care about to determine the final shape of the polygon, which
@@ -731,30 +754,25 @@
}
}
-int TessellationHelper::adjustVertices(const OutsetRequest& outsetRequest,
- bool inset,
- const EdgeVectors& edgeVectors,
- const EdgeEquations* edgeEquations,
- Vertices* vertices) {
+int TessellationHelper::adjustVertices(bool inset, Vertices* vertices) {
SkASSERT(vertices);
SkASSERT(vertices->fUVRCount == 0 || vertices->fUVRCount == 2 || vertices->fUVRCount == 3);
+ const OutsetRequest& outsetRequest = this->getOutsetRequest();
if (fDeviceType == GrQuad::Type::kPerspective || outsetRequest.fDegenerate) {
- Vertices projected = { edgeVectors.fX2D, edgeVectors.fY2D, /*w*/ 1.f, 0.f, 0.f, 0.f, 0};
+ Vertices projected = { fEdgeVectors.fX2D, fEdgeVectors.fY2D, /*w*/ 1.f, 0.f, 0.f, 0.f, 0};
int vertexCount;
if (outsetRequest.fDegenerate) {
// Must use the slow path to handle numerical issues and self intersecting geometry
- SkASSERT(edgeEquations);
V4f signedEdgeDistances = outsetRequest.fEdgeDistances;
if (inset) {
signedEdgeDistances *= -1.f;
}
- vertexCount = computeDegenerateQuad(signedEdgeDistances, *edgeEquations,
- &projected.fX, &projected.fY);
+ vertexCount = computeDegenerateQuad(signedEdgeDistances, &projected.fX, &projected.fY);
} else {
// Move the projected quad with the fast path, even though we will reconstruct the
// perspective corners afterwards.
- projected.moveAlong(edgeVectors, outsetRequest, inset);
+ projected.moveAlong(fEdgeVectors, outsetRequest, inset);
vertexCount = 4;
}
vertices->moveTo(projected.fX, projected.fY, outsetRequest.fEdgeDistances != 0.f);
@@ -762,93 +780,55 @@
} else {
// Quad is 2D and the inset/outset request does not cause the geometry to self intersect, so
// we can directly move the corners along the already calculated edge vectors.
- vertices->moveAlong(edgeVectors, outsetRequest, inset);
+ vertices->moveAlong(fEdgeVectors, outsetRequest, inset);
return 4;
}
}
-TessellationHelper::TessellationHelper(const GrQuad& deviceQuad, const GrQuad* localQuad)
- : fAAFlags(GrQuadAAFlags::kNone)
- , fCoverage(1.f)
- , fDeviceType(deviceQuad.quadType())
- , fLocalType(localQuad ? localQuad->quadType() : GrQuad::Type::kAxisAligned) {
- fOriginal.fX = deviceQuad.x4f();
- fOriginal.fY = deviceQuad.y4f();
- fOriginal.fW = deviceQuad.w4f();
-
- if (localQuad) {
- fOriginal.fU = localQuad->x4f();
- fOriginal.fV = localQuad->y4f();
- fOriginal.fR = localQuad->w4f();
- fOriginal.fUVRCount = fLocalType == GrQuad::Type::kPerspective ? 3 : 2;
- } else {
- fOriginal.fUVRCount = 0;
- }
-}
-
V4f TessellationHelper::inset(GrQuadAAFlags aaFlags, GrQuad* deviceInset, GrQuad* localInset) {
if (aaFlags != fAAFlags) {
fAAFlags = aaFlags;
- if (aaFlags != GrQuadAAFlags::kNone) {
- this->recomputeInsetAndOutset();
- }
+ this->reset();
}
if (fAAFlags == GrQuadAAFlags::kNone) {
+ // No need to calculate anything since none of the edges are allowed to move. Since it will
+ // be drawn without anti-aliasing, can just return full coverage.
this->setQuads(fOriginal, deviceInset, localInset);
return 1.f;
} else {
- this->setQuads(fInset, deviceInset, localInset);
- return fCoverage;
+ Vertices inset = fOriginal;
+ int vertexCount = this->adjustVertices(true, &inset);
+ this->setQuads(inset, deviceInset, localInset);
+
+ if (vertexCount < 3) {
+ // The interior has less than a full pixel's area so estimate reduced coverage using
+ // the distance of the inset's projected corners to the original edges.
+ return this->getEdgeEquations().estimateCoverage(inset.fX / inset.fW,
+ inset.fY / inset.fW);
+ } else {
+ return 1.f;
+ }
}
}
void TessellationHelper::outset(GrQuadAAFlags aaFlags, GrQuad* deviceOutset, GrQuad* localOutset) {
if (aaFlags != fAAFlags) {
fAAFlags = aaFlags;
- if (aaFlags != GrQuadAAFlags::kNone) {
- this->recomputeInsetAndOutset();
- }
+ this->reset();
}
if (fAAFlags == GrQuadAAFlags::kNone) {
+ // No need to calculate anything since none of the edges are allowed to move
this->setQuads(fOriginal, deviceOutset, localOutset);
} else {
- this->setQuads(fOutset, deviceOutset, localOutset);
+ Vertices outset = fOriginal;
+ this->adjustVertices(false, &outset);
+ this->setQuads(outset, deviceOutset, localOutset);
}
}
-void TessellationHelper::recomputeInsetAndOutset() {
- // Start from the original geometry
- fInset = fOriginal;
- fOutset = fOriginal;
-
- // Calculate state that can be shared between both inset and outset quads
- EdgeVectors edgeVectors = this->getEdgeVectors();
- OutsetRequest outsetRequest = this->getOutsetRequest(edgeVectors);
-
- // Adjust inset and outset vertices to match the request
- if (outsetRequest.fDegenerate) {
- // adjustVertices requires edge equations too
- EdgeEquations edgeEquations = this->getEdgeEquations(edgeVectors);
- this->adjustVertices(outsetRequest, false, edgeVectors, &edgeEquations, &fOutset);
- int innerVertexCount = this->adjustVertices(outsetRequest, true, edgeVectors,
- &edgeEquations, &fInset);
- if (innerVertexCount < 3) {
- // The interior has less than a full pixel's area so estimate reduced coverage using
- // the distance of the inset's projected corners to the original edges.
- fCoverage = edgeEquations.estimateCoverage(fInset.fX / fInset.fW,
- fInset.fY / fInset.fW);
- } else {
- fCoverage = 1.f;
- }
- } else {
- // skip calculating edge equations
- this->adjustVertices(outsetRequest, false, edgeVectors, nullptr, &fOutset);
- int innerVertexCount = this->adjustVertices(outsetRequest, true, edgeVectors,
- nullptr, &fInset);
- (void) innerVertexCount; // silence clang
- SkASSERT(innerVertexCount == 4);
- fCoverage = 1.f;
- }
+void TessellationHelper::reset() {
+ fOutsetRequest.fValid = false;
+ fEdgeEquations.fValid = false;
}
void TessellationHelper::setQuads(const Vertices& vertices,
diff --git a/src/gpu/geometry/GrQuadUtils.h b/src/gpu/geometry/GrQuadUtils.h
index fc6fc9d..d30eafb 100644
--- a/src/gpu/geometry/GrQuadUtils.h
+++ b/src/gpu/geometry/GrQuadUtils.h
@@ -90,9 +90,8 @@
struct EdgeEquations {
// a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR.
skvx::Vec<4, float> fA, fB, fC;
- // Whether or not the edge normals had to be flipped to preserve positive distance on
- // the inside
- bool fFlipped;
+ // True if the field is up to date with the state of fOriginal+fAAFlags
+ bool fValid = false;
skvx::Vec<4, float> estimateCoverage(const skvx::Vec<4, float>& x2d,
const skvx::Vec<4, float>& y2d) const;
@@ -113,39 +112,38 @@
// True if the new corners cannot be calculated by simply adding scaled edge vectors.
// If degenerate, fOutsets[CW] should be ignored.
bool fDegenerate;
+ // True if the field is up to date with the state of fOriginal+fAAFlags
+ bool fValid = false;
};
// Repeated calls to inset/outset with the same mask skip calculations
GrQuadAAFlags fAAFlags;
+ // Always valid
Vertices fOriginal;
- Vertices fInset;
- Vertices fOutset;
- skvx::Vec<4, float> fCoverage;
-
+ EdgeVectors fEdgeVectors;
GrQuad::Type fDeviceType;
GrQuad::Type fLocalType;
- void recomputeInsetAndOutset();
+ // Lazily computed as needed; use accessor functions instead of direct access.
+ OutsetRequest fOutsetRequest;
+ EdgeEquations fEdgeEquations;
+
+ void reset();
void setQuads(const Vertices& vertices, GrQuad* deviceOut, GrQuad* localOut) const;
- EdgeVectors getEdgeVectors() const;
- OutsetRequest getOutsetRequest(const EdgeVectors& edgeVectors) const;
- EdgeEquations getEdgeEquations(const EdgeVectors& edgeVectors) const;
+ const OutsetRequest& getOutsetRequest();
+ const EdgeEquations& getEdgeEquations();
// Outsets or insets 'x2d' and 'y2d' in place. To be used when the interior is very small,
// edges are near parallel, or edges are very short/zero-length. Returns number of effective
// vertices in the degenerate quad.
int computeDegenerateQuad(const skvx::Vec<4, float>& signedEdgeDistances,
- const EdgeEquations& edges,
- skvx::Vec<4, float>* x2d,
- skvx::Vec<4, float>* y2d);
+ skvx::Vec<4, float>* x2d, skvx::Vec<4, float>* y2d);
// Outsets or insets 'vertices' based on the outset request described by 'outsetRequest'
// and 'inset' (true for insetting instead). If the outset is not degenerate,
// 'edgeEquations' can be null. Returns number of effective vertices in the adjusted quad.
- int adjustVertices(const OutsetRequest& outsetRequest, bool inset,
- const EdgeVectors& edgeVectors, const EdgeEquations* edgeEquations,
- Vertices* vertices);
+ int adjustVertices(bool inset, Vertices* vertices);
};
}; // namespace GrQuadUtils