Home | History | Annotate | Download | only in ops
      1 /*
      2  * Copyright 2018 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "GrQuadPerEdgeAA.h"
      9 #include "GrQuad.h"
     10 #include "GrVertexWriter.h"
     11 #include "glsl/GrGLSLColorSpaceXformHelper.h"
     12 #include "glsl/GrGLSLGeometryProcessor.h"
     13 #include "glsl/GrGLSLPrimitiveProcessor.h"
     14 #include "glsl/GrGLSLFragmentShaderBuilder.h"
     15 #include "glsl/GrGLSLVarying.h"
     16 #include "glsl/GrGLSLVertexGeoBuilder.h"
     17 #include "SkNx.h"
     18 
     19 #define AI SK_ALWAYS_INLINE
     20 
     21 namespace {
     22 
     23 static AI Sk4f fma(const Sk4f& f, const Sk4f& m, const Sk4f& a) {
     24     return SkNx_fma<4, float>(f, m, a);
     25 }
     26 
     27 // These rotate the points/edge values either clockwise or counterclockwise assuming tri strip
     28 // order.
     29 static AI Sk4f nextCW(const Sk4f& v) {
     30     return SkNx_shuffle<2, 0, 3, 1>(v);
     31 }
     32 
     33 static AI Sk4f nextCCW(const Sk4f& v) {
     34     return SkNx_shuffle<1, 3, 0, 2>(v);
     35 }
     36 
     37 // Fills Sk4f with 1f if edge bit is set, 0f otherwise. Edges are ordered LBTR to match CCW ordering
     38 // of vertices in the quad.
     39 static AI Sk4f compute_edge_mask(GrQuadAAFlags aaFlags) {
     40     return Sk4f((GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f,
     41                 (GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f,
     42                 (GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f,
     43                 (GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f);
     44 }
     45 
     46 // Outputs normalized edge vectors in xdiff and ydiff, as well as the reciprocal of the original
     47 // edge lengths in invLengths
     48 static AI void compute_edge_vectors(const Sk4f& x, const Sk4f& y, const Sk4f& xnext,
     49                                     const Sk4f& ynext, Sk4f* xdiff, Sk4f* ydiff, Sk4f* invLengths) {
     50     *xdiff = xnext - x;
     51     *ydiff = ynext - y;
     52     *invLengths = fma(*xdiff, *xdiff, *ydiff * *ydiff).rsqrt();
     53     *xdiff *= *invLengths;
     54     *ydiff *= *invLengths;
     55 }
     56 
     57 // outset and outsetCW are provided separately to allow for different magnitude outsets for
     58 // with-edge and "perpendicular" edge shifts. This is needed when one axis cannot be inset the full
     59 // half pixel without crossing over the other side.
     60 static AI void outset_masked_vertices(const Sk4f& outset, const Sk4f& outsetCW, const Sk4f& xdiff,
     61                                       const Sk4f& ydiff, const Sk4f& invLengths, const Sk4f& mask,
     62                                       Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
     63     // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on"
     64     // both its points need to be moved along their other edge vectors.
     65     auto maskedOutset = -outset * nextCW(mask);
     66     auto maskedOutsetCW = outsetCW * mask;
     67     // x = x + outsetCW * mask * nextCW(xdiff) - outset * nextCW(mask) * xdiff
     68     *x += fma(maskedOutsetCW, nextCW(xdiff), maskedOutset * xdiff);
     69     *y += fma(maskedOutsetCW, nextCW(ydiff), maskedOutset * ydiff);
     70     if (uvrCount > 0) {
     71         // We want to extend the texture coords by the same proportion as the positions.
     72         maskedOutset *= invLengths;
     73         maskedOutsetCW *= nextCW(invLengths);
     74         Sk4f udiff = nextCCW(*u) - *u;
     75         Sk4f vdiff = nextCCW(*v) - *v;
     76         *u += fma(maskedOutsetCW, nextCW(udiff), maskedOutset * udiff);
     77         *v += fma(maskedOutsetCW, nextCW(vdiff), maskedOutset * vdiff);
     78         if (uvrCount == 3) {
     79             Sk4f rdiff = nextCCW(*r) - *r;
     80             *r += fma(maskedOutsetCW, nextCW(rdiff), maskedOutset * rdiff);
     81         }
     82     }
     83 }
     84 
     85 static AI void outset_vertices(const Sk4f& outset, const Sk4f& outsetCW, const Sk4f& xdiff,
     86                                const Sk4f& ydiff, const Sk4f& invLengths,
     87                                Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
     88     // x = x + outsetCW * nextCW(xdiff) - outset * xdiff (as above, but where mask = (1,1,1,1))
     89     *x += fma(outsetCW, nextCW(xdiff), -outset * xdiff);
     90     *y += fma(outsetCW, nextCW(ydiff), -outset * ydiff);
     91     if (uvrCount > 0) {
     92         Sk4f t = -outset * invLengths; // Bake minus sign in here
     93         Sk4f tCW = outsetCW * nextCW(invLengths);
     94         Sk4f udiff = nextCCW(*u) - *u;
     95         Sk4f vdiff = nextCCW(*v) - *v;
     96         *u += fma(tCW, nextCW(udiff), t * udiff);
     97         *v += fma(tCW, nextCW(vdiff), t * vdiff);
     98         if (uvrCount == 3) {
     99             Sk4f rdiff = nextCCW(*r) - *r;
    100             *r += fma(tCW, nextCW(rdiff), t * rdiff);
    101         }
    102     }
    103 }
    104 
    105 // Updates outset in place to account for non-90 degree angles of the quad edges stored in
    106 // xdiff, ydiff (which are assumed to be normalized).
    107 static void adjust_non_rectilinear_outset(const Sk4f& xdiff, const Sk4f& ydiff, Sk4f* outset) {
    108     // The distance the point needs to move is outset/sqrt(1-cos^2(theta)), where theta is the angle
    109     // between the two edges at that point. cos(theta) is equal to dot(xydiff, nextCW(xydiff)),
    110     Sk4f cosTheta = fma(xdiff, nextCW(xdiff), ydiff * nextCW(ydiff));
    111     *outset *= (1.f - cosTheta * cosTheta).rsqrt();
    112     // But clamp to make sure we don't expand by a giant amount if the sheer is really high
    113     *outset = Sk4f::Max(-3.f, Sk4f::Min(*outset, 3.f));
    114 }
    115 
    116 // Computes the vertices for the two nested quads used to create AA edges. The original single quad
    117 // should be duplicated as input in x1 and x2, y1 and y2, and possibly u1|u2, v1|v2, [r1|r2]
    118 // (controlled by uvrChannelCount).  While the values should be duplicated, they should be separate
    119 // pointers. The outset quad is written in-place back to x1, y1, etc. and the inset inner quad is
    120 // written to x2, y2, etc.
    121 static float compute_nested_quad_vertices(GrQuadAAFlags aaFlags, Sk4f* x1, Sk4f* y1,
    122         Sk4f* u1, Sk4f* v1, Sk4f* r1, Sk4f* x2, Sk4f* y2, Sk4f* u2, Sk4f* v2, Sk4f* r2,
    123         int uvrCount, bool rectilinear) {
    124     SkASSERT(uvrCount == 0 || uvrCount == 2 || uvrCount == 3);
    125 
    126     // Compute edge vectors for the quad.
    127     auto xnext = nextCCW(*x1);
    128     auto ynext = nextCCW(*y1);
    129     // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
    130     Sk4f xdiff, ydiff, invLengths;
    131     compute_edge_vectors(*x1, *y1, xnext, ynext, &xdiff, &ydiff, &invLengths);
    132 
    133     // When outsetting, we want the new edge to be .5px away from the old line, which means the
    134     // corners may need to be adjusted by more than .5px if the matrix had sheer.
    135     Sk4f outset = 0.5f;
    136     if (!rectilinear) {
    137         adjust_non_rectilinear_outset(xdiff, ydiff, &outset);
    138     }
    139 
    140     // When insetting, cap the inset amount to be half of the edge length, except that each edge
    141     // has to remain parallel, so we separately limit LR and TB to half of the smallest of the
    142     // opposing edges.
    143     Sk4f lengths = invLengths.invert();
    144     Sk2f sides(SkMinScalar(lengths[0], lengths[3]), SkMinScalar(lengths[1], lengths[2]));
    145     Sk4f edgeLimits = 0.5f * SkNx_shuffle<0, 1, 1, 0>(sides);
    146 
    147     if ((edgeLimits < 0.5f).anyTrue()) {
    148         // Dealing with a subpixel rectangle, so must calculate clamped insets and padded outsets.
    149         // The outsets are padded to ensure that the quad spans 2 pixels for improved interpolation.
    150         Sk4f inset = -Sk4f::Min(outset, edgeLimits);
    151         Sk4f insetCW = -Sk4f::Min(outset, nextCW(edgeLimits));
    152 
    153         // The parallel distance shift caused by outset is currently 0.5, but need to scale it up to
    154         // 0.5*(2 - side) so that (side + 2*shift) = 2px. Thus scale outsets for thin edges by
    155         // (2 - side) since it already has the 1/2.
    156         Sk4f outsetScale = 2.f - 2.f * Sk4f::Min(edgeLimits, 0.5f); // == 1 for non-thin edges
    157         Sk4f outsetCW = outset * nextCW(outsetScale);
    158         outset *= outsetScale;
    159 
    160         if (aaFlags != GrQuadAAFlags::kAll) {
    161             Sk4f mask = compute_edge_mask(aaFlags);
    162             outset_masked_vertices(outset, outsetCW, xdiff, ydiff, invLengths, mask, x1, y1,
    163                                    u1, v1, r1, uvrCount);
    164             outset_masked_vertices(inset, insetCW, xdiff, ydiff, invLengths, mask, x2, y2,
    165                                    u2, v2, r2, uvrCount);
    166         } else {
    167             outset_vertices(outset, outsetCW, xdiff, ydiff, invLengths, x1, y1, u1, v1, r1, uvrCount);
    168             outset_vertices(inset, insetCW, xdiff, ydiff, invLengths, x2, y2, u2, v2, r2, uvrCount);
    169         }
    170     } else {
    171         // Since it's not subpixel, the inset is just the opposite of the outset and there's no
    172         // difference between CCW and CW behavior.
    173         Sk4f inset = -outset;
    174         if (aaFlags != GrQuadAAFlags::kAll) {
    175             Sk4f mask = compute_edge_mask(aaFlags);
    176             outset_masked_vertices(outset, outset, xdiff, ydiff, invLengths, mask, x1, y1,
    177                                    u1, v1, r1, uvrCount);
    178             outset_masked_vertices(inset, inset, xdiff, ydiff, invLengths, mask, x2, y2,
    179                                    u2, v2, r2, uvrCount);
    180         } else {
    181             outset_vertices(outset, outset, xdiff, ydiff, invLengths, x1, y1, u1, v1, r1, uvrCount);
    182             outset_vertices(inset, inset, xdiff, ydiff, invLengths, x2, y2, u2, v2, r2, uvrCount);
    183         }
    184     }
    185 
    186     // An approximation of the pixel area covered by the quad
    187     sides = Sk2f::Min(1.f, sides);
    188     return sides[0] * sides[1];
    189 }
    190 
    191 // For each device space corner, devP, label its left/right or top/bottom opposite device space
    192 // point opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is
    193 // (length(devPt - opDevPt) + outset) / length(devPt - opDevPt); This returns the interpolant s,
    194 // adjusted for any subpixel corrections. If subpixel, it also updates the max coverage.
    195 static Sk4f get_projected_interpolant(const Sk4f& len, const Sk4f& outsets, float* maxCoverage) {
    196     if ((len < 1.f).anyTrue()) {
    197         *maxCoverage *= len.min();
    198 
    199         // When insetting, the amount is clamped to be half the minimum edge length to prevent
    200         // overlap. When outsetting, the amount is padded to cover 2 pixels.
    201         if ((outsets < 0.f).anyTrue()) {
    202             return (len - 0.5f * len.min()) / len;
    203         } else {
    204             return (len + outsets * (2.f - len.min())) / len;
    205         }
    206     } else {
    207         return (len + outsets) / len;
    208     }
    209 }
    210 
    211 // Generalizes compute_nested_quad_vertices to extrapolate local coords such that
    212 // after perspective division of the device coordinate, the original local coordinate value is at
    213 // the original un-outset device position. r is the local coordinate's w component. However, since
    214 // the projected edges will be different for inner and outer quads, there isn't much reuse between
    215 // the calculations, so it's easier to just have this operate on one quad a time.
    216 static float compute_quad_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y,
    217         Sk4f* w, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount, bool inset) {
    218     SkASSERT(uvrCount == 0 || uvrCount == 2 || uvrCount == 3);
    219 
    220     auto iw = (*w).invert();
    221     auto x2d = (*x) * iw;
    222     auto y2d = (*y) * iw;
    223 
    224     // Must compute non-rectilinear outset quantity using the projected 2d edge vectors
    225     Sk4f xdiff, ydiff, invLengths;
    226     compute_edge_vectors(x2d, y2d, nextCCW(x2d), nextCCW(y2d), &xdiff, &ydiff, &invLengths);
    227     Sk4f outset = inset ? -0.5f : 0.5f;
    228     adjust_non_rectilinear_outset(xdiff, ydiff, &outset);
    229 
    230     float maxProjectedCoverage = 1.f;
    231 
    232     if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) {
    233         // For each entry in x the equivalent entry in opX is the left/right opposite and so on.
    234         Sk4f opX = SkNx_shuffle<2, 3, 0, 1>(*x);
    235         Sk4f opW = SkNx_shuffle<2, 3, 0, 1>(*w);
    236         Sk4f opY = SkNx_shuffle<2, 3, 0, 1>(*y);
    237         // vx/vy holds the device space left-to-right vectors along top and bottom of the quad.
    238         Sk2f vx = SkNx_shuffle<2, 3>(x2d) - SkNx_shuffle<0, 1>(x2d);
    239         Sk2f vy = SkNx_shuffle<2, 3>(y2d) - SkNx_shuffle<0, 1>(y2d);
    240         Sk4f len = SkNx_shuffle<0, 1, 0, 1>(SkNx_fma(vx, vx, vy * vy).sqrt());
    241 
    242         // Compute t in homogeneous space from s using similar triangles so that we can produce
    243         // homogeneous outset vertices for perspective-correct interpolation.
    244         Sk4f s = get_projected_interpolant(len, outset, &maxProjectedCoverage);
    245         Sk4f sOpW = s * opW;
    246         Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
    247         // mask is used to make the t values be 1 when the left/right side is not antialiased.
    248         Sk4f mask(GrQuadAAFlags::kLeft & aaFlags  ? 1.f : 0.f,
    249                   GrQuadAAFlags::kLeft & aaFlags  ? 1.f : 0.f,
    250                   GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f,
    251                   GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f);
    252         t = t * mask + (1.f - mask);
    253         *x = opX + t * (*x - opX);
    254         *y = opY + t * (*y - opY);
    255         *w = opW + t * (*w - opW);
    256 
    257         if (uvrCount > 0) {
    258             Sk4f opU = SkNx_shuffle<2, 3, 0, 1>(*u);
    259             Sk4f opV = SkNx_shuffle<2, 3, 0, 1>(*v);
    260             *u = opU + t * (*u - opU);
    261             *v = opV + t * (*v - opV);
    262             if (uvrCount == 3) {
    263                 Sk4f opR = SkNx_shuffle<2, 3, 0, 1>(*r);
    264                 *r = opR + t * (*r - opR);
    265             }
    266         }
    267 
    268         if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) {
    269             // Update the 2D points for the top/bottom calculation.
    270             iw = (*w).invert();
    271             x2d = (*x) * iw;
    272             y2d = (*y) * iw;
    273         }
    274     }
    275 
    276     if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) {
    277         // This operates the same as above but for top/bottom rather than left/right.
    278         Sk4f opX = SkNx_shuffle<1, 0, 3, 2>(*x);
    279         Sk4f opW = SkNx_shuffle<1, 0, 3, 2>(*w);
    280         Sk4f opY = SkNx_shuffle<1, 0, 3, 2>(*y);
    281 
    282         Sk2f vx = SkNx_shuffle<1, 3>(x2d) - SkNx_shuffle<0, 2>(x2d);
    283         Sk2f vy = SkNx_shuffle<1, 3>(y2d) - SkNx_shuffle<0, 2>(y2d);
    284         Sk4f len = SkNx_shuffle<0, 0, 1, 1>(SkNx_fma(vx, vx, vy * vy).sqrt());
    285 
    286         Sk4f s = get_projected_interpolant(len, outset, &maxProjectedCoverage);
    287         Sk4f sOpW = s * opW;
    288         Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
    289 
    290         Sk4f mask(GrQuadAAFlags::kTop    & aaFlags ? 1.f : 0.f,
    291                   GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f,
    292                   GrQuadAAFlags::kTop    & aaFlags ? 1.f : 0.f,
    293                   GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f);
    294         t = t * mask + (1.f - mask);
    295         *x = opX + t * (*x - opX);
    296         *y = opY + t * (*y - opY);
    297         *w = opW + t * (*w - opW);
    298 
    299         if (uvrCount > 0) {
    300             Sk4f opU = SkNx_shuffle<1, 0, 3, 2>(*u);
    301             Sk4f opV = SkNx_shuffle<1, 0, 3, 2>(*v);
    302             *u = opU + t * (*u - opU);
    303             *v = opV + t * (*v - opV);
    304             if (uvrCount == 3) {
    305                 Sk4f opR = SkNx_shuffle<1, 0, 3, 2>(*r);
    306                 *r = opR + t * (*r - opR);
    307             }
    308         }
    309     }
    310 
    311     return maxProjectedCoverage;
    312 }
    313 
    314 enum class CoverageMode {
    315     kNone,
    316     kWithPosition,
    317     kWithColor
    318 };
    319 
    320 static CoverageMode get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec& spec) {
    321     if (spec.usesCoverageAA()) {
    322         if (spec.compatibleWithAlphaAsCoverage() && spec.hasVertexColors()) {
    323             return CoverageMode::kWithColor;
    324         } else {
    325             return CoverageMode::kWithPosition;
    326         }
    327     } else {
    328         return CoverageMode::kNone;
    329     }
    330 }
    331 
    332 // Writes four vertices in triangle strip order, including the additional data for local
    333 // coordinates, domain, color, and coverage as needed to satisfy the vertex spec.
    334 static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
    335                        CoverageMode mode, float coverage,
    336                        SkPMColor4f color4f, bool wideColor,
    337                        const SkRect& domain,
    338                        const Sk4f& x, const Sk4f& y, const Sk4f& w,
    339                        const Sk4f& u, const Sk4f& v, const Sk4f& r) {
    340     static constexpr auto If = GrVertexWriter::If<float>;
    341 
    342     if (mode == CoverageMode::kWithColor) {
    343         // Multiply the color by the coverage up front
    344         SkASSERT(spec.hasVertexColors());
    345         color4f = color4f * coverage;
    346     }
    347     GrVertexColor color(color4f, wideColor);
    348 
    349     for (int i = 0; i < 4; ++i) {
    350         // save position, this is a float2 or float3 or float4 depending on the combination of
    351         // perspective and coverage mode.
    352         vb->write(x[i], y[i], If(spec.deviceQuadType() == GrQuadType::kPerspective, w[i]),
    353                   If(mode == CoverageMode::kWithPosition, coverage));
    354 
    355         // save color
    356         if (spec.hasVertexColors()) {
    357             vb->write(color);
    358         }
    359 
    360         // save local position
    361         if (spec.hasLocalCoords()) {
    362             vb->write(u[i], v[i], If(spec.localQuadType() == GrQuadType::kPerspective, r[i]));
    363         }
    364 
    365         // save the domain
    366         if (spec.hasDomain()) {
    367             vb->write(domain);
    368         }
    369     }
    370 }
    371 
    372 GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
    373 
    374 static const int kVertsPerAAFillRect = 8;
    375 static const int kIndicesPerAAFillRect = 30;
    376 
    377 static sk_sp<const GrBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
    378     GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
    379 
    380     // clang-format off
    381     static const uint16_t gFillAARectIdx[] = {
    382         0, 1, 2, 1, 3, 2,
    383         0, 4, 1, 4, 5, 1,
    384         0, 6, 4, 0, 2, 6,
    385         2, 3, 6, 3, 7, 6,
    386         1, 5, 3, 3, 5, 7,
    387     };
    388     // clang-format on
    389 
    390     GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
    391     return resourceProvider->findOrCreatePatternedIndexBuffer(
    392             gFillAARectIdx, kIndicesPerAAFillRect, GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer,
    393             kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
    394 }
    395 
    396 } // anonymous namespace
    397 
    398 namespace GrQuadPerEdgeAA {
    399 
    400 ////////////////// Tessellate Implementation
    401 
    402 void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
    403                  const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
    404                  GrQuadAAFlags aaFlags) {
    405     bool wideColor = GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType();
    406     CoverageMode mode = get_mode_for_spec(spec);
    407 
    408     // Load position data into Sk4fs (always x, y, and load w to avoid branching down the road)
    409     Sk4f oX = deviceQuad.x4f();
    410     Sk4f oY = deviceQuad.y4f();
    411     Sk4f oW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
    412 
    413     // Load local position data into Sk4fs (either none, just u,v or all three)
    414     Sk4f oU, oV, oR;
    415     if (spec.hasLocalCoords()) {
    416         oU = localQuad.x4f();
    417         oV = localQuad.y4f();
    418         oR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
    419     }
    420 
    421     GrVertexWriter vb{vertices};
    422     if (spec.usesCoverageAA()) {
    423         SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
    424 
    425         // Must calculate two new quads, an outset and inset by .5 in projected device space, so
    426         // duplicate the original quad into new Sk4fs for the inset.
    427         Sk4f iX = oX, iY = oY, iW = oW;
    428         Sk4f iU = oU, iV = oV, iR = oR;
    429 
    430         float maxCoverage = 1.f;
    431         if (aaFlags != GrQuadAAFlags::kNone) {
    432             if (spec.deviceQuadType() == GrQuadType::kPerspective) {
    433                 // Outset and inset the quads independently because perspective makes each shift
    434                 // unique. Since iX copied pre-outset oX, this will compute the proper inset too.
    435                 compute_quad_persp_vertices(aaFlags, &oX, &oY, &oW, &oU, &oV, &oW,
    436                                             spec.localDimensionality(), /* inset */ false);
    437                 // Save coverage limit when computing inset quad
    438                 maxCoverage = compute_quad_persp_vertices(aaFlags, &iX, &iY, &iW, &iU, &iV, &iW,
    439                                                           spec.localDimensionality(), true);
    440             } else {
    441                 // In the 2D case, insetting and outsetting can reuse the edge vectors, so the
    442                 // nested quads are computed together
    443                 maxCoverage = compute_nested_quad_vertices(aaFlags, &oX, &oY, &oU, &oV, &oR,
    444                         &iX, &iY, &iU, &iV, &iR, spec.localDimensionality(),
    445                         spec.deviceQuadType() <= GrQuadType::kRectilinear);
    446             }
    447             // NOTE: could provide an even more optimized tessellation function for axis-aligned
    448             // rects since the positions can be outset by constants without doing vector math,
    449             // except it must handle identifying the winding of the quad vertices if the transform
    450             // applied a mirror, etc. The current 2D case is already adequately fast.
    451         } // else don't adjust any positions, let the outer quad form degenerate triangles
    452 
    453         // Write two quads for inner and outer, inner will use the
    454         write_quad(&vb, spec, mode, maxCoverage, color4f, wideColor, domain,
    455                    iX, iY, iW, iU, iV, iR);
    456         write_quad(&vb, spec, mode, 0.f, color4f, wideColor, domain, oX, oY, oW, oU, oV, oR);
    457     } else {
    458         // No outsetting needed, just write a single quad with full coverage
    459         SkASSERT(mode == CoverageMode::kNone);
    460         write_quad(&vb, spec, mode, 1.f, color4f, wideColor, domain, oX, oY, oW, oU, oV, oR);
    461     }
    462 
    463     return vb.fPtr;
    464 }
    465 
    466 bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec,
    467                           int quadCount) {
    468     if (spec.usesCoverageAA()) {
    469         // AA quads use 8 vertices, basically nested rectangles
    470         sk_sp<const GrBuffer> ibuffer = get_index_buffer(target->resourceProvider());
    471         if (!ibuffer) {
    472             return false;
    473         }
    474 
    475         mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
    476         mesh->setIndexedPatterned(std::move(ibuffer), kIndicesPerAAFillRect, kVertsPerAAFillRect,
    477                                   quadCount, kNumAAQuadsInIndexBuffer);
    478     } else {
    479         // Non-AA quads use 4 vertices, and regular triangle strip layout
    480         if (quadCount > 1) {
    481             sk_sp<const GrBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
    482             if (!ibuffer) {
    483                 return false;
    484             }
    485 
    486             mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
    487             mesh->setIndexedPatterned(std::move(ibuffer), 6, 4, quadCount,
    488                                       GrResourceProvider::QuadCountOfQuadBuffer());
    489         } else {
    490             mesh->setPrimitiveType(GrPrimitiveType::kTriangleStrip);
    491             mesh->setNonIndexedNonInstanced(4);
    492         }
    493     }
    494 
    495     return true;
    496 }
    497 
    498 ////////////////// VertexSpec Implementation
    499 
    500 int VertexSpec::deviceDimensionality() const {
    501     return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2;
    502 }
    503 
    504 int VertexSpec::localDimensionality() const {
    505     return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0;
    506 }
    507 
    508 ////////////////// Geometry Processor Implementation
    509 
    510 class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
    511 public:
    512 
    513     static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
    514         return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
    515     }
    516 
    517     static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
    518                                            GrTextureType textureType, GrPixelConfig textureConfig,
    519                                            const GrSamplerState& samplerState,
    520                                            uint32_t extraSamplerKey,
    521                                            sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
    522         return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
    523                 vertexSpec, caps, textureType, textureConfig, samplerState, extraSamplerKey,
    524                 std::move(textureColorSpaceXform)));
    525     }
    526 
    527     const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
    528 
    529     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
    530         // domain, texturing, device-dimensions are single bit flags
    531         uint32_t x = fDomain.isInitialized() ? 0 : 1;
    532         x |= fSampler.isInitialized() ? 0 : 2;
    533         x |= fNeedsPerspective ? 0 : 4;
    534         // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
    535         if (fLocalCoord.isInitialized()) {
    536             x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 8 : 16;
    537         }
    538         // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
    539         if (fColor.isInitialized()) {
    540             x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 32 : 64;
    541         }
    542         // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor
    543         if (fCoverageMode != CoverageMode::kNone) {
    544             x |= CoverageMode::kWithPosition == fCoverageMode ? 128 : 256;
    545         }
    546 
    547         b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
    548         b->add32(x);
    549     }
    550 
    551     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
    552         class GLSLProcessor : public GrGLSLGeometryProcessor {
    553         public:
    554             void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
    555                          FPCoordTransformIter&& transformIter) override {
    556                 const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
    557                 if (gp.fLocalCoord.isInitialized()) {
    558                     this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
    559                 }
    560                 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
    561             }
    562 
    563         private:
    564             void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
    565                 using Interpolation = GrGLSLVaryingHandler::Interpolation;
    566 
    567                 const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
    568                 fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
    569                                                        gp.fTextureColorSpaceXform.get());
    570 
    571                 args.fVaryingHandler->emitAttributes(gp);
    572 
    573                 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
    574                     // Strip last channel from the vertex attribute to remove coverage and get the
    575                     // actual position
    576                     if (gp.fNeedsPerspective) {
    577                         args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
    578                                                        gp.fPosition.name());
    579                     } else {
    580                         args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
    581                                                        gp.fPosition.name());
    582                     }
    583                     gpArgs->fPositionVar = {"position",
    584                                             gp.fNeedsPerspective ? kFloat3_GrSLType
    585                                                                  : kFloat2_GrSLType,
    586                                             GrShaderVar::kNone_TypeModifier};
    587                 } else {
    588                     // No coverage to eliminate
    589                     gpArgs->fPositionVar = gp.fPosition.asShaderVar();
    590                 }
    591 
    592                 // Handle local coordinates if they exist
    593                 if (gp.fLocalCoord.isInitialized()) {
    594                     // NOTE: If the only usage of local coordinates is for the inline texture fetch
    595                     // before FPs, then there are no registered FPCoordTransforms and this ends up
    596                     // emitting nothing, so there isn't a duplication of local coordinates
    597                     this->emitTransforms(args.fVertBuilder,
    598                                          args.fVaryingHandler,
    599                                          args.fUniformHandler,
    600                                          gp.fLocalCoord.asShaderVar(),
    601                                          args.fFPCoordTransformHandler);
    602                 }
    603 
    604                 // Solid color before any texturing gets modulated in
    605                 if (gp.fColor.isInitialized()) {
    606                     // The color cannot be flat if the varying coverage has been modulated into it
    607                     args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
    608                             gp.fCoverageMode == CoverageMode::kWithColor ?
    609                             Interpolation::kInterpolated : Interpolation::kCanBeFlat);
    610                 } else {
    611                     // Output color must be initialized to something
    612                     args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor);
    613                 }
    614 
    615                 // If there is a texture, must also handle texture coordinates and reading from
    616                 // the texture in the fragment shader before continuing to fragment processors.
    617                 if (gp.fSampler.isInitialized()) {
    618                     // Texture coordinates clamped by the domain on the fragment shader; if the GP
    619                     // has a texture, it's guaranteed to have local coordinates
    620                     args.fFragBuilder->codeAppend("float2 texCoord;");
    621                     if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
    622                         // Can't do a pass through since we need to perform perspective division
    623                         GrGLSLVarying v(gp.fLocalCoord.gpuType());
    624                         args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
    625                         args.fVertBuilder->codeAppendf("%s = %s;",
    626                                                        v.vsOut(), gp.fLocalCoord.name());
    627                         args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
    628                                                        v.fsIn(), v.fsIn());
    629                     } else {
    630                         args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord");
    631                     }
    632 
    633                     // Clamp the now 2D localCoordName variable by the domain if it is provided
    634                     if (gp.fDomain.isInitialized()) {
    635                         args.fFragBuilder->codeAppend("float4 domain;");
    636                         args.fVaryingHandler->addPassThroughAttribute(gp.fDomain, "domain",
    637                                                                       Interpolation::kCanBeFlat);
    638                         args.fFragBuilder->codeAppend(
    639                                 "texCoord = clamp(texCoord, domain.xy, domain.zw);");
    640                     }
    641 
    642                     // Now modulate the starting output color by the texture lookup
    643                     args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
    644                     args.fFragBuilder->appendTextureLookupAndModulate(
    645                         args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
    646                         &fTextureColorSpaceXformHelper);
    647                     args.fFragBuilder->codeAppend(";");
    648                 }
    649 
    650                 // And lastly, output the coverage calculation code
    651                 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
    652                     GrGLSLVarying coverage(kFloat_GrSLType);
    653                     args.fVaryingHandler->addVarying("coverage", &coverage);
    654                     if (gp.fNeedsPerspective) {
    655                         args.fVertBuilder->codeAppendf("%s = %s.w;",
    656                                                        coverage.vsOut(), gp.fPosition.name());
    657                     } else {
    658                         args.fVertBuilder->codeAppendf("%s = %s.z;",
    659                                                        coverage.vsOut(), gp.fPosition.name());
    660                     }
    661 
    662                     args.fFragBuilder->codeAppendf("%s = float4(%s);",
    663                                                    args.fOutputCoverage, coverage.fsIn());
    664                 } else {
    665                     // Set coverage to 1, since it's either non-AA or the coverage was already
    666                     // folded into the output color
    667                     args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
    668                 }
    669             }
    670             GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
    671         };
    672         return new GLSLProcessor;
    673     }
    674 
    675 private:
    676     QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
    677             : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
    678             , fTextureColorSpaceXform(nullptr) {
    679         SkASSERT(!spec.hasDomain());
    680         this->initializeAttrs(spec);
    681         this->setTextureSamplerCnt(0);
    682     }
    683 
    684     QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
    685                                    GrTextureType textureType, GrPixelConfig textureConfig,
    686                                    const GrSamplerState& samplerState,
    687                                    uint32_t extraSamplerKey,
    688                                    sk_sp<GrColorSpaceXform> textureColorSpaceXform)
    689             : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
    690             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
    691             , fSampler(textureType, textureConfig, samplerState, extraSamplerKey) {
    692         SkASSERT(spec.hasLocalCoords());
    693         this->initializeAttrs(spec);
    694         this->setTextureSamplerCnt(1);
    695     }
    696 
    697     void initializeAttrs(const VertexSpec& spec) {
    698         fNeedsPerspective = spec.deviceDimensionality() == 3;
    699         fCoverageMode = get_mode_for_spec(spec);
    700 
    701         if (fCoverageMode == CoverageMode::kWithPosition) {
    702             if (fNeedsPerspective) {
    703                 fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
    704             } else {
    705                 fPosition = {"positionWithCoverage", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
    706             }
    707         } else {
    708             if (fNeedsPerspective) {
    709                 fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
    710             } else {
    711                 fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
    712             }
    713         }
    714 
    715         int localDim = spec.localDimensionality();
    716         if (localDim == 3) {
    717             fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
    718         } else if (localDim == 2) {
    719             fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
    720         } // else localDim == 0 and attribute remains uninitialized
    721 
    722         if (ColorType::kByte == spec.colorType()) {
    723             fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
    724         } else if (ColorType::kHalf == spec.colorType()) {
    725             fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
    726         }
    727 
    728         if (spec.hasDomain()) {
    729             fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
    730         }
    731 
    732         this->setVertexAttributes(&fPosition, 4);
    733     }
    734 
    735     const TextureSampler& onTextureSampler(int) const override { return fSampler; }
    736 
    737     Attribute fPosition; // May contain coverage as last channel
    738     Attribute fColor; // May have coverage modulated in if the FPs support it
    739     Attribute fLocalCoord;
    740     Attribute fDomain;
    741 
    742     // The positions attribute may have coverage built into it, so float3 is an ambiguous type
    743     // and may mean 2d with coverage, or 3d with no coverage
    744     bool fNeedsPerspective;
    745     CoverageMode fCoverageMode;
    746 
    747     // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
    748     // to skip texturing.
    749     sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
    750     TextureSampler fSampler;
    751 
    752     typedef GrGeometryProcessor INHERITED;
    753 };
    754 
    755 sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) {
    756     return QuadPerEdgeAAGeometryProcessor::Make(spec);
    757 }
    758 
    759 sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
    760         GrTextureType textureType, GrPixelConfig textureConfig,
    761         const GrSamplerState& samplerState, uint32_t extraSamplerKey,
    762         sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
    763     return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig,
    764                                                 samplerState, extraSamplerKey,
    765                                                 std::move(textureColorSpaceXform));
    766 }
    767 
    768 } // namespace GrQuadPerEdgeAA
    769