diff --git a/src/gpu/GrQuad.cpp b/src/gpu/GrQuad.cpp index cce49f19c3..cce70dbee5 100644 --- a/src/gpu/GrQuad.cpp +++ b/src/gpu/GrQuad.cpp @@ -19,7 +19,6 @@ // Allow some tolerance from floating point matrix transformations, but SkScalarNearlyEqual doesn't // support comparing infinity, and coords_form_rect should return true for infinite edges #define NEARLY_EQUAL(f1, f2) (f1 == f2 || SkScalarNearlyEqual(f1, f2, 1e-5f)) -#define NEARLY_ZERO(f1) NEARLY_EQUAL(f1, 0.f) // This is not the most performance critical function; code using GrQuad should rely on the faster // quad type from matrix path, so this will only be called as part of SkASSERT. @@ -30,31 +29,10 @@ static bool coords_form_rect(const float xs[4], const float ys[4]) { NEARLY_EQUAL(ys[0], ys[1]) && NEARLY_EQUAL(ys[2], ys[3])); } -static bool coords_rectilinear(const float xs[4], const float ys[4]) { - // Edge from 0 to 1 should have the same length as edge from 3 to 2 - // and edge from 1 to 3 should have the same length as edge from 2 to 0 - // noo that makes it a parallelogram, need dot product between edge 0 to 1 and edge 1 to 3 = 0 - // and 0-2 and 2-3 is 0. - static constexpr auto dot = SkPoint::DotProduct; - SkVector e0{xs[1] - xs[0], ys[1] - ys[0]}; // Connects to e1 and e2(repeat) - SkVector e1{xs[3] - xs[1], ys[3] - ys[1]}; // connects to e0(repeat) and e3 - SkVector e2{xs[0] - xs[2], ys[0] - ys[2]}; // connects to e0 and e3(repeat) - SkVector e3{xs[2] - xs[3], ys[2] - ys[3]}; // connects to e1(repeat) and e2 - - return NEARLY_ZERO(dot(e0, e1)) && NEARLY_ZERO(dot(e1, e3)) && - NEARLY_ZERO(dot(e2, e0)) && NEARLY_ZERO(dot(e3, e2)); -} - GrQuadType GrQuad::quadType() const { // Since GrQuad applies any perspective information at construction time, there's only two // types to choose from. - if (coords_form_rect(fX, fY)) { - return GrQuadType::kRect; - } else if (coords_rectilinear(fX, fY)) { - return GrQuadType::kRectilinear; - } else { - return GrQuadType::kStandard; - } + return coords_form_rect(fX, fY) ? GrQuadType::kRect : GrQuadType::kStandard; } GrQuadType GrPerspQuad::quadType() const { @@ -62,13 +40,7 @@ GrQuadType GrPerspQuad::quadType() const { return GrQuadType::kPerspective; } else { // Rect or standard quad, can ignore w since they are all ones - if (coords_form_rect(fX, fY)) { - return GrQuadType::kRect; - } else if (coords_rectilinear(fX, fY)) { - return GrQuadType::kRectilinear; - } else { - return GrQuadType::kStandard; - } + return coords_form_rect(fX, fY) ? GrQuadType::kRect : GrQuadType::kStandard; } } #endif @@ -125,8 +97,6 @@ template void GrResolveAATypeForQuad(GrAAType, GrQuadAAFlags, const GrPerspQuad& GrQuadType GrQuadTypeForTransformedRect(const SkMatrix& matrix) { if (matrix.rectStaysRect()) { return GrQuadType::kRect; - } else if (matrix.preservesRightAngles()) { - return GrQuadType::kRectilinear; } else if (matrix.hasPerspective()) { return GrQuadType::kPerspective; } else { diff --git a/src/gpu/GrQuad.h b/src/gpu/GrQuad.h index 824131b1b4..5f62682ef8 100644 --- a/src/gpu/GrQuad.h +++ b/src/gpu/GrQuad.h @@ -20,13 +20,11 @@ enum class GrQuadAAFlags; // 1. Stays a rectangle - the matrix rectStaysRect() is true, or x(0) == x(1) && x(2) == x(3) // and y(0) == y(2) && y(1) == y(3). Or under mirrors, x(0) == x(2) && x(1) == x(3) and // y(0) == y(1) && y(2) == y(3). -// 2. Is rectilinear - the matrix does not have skew or perspective, but may rotate (unlike #1) -// 3. Is a quadrilateral - the matrix does not have perspective, but may rotate or skew, or +// 2. Is a quadrilateral - the matrix does not have perspective, but may rotate or skew, or // ws() == all ones. -// 4. Is a perspective quad - the matrix has perspective, subsuming all previous quad types. +// 3. Is a perspective quad - the matrix has perspective, subsuming all previous quad types. enum class GrQuadType { kRect, - kRectilinear, kStandard, kPerspective, kLast = kPerspective diff --git a/src/gpu/GrVertexWriter.h b/src/gpu/GrVertexWriter.h index e215793356..5e2e979a5d 100644 --- a/src/gpu/GrVertexWriter.h +++ b/src/gpu/GrVertexWriter.h @@ -79,14 +79,6 @@ struct GrVertexWriter { this->write(remainder...); } - template - void write(const Sk4f& vector, const Args&... remainder) { - float buffer[4]; - vector.store(buffer); - this->write(buffer); - this->write(remainder...); - } - void write() {} /** diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp index 6f9ca2ad60..60a7c1b366 100644 --- a/src/gpu/ops/GrQuadPerEdgeAA.cpp +++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp @@ -15,91 +15,25 @@ #include "glsl/GrGLSLVertexGeoBuilder.h" #include "SkNx.h" -#define AI SK_ALWAYS_INLINE - namespace { -static AI Sk4f fma(const Sk4f& f, const Sk4f& m, const Sk4f& a) { - return SkNx_fma<4, float>(f, m, a); -} - -// These rotate the points/edge values either clockwise or counterclockwise assuming tri strip -// order. -static AI Sk4f nextCW(const Sk4f& v) { - return SkNx_shuffle<2, 0, 3, 1>(v); -} - -static AI Sk4f nextCCW(const Sk4f& v) { - return SkNx_shuffle<1, 3, 0, 2>(v); -} - -// Fills Sk4f with 1f if edge bit is set, 0f otherwise. Edges are ordered LBTR to match CCW ordering -// of vertices in the quad. -static AI Sk4f compute_edge_mask(GrQuadAAFlags aaFlags) { - return Sk4f((GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f, - (GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f, - (GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f, - (GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f); -} - -static AI void outset_masked_vertices(const Sk4f& xdiff, const Sk4f& ydiff, const Sk4f& invLengths, - const Sk4f& mask, Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, - int uvrCount) { - auto halfMask = 0.5f * mask; - auto maskCW = nextCW(halfMask); - *x += maskCW * -xdiff + halfMask * nextCW(xdiff); - *y += maskCW * -ydiff + halfMask * nextCW(ydiff); - if (uvrCount > 0) { - // We want to extend the texture coords by the same proportion as the positions. - maskCW *= invLengths; - halfMask *= nextCW(invLengths); - Sk4f udiff = nextCCW(*u) - *u; - Sk4f vdiff = nextCCW(*v) - *v; - *u += maskCW * -udiff + halfMask * nextCW(udiff); - *v += maskCW * -vdiff + halfMask * nextCW(vdiff); - if (uvrCount == 3) { - Sk4f rdiff = nextCCW(*r) - *r; - *r += maskCW * -rdiff + halfMask * nextCW(rdiff); - } - } -} - -static AI void outset_vertices(const Sk4f& xdiff, const Sk4f& ydiff, const Sk4f& invLengths, - Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) { - *x += 0.5f * (-xdiff + nextCW(xdiff)); - *y += 0.5f * (-ydiff + nextCW(ydiff)); - if (uvrCount > 0) { - Sk4f t = 0.5f * invLengths; - Sk4f udiff = nextCCW(*u) - *u; - Sk4f vdiff = nextCCW(*v) - *v; - *u += t * -udiff + nextCW(t) * nextCW(udiff); - *v += t * -vdiff + nextCW(t) * nextCW(vdiff); - if (uvrCount == 3) { - Sk4f rdiff = nextCCW(*r) - *r; - *r += t * -rdiff + nextCW(t) * nextCW(rdiff); - } - } -} - -static AI void compute_edge_distances(const Sk4f& a, const Sk4f& b, const Sk4f& c, const Sk4f& x, - const Sk4f& y, const Sk4f& w, Sk4f edgeDistances[]) { - for (int i = 0; i < 4; ++i) { - edgeDistances[i] = a * x[i] + b * y[i] + c * w[i]; - } -} - // This computes the four edge equations for a quad, then outsets them and optionally computes a new // quad as the intersection points of the outset edges. 'x' and 'y' contain the original points as -// input and the outset points as output. In order to be used as a component of perspective edge -// distance calculation, this exports edge equations in 'a', 'b', and 'c'. Use -// compute_edge_distances to turn these equations into the distances needed by the shader. The -// values in x, y, u, v, and r are possibly updated if outsetting is needed. r is the local -// position's w component if it exists. -static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, - Sk4f* a, Sk4f* b, Sk4f* c, Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount, bool outset) { +// input and the outset points as output. 'a', 'b', and 'c' are the edge equation coefficients on +// output. The values in x, y, u, v, and r are possibly updated if outsetting is needed. +// r is the local position's w component if it exists. +static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, Sk4f* a, + Sk4f* b, Sk4f* c, Sk4f* u, Sk4f* v, Sk4f* r, + int uvrChannelCount, bool outsetCorners) { SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); - // Compute edge vectors for the quad. + static constexpr auto fma = SkNx_fma<4, float>; + // These rotate the points/edge values either clockwise or counterclockwise assuming tri strip + // order. + auto nextCW = [](const Sk4f& v) { return SkNx_shuffle<2, 0, 3, 1>(v); }; + auto nextCCW = [](const Sk4f& v) { return SkNx_shuffle<1, 3, 0, 2>(v); }; + + // Compute edge equations for the quad. auto xnext = nextCCW(*x); auto ynext = nextCCW(*y); // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge. @@ -109,7 +43,7 @@ static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* xdiff *= invLengths; ydiff *= invLengths; - // Use above vectors to compute edge equations (importantly before we outset positions). + // Use above vectors to compute edge equations. *c = fma(xnext, *y, -ynext * *x) * invLengths; // Make sure the edge equations have their normals facing into the quad in device space. auto test = fma(ydiff, nextCW(*x), fma(-xdiff, nextCW(*y), *c)); @@ -128,91 +62,66 @@ static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* if (aaFlags != GrQuadAAFlags::kAll) { // This order is the same order the edges appear in xdiff/ydiff and therefore as the // edges in a/b/c. - Sk4f mask = compute_edge_mask(aaFlags); - + auto mask = Sk4f(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f, + GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f, + GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f, + GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f); // Outset edge equations for masked out edges another pixel so that they always evaluate // >= 1. *c += (1.f - mask); - if (outset) { - outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount); + if (outsetCorners) { + // Do the vertex outset. + mask *= 0.5f; + auto maskCW = nextCW(mask); + *x += maskCW * -xdiff + mask * nextCW(xdiff); + *y += maskCW * -ydiff + mask * nextCW(ydiff); + if (uvrChannelCount > 0) { + // We want to extend the texture coords by the same proportion as the positions. + maskCW *= invLengths; + mask *= nextCW(invLengths); + Sk4f udiff = nextCCW(*u) - *u; + Sk4f vdiff = nextCCW(*v) - *v; + *u += maskCW * -udiff + mask * nextCW(udiff); + *v += maskCW * -vdiff + mask * nextCW(vdiff); + if (uvrChannelCount == 3) { + Sk4f rdiff = nextCCW(*r) - *r; + *r += maskCW * -rdiff + mask * nextCW(rdiff); + } + } + } + } else if (outsetCorners) { + *x += 0.5f * (-xdiff + nextCW(xdiff)); + *y += 0.5f * (-ydiff + nextCW(ydiff)); + if (uvrChannelCount > 0) { + Sk4f t = 0.5f * invLengths; + Sk4f udiff = nextCCW(*u) - *u; + Sk4f vdiff = nextCCW(*v) - *v; + *u += t * -udiff + nextCW(t) * nextCW(udiff); + *v += t * -vdiff + nextCW(t) * nextCW(vdiff); + if (uvrChannelCount == 3) { + Sk4f rdiff = nextCCW(*r) - *r; + *r += t * -rdiff + nextCW(t) * nextCW(rdiff); + } } - } else if (outset) { - outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount); } } -// A specialization of the above function that can compute edge distances very quickly when it knows -// that the edges intersect at right angles, i.e. any transform other than skew and perspective -// (GrQuadType::kRectilinear). Unlike the above function, this always outsets the corners since it -// cannot be reused in the perspective case. -static void compute_rectilinear_dists_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, - Sk4f* y, Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) { - SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); - auto xnext = nextCCW(*x); - auto ynext = nextCCW(*y); - // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge. - auto xdiff = xnext - *x; - auto ydiff = ynext - *y; - // Need length and 1/length in this variant - auto lengths = fma(xdiff, xdiff, ydiff * ydiff).sqrt(); - auto invLengths = lengths.invert(); - xdiff *= invLengths; - ydiff *= invLengths; - - // Since the quad is rectilinear, the edge distances are predictable and independent of the - // actual orientation of the quad. The lengths vector stores |p1-p0|, |p3-p1|, |p0-p2|, |p2-p3|, - // matching the CCW order. For instance, edge distances for p0 are 0 for e0 and e2 since they - // intersect at p0. Distance to e1 is the same as p0 to p1. Distance to e3 is p0 to p2 since - // e3 goes through p2 and since the quad is rectilinear, we know that's the shortest distance. - edgeDistances[0] = Sk4f(0.f, lengths[0], 0.f, lengths[2]); - edgeDistances[1] = Sk4f(0.f, 0.f, lengths[0], lengths[1]); - edgeDistances[2] = Sk4f(lengths[2], lengths[3], 0.f, 0.f); - edgeDistances[3] = Sk4f(lengths[1], 0.f, lengths[3], 0.f); - - if (aaFlags != GrQuadAAFlags::kAll) { - // This order is the same order the edges appear in xdiff/ydiff and therefore as the - // edges in a/b/c. - Sk4f mask = compute_edge_mask(aaFlags); - - // Update opposite corner distances by the 0.5 pixel outset - edgeDistances[0] += Sk4f(0.f, 0.5f, 0.f, 0.5f) * mask; - edgeDistances[1] += Sk4f(0.f, 0.f, 0.5f, 0.5f) * mask; - edgeDistances[2] += Sk4f(0.5f, 0.5f, 0.f, 0.f) * mask; - edgeDistances[3] += Sk4f(0.5f, 0.f, 0.5f, 0.f) * mask; - - // Outset edge equations for masked out edges another pixel so that they always evaluate - // So add 1-mask to each point's edge distances vector so that coverage >= 1 on non-aa - for (int i = 0; i < 4; ++i) { - edgeDistances[i] += (1.f - mask); - } - outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount); - } else { - // Update opposite corner distances by 0.5 pixel, skipping the need for mask since that's 1s - edgeDistances[0] += Sk4f(0.f, 0.5f, 0.f, 0.5f); - edgeDistances[1] += Sk4f(0.f, 0.f, 0.5f, 0.5f); - edgeDistances[2] += Sk4f(0.5f, 0.5f, 0.f, 0.f); - edgeDistances[3] += Sk4f(0.5f, 0.f, 0.5f, 0.f); - - outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount); - } -} - -// Generalizes compute_quad_edge_distances_and_outset_vertices to extrapolate local coords such that -// after perspective division of the device coordinate, the original local coordinate value is at -// the original un-outset device position. r is the local coordinate's w component. -static void compute_quad_dists_and_outset_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, - Sk4f* y, Sk4f* w, Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) { +// Generalizes the above function to extrapolate local coords such that after perspective division +// of the device coordinate, the original local coordinate value is at the original un-outset +// device position. r is the local coordinate's w component. +static void compute_quad_edges_and_outset_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, + Sk4f* w, Sk4f* a, Sk4f* b, Sk4f* c, + Sk4f* u, Sk4f* v, Sk4f* r, + int uvrChannelCount) { SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3); auto iw = (*w).invert(); auto x2d = (*x) * iw; auto y2d = (*y) * iw; - Sk4f a, b, c; // Don't compute outset corners in the normalized space, which means u, v, and r don't need - // to be provided here (outset separately below). Since this is computing distances for a - // projected quad, there is a very good chance it's not rectilinear so use the general 2D path. - compute_quad_edges_and_outset_vertices(aaFlags, &x2d, &y2d, &a, &b, &c, nullptr, nullptr, - nullptr, /* uvr ct */ 0, /* outsetCorners */ false); + // to be provided here (outset separately below). + compute_quad_edges_and_outset_vertices(aaFlags, &x2d, &y2d, a, b, c, nullptr, nullptr, nullptr, + /* uvr ct */ 0, /* outsetCorners */ false); static const float kOutset = 0.5f; if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) { @@ -296,13 +205,36 @@ static void compute_quad_dists_and_outset_persp_vertices(GrQuadAAFlags aaFlags, } } } - - // Use the original edge equations with the outset homogeneous coordinates to get the edge - // distance (technically multiplied by w, so that the fragment shader can do perspective - // interpolation when it multiplies by 1/w later). - compute_edge_distances(a, b, c, *x, *y, *w, edgeDistances); } +// Fast path for non-AA quads batched into an AA op. Since they are part of the AA op, the vertices +// need to have valid edge equations that ensure coverage is set to 1. To get perspective +// interpolation of the edge distance, the vertex shader outputs d*w and then multiplies by 1/w in +// the fragment shader. For non-AA edges, the edge equation can be simplified to 0*x/w + y/w + c >= +// 1, so the vertex shader outputs c*w. The quad is sent as two triangles, so a fragment is the +// interpolation between 3 of the 4 vertices. If iX are the weights for the 3 involved quad +// vertices, then the fragment shader's state is: +// f_cw = c * (iA*wA + iB*wB + iC*wC) and f_1/w = iA/wA + iB/wB + iC/wC +// (where A,B,C are chosen from {1,2,3, 4}) +// When there's no perspective, then f_cw*f_1/w = c and setting c = 1 guarantees a proper non-AA +// edge. Unfortunately when there is perspective, f_cw*f_1/w != c unless the fragment is at a +// vertex. We must pick a c such that f_cw*f_1/w >= 1 across the whole primitive. +// Let n = min(w1,w2,w3,w4) and m = max(w1,w2,w3,w4) and rewrite +// f_1/w=(iA*wB*wC + iB*wA*wC + iC*wA*wB) / (wA*wB*wC) +// Since the iXs are weights for the interior of the primitive, then we have: +// n <= (iA*wA + iB*wB + iC*wC) <= m and +// n^2 <= (iA*wB*wC + iB*wA*wC + iC*wA*wB) <= m^2 and +// n^3 <= wA*wB*wC <= m^3 regardless of the choice of A,B, and C +// Thus if we set c = m^3/n^3, it guarantees f_cw*f_1/w >= 1 for any perspective. +static SkPoint3 compute_non_aa_persp_edge_coeffs(const Sk4f& w) { + float n = w.min(); + float m = w.max(); + return {0.f, 0.f, (m * m * m) / (n * n * n)}; +} + +// When there's guaranteed no perspective, the edge coefficients for non-AA quads is constant +static constexpr SkPoint3 kNonAANoPerspEdgeCoeffs = {0, 0, 1}; + } // anonymous namespace namespace GrQuadPerEdgeAA { @@ -316,10 +248,13 @@ void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& devi bool localHasPerspective = spec.localQuadType() == GrQuadType::kPerspective; GrVertexColor color(color4f, GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType()); - // Load position data into Sk4fs (always x, y, and load w to avoid branching down the road) + // Load position data into Sk4fs (always x, y and maybe w) Sk4f x = deviceQuad.x4f(); Sk4f y = deviceQuad.y4f(); - Sk4f w = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective + Sk4f w; + if (deviceHasPerspective) { + w = deviceQuad.w4f(); + } // Load local position data into Sk4fs (either none, just u,v or all three) Sk4f u, v, r; @@ -332,42 +267,48 @@ void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& devi } } - // Index into array refers to vertex. Index into particular Sk4f refers to edge. - Sk4f edgeDistances[4]; + Sk4f a, b, c; if (spec.usesCoverageAA()) { // Must calculate edges and possibly outside the positions if (aaFlags == GrQuadAAFlags::kNone) { - // A non-AA quad that got batched into an AA group, so it should have full coverage - // everywhere, so set the edge distances to w for each vertex (so that after perspective - // division, it is equal to 1). - for (int i = 0; i < 4; ++i) { - edgeDistances[i] = w[i]; + // A non-AA quad that got batched into an AA group, so its edges will be the same for + // all four vertices and it does not need to be outset + SkPoint3 edgeCoeffs; + if (deviceHasPerspective) { + edgeCoeffs = compute_non_aa_persp_edge_coeffs(w); + } else { + edgeCoeffs = kNonAANoPerspEdgeCoeffs; } + + // Copy the coefficients into all four equations + a = edgeCoeffs.fX; + b = edgeCoeffs.fY; + c = edgeCoeffs.fZ; } else if (deviceHasPerspective) { // For simplicity, pointers to u, v, and r are always provided, but the local dim param // ensures that only loaded Sk4fs are modified in the compute functions. - compute_quad_dists_and_outset_persp_vertices(aaFlags, &x, &y, &w, edgeDistances, + compute_quad_edges_and_outset_persp_vertices(aaFlags, &x, &y, &w, &a, &b, &c, &u, &v, &r, spec.localDimensionality()); - } else if (spec.deviceQuadType() <= GrQuadType::kRectilinear) { - compute_rectilinear_dists_and_outset_vertices(aaFlags, &x, &y, edgeDistances, - &u, &v, &r, spec.localDimensionality()); } else { - Sk4f a, b, c; compute_quad_edges_and_outset_vertices(aaFlags, &x, &y, &a, &b, &c, &u, &v, &r, - spec.localDimensionality(), /*outset*/ true); - compute_edge_distances(a, b, c, x, y, w, edgeDistances); // w holds 1.f as desired + spec.localDimensionality(), /* outset */ true); } } // Now rearrange the Sk4fs into the interleaved vertex layout: // i.e. x1x2x3x4 y1y2y3y4 -> x1y1 x2y2 x3y3 x4y + // while also calculating the edge distance for each outset vertex for the four edges. + SkPoint3 p; // Stores the homogeneous vertex position, w = 1 explicitly for 2D cases + Sk4f edgeDists; GrVertexWriter vb{vertices}; for (int i = 0; i < 4; ++i) { // save position and hold on to the homogeneous point for later if (deviceHasPerspective) { - vb.write({x[i], y[i], w[i]}); + p.set(x[i], y[i], w[i]); + vb.write(p); } else { - vb.write({x[i], y[i]}); + p.set(x[i], y[i], 1.f); + vb.write({p.fX, p.fY}); } // save color @@ -386,12 +327,16 @@ void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& devi // save the domain if (spec.hasDomain()) { - vb.write(domain); + vb.write(domain); } // save the edges if (spec.usesCoverageAA()) { - vb.write(edgeDistances[i]); + // w is stored in point's fZ, and set to 1 for 2D, + // fragment shader will automatically divide by pixel's w if the quad has perspective + // to get the perceptually interpolated edge distance. + edgeDists = p.fX * a + p.fY * b + p.fZ * c; + vb.write(edgeDists[0], edgeDists[1], edgeDists[2], edgeDists[3]); } } diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp index f3a1d1ac1a..22ef9fc636 100644 --- a/src/gpu/ops/GrTextureOp.cpp +++ b/src/gpu/ops/GrTextureOp.cpp @@ -307,7 +307,7 @@ private: GrResolveAATypeForQuad(aaType, aaFlags, quad, quadType, &aaType, &aaFlags); fAAType = static_cast(aaType); - fQuadType = static_cast(quadType); + fPerspective = static_cast(quadType == GrQuadType::kPerspective); // We expect our caller to have already caught this optimization. SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) || constraint == SkCanvas::kFast_SrcRectConstraint); @@ -386,7 +386,7 @@ private: fFilter = static_cast(GrSamplerState::Filter::kNearest); } this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo); - fQuadType = static_cast(quadType); + fPerspective = static_cast(viewMatrix.hasPerspective()); fDomain = static_cast(false); fWideColor = static_cast(false); } @@ -410,7 +410,7 @@ private: void onPrepareDraws(Target* target) override { TRACE_EVENT0("skia", TRACE_FUNC); - GrQuadType quadType = GrQuadType::kRect; + bool hasPerspective = false; Domain domain = Domain::kNo; bool wideColor = false; int numProxies = 0; @@ -419,9 +419,7 @@ private: auto config = fProxies[0].fProxy->config(); GrAAType aaType = this->aaType(); for (const auto& op : ChainRange(this)) { - if (op.quadType() > quadType) { - quadType = op.quadType(); - } + hasPerspective |= op.fPerspective; if (op.fDomain) { domain = Domain::kYes; } @@ -442,8 +440,9 @@ private: } } - VertexSpec vertexSpec(quadType, wideColor ? ColorType::kHalf : ColorType::kByte, - GrQuadType::kRect, /* hasLocal */ true, domain, aaType); + VertexSpec vertexSpec(hasPerspective ? GrQuadType::kPerspective : GrQuadType::kStandard, + wideColor ? ColorType::kHalf : ColorType::kByte, GrQuadType::kRect, + /* hasLocal */ true, domain, aaType); sk_sp gp = TextureGeometryProcessor::Make( textureType, config, this->filter(), std::move(fTextureColorSpaceXform), @@ -566,9 +565,7 @@ private: } fProxies[0].fQuadCnt += that->fQuads.count(); fQuads.push_back_n(that->fQuads.count(), that->fQuads.begin()); - if (that->fQuadType > fQuadType) { - fQuadType = that->fQuadType; - } + fPerspective |= that->fPerspective; fDomain |= that->fDomain; fWideColor |= that->fWideColor; if (upgradeToCoverageAAOnMerge) { @@ -579,7 +576,6 @@ private: GrAAType aaType() const { return static_cast(fAAType); } GrSamplerState::Filter filter() const { return static_cast(fFilter); } - GrQuadType quadType() const { return static_cast(fQuadType); } class Quad { public: @@ -613,17 +609,15 @@ private: sk_sp fTextureColorSpaceXform; unsigned fFilter : 2; unsigned fAAType : 2; - unsigned fQuadType : 2; // Device quad, src quad is always trivial + unsigned fPerspective : 1; unsigned fDomain : 1; unsigned fWideColor : 1; // Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called. unsigned fFinalized : 1; unsigned fCanSkipAllocatorGather : 1; - unsigned fProxyCnt : 32 - 10; + unsigned fProxyCnt : 32 - 8; Proxy fProxies[1]; - static_assert(kGrQuadTypeCount <= 4, "GrQuadType does not fit in 2 bits"); - typedef GrMeshDrawOp INHERITED; };