Specialize inset/outset/reset based on known quad type

Change-Id: I5ca35be216cf6e7d863f2d7638905a61a0b9ef00
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/257392
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Michael Ludwig 2019-12-04 16:04:40 -05:00 committed by Skia Commit-Bot
parent 039368775c
commit d83912bc81
2 changed files with 319 additions and 120 deletions

View File

@ -31,24 +31,34 @@ static AI V4f next_ccw(const V4f& v) {
// Replaces zero-length 'bad' edge vectors with the reversed opposite edge vector.
// e3 may be null if only 2D edges need to be corrected for.
template<bool threeD>
static AI void correct_bad_edges(const M4f& bad, V4f* e1, V4f* e2, V4f* e3) {
if (any(bad)) {
// Want opposite edges, L B T R -> R T B L but with flipped sign to preserve winding
*e1 = if_then_else(bad, -skvx::shuffle<3, 2, 1, 0>(*e1), *e1);
*e2 = if_then_else(bad, -skvx::shuffle<3, 2, 1, 0>(*e2), *e2);
if (e3) {
if constexpr (threeD) {
SkASSERT(e3);
*e3 = if_then_else(bad, -skvx::shuffle<3, 2, 1, 0>(*e3), *e3);
} else {
SkASSERT(!e3);
(void) e3; // Silence GCC warning
}
}
}
// Replace 'bad' coordinates by rotating CCW to get the next point. c3 may be null for 2D points.
template<bool threeD>
static AI void correct_bad_coords(const M4f& bad, V4f* c1, V4f* c2, V4f* c3) {
if (any(bad)) {
*c1 = if_then_else(bad, next_ccw(*c1), *c1);
*c2 = if_then_else(bad, next_ccw(*c2), *c2);
if (c3) {
if constexpr (threeD) {
SkASSERT(c3);
*c3 = if_then_else(bad, next_ccw(*c3), *c3);
} else {
SkASSERT(!c3);
(void) c3; // Silence GCC warning
}
}
}
@ -413,18 +423,25 @@ bool CropToRect(const SkRect& cropRect, GrAA cropAA, GrQuadAAFlags* edgeFlags, G
//** EdgeVectors implementation
template<TessellationHelper::DeviceMode kDM>
void TessellationHelper::EdgeVectors::reset(const skvx::Vec<4, float>& xs,
const skvx::Vec<4, float>& ys,
const skvx::Vec<4, float>& ws,
GrQuad::Type quadType) {
// Calculate all projected edge vector values for this quad.
if (quadType == GrQuad::Type::kPerspective) {
if constexpr (kDM == DeviceMode::kRectilinear) {
SkASSERT(quadType <= GrQuad::Type::kRectilinear);
fX2D = xs;
fY2D = ys;
} else if (quadType < GrQuad::Type::kPerspective) {
// Same as above, but can't be part of constexpr-if since it uses 'quadType'.
SkASSERT(quadType == GrQuad::Type::kGeneral);
fX2D = xs;
fY2D = ys;
} else {
V4f iw = 1.0 / ws;
fX2D = xs * iw;
fY2D = ys * iw;
} else {
fX2D = xs;
fY2D = ys;
}
fDX = next_ccw(fX2D) - fX2D;
@ -436,7 +453,10 @@ void TessellationHelper::EdgeVectors::reset(const skvx::Vec<4, float>& xs,
fDY *= fInvLengths;
// Calculate angles between vectors
if (quadType <= GrQuad::Type::kRectilinear) {
if constexpr (kDM == DeviceMode::kRectilinear) {
// NOTE: Technically this assignment isn't strictly necessary since kRectilinear
// functions will never read fInvSinTheta or fCosTheta
SkASSERT(quadType <= GrQuad::Type::kRectilinear);
fCosTheta = 0.f;
fInvSinTheta = 1.f;
} else {
@ -453,7 +473,7 @@ void TessellationHelper::EdgeEquations::reset(const EdgeVectors& edgeVectors) {
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);
correct_bad_edges<false>(edgeVectors.fInvLengths >= 1.f / kTolerance, &dx, &dy, nullptr);
V4f c = mad(dx, edgeVectors.fY2D, -dy * edgeVectors.fX2D);
// Make sure normals point into the shape
@ -499,7 +519,7 @@ int TessellationHelper::EdgeEquations::computeDegenerateQuad(const V4f& signedEd
V4f denom = fA * next_cw(fB) - fB * next_cw(fA);
V4f px = (fB * next_cw(oc) - oc * next_cw(fB)) / denom;
V4f py = (oc * next_cw(fA) - fA * next_cw(oc)) / denom;
correct_bad_coords(abs(denom) < kTolerance, &px, &py, nullptr);
correct_bad_coords<false>(abs(denom) < kTolerance, &px, &py, nullptr);
// Calculate the signed distances from these 4 corners to the other two edges that did not
// define the intersection. So p(0) is compared to e3,e1, p(1) to e3,e2 , p(2) to e0,e1, and
@ -578,13 +598,15 @@ int TessellationHelper::EdgeEquations::computeDegenerateQuad(const V4f& signedEd
//** OutsetRequest implementation
template<TessellationHelper::DeviceMode kDM>
void TessellationHelper::OutsetRequest::reset(const EdgeVectors& edgeVectors, GrQuad::Type quadType,
const skvx::Vec<4, float>& edgeDistances) {
fEdgeDistances = edgeDistances;
// Based on the edge distances, determine if it's acceptable to use fInvSinTheta to
// calculate the inset or outset geometry.
if (quadType <= GrQuad::Type::kRectilinear) {
if constexpr (kDM == DeviceMode::kRectilinear) {
SkASSERT(quadType <= GrQuad::Type::kRectilinear);
// Since it's rectangular, the width (edge[1] or edge[2]) collapses if subtracting
// (dist[0] + dist[3]) makes the new width negative (minus for inset, outsetting will
// never be degenerate in this case). The same applies for height (edge[0] or edge[3])
@ -631,44 +653,63 @@ void TessellationHelper::OutsetRequest::reset(const EdgeVectors& edgeVectors, Gr
//** Vertices implementation
template<TessellationHelper::DeviceMode kDM, TessellationHelper::LocalMode kLM>
void TessellationHelper::Vertices::reset(const GrQuad& deviceQuad, const GrQuad* localQuad) {
// Set vertices to match the device and local quad
fX = deviceQuad.x4f();
fY = deviceQuad.y4f();
fW = deviceQuad.w4f();
// Skip w when in the fast path, since they will never be used for rectilinear insets/outsets
if constexpr (kDM != DeviceMode::kRectilinear) {
fW = deviceQuad.w4f();
} else {
SkASSERT(deviceQuad.quadType() <= GrQuad::Type::kRectilinear);
}
if (localQuad) {
if constexpr (kLM != LocalMode::kNone) {
SkASSERT(localQuad);
fU = localQuad->x4f();
fV = localQuad->y4f();
fR = localQuad->w4f();
fUVRCount = localQuad->hasPerspective() ? 3 : 2;
} else {
fUVRCount = 0;
}
if constexpr (kLM == LocalMode::kUVR) {
fR = localQuad->w4f();
} else {
SkASSERT(localQuad->quadType() < GrQuad::Type::kPerspective);
}
} // Else do nothing, localQuad is ignored entirely
}
template<TessellationHelper::DeviceMode kDM, TessellationHelper::LocalMode kLM>
void TessellationHelper::Vertices::asGrQuads(GrQuad* deviceOut, GrQuad::Type deviceType,
GrQuad* localOut, GrQuad::Type localType) const {
SkASSERT(deviceOut);
SkASSERT(fUVRCount == 0 || localOut);
fX.store(deviceOut->xs());
fY.store(deviceOut->ys());
if (deviceType == GrQuad::Type::kPerspective) {
fW.store(deviceOut->ws());
if constexpr (kDM != DeviceMode::kRectilinear) {
if (deviceType == GrQuad::Type::kPerspective) {
fW.store(deviceOut->ws());
}
} else {
SkASSERT(deviceType <= GrQuad::Type::kRectilinear);
}
deviceOut->setQuadType(deviceType); // This sets ws == 1 when device type != perspective
if (fUVRCount > 0) {
if constexpr (kLM != LocalMode::kNone) {
SkASSERT(localOut);
fU.store(localOut->xs());
fV.store(localOut->ys());
if (fUVRCount == 3) {
fR.store(localOut->ws());
if constexpr (kLM == LocalMode::kUVR) {
if (localType == GrQuad::Type::kPerspective) {
fR.store(localOut->ws());
}
} else {
SkASSERT(localType < GrQuad::Type::kPerspective);
}
localOut->setQuadType(localType);
localOut->setQuadType(localType); // This sets ws == 1 when local type != perspective
}
}
template<TessellationHelper::DeviceMode kDM, TessellationHelper::LocalMode kLM>
void TessellationHelper::Vertices::moveAlong(const EdgeVectors& edgeVectors,
const V4f& signedEdgeDistances) {
// This shouldn't be called if fInvSinTheta is close to infinity (cosTheta close to 1).
@ -679,13 +720,19 @@ void TessellationHelper::Vertices::moveAlong(const EdgeVectors& edgeVectors,
// inwards and the cw-rotated edge points outwards, hence the minus-sign.
// The edge distances are rotated compared to the corner outsets and (dx, dy), since if
// the edge is "on" both its corners need to be moved along their other edge vectors.
V4f signedOutsets = -edgeVectors.fInvSinTheta * next_cw(signedEdgeDistances);
V4f signedOutsetsCW = edgeVectors.fInvSinTheta * signedEdgeDistances;
V4f signedOutsets = -next_cw(signedEdgeDistances);
V4f signedOutsetsCW = signedEdgeDistances;
if constexpr (kDM == DeviceMode::kAny) {
// Must scale by inv sin theta (when kDM == kRectilinear, inv sin theta is 1, so there's no
// need for these multiplies).
signedOutsets *= edgeVectors.fInvSinTheta;
signedOutsetsCW *= edgeVectors.fInvSinTheta;
}
// x = x + outset * mask * next_cw(xdiff) - outset * next_cw(mask) * xdiff
fX += mad(signedOutsetsCW, next_cw(edgeVectors.fDX), signedOutsets * edgeVectors.fDX);
fY += mad(signedOutsetsCW, next_cw(edgeVectors.fDY), signedOutsets * edgeVectors.fDY);
if (fUVRCount > 0) {
if constexpr (kLM != LocalMode::kNone) {
// We want to extend the texture coords by the same proportion as the positions.
signedOutsets *= edgeVectors.fInvLengths;
signedOutsetsCW *= next_cw(edgeVectors.fInvLengths);
@ -693,25 +740,26 @@ void TessellationHelper::Vertices::moveAlong(const EdgeVectors& edgeVectors,
V4f dv = next_ccw(fV) - fV;
fU += mad(signedOutsetsCW, next_cw(du), signedOutsets * du);
fV += mad(signedOutsetsCW, next_cw(dv), signedOutsets * dv);
if (fUVRCount == 3) {
if constexpr (kLM == LocalMode::kUVR) {
V4f dr = next_ccw(fR) - fR;
fR += mad(signedOutsetsCW, next_cw(dr), signedOutsets * dr);
}
}
}
template<TessellationHelper::LocalMode kLM>
void TessellationHelper::Vertices::moveTo(const V4f& x2d, const V4f& y2d, const M4f& mask) {
// Left to right, in device space, for each point
V4f e1x = skvx::shuffle<2, 3, 2, 3>(fX) - skvx::shuffle<0, 1, 0, 1>(fX);
V4f e1y = skvx::shuffle<2, 3, 2, 3>(fY) - skvx::shuffle<0, 1, 0, 1>(fY);
V4f e1w = skvx::shuffle<2, 3, 2, 3>(fW) - skvx::shuffle<0, 1, 0, 1>(fW);
correct_bad_edges(mad(e1x, e1x, e1y * e1y) < kTolerance * kTolerance, &e1x, &e1y, &e1w);
correct_bad_edges<true>(mad(e1x, e1x, e1y * e1y) < kTolerance * kTolerance, &e1x, &e1y, &e1w);
// // Top to bottom, in device space, for each point
V4f e2x = skvx::shuffle<1, 1, 3, 3>(fX) - skvx::shuffle<0, 0, 2, 2>(fX);
V4f e2y = skvx::shuffle<1, 1, 3, 3>(fY) - skvx::shuffle<0, 0, 2, 2>(fY);
V4f e2w = skvx::shuffle<1, 1, 3, 3>(fW) - skvx::shuffle<0, 0, 2, 2>(fW);
correct_bad_edges(mad(e2x, e2x, e2y * e2y) < kTolerance * kTolerance, &e2x, &e2y, &e2w);
correct_bad_edges<true>(mad(e2x, e2x, e2y * e2y) < kTolerance * kTolerance, &e2x, &e2y, &e2w);
// Can only move along e1 and e2 to reach the new 2D point, so we have
// x2d = (x + a*e1x + b*e2x) / (w + a*e1w + b*e2w) and
@ -780,27 +828,35 @@ void TessellationHelper::Vertices::moveTo(const V4f& x2d, const V4f& y2d, const
fX += a * e1x + b * e2x;
fY += a * e1y + b * e2y;
fW += a * e1w + b * e2w;
correct_bad_coords(abs(denom) < kTolerance, &fX, &fY, &fW);
correct_bad_coords<true>(abs(denom) < kTolerance, &fX, &fY, &fW);
if (fUVRCount > 0) {
if constexpr (kLM != LocalMode::kNone) {
// Calculate R here so it can be corrected with U and V in case it's needed later
V4f e1u = skvx::shuffle<2, 3, 2, 3>(fU) - skvx::shuffle<0, 1, 0, 1>(fU);
V4f e1v = skvx::shuffle<2, 3, 2, 3>(fV) - skvx::shuffle<0, 1, 0, 1>(fV);
V4f e1r = skvx::shuffle<2, 3, 2, 3>(fR) - skvx::shuffle<0, 1, 0, 1>(fR);
correct_bad_edges(mad(e1u, e1u, e1v * e1v) < kTolerance * kTolerance, &e1u, &e1v, &e1r);
V4f e2u = skvx::shuffle<1, 1, 3, 3>(fU) - skvx::shuffle<0, 0, 2, 2>(fU);
V4f e2v = skvx::shuffle<1, 1, 3, 3>(fV) - skvx::shuffle<0, 0, 2, 2>(fV);
V4f e2r = skvx::shuffle<1, 1, 3, 3>(fR) - skvx::shuffle<0, 0, 2, 2>(fR);
correct_bad_edges(mad(e2u, e2u, e2v * e2v) < kTolerance * kTolerance, &e2u, &e2v, &e2r);
fU += a * e1u + b * e2u;
fV += a * e1v + b * e2v;
if (fUVRCount == 3) {
fR += a * e1r + b * e2r;
correct_bad_coords(abs(denom) < kTolerance, &fU, &fV, &fR);
if constexpr (kLM == LocalMode::kUV) {
correct_bad_edges<false>(mad(e1u, e1u, e1v * e1v) < kTolerance * kTolerance,
&e1u, &e1v, nullptr);
correct_bad_edges<false>(mad(e2u, e2u, e2v * e2v) < kTolerance * kTolerance,
&e2u, &e2v, nullptr);
fU += a * e1u + b * e2u;
fV += a * e1v + b * e2v;
correct_bad_coords<false>(abs(denom) < kTolerance, &fU, &fV, nullptr);
} else {
correct_bad_coords(abs(denom) < kTolerance, &fU, &fV, nullptr);
V4f e1r = skvx::shuffle<2, 3, 2, 3>(fR) - skvx::shuffle<0, 1, 0, 1>(fR);
V4f e2r = skvx::shuffle<1, 1, 3, 3>(fR) - skvx::shuffle<0, 0, 2, 2>(fR);
correct_bad_edges<true>(mad(e1u, e1u, e1v * e1v) < kTolerance * kTolerance,
&e1u, &e1v, &e1r);
correct_bad_edges<true>(mad(e2u, e2u, e2v * e2v) < kTolerance * kTolerance,
&e2u, &e2v, &e2r);
fU += a * e1u + b * e2u;
fV += a * e1v + b * e2v;
fR += a * e1r + b * e2r;
correct_bad_coords<true>(abs(denom) < kTolerance, &fU, &fV, &fR);
}
}
}
@ -816,53 +872,32 @@ void TessellationHelper::reset(const GrQuad& deviceQuad, const GrQuad* localQuad
fOutsetRequestValid = false;
fEdgeEquationsValid = false;
// Compute vertex properties that are always needed for a quad, so no point in doing it lazily.
fOriginal.reset(deviceQuad, localQuad);
fEdgeVectors.reset(fOriginal.fX, fOriginal.fY, fOriginal.fW, fDeviceType);
// Select processing functions that are most optimal for the given quad
DeviceMode dm = deviceQuad.quadType() <= GrQuad::Type::kRectilinear ? DeviceMode::kRectilinear
: DeviceMode::kAny;
LocalMode lm = localQuad ? (localQuad->hasPerspective() ? LocalMode::kUVR
: LocalMode::kUV)
: LocalMode::kNone;
fFunctions = GetFunctionTable(dm, lm);
SkASSERT(fFunctions);
fVerticesValid = true;
// Compute fixed geometric properties of the quads
fFunctions->fResetProc(this, deviceQuad, localQuad);
}
V4f TessellationHelper::inset(const skvx::Vec<4, float>& edgeDistances,
GrQuad* deviceInset, GrQuad* localInset) {
SkASSERT(fVerticesValid);
Vertices inset = fOriginal;
const OutsetRequest& request = this->getOutsetRequest(edgeDistances);
int vertexCount;
if (request.fInsetDegenerate) {
vertexCount = this->adjustDegenerateVertices(-request.fEdgeDistances, &inset);
} else {
this->adjustVertices(-request.fEdgeDistances, &inset);
vertexCount = 4;
}
inset.asGrQuads(deviceInset, fDeviceType, localInset, fLocalType);
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;
}
SkASSERT(fFunctions); // reset() must be called before first inset()
return fFunctions->fInsetProc(this, edgeDistances, deviceInset, localInset);
}
void TessellationHelper::outset(const skvx::Vec<4, float>& edgeDistances,
GrQuad* deviceOutset, GrQuad* localOutset) {
SkASSERT(fVerticesValid);
Vertices outset = fOriginal;
const OutsetRequest& request = this->getOutsetRequest(edgeDistances);
if (request.fOutsetDegenerate) {
this->adjustDegenerateVertices(request.fEdgeDistances, &outset);
} else {
this->adjustVertices(request.fEdgeDistances, &outset);
}
outset.asGrQuads(deviceOutset, fDeviceType, localOutset, fLocalType);
SkASSERT(fFunctions); // reset() must be called before first outset()
fFunctions->fOutsetProc(this, edgeDistances, deviceOutset, localOutset);
}
template<TessellationHelper::DeviceMode kDM>
const TessellationHelper::OutsetRequest& TessellationHelper::getOutsetRequest(
const skvx::Vec<4, float>& edgeDistances) {
// Much of the code assumes that we start from positive distances and apply it unmodified to
@ -871,7 +906,7 @@ const TessellationHelper::OutsetRequest& TessellationHelper::getOutsetRequest(
// Rebuild outset request if invalid or if the edge distances have changed.
if (!fOutsetRequestValid || any(edgeDistances != fOutsetRequest.fEdgeDistances)) {
fOutsetRequest.reset(fEdgeVectors, fDeviceType, edgeDistances);
fOutsetRequest.reset<kDM>(fEdgeVectors, fDeviceType, edgeDistances);
fOutsetRequestValid = true;
}
return fOutsetRequest;
@ -885,36 +920,43 @@ const TessellationHelper::EdgeEquations& TessellationHelper::getEdgeEquations()
return fEdgeEquations;
}
template<TessellationHelper::DeviceMode kDM, TessellationHelper::LocalMode kLM>
void TessellationHelper::adjustVertices(const skvx::Vec<4, float>& signedEdgeDistances,
Vertices* vertices) {
SkASSERT(vertices);
SkASSERT(vertices->fUVRCount == 0 || vertices->fUVRCount == 2 || vertices->fUVRCount == 3);
if (fDeviceType < GrQuad::Type::kPerspective) {
if constexpr (kDM == DeviceMode::kRectilinear) {
// For non-perspective, non-degenerate quads, moveAlong is correct and most efficient
vertices->moveAlong(fEdgeVectors, signedEdgeDistances);
SkASSERT(fDeviceType <= GrQuad::Type::kRectilinear);
vertices->moveAlong<kDM, kLM>(fEdgeVectors, signedEdgeDistances);
} else if (fDeviceType < GrQuad::Type::kPerspective) {
// This is the same as above, but can't be part of the constexpr-if since it depends on
// fDeviceType (but the only non-perspective type left is kGeneral).
SkASSERT(fDeviceType == GrQuad::Type::kGeneral);
vertices->moveAlong<kDM, kLM>(fEdgeVectors, signedEdgeDistances);
} else {
// For perspective, non-degenerate quads, use moveAlong for the projected points and then
// reconstruct Ws with moveTo.
Vertices projected = { fEdgeVectors.fX2D, fEdgeVectors.fY2D, /*w*/ 1.f, 0.f, 0.f, 0.f, 0 };
projected.moveAlong(fEdgeVectors, signedEdgeDistances);
vertices->moveTo(projected.fX, projected.fY, signedEdgeDistances != 0.f);
Vertices projected = { fEdgeVectors.fX2D, fEdgeVectors.fY2D, /*w*/ 1.f, 0.f, 0.f, 0.f };
projected.moveAlong<kDM, LocalMode::kNone>(fEdgeVectors, signedEdgeDistances);
vertices->moveTo<kLM>(projected.fX, projected.fY, signedEdgeDistances != 0.f);
}
}
template<TessellationHelper::DeviceMode kDM, TessellationHelper::LocalMode kLM>
int TessellationHelper::adjustDegenerateVertices(const skvx::Vec<4, float>& signedEdgeDistances,
Vertices* vertices) {
SkASSERT(vertices);
SkASSERT(vertices->fUVRCount == 0 || vertices->fUVRCount == 2 || vertices->fUVRCount == 3);
if (fDeviceType <= GrQuad::Type::kRectilinear) {
if constexpr (kDM == DeviceMode::kRectilinear) {
// For rectilinear, degenerate quads, can use moveAlong if the edge distances are adjusted
// to not cross over each other.
SkASSERT(fDeviceType <= GrQuad::Type::kRectilinear);
SkASSERT(all(signedEdgeDistances <= 0.f)); // Only way rectilinear can degenerate is insets
V4f halfLengths = -0.5f / next_cw(fEdgeVectors.fInvLengths); // Negate to inset
M4f crossedEdges = halfLengths > signedEdgeDistances;
V4f safeInsets = if_then_else(crossedEdges, halfLengths, signedEdgeDistances);
vertices->moveAlong(fEdgeVectors, safeInsets);
vertices->moveAlong<kDM, kLM>(fEdgeVectors, safeInsets);
// A degenerate rectilinear quad is either a point (both w and h crossed), or a line
return all(crossedEdges) ? 1 : 2;
@ -925,9 +967,80 @@ int TessellationHelper::adjustDegenerateVertices(const skvx::Vec<4, float>& sign
V4f y2d = fEdgeVectors.fY2D;
int vertexCount = this->getEdgeEquations().computeDegenerateQuad(signedEdgeDistances,
&x2d, &y2d);
vertices->moveTo(x2d, y2d, signedEdgeDistances != 0.f);
vertices->moveTo<kLM>(x2d, y2d, signedEdgeDistances != 0.f);
return vertexCount;
}
}
template<TessellationHelper::DeviceMode kDM, TessellationHelper::LocalMode kLM>
void TessellationHelper::Reset(TessellationHelper* t,
const GrQuad& deviceQuad,
const GrQuad* localQuad) {
t->fOriginal.reset<kDM, kLM>(deviceQuad, localQuad);
t->fEdgeVectors.reset<kDM>(t->fOriginal.fX, t->fOriginal.fY, t->fOriginal.fW, t->fDeviceType);
}
template<TessellationHelper::DeviceMode kDM, TessellationHelper::LocalMode kLM>
V4f TessellationHelper::Inset(TessellationHelper* t, const skvx::Vec<4, float>& edgeDistances,
GrQuad* deviceInset, GrQuad* localInset) {
Vertices inset = t->fOriginal;
const OutsetRequest& request = t->getOutsetRequest<kDM>(edgeDistances);
int vertexCount;
if (request.fInsetDegenerate) {
vertexCount = t->adjustDegenerateVertices<kDM, kLM>(-request.fEdgeDistances, &inset);
} else {
t->adjustVertices<kDM, kLM>(-request.fEdgeDistances, &inset);
vertexCount = 4;
}
inset.asGrQuads<kDM, kLM>(deviceInset, t->fDeviceType, localInset, t->fLocalType);
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.
if constexpr (kDM == DeviceMode::kRectilinear) {
return t->getEdgeEquations().estimateCoverage(inset.fX, inset.fY);
} else {
return t->getEdgeEquations().estimateCoverage(inset.fX / inset.fW, inset.fY / inset.fW);
}
} else {
return 1.f;
}
}
template<TessellationHelper::DeviceMode kDM, TessellationHelper::LocalMode kLM>
void TessellationHelper::Outset(TessellationHelper* t, const skvx::Vec<4, float>& edgeDistances,
GrQuad* deviceOutset, GrQuad* localOutset) {
Vertices outset = t->fOriginal;
const OutsetRequest& request = t->getOutsetRequest<kDM>(edgeDistances);
if constexpr (kDM == DeviceMode::kRectilinear) {
// Rectilinear outsetting is never degenerate, so avoid the branch and inclusion of
// adjustDegenerateVertices().
SkASSERT(!request.fOutsetDegenerate);
t->adjustVertices<kDM, kLM>(request.fEdgeDistances, &outset);
} else if (request.fOutsetDegenerate) {
t->adjustDegenerateVertices<kDM, kLM>(request.fEdgeDistances, &outset);
} else {
t->adjustVertices<kDM, kLM>(request.fEdgeDistances, &outset);
}
outset.asGrQuads<kDM, kLM>(deviceOutset, t->fDeviceType, localOutset, t->fLocalType);
}
// The initialization order in this array must match the index calculated in GetFunctionTable.
TessellationHelper::FunctionTable TessellationHelper::kFunctionTables[] = {
FunctionTable(FunctionTable::Config<DeviceMode::kRectilinear, LocalMode::kNone>()),
FunctionTable(FunctionTable::Config<DeviceMode::kAny, LocalMode::kNone>()),
FunctionTable(FunctionTable::Config<DeviceMode::kRectilinear, LocalMode::kUV>()),
FunctionTable(FunctionTable::Config<DeviceMode::kAny, LocalMode::kUV>()),
FunctionTable(FunctionTable::Config<DeviceMode::kRectilinear, LocalMode::kUVR>()),
FunctionTable(FunctionTable::Config<DeviceMode::kAny, LocalMode::kUVR>())
};
const TessellationHelper::FunctionTable* TessellationHelper::GetFunctionTable(DeviceMode devMode,
LocalMode localMode) {
int index = ((int) localMode << 1) + (int) devMode;
return &(kFunctionTables[index]);
}
}; // namespace GrQuadUtils

View File

@ -70,6 +70,31 @@ namespace GrQuadUtils {
GrQuad* deviceOutset, GrQuad* localOutset);
private:
// This enum is used to specialize the methods on the various following helper structs
// when assumptions can be made on the geometric nature of the device coordinates.
enum class DeviceMode : int {
// All device-space quads submitted to the TessellationHelper will have
// GrQuad::Type::kRectilinear or kAxisAligned (all corners are 90 degrees).
// Fast paths will always be taken.
kRectilinear,
// The device-space quads submitted may have any GrQuad::Type; fast paths will be taken
// on a per-quad basis.
kAny
};
// Like DeviceMode, this is used to specialize the helper structs based on the
// dimensionality of the local coordinates that are kept in-sync with device inset/outsets.
enum class LocalMode : int {
// The localQuad will be ignored and is allowed to be null
kNone,
// The localQuad will have its x and y coordinates updated, asserts that it's never null
// and that its type is not kPerspective
kUV,
// The localQuad will have its x, y, and w coordinates updated, asserts that it's never
// null and supports any type of local coords.
kUVR,
};
// NOTE: This struct is named 'EdgeVectors' because it holds a lot of cached calculations
// pertaining to the edge vectors of the input quad, projected into 2D device coordinates.
// While they are not direction vectors, this struct represents a convenient storage space
@ -87,24 +112,28 @@ namespace GrQuadUtils {
skvx::Vec<4, float> fCosTheta;
skvx::Vec<4, float> fInvSinTheta; // 1 / sin(theta)
void reset(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys,
const skvx::Vec<4, float>& ws, GrQuad::Type quadType);
template<DeviceMode kDM>
SK_ALWAYS_INLINE void reset(const skvx::Vec<4, float>& xs,
const skvx::Vec<4, float>& ys,
const skvx::Vec<4, float>& ws,
GrQuad::Type quadType);
};
struct EdgeEquations {
// a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR.
skvx::Vec<4, float> fA, fB, fC;
void reset(const EdgeVectors& edgeVectors);
SK_ALWAYS_INLINE void reset(const EdgeVectors& edgeVectors);
skvx::Vec<4, float> estimateCoverage(const skvx::Vec<4, float>& x2d,
const skvx::Vec<4, float>& y2d) const;
SK_ALWAYS_INLINE skvx::Vec<4, float> estimateCoverage(
const skvx::Vec<4, float>& x2d, const skvx::Vec<4, float>& y2d) const;
// 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,
skvx::Vec<4, float>* x2d, skvx::Vec<4, float>* y2d) const;
SK_ALWAYS_INLINE int computeDegenerateQuad(
const skvx::Vec<4, float>& signedEdgeDistances,
skvx::Vec<4, float>* x2d, skvx::Vec<4, float>* y2d) const;
};
struct OutsetRequest {
@ -118,50 +147,86 @@ namespace GrQuadUtils {
bool fInsetDegenerate;
bool fOutsetDegenerate;
void reset(const EdgeVectors& edgeVectors, GrQuad::Type quadType,
const skvx::Vec<4, float>& edgeDistances);
template<DeviceMode kDM>
SK_ALWAYS_INLINE void reset(const EdgeVectors& edgeVectors, GrQuad::Type quadType,
const skvx::Vec<4, float>& edgeDistances);
};
struct Vertices {
// X, Y, and W coordinates in device space. If not perspective, w should be set to 1.f
skvx::Vec<4, float> fX, fY, fW;
// U, V, and R coordinates representing local quad.
// Ignored depending on uvrCount (0, 1, 2).
// Ignored depending on the LocalMode of the function calls.
skvx::Vec<4, float> fU, fV, fR;
int fUVRCount;
void reset(const GrQuad& deviceQuad, const GrQuad* localQuad);
template<DeviceMode kDM, LocalMode kLM>
SK_ALWAYS_INLINE void reset(const GrQuad& deviceQuad, const GrQuad* localQuad);
void asGrQuads(GrQuad* deviceOut, GrQuad::Type deviceType,
GrQuad* localOut, GrQuad::Type localType) const;
template<DeviceMode kDM, LocalMode kLM>
SK_ALWAYS_INLINE void asGrQuads(GrQuad* deviceOut, GrQuad::Type deviceType,
GrQuad* localOut, GrQuad::Type localType) const;
// Update the device and optional local coordinates by moving the corners along their
// edge vectors such that the new edges have moved 'signedEdgeDistances' from their
// original lines. This should only be called if the 'edgeVectors' fInvSinTheta data is
// numerically sound.
void moveAlong(const EdgeVectors& edgeVectors,
const skvx::Vec<4, float>& signedEdgeDistances);
template<DeviceMode kDM, LocalMode kLM>
SK_ALWAYS_INLINE void moveAlong(const EdgeVectors& edgeVectors,
const skvx::Vec<4, float>& signedEdgeDistances);
// Update the device coordinates by deriving (x,y,w) that project to (x2d, y2d), with
// optional local coordinates updated to match the new vertices. It is assumed that
// 'mask' was respected when determining (x2d, y2d), but it is used to ensure that only
// unmasked unprojected edge vectors are used when computing device and local coords.
void moveTo(const skvx::Vec<4, float>& x2d,
const skvx::Vec<4, float>& y2d,
const skvx::Vec<4, int32_t>& mask);
template<LocalMode kLM>
SK_ALWAYS_INLINE void moveTo(const skvx::Vec<4, float>& x2d,
const skvx::Vec<4, float>& y2d,
const skvx::Vec<4, int32_t>& mask);
};
Vertices fOriginal;
EdgeVectors fEdgeVectors;
GrQuad::Type fDeviceType;
GrQuad::Type fLocalType;
// Dynamic way to go from the public, un-templated API functions reset/inset/outset to the
// templated versions that use compile-time branch removal to specialize the tessellation
// based on what's known about the quads.
struct FunctionTable {
typedef void (*ResetProc)(TessellationHelper*, const GrQuad&, const GrQuad*);
typedef skvx::Vec<4, float> (*InsetProc)(TessellationHelper*,
const skvx::Vec<4, float>&,
GrQuad*, GrQuad*);
typedef void (*OutsetProc)(TessellationHelper*, const skvx::Vec<4, float>&,
GrQuad*, GrQuad*);
// This is needed so we can specify the static function template parameters as
// constructor arguments. The FunctionTable ctor cannot be templated if it has no args.
template<DeviceMode kDM, LocalMode kLM>
struct Config {
static constexpr DeviceMode kDeviceMode = kDM;
static constexpr LocalMode kLocalMode = kLM;
};
template<typename C>
constexpr FunctionTable(C config)
: fResetProc(Reset<C::kDeviceMode, C::kLocalMode>)
, fInsetProc(Inset<C::kDeviceMode, C::kLocalMode>)
, fOutsetProc(Outset<C::kDeviceMode, C::kLocalMode>) {}
ResetProc fResetProc;
InsetProc fInsetProc;
OutsetProc fOutsetProc;
};
// 2 device modes X 3 local modes = 6 total function table configs
static FunctionTable kFunctionTables[6];
static SK_ALWAYS_INLINE const FunctionTable* GetFunctionTable(DeviceMode, LocalMode);
const FunctionTable* fFunctions = nullptr; // Refers to an entry in kFunctionTables
Vertices fOriginal;
EdgeVectors fEdgeVectors;
GrQuad::Type fDeviceType;
GrQuad::Type fLocalType;
// Lazily computed as needed; use accessor functions instead of direct access.
OutsetRequest fOutsetRequest;
EdgeEquations fEdgeEquations;
// Validity of Vertices/EdgeVectors (always true after first call to set()).
bool fVerticesValid = false;
// Validity of outset request (true after calling getOutsetRequest() until next set() call
// or next inset/outset() with different edge distances).
bool fOutsetRequestValid = false;
@ -170,16 +235,37 @@ namespace GrQuadUtils {
// The requested edge distances must be positive so that they can be reused between inset
// and outset calls.
const OutsetRequest& getOutsetRequest(const skvx::Vec<4, float>& edgeDistances);
const EdgeEquations& getEdgeEquations();
template<DeviceMode kDM>
SK_ALWAYS_INLINE const OutsetRequest& getOutsetRequest(
const skvx::Vec<4, float>& edgeDistances);
SK_ALWAYS_INLINE const EdgeEquations& getEdgeEquations();
// Outsets or insets 'vertices' by the given perpendicular 'signedEdgeDistances' (inset or
// outset is determined implicitly by the sign of the distances).
void adjustVertices(const skvx::Vec<4, float>& signedEdgeDistances, Vertices* vertices);
template<DeviceMode kDM, LocalMode kLM>
SK_ALWAYS_INLINE void adjustVertices(
const skvx::Vec<4, float>& signedEdgeDistances, Vertices* vertices);
// Like adjustVertices() but handles empty edges, collapsed quads, numerical issues, and
// returns the number of effective vertices in the adjusted shape.
int adjustDegenerateVertices(const skvx::Vec<4, float>& signedEdgeDistances,
Vertices* vertices);
template<DeviceMode kDM, LocalMode kLM>
SK_ALWAYS_INLINE int adjustDegenerateVertices(
const skvx::Vec<4, float>& signedEdgeDistances, Vertices* vertices);
// Specialized implementations of public API, filled in via FunctionTable.
// All helper functions that these use are marked SK_ALWAYS_INLINE so we end up with
// 6 blocks of skvx/vector instructions (w/o any branches for kDM == kRectilinear).
template<DeviceMode kDM, LocalMode kLM>
static void Reset(TessellationHelper*, const GrQuad& deviceQuad, const GrQuad* localQuad);
template<DeviceMode kDM, LocalMode kLM>
static skvx::Vec<4, float> Inset(TessellationHelper*,
const skvx::Vec<4, float>& edgeDistances,
GrQuad* deviceInset, GrQuad* localInset);
template<DeviceMode kDM, LocalMode kLM>
static void Outset(TessellationHelper*, const skvx::Vec<4, float>& edgeDistances,
GrQuad* deviceOutset, GrQuad* localOutset);
};
}; // namespace GrQuadUtils