LCOV - code coverage report
Current view: top level - gfx/skia/skia/src/gpu/ops - GrAAHairLinePathRenderer.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 429 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 37 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Copyright 2011 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 "GrAAHairLinePathRenderer.h"
       9             : #include "GrBuffer.h"
      10             : #include "GrCaps.h"
      11             : #include "GrClip.h"
      12             : #include "GrContext.h"
      13             : #include "GrDefaultGeoProcFactory.h"
      14             : #include "GrDrawOpTest.h"
      15             : #include "GrOpFlushState.h"
      16             : #include "GrPathUtils.h"
      17             : #include "GrPipelineBuilder.h"
      18             : #include "GrProcessor.h"
      19             : #include "GrResourceProvider.h"
      20             : #include "SkGeometry.h"
      21             : #include "SkStroke.h"
      22             : #include "SkTemplates.h"
      23             : #include "effects/GrBezierEffect.h"
      24             : #include "ops/GrMeshDrawOp.h"
      25             : 
      26             : #define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true>
      27             : 
      28             : // quadratics are rendered as 5-sided polys in order to bound the
      29             : // AA stroke around the center-curve. See comments in push_quad_index_buffer and
      30             : // bloat_quad. Quadratics and conics share an index buffer
      31             : 
      32             : // lines are rendered as:
      33             : //      *______________*
      34             : //      |\ -_______   /|
      35             : //      | \        \ / |
      36             : //      |  *--------*  |
      37             : //      | /  ______/ \ |
      38             : //      */_-__________\*
      39             : // For: 6 vertices and 18 indices (for 6 triangles)
      40             : 
      41             : // Each quadratic is rendered as a five sided polygon. This poly bounds
      42             : // the quadratic's bounding triangle but has been expanded so that the
      43             : // 1-pixel wide area around the curve is inside the poly.
      44             : // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1
      45             : // that is rendered would look like this:
      46             : //              b0
      47             : //              b
      48             : //
      49             : //     a0              c0
      50             : //      a            c
      51             : //       a1       c1
      52             : // Each is drawn as three triangles ((a0,a1,b0), (b0,c1,c0), (a1,c1,b0))
      53             : // specified by these 9 indices:
      54             : static const uint16_t kQuadIdxBufPattern[] = {
      55             :     0, 1, 2,
      56             :     2, 4, 3,
      57             :     1, 4, 2
      58             : };
      59             : 
      60             : static const int kIdxsPerQuad = SK_ARRAY_COUNT(kQuadIdxBufPattern);
      61             : static const int kQuadNumVertices = 5;
      62             : static const int kQuadsNumInIdxBuffer = 256;
      63             : GR_DECLARE_STATIC_UNIQUE_KEY(gQuadsIndexBufferKey);
      64             : 
      65           0 : static const GrBuffer* ref_quads_index_buffer(GrResourceProvider* resourceProvider) {
      66           0 :     GR_DEFINE_STATIC_UNIQUE_KEY(gQuadsIndexBufferKey);
      67           0 :     return resourceProvider->findOrCreateInstancedIndexBuffer(
      68             :         kQuadIdxBufPattern, kIdxsPerQuad, kQuadsNumInIdxBuffer, kQuadNumVertices,
      69           0 :         gQuadsIndexBufferKey);
      70             : }
      71             : 
      72             : 
      73             : // Each line segment is rendered as two quads and two triangles.
      74             : // p0 and p1 have alpha = 1 while all other points have alpha = 0.
      75             : // The four external points are offset 1 pixel perpendicular to the
      76             : // line and half a pixel parallel to the line.
      77             : //
      78             : // p4                  p5
      79             : //      p0         p1
      80             : // p2                  p3
      81             : //
      82             : // Each is drawn as six triangles specified by these 18 indices:
      83             : 
      84             : static const uint16_t kLineSegIdxBufPattern[] = {
      85             :     0, 1, 3,
      86             :     0, 3, 2,
      87             :     0, 4, 5,
      88             :     0, 5, 1,
      89             :     0, 2, 4,
      90             :     1, 5, 3
      91             : };
      92             : 
      93             : static const int kIdxsPerLineSeg = SK_ARRAY_COUNT(kLineSegIdxBufPattern);
      94             : static const int kLineSegNumVertices = 6;
      95             : static const int kLineSegsNumInIdxBuffer = 256;
      96             : 
      97             : GR_DECLARE_STATIC_UNIQUE_KEY(gLinesIndexBufferKey);
      98             : 
      99           0 : static const GrBuffer* ref_lines_index_buffer(GrResourceProvider* resourceProvider) {
     100           0 :     GR_DEFINE_STATIC_UNIQUE_KEY(gLinesIndexBufferKey);
     101           0 :     return resourceProvider->findOrCreateInstancedIndexBuffer(
     102             :         kLineSegIdxBufPattern, kIdxsPerLineSeg,  kLineSegsNumInIdxBuffer, kLineSegNumVertices,
     103           0 :         gLinesIndexBufferKey);
     104             : }
     105             : 
     106             : // Takes 178th time of logf on Z600 / VC2010
     107           0 : static int get_float_exp(float x) {
     108             :     GR_STATIC_ASSERT(sizeof(int) == sizeof(float));
     109             : #ifdef SK_DEBUG
     110             :     static bool tested;
     111           0 :     if (!tested) {
     112           0 :         tested = true;
     113           0 :         SkASSERT(get_float_exp(0.25f) == -2);
     114           0 :         SkASSERT(get_float_exp(0.3f) == -2);
     115           0 :         SkASSERT(get_float_exp(0.5f) == -1);
     116           0 :         SkASSERT(get_float_exp(1.f) == 0);
     117           0 :         SkASSERT(get_float_exp(2.f) == 1);
     118           0 :         SkASSERT(get_float_exp(2.5f) == 1);
     119           0 :         SkASSERT(get_float_exp(8.f) == 3);
     120           0 :         SkASSERT(get_float_exp(100.f) == 6);
     121           0 :         SkASSERT(get_float_exp(1000.f) == 9);
     122           0 :         SkASSERT(get_float_exp(1024.f) == 10);
     123           0 :         SkASSERT(get_float_exp(3000000.f) == 21);
     124             :     }
     125             : #endif
     126           0 :     const int* iptr = (const int*)&x;
     127           0 :     return (((*iptr) & 0x7f800000) >> 23) - 127;
     128             : }
     129             : 
     130             : // Uses the max curvature function for quads to estimate
     131             : // where to chop the conic. If the max curvature is not
     132             : // found along the curve segment it will return 1 and
     133             : // dst[0] is the original conic. If it returns 2 the dst[0]
     134             : // and dst[1] are the two new conics.
     135           0 : static int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) {
     136           0 :     SkScalar t = SkFindQuadMaxCurvature(src);
     137           0 :     if (t == 0) {
     138           0 :         if (dst) {
     139           0 :             dst[0].set(src, weight);
     140             :         }
     141           0 :         return 1;
     142             :     } else {
     143           0 :         if (dst) {
     144           0 :             SkConic conic;
     145           0 :             conic.set(src, weight);
     146           0 :             if (!conic.chopAt(t, dst)) {
     147           0 :                 dst[0].set(src, weight);
     148           0 :                 return 1;
     149             :             }
     150             :         }
     151           0 :         return 2;
     152             :     }
     153             : }
     154             : 
     155             : // Calls split_conic on the entire conic and then once more on each subsection.
     156             : // Most cases will result in either 1 conic (chop point is not within t range)
     157             : // or 3 points (split once and then one subsection is split again).
     158           0 : static int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) {
     159           0 :     SkConic dstTemp[2];
     160           0 :     int conicCnt = split_conic(src, dstTemp, weight);
     161           0 :     if (2 == conicCnt) {
     162           0 :         int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW);
     163           0 :         conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW);
     164             :     } else {
     165           0 :         dst[0] = dstTemp[0];
     166             :     }
     167           0 :     return conicCnt;
     168             : }
     169             : 
     170             : // returns 0 if quad/conic is degen or close to it
     171             : // in this case approx the path with lines
     172             : // otherwise returns 1
     173           0 : static int is_degen_quad_or_conic(const SkPoint p[3], SkScalar* dsqd) {
     174             :     static const SkScalar gDegenerateToLineTol = GrPathUtils::kDefaultTolerance;
     175             :     static const SkScalar gDegenerateToLineTolSqd =
     176             :         gDegenerateToLineTol * gDegenerateToLineTol;
     177             : 
     178           0 :     if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd ||
     179           0 :         p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) {
     180           0 :         return 1;
     181             :     }
     182             : 
     183           0 :     *dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
     184           0 :     if (*dsqd < gDegenerateToLineTolSqd) {
     185           0 :         return 1;
     186             :     }
     187             : 
     188           0 :     if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) {
     189           0 :         return 1;
     190             :     }
     191           0 :     return 0;
     192             : }
     193             : 
     194           0 : static int is_degen_quad_or_conic(const SkPoint p[3]) {
     195             :     SkScalar dsqd;
     196           0 :     return is_degen_quad_or_conic(p, &dsqd);
     197             : }
     198             : 
     199             : // we subdivide the quads to avoid huge overfill
     200             : // if it returns -1 then should be drawn as lines
     201           0 : static int num_quad_subdivs(const SkPoint p[3]) {
     202             :     SkScalar dsqd;
     203           0 :     if (is_degen_quad_or_conic(p, &dsqd)) {
     204           0 :         return -1;
     205             :     }
     206             : 
     207             :     // tolerance of triangle height in pixels
     208             :     // tuned on windows  Quadro FX 380 / Z600
     209             :     // trade off of fill vs cpu time on verts
     210             :     // maybe different when do this using gpu (geo or tess shaders)
     211             :     static const SkScalar gSubdivTol = 175 * SK_Scalar1;
     212             : 
     213           0 :     if (dsqd <= gSubdivTol * gSubdivTol) {
     214           0 :         return 0;
     215             :     } else {
     216             :         static const int kMaxSub = 4;
     217             :         // subdividing the quad reduces d by 4. so we want x = log4(d/tol)
     218             :         // = log4(d*d/tol*tol)/2
     219             :         // = log2(d*d/tol*tol)
     220             : 
     221             :         // +1 since we're ignoring the mantissa contribution.
     222           0 :         int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1;
     223           0 :         log = SkTMin(SkTMax(0, log), kMaxSub);
     224           0 :         return log;
     225             :     }
     226             : }
     227             : 
     228             : /**
     229             :  * Generates the lines and quads to be rendered. Lines are always recorded in
     230             :  * device space. We will do a device space bloat to account for the 1pixel
     231             :  * thickness.
     232             :  * Quads are recorded in device space unless m contains
     233             :  * perspective, then in they are in src space. We do this because we will
     234             :  * subdivide large quads to reduce over-fill. This subdivision has to be
     235             :  * performed before applying the perspective matrix.
     236             :  */
     237           0 : static int gather_lines_and_quads(const SkPath& path,
     238             :                                   const SkMatrix& m,
     239             :                                   const SkIRect& devClipBounds,
     240             :                                   GrAAHairLinePathRenderer::PtArray* lines,
     241             :                                   GrAAHairLinePathRenderer::PtArray* quads,
     242             :                                   GrAAHairLinePathRenderer::PtArray* conics,
     243             :                                   GrAAHairLinePathRenderer::IntArray* quadSubdivCnts,
     244             :                                   GrAAHairLinePathRenderer::FloatArray* conicWeights) {
     245           0 :     SkPath::Iter iter(path, false);
     246             : 
     247           0 :     int totalQuadCount = 0;
     248             :     SkRect bounds;
     249             :     SkIRect ibounds;
     250             : 
     251           0 :     bool persp = m.hasPerspective();
     252             : 
     253             :     for (;;) {
     254             :         SkPoint pathPts[4];
     255             :         SkPoint devPts[4];
     256           0 :         SkPath::Verb verb = iter.next(pathPts);
     257           0 :         switch (verb) {
     258             :             case SkPath::kConic_Verb: {
     259           0 :                 SkConic dst[4];
     260             :                 // We chop the conics to create tighter clipping to hide error
     261             :                 // that appears near max curvature of very thin conics. Thin
     262             :                 // hyperbolas with high weight still show error.
     263           0 :                 int conicCnt = chop_conic(pathPts, dst, iter.conicWeight());
     264           0 :                 for (int i = 0; i < conicCnt; ++i) {
     265           0 :                     SkPoint* chopPnts = dst[i].fPts;
     266           0 :                     m.mapPoints(devPts, chopPnts, 3);
     267           0 :                     bounds.setBounds(devPts, 3);
     268           0 :                     bounds.outset(SK_Scalar1, SK_Scalar1);
     269           0 :                     bounds.roundOut(&ibounds);
     270           0 :                     if (SkIRect::Intersects(devClipBounds, ibounds)) {
     271           0 :                         if (is_degen_quad_or_conic(devPts)) {
     272           0 :                             SkPoint* pts = lines->push_back_n(4);
     273           0 :                             pts[0] = devPts[0];
     274           0 :                             pts[1] = devPts[1];
     275           0 :                             pts[2] = devPts[1];
     276           0 :                             pts[3] = devPts[2];
     277             :                         } else {
     278             :                             // when in perspective keep conics in src space
     279           0 :                             SkPoint* cPts = persp ? chopPnts : devPts;
     280           0 :                             SkPoint* pts = conics->push_back_n(3);
     281           0 :                             pts[0] = cPts[0];
     282           0 :                             pts[1] = cPts[1];
     283           0 :                             pts[2] = cPts[2];
     284           0 :                             conicWeights->push_back() = dst[i].fW;
     285             :                         }
     286             :                     }
     287             :                 }
     288           0 :                 break;
     289             :             }
     290             :             case SkPath::kMove_Verb:
     291           0 :                 break;
     292             :             case SkPath::kLine_Verb:
     293           0 :                 m.mapPoints(devPts, pathPts, 2);
     294           0 :                 bounds.setBounds(devPts, 2);
     295           0 :                 bounds.outset(SK_Scalar1, SK_Scalar1);
     296           0 :                 bounds.roundOut(&ibounds);
     297           0 :                 if (SkIRect::Intersects(devClipBounds, ibounds)) {
     298           0 :                     SkPoint* pts = lines->push_back_n(2);
     299           0 :                     pts[0] = devPts[0];
     300           0 :                     pts[1] = devPts[1];
     301             :                 }
     302           0 :                 break;
     303             :             case SkPath::kQuad_Verb: {
     304             :                 SkPoint choppedPts[5];
     305             :                 // Chopping the quad helps when the quad is either degenerate or nearly degenerate.
     306             :                 // When it is degenerate it allows the approximation with lines to work since the
     307             :                 // chop point (if there is one) will be at the parabola's vertex. In the nearly
     308             :                 // degenerate the QuadUVMatrix computed for the points is almost singular which
     309             :                 // can cause rendering artifacts.
     310           0 :                 int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts);
     311           0 :                 for (int i = 0; i < n; ++i) {
     312           0 :                     SkPoint* quadPts = choppedPts + i * 2;
     313           0 :                     m.mapPoints(devPts, quadPts, 3);
     314           0 :                     bounds.setBounds(devPts, 3);
     315           0 :                     bounds.outset(SK_Scalar1, SK_Scalar1);
     316           0 :                     bounds.roundOut(&ibounds);
     317             : 
     318           0 :                     if (SkIRect::Intersects(devClipBounds, ibounds)) {
     319           0 :                         int subdiv = num_quad_subdivs(devPts);
     320           0 :                         SkASSERT(subdiv >= -1);
     321           0 :                         if (-1 == subdiv) {
     322           0 :                             SkPoint* pts = lines->push_back_n(4);
     323           0 :                             pts[0] = devPts[0];
     324           0 :                             pts[1] = devPts[1];
     325           0 :                             pts[2] = devPts[1];
     326           0 :                             pts[3] = devPts[2];
     327             :                         } else {
     328             :                             // when in perspective keep quads in src space
     329           0 :                             SkPoint* qPts = persp ? quadPts : devPts;
     330           0 :                             SkPoint* pts = quads->push_back_n(3);
     331           0 :                             pts[0] = qPts[0];
     332           0 :                             pts[1] = qPts[1];
     333           0 :                             pts[2] = qPts[2];
     334           0 :                             quadSubdivCnts->push_back() = subdiv;
     335           0 :                             totalQuadCount += 1 << subdiv;
     336             :                         }
     337             :                     }
     338             :                 }
     339           0 :                 break;
     340             :             }
     341             :             case SkPath::kCubic_Verb:
     342           0 :                 m.mapPoints(devPts, pathPts, 4);
     343           0 :                 bounds.setBounds(devPts, 4);
     344           0 :                 bounds.outset(SK_Scalar1, SK_Scalar1);
     345           0 :                 bounds.roundOut(&ibounds);
     346           0 :                 if (SkIRect::Intersects(devClipBounds, ibounds)) {
     347           0 :                     PREALLOC_PTARRAY(32) q;
     348             :                     // We convert cubics to quadratics (for now).
     349             :                     // In perspective have to do conversion in src space.
     350           0 :                     if (persp) {
     351             :                         SkScalar tolScale =
     352           0 :                             GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds());
     353           0 :                         GrPathUtils::convertCubicToQuads(pathPts, tolScale, &q);
     354             :                     } else {
     355           0 :                         GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, &q);
     356             :                     }
     357           0 :                     for (int i = 0; i < q.count(); i += 3) {
     358             :                         SkPoint* qInDevSpace;
     359             :                         // bounds has to be calculated in device space, but q is
     360             :                         // in src space when there is perspective.
     361           0 :                         if (persp) {
     362           0 :                             m.mapPoints(devPts, &q[i], 3);
     363           0 :                             bounds.setBounds(devPts, 3);
     364           0 :                             qInDevSpace = devPts;
     365             :                         } else {
     366           0 :                             bounds.setBounds(&q[i], 3);
     367           0 :                             qInDevSpace = &q[i];
     368             :                         }
     369           0 :                         bounds.outset(SK_Scalar1, SK_Scalar1);
     370           0 :                         bounds.roundOut(&ibounds);
     371           0 :                         if (SkIRect::Intersects(devClipBounds, ibounds)) {
     372           0 :                             int subdiv = num_quad_subdivs(qInDevSpace);
     373           0 :                             SkASSERT(subdiv >= -1);
     374           0 :                             if (-1 == subdiv) {
     375           0 :                                 SkPoint* pts = lines->push_back_n(4);
     376             :                                 // lines should always be in device coords
     377           0 :                                 pts[0] = qInDevSpace[0];
     378           0 :                                 pts[1] = qInDevSpace[1];
     379           0 :                                 pts[2] = qInDevSpace[1];
     380           0 :                                 pts[3] = qInDevSpace[2];
     381             :                             } else {
     382           0 :                                 SkPoint* pts = quads->push_back_n(3);
     383             :                                 // q is already in src space when there is no
     384             :                                 // perspective and dev coords otherwise.
     385           0 :                                 pts[0] = q[0 + i];
     386           0 :                                 pts[1] = q[1 + i];
     387           0 :                                 pts[2] = q[2 + i];
     388           0 :                                 quadSubdivCnts->push_back() = subdiv;
     389           0 :                                 totalQuadCount += 1 << subdiv;
     390             :                             }
     391             :                         }
     392             :                     }
     393             :                 }
     394           0 :                 break;
     395             :             case SkPath::kClose_Verb:
     396           0 :                 break;
     397             :             case SkPath::kDone_Verb:
     398           0 :                 return totalQuadCount;
     399             :         }
     400           0 :     }
     401             : }
     402             : 
     403             : struct LineVertex {
     404             :     SkPoint fPos;
     405             :     float fCoverage;
     406             : };
     407             : 
     408             : struct BezierVertex {
     409             :     SkPoint fPos;
     410             :     union {
     411             :         struct {
     412             :             SkScalar fKLM[3];
     413             :         } fConic;
     414             :         SkVector   fQuadCoord;
     415             :         struct {
     416             :             SkScalar fBogus[4];
     417             :         };
     418             :     };
     419             : };
     420             : 
     421             : GR_STATIC_ASSERT(sizeof(BezierVertex) == 3 * sizeof(SkPoint));
     422             : 
     423           0 : static void intersect_lines(const SkPoint& ptA, const SkVector& normA,
     424             :                             const SkPoint& ptB, const SkVector& normB,
     425             :                             SkPoint* result) {
     426             : 
     427           0 :     SkScalar lineAW = -normA.dot(ptA);
     428           0 :     SkScalar lineBW = -normB.dot(ptB);
     429             : 
     430           0 :     SkScalar wInv = normA.fX * normB.fY - normA.fY * normB.fX;
     431           0 :     wInv = SkScalarInvert(wInv);
     432             : 
     433           0 :     result->fX = normA.fY * lineBW - lineAW * normB.fY;
     434           0 :     result->fX *= wInv;
     435             : 
     436           0 :     result->fY = lineAW * normB.fX - normA.fX * lineBW;
     437           0 :     result->fY *= wInv;
     438           0 : }
     439             : 
     440           0 : static void set_uv_quad(const SkPoint qpts[3], BezierVertex verts[kQuadNumVertices]) {
     441             :     // this should be in the src space, not dev coords, when we have perspective
     442           0 :     GrPathUtils::QuadUVMatrix DevToUV(qpts);
     443           0 :     DevToUV.apply<kQuadNumVertices, sizeof(BezierVertex), sizeof(SkPoint)>(verts);
     444           0 : }
     445             : 
     446           0 : static void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
     447             :                        const SkMatrix* toSrc, BezierVertex verts[kQuadNumVertices]) {
     448           0 :     SkASSERT(!toDevice == !toSrc);
     449             :     // original quad is specified by tri a,b,c
     450           0 :     SkPoint a = qpts[0];
     451           0 :     SkPoint b = qpts[1];
     452           0 :     SkPoint c = qpts[2];
     453             : 
     454           0 :     if (toDevice) {
     455           0 :         toDevice->mapPoints(&a, 1);
     456           0 :         toDevice->mapPoints(&b, 1);
     457           0 :         toDevice->mapPoints(&c, 1);
     458             :     }
     459             :     // make a new poly where we replace a and c by a 1-pixel wide edges orthog
     460             :     // to edges ab and bc:
     461             :     //
     462             :     //   before       |        after
     463             :     //                |              b0
     464             :     //         b      |
     465             :     //                |
     466             :     //                |     a0            c0
     467             :     // a         c    |        a1       c1
     468             :     //
     469             :     // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
     470             :     // respectively.
     471           0 :     BezierVertex& a0 = verts[0];
     472           0 :     BezierVertex& a1 = verts[1];
     473           0 :     BezierVertex& b0 = verts[2];
     474           0 :     BezierVertex& c0 = verts[3];
     475           0 :     BezierVertex& c1 = verts[4];
     476             : 
     477           0 :     SkVector ab = b;
     478           0 :     ab -= a;
     479           0 :     SkVector ac = c;
     480           0 :     ac -= a;
     481           0 :     SkVector cb = b;
     482           0 :     cb -= c;
     483             : 
     484             :     // We should have already handled degenerates
     485           0 :     SkASSERT(ab.length() > 0 && cb.length() > 0);
     486             : 
     487           0 :     ab.normalize();
     488             :     SkVector abN;
     489           0 :     abN.setOrthog(ab, SkVector::kLeft_Side);
     490           0 :     if (abN.dot(ac) > 0) {
     491           0 :         abN.negate();
     492             :     }
     493             : 
     494           0 :     cb.normalize();
     495             :     SkVector cbN;
     496           0 :     cbN.setOrthog(cb, SkVector::kLeft_Side);
     497           0 :     if (cbN.dot(ac) < 0) {
     498           0 :         cbN.negate();
     499             :     }
     500             : 
     501           0 :     a0.fPos = a;
     502           0 :     a0.fPos += abN;
     503           0 :     a1.fPos = a;
     504           0 :     a1.fPos -= abN;
     505             : 
     506           0 :     c0.fPos = c;
     507           0 :     c0.fPos += cbN;
     508           0 :     c1.fPos = c;
     509           0 :     c1.fPos -= cbN;
     510             : 
     511           0 :     intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
     512             : 
     513           0 :     if (toSrc) {
     514           0 :         toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(BezierVertex), kQuadNumVertices);
     515             :     }
     516           0 : }
     517             : 
     518             : // Equations based off of Loop-Blinn Quadratic GPU Rendering
     519             : // Input Parametric:
     520             : // P(t) = (P0*(1-t)^2 + 2*w*P1*t*(1-t) + P2*t^2) / (1-t)^2 + 2*w*t*(1-t) + t^2)
     521             : // Output Implicit:
     522             : // f(x, y, w) = f(P) = K^2 - LM
     523             : // K = dot(k, P), L = dot(l, P), M = dot(m, P)
     524             : // k, l, m are calculated in function GrPathUtils::getConicKLM
     525           0 : static void set_conic_coeffs(const SkPoint p[3], BezierVertex verts[kQuadNumVertices],
     526             :                              const SkScalar weight) {
     527             :     SkMatrix klm;
     528             : 
     529           0 :     GrPathUtils::getConicKLM(p, weight, &klm);
     530             : 
     531           0 :     for (int i = 0; i < kQuadNumVertices; ++i) {
     532           0 :         const SkScalar pt3[3] = {verts[i].fPos.x(), verts[i].fPos.y(), 1.f};
     533           0 :         klm.mapHomogeneousPoints(verts[i].fConic.fKLM, pt3, 1);
     534             :     }
     535           0 : }
     536             : 
     537           0 : static void add_conics(const SkPoint p[3],
     538             :                        const SkScalar weight,
     539             :                        const SkMatrix* toDevice,
     540             :                        const SkMatrix* toSrc,
     541             :                        BezierVertex** vert) {
     542           0 :     bloat_quad(p, toDevice, toSrc, *vert);
     543           0 :     set_conic_coeffs(p, *vert, weight);
     544           0 :     *vert += kQuadNumVertices;
     545           0 : }
     546             : 
     547           0 : static void add_quads(const SkPoint p[3],
     548             :                       int subdiv,
     549             :                       const SkMatrix* toDevice,
     550             :                       const SkMatrix* toSrc,
     551             :                       BezierVertex** vert) {
     552           0 :     SkASSERT(subdiv >= 0);
     553           0 :     if (subdiv) {
     554             :         SkPoint newP[5];
     555           0 :         SkChopQuadAtHalf(p, newP);
     556           0 :         add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert);
     557           0 :         add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert);
     558             :     } else {
     559           0 :         bloat_quad(p, toDevice, toSrc, *vert);
     560           0 :         set_uv_quad(p, *vert);
     561           0 :         *vert += kQuadNumVertices;
     562             :     }
     563           0 : }
     564             : 
     565           0 : static void add_line(const SkPoint p[2],
     566             :                      const SkMatrix* toSrc,
     567             :                      uint8_t coverage,
     568             :                      LineVertex** vert) {
     569           0 :     const SkPoint& a = p[0];
     570           0 :     const SkPoint& b = p[1];
     571             : 
     572           0 :     SkVector ortho, vec = b;
     573           0 :     vec -= a;
     574             : 
     575           0 :     if (vec.setLength(SK_ScalarHalf)) {
     576             :         // Create a vector orthogonal to 'vec' and of unit length
     577           0 :         ortho.fX = 2.0f * vec.fY;
     578           0 :         ortho.fY = -2.0f * vec.fX;
     579             : 
     580           0 :         float floatCoverage = GrNormalizeByteToFloat(coverage);
     581             : 
     582           0 :         (*vert)[0].fPos = a;
     583           0 :         (*vert)[0].fCoverage = floatCoverage;
     584           0 :         (*vert)[1].fPos = b;
     585           0 :         (*vert)[1].fCoverage = floatCoverage;
     586           0 :         (*vert)[2].fPos = a - vec + ortho;
     587           0 :         (*vert)[2].fCoverage = 0;
     588           0 :         (*vert)[3].fPos = b + vec + ortho;
     589           0 :         (*vert)[3].fCoverage = 0;
     590           0 :         (*vert)[4].fPos = a - vec - ortho;
     591           0 :         (*vert)[4].fCoverage = 0;
     592           0 :         (*vert)[5].fPos = b + vec - ortho;
     593           0 :         (*vert)[5].fCoverage = 0;
     594             : 
     595           0 :         if (toSrc) {
     596           0 :             toSrc->mapPointsWithStride(&(*vert)->fPos,
     597             :                                        sizeof(LineVertex),
     598           0 :                                        kLineSegNumVertices);
     599             :         }
     600             :     } else {
     601             :         // just make it degenerate and likely offscreen
     602           0 :         for (int i = 0; i < kLineSegNumVertices; ++i) {
     603           0 :             (*vert)[i].fPos.set(SK_ScalarMax, SK_ScalarMax);
     604             :         }
     605             :     }
     606             : 
     607           0 :     *vert += kLineSegNumVertices;
     608           0 : }
     609             : 
     610             : ///////////////////////////////////////////////////////////////////////////////
     611             : 
     612           0 : bool GrAAHairLinePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
     613           0 :     if (GrAAType::kCoverage != args.fAAType) {
     614           0 :         return false;
     615             :     }
     616             : 
     617           0 :     if (!IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr)) {
     618           0 :         return false;
     619             :     }
     620             : 
     621             :     // We don't currently handle dashing in this class though perhaps we should.
     622           0 :     if (args.fShape->style().pathEffect()) {
     623           0 :         return false;
     624             :     }
     625             : 
     626           0 :     if (SkPath::kLine_SegmentMask == args.fShape->segmentMask() ||
     627           0 :         args.fShaderCaps->shaderDerivativeSupport()) {
     628           0 :         return true;
     629             :     }
     630             : 
     631           0 :     return false;
     632             : }
     633             : 
     634             : template <class VertexType>
     635             : bool check_bounds(const SkMatrix& viewMatrix, const SkRect& devBounds, void* vertices, int vCount)
     636             : {
     637             :     SkRect tolDevBounds = devBounds;
     638             :     // The bounds ought to be tight, but in perspective the below code runs the verts
     639             :     // through the view matrix to get back to dev coords, which can introduce imprecision.
     640             :     if (viewMatrix.hasPerspective()) {
     641             :         tolDevBounds.outset(SK_Scalar1 / 1000, SK_Scalar1 / 1000);
     642             :     } else {
     643             :         // Non-persp matrices cause this path renderer to draw in device space.
     644             :         SkASSERT(viewMatrix.isIdentity());
     645             :     }
     646             :     SkRect actualBounds;
     647             : 
     648             :     VertexType* verts = reinterpret_cast<VertexType*>(vertices);
     649             :     bool first = true;
     650             :     for (int i = 0; i < vCount; ++i) {
     651             :         SkPoint pos = verts[i].fPos;
     652             :         // This is a hack to workaround the fact that we move some degenerate segments offscreen.
     653             :         if (SK_ScalarMax == pos.fX) {
     654             :             continue;
     655             :         }
     656             :         viewMatrix.mapPoints(&pos, 1);
     657             :         if (first) {
     658             :             actualBounds.set(pos.fX, pos.fY, pos.fX, pos.fY);
     659             :             first = false;
     660             :         } else {
     661             :             actualBounds.growToInclude(pos.fX, pos.fY);
     662             :         }
     663             :     }
     664             :     if (!first) {
     665             :         return tolDevBounds.contains(actualBounds);
     666             :     }
     667             : 
     668             :     return true;
     669             : }
     670             : 
     671           0 : class AAHairlineOp final : public GrLegacyMeshDrawOp {
     672             : public:
     673           0 :     DEFINE_OP_CLASS_ID
     674             : 
     675           0 :     static std::unique_ptr<GrLegacyMeshDrawOp> Make(GrColor color,
     676             :                                                     const SkMatrix& viewMatrix,
     677             :                                                     const SkPath& path,
     678             :                                                     const GrStyle& style,
     679             :                                                     const SkIRect& devClipBounds) {
     680             :         SkScalar hairlineCoverage;
     681           0 :         uint8_t newCoverage = 0xff;
     682           0 :         if (GrPathRenderer::IsStrokeHairlineOrEquivalent(style, viewMatrix, &hairlineCoverage)) {
     683           0 :             newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
     684             :         }
     685             : 
     686             :         return std::unique_ptr<GrLegacyMeshDrawOp>(
     687           0 :                 new AAHairlineOp(color, newCoverage, viewMatrix, path, devClipBounds));
     688             :     }
     689             : 
     690           0 :     const char* name() const override { return "AAHairlineOp"; }
     691             : 
     692           0 :     SkString dumpInfo() const override {
     693           0 :         SkString string;
     694           0 :         string.appendf("Color: 0x%08x Coverage: 0x%02x, Count: %d\n", fColor, fCoverage,
     695           0 :                        fPaths.count());
     696           0 :         string.append(INHERITED::dumpInfo());
     697           0 :         return string;
     698             :     }
     699             : 
     700             : private:
     701           0 :     AAHairlineOp(GrColor color,
     702             :                  uint8_t coverage,
     703             :                  const SkMatrix& viewMatrix,
     704             :                  const SkPath& path,
     705             :                  SkIRect devClipBounds)
     706           0 :             : INHERITED(ClassID()), fColor(color), fCoverage(coverage) {
     707           0 :         fPaths.emplace_back(PathData{viewMatrix, path, devClipBounds});
     708             : 
     709           0 :         this->setTransformedBounds(path.getBounds(), viewMatrix, HasAABloat::kYes,
     710           0 :                                    IsZeroArea::kYes);
     711           0 :     }
     712             : 
     713           0 :     void getProcessorAnalysisInputs(GrProcessorAnalysisColor* color,
     714             :                                     GrProcessorAnalysisCoverage* coverage) const override {
     715           0 :         color->setToConstant(fColor);
     716           0 :         *coverage = GrProcessorAnalysisCoverage::kSingleChannel;
     717           0 :     }
     718             : 
     719           0 :     void applyPipelineOptimizations(const PipelineOptimizations& optimizations) override {
     720           0 :         optimizations.getOverrideColorIfSet(&fColor);
     721           0 :         fUsesLocalCoords = optimizations.readsLocalCoords();
     722           0 :     }
     723             : 
     724             :     void onPrepareDraws(Target*) const override;
     725             : 
     726             :     typedef SkTArray<SkPoint, true> PtArray;
     727             :     typedef SkTArray<int, true> IntArray;
     728             :     typedef SkTArray<float, true> FloatArray;
     729             : 
     730           0 :     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
     731           0 :         AAHairlineOp* that = t->cast<AAHairlineOp>();
     732             : 
     733           0 :         if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
     734             :                                     that->bounds(), caps)) {
     735           0 :             return false;
     736             :         }
     737             : 
     738           0 :         if (this->viewMatrix().hasPerspective() != that->viewMatrix().hasPerspective()) {
     739           0 :             return false;
     740             :         }
     741             : 
     742             :         // We go to identity if we don't have perspective
     743           0 :         if (this->viewMatrix().hasPerspective() &&
     744           0 :             !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
     745           0 :             return false;
     746             :         }
     747             : 
     748             :         // TODO we can actually combine hairlines if they are the same color in a kind of bulk
     749             :         // method but we haven't implemented this yet
     750             :         // TODO investigate going to vertex color and coverage?
     751           0 :         if (this->coverage() != that->coverage()) {
     752           0 :             return false;
     753             :         }
     754             : 
     755           0 :         if (this->color() != that->color()) {
     756           0 :             return false;
     757             :         }
     758             : 
     759           0 :         SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
     760           0 :         if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
     761           0 :             return false;
     762             :         }
     763             : 
     764           0 :         fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
     765           0 :         this->joinBounds(*that);
     766           0 :         return true;
     767             :     }
     768             : 
     769           0 :     GrColor color() const { return fColor; }
     770           0 :     uint8_t coverage() const { return fCoverage; }
     771           0 :     bool usesLocalCoords() const { return fUsesLocalCoords; }
     772           0 :     const SkMatrix& viewMatrix() const { return fPaths[0].fViewMatrix; }
     773             : 
     774           0 :     struct PathData {
     775             :         SkMatrix fViewMatrix;
     776             :         SkPath fPath;
     777             :         SkIRect fDevClipBounds;
     778             :     };
     779             : 
     780             :     GrColor fColor;
     781             :     uint8_t fCoverage;
     782             :     bool fUsesLocalCoords;
     783             : 
     784             :     SkSTArray<1, PathData, true> fPaths;
     785             : 
     786             :     typedef GrLegacyMeshDrawOp INHERITED;
     787             : };
     788             : 
     789           0 : void AAHairlineOp::onPrepareDraws(Target* target) const {
     790             :     // Setup the viewmatrix and localmatrix for the GrGeometryProcessor.
     791             :     SkMatrix invert;
     792           0 :     if (!this->viewMatrix().invert(&invert)) {
     793           0 :         return;
     794             :     }
     795             : 
     796             :     // we will transform to identity space if the viewmatrix does not have perspective
     797           0 :     bool hasPerspective = this->viewMatrix().hasPerspective();
     798           0 :     const SkMatrix* geometryProcessorViewM = &SkMatrix::I();
     799           0 :     const SkMatrix* geometryProcessorLocalM = &invert;
     800           0 :     const SkMatrix* toDevice = nullptr;
     801           0 :     const SkMatrix* toSrc = nullptr;
     802           0 :     if (hasPerspective) {
     803           0 :         geometryProcessorViewM = &this->viewMatrix();
     804           0 :         geometryProcessorLocalM = &SkMatrix::I();
     805           0 :         toDevice = &this->viewMatrix();
     806           0 :         toSrc = &invert;
     807             :     }
     808             : 
     809             :     // This is hand inlined for maximum performance.
     810           0 :     PREALLOC_PTARRAY(128) lines;
     811           0 :     PREALLOC_PTARRAY(128) quads;
     812           0 :     PREALLOC_PTARRAY(128) conics;
     813           0 :     IntArray qSubdivs;
     814           0 :     FloatArray cWeights;
     815           0 :     int quadCount = 0;
     816             : 
     817           0 :     int instanceCount = fPaths.count();
     818           0 :     for (int i = 0; i < instanceCount; i++) {
     819           0 :         const PathData& args = fPaths[i];
     820           0 :         quadCount += gather_lines_and_quads(args.fPath, args.fViewMatrix, args.fDevClipBounds,
     821             :                                             &lines, &quads, &conics, &qSubdivs, &cWeights);
     822             :     }
     823             : 
     824           0 :     int lineCount = lines.count() / 2;
     825           0 :     int conicCount = conics.count() / 3;
     826             : 
     827             :     // do lines first
     828           0 :     if (lineCount) {
     829           0 :         sk_sp<GrGeometryProcessor> lineGP;
     830             :         {
     831             :             using namespace GrDefaultGeoProcFactory;
     832             : 
     833           0 :             Color color(this->color());
     834           0 :             LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosition_Type :
     835           0 :                                     LocalCoords::kUnused_Type);
     836           0 :             localCoords.fMatrix = geometryProcessorLocalM;
     837           0 :             lineGP = GrDefaultGeoProcFactory::Make(color, Coverage::kAttribute_Type, localCoords,
     838           0 :                                                    *geometryProcessorViewM);
     839             :         }
     840             : 
     841             :         sk_sp<const GrBuffer> linesIndexBuffer(
     842           0 :             ref_lines_index_buffer(target->resourceProvider()));
     843             : 
     844             :         const GrBuffer* vertexBuffer;
     845             :         int firstVertex;
     846             : 
     847           0 :         size_t vertexStride = lineGP->getVertexStride();
     848           0 :         int vertexCount = kLineSegNumVertices * lineCount;
     849             :         LineVertex* verts = reinterpret_cast<LineVertex*>(
     850           0 :             target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex));
     851             : 
     852           0 :         if (!verts|| !linesIndexBuffer) {
     853           0 :             SkDebugf("Could not allocate vertices\n");
     854           0 :             return;
     855             :         }
     856             : 
     857           0 :         SkASSERT(lineGP->getVertexStride() == sizeof(LineVertex));
     858             : 
     859           0 :         for (int i = 0; i < lineCount; ++i) {
     860           0 :             add_line(&lines[2*i], toSrc, this->coverage(), &verts);
     861             :         }
     862             : 
     863           0 :         GrMesh mesh;
     864           0 :         mesh.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, linesIndexBuffer.get(),
     865             :                            firstVertex, kLineSegNumVertices, kIdxsPerLineSeg, lineCount,
     866           0 :                            kLineSegsNumInIdxBuffer);
     867           0 :         target->draw(lineGP.get(), this->pipeline(), mesh);
     868             :     }
     869             : 
     870           0 :     if (quadCount || conicCount) {
     871             :         sk_sp<GrGeometryProcessor> quadGP(
     872             :             GrQuadEffect::Make(this->color(),
     873             :                                *geometryProcessorViewM,
     874             :                                kHairlineAA_GrProcessorEdgeType,
     875             :                                target->caps(),
     876             :                                *geometryProcessorLocalM,
     877           0 :                                this->usesLocalCoords(),
     878           0 :                                this->coverage()));
     879             : 
     880             :         sk_sp<GrGeometryProcessor> conicGP(
     881             :             GrConicEffect::Make(this->color(),
     882             :                                 *geometryProcessorViewM,
     883             :                                 kHairlineAA_GrProcessorEdgeType,
     884             :                                 target->caps(),
     885             :                                 *geometryProcessorLocalM,
     886           0 :                                 this->usesLocalCoords(),
     887           0 :                                 this->coverage()));
     888             : 
     889             :         const GrBuffer* vertexBuffer;
     890             :         int firstVertex;
     891             : 
     892             :         sk_sp<const GrBuffer> quadsIndexBuffer(
     893           0 :             ref_quads_index_buffer(target->resourceProvider()));
     894             : 
     895           0 :         size_t vertexStride = sizeof(BezierVertex);
     896           0 :         int vertexCount = kQuadNumVertices * quadCount + kQuadNumVertices * conicCount;
     897             :         void *vertices = target->makeVertexSpace(vertexStride, vertexCount,
     898           0 :                                                  &vertexBuffer, &firstVertex);
     899             : 
     900           0 :         if (!vertices || !quadsIndexBuffer) {
     901           0 :             SkDebugf("Could not allocate vertices\n");
     902           0 :             return;
     903             :         }
     904             : 
     905             :         // Setup vertices
     906           0 :         BezierVertex* bezVerts = reinterpret_cast<BezierVertex*>(vertices);
     907             : 
     908           0 :         int unsubdivQuadCnt = quads.count() / 3;
     909           0 :         for (int i = 0; i < unsubdivQuadCnt; ++i) {
     910           0 :             SkASSERT(qSubdivs[i] >= 0);
     911           0 :             add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &bezVerts);
     912             :         }
     913             : 
     914             :         // Start Conics
     915           0 :         for (int i = 0; i < conicCount; ++i) {
     916           0 :             add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &bezVerts);
     917             :         }
     918             : 
     919           0 :         if (quadCount > 0) {
     920           0 :             GrMesh mesh;
     921           0 :             mesh.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, quadsIndexBuffer.get(),
     922             :                                firstVertex, kQuadNumVertices, kIdxsPerQuad, quadCount,
     923           0 :                                kQuadsNumInIdxBuffer);
     924           0 :             target->draw(quadGP.get(), this->pipeline(), mesh);
     925           0 :             firstVertex += quadCount * kQuadNumVertices;
     926             :         }
     927             : 
     928           0 :         if (conicCount > 0) {
     929           0 :             GrMesh mesh;
     930           0 :             mesh.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, quadsIndexBuffer.get(),
     931             :                                firstVertex, kQuadNumVertices, kIdxsPerQuad, conicCount,
     932           0 :                                kQuadsNumInIdxBuffer);
     933           0 :             target->draw(conicGP.get(), this->pipeline(), mesh);
     934             :         }
     935             :     }
     936             : }
     937             : 
     938           0 : bool GrAAHairLinePathRenderer::onDrawPath(const DrawPathArgs& args) {
     939           0 :     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
     940             :                               "GrAAHairlinePathRenderer::onDrawPath");
     941           0 :     SkASSERT(!args.fRenderTargetContext->isUnifiedMultisampled());
     942             : 
     943             :     SkIRect devClipBounds;
     944           0 :     args.fClip->getConservativeBounds(args.fRenderTargetContext->width(),
     945           0 :                                       args.fRenderTargetContext->height(),
     946           0 :                                       &devClipBounds);
     947           0 :     SkPath path;
     948           0 :     args.fShape->asPath(&path);
     949             :     std::unique_ptr<GrLegacyMeshDrawOp> op = AAHairlineOp::Make(
     950           0 :             args.fPaint.getColor(), *args.fViewMatrix, path, args.fShape->style(), devClipBounds);
     951           0 :     GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
     952           0 :     pipelineBuilder.setUserStencil(args.fUserStencilSettings);
     953           0 :     args.fRenderTargetContext->addLegacyMeshDrawOp(std::move(pipelineBuilder), *args.fClip,
     954           0 :                                                    std::move(op));
     955           0 :     return true;
     956             : }
     957             : 
     958             : ///////////////////////////////////////////////////////////////////////////////////////////////////
     959             : 
     960             : #if GR_TEST_UTILS
     961             : 
     962           0 : DRAW_OP_TEST_DEFINE(AAHairlineOp) {
     963           0 :     GrColor color = GrRandomColor(random);
     964           0 :     SkMatrix viewMatrix = GrTest::TestMatrix(random);
     965           0 :     SkPath path = GrTest::TestPath(random);
     966             :     SkIRect devClipBounds;
     967           0 :     devClipBounds.setEmpty();
     968           0 :     return AAHairlineOp::Make(color, viewMatrix, path, GrStyle::SimpleHairline(), devClipBounds);
     969             : }
     970             : 
     971             : #endif

Generated by: LCOV version 1.13