Home | History | Annotate | Download | only in hwui
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "OpenGLRenderer"
     18 #define LOG_NDEBUG 1
     19 #define ATRACE_TAG ATRACE_TAG_VIEW
     20 
     21 #define VERTEX_DEBUG 0
     22 
     23 #if VERTEX_DEBUG
     24 #define DEBUG_DUMP_ALPHA_BUFFER() \
     25     for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \
     26         ALOGD("point %d at %f %f, alpha %f", \
     27         i, buffer[i].x, buffer[i].y, buffer[i].alpha); \
     28     }
     29 #define DEBUG_DUMP_BUFFER() \
     30     for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \
     31         ALOGD("point %d at %f %f", i, buffer[i].x, buffer[i].y); \
     32     }
     33 #else
     34 #define DEBUG_DUMP_ALPHA_BUFFER()
     35 #define DEBUG_DUMP_BUFFER()
     36 #endif
     37 
     38 #include <SkPath.h>
     39 #include <SkPaint.h>
     40 
     41 #include <stdlib.h>
     42 #include <stdint.h>
     43 #include <sys/types.h>
     44 
     45 #include <utils/Log.h>
     46 #include <utils/Trace.h>
     47 
     48 #include "PathTessellator.h"
     49 #include "Matrix.h"
     50 #include "Vector.h"
     51 #include "Vertex.h"
     52 #include "utils/MathUtils.h"
     53 
     54 namespace android {
     55 namespace uirenderer {
     56 
     57 #define OUTLINE_REFINE_THRESHOLD_SQUARED (0.5f * 0.5f)
     58 #define ROUND_CAP_THRESH 0.25f
     59 #define PI 3.1415926535897932f
     60 #define MAX_DEPTH 15
     61 
     62 /**
     63  * Extracts the x and y scale from the transform as positive values, and clamps them
     64  */
     65 void PathTessellator::extractTessellationScales(const Matrix4& transform,
     66         float* scaleX, float* scaleY) {
     67     if (CC_LIKELY(transform.isPureTranslate())) {
     68         *scaleX = 1.0f;
     69         *scaleY = 1.0f;
     70     } else {
     71         float m00 = transform.data[Matrix4::kScaleX];
     72         float m01 = transform.data[Matrix4::kSkewY];
     73         float m10 = transform.data[Matrix4::kSkewX];
     74         float m11 = transform.data[Matrix4::kScaleY];
     75         *scaleX = MathUtils::clampTessellationScale(sqrt(m00 * m00 + m01 * m01));
     76         *scaleY = MathUtils::clampTessellationScale(sqrt(m10 * m10 + m11 * m11));
     77     }
     78 }
     79 
     80 /**
     81  * Produces a pseudo-normal for a vertex, given the normals of the two incoming lines. If the offset
     82  * from each vertex in a perimeter is calculated, the resultant lines connecting the offset vertices
     83  * will be offset by 1.0
     84  *
     85  * Note that we can't add and normalize the two vectors, that would result in a rectangle having an
     86  * offset of (sqrt(2)/2, sqrt(2)/2) at each corner, instead of (1, 1)
     87  *
     88  * NOTE: assumes angles between normals 90 degrees or less
     89  */
     90 inline static Vector2 totalOffsetFromNormals(const Vector2& normalA, const Vector2& normalB) {
     91     return (normalA + normalB) / (1 + fabs(normalA.dot(normalB)));
     92 }
     93 
     94 /**
     95  * Structure used for storing useful information about the SkPaint and scale used for tessellating
     96  */
     97 struct PaintInfo {
     98 public:
     99     PaintInfo(const SkPaint* paint, const mat4& transform) :
    100             style(paint->getStyle()), cap(paint->getStrokeCap()), isAA(paint->isAntiAlias()),
    101             halfStrokeWidth(paint->getStrokeWidth() * 0.5f), maxAlpha(1.0f) {
    102         // compute inverse scales
    103         if (CC_LIKELY(transform.isPureTranslate())) {
    104             inverseScaleX = 1.0f;
    105             inverseScaleY = 1.0f;
    106         } else {
    107             float scaleX, scaleY;
    108             PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
    109             inverseScaleX = 1.0f / scaleX;
    110             inverseScaleY = 1.0f / scaleY;
    111         }
    112 
    113         if (isAA && halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
    114                 2 * halfStrokeWidth < inverseScaleX) {
    115             // AA, with non-hairline stroke, width < 1 pixel. Scale alpha and treat as hairline.
    116             maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
    117             halfStrokeWidth = 0.0f;
    118         }
    119     }
    120 
    121     SkPaint::Style style;
    122     SkPaint::Cap cap;
    123     bool isAA;
    124     float inverseScaleX;
    125     float inverseScaleY;
    126     float halfStrokeWidth;
    127     float maxAlpha;
    128 
    129     inline void scaleOffsetForStrokeWidth(Vector2& offset) const {
    130         if (halfStrokeWidth == 0.0f) {
    131             // hairline - compensate for scale
    132             offset.x *= 0.5f * inverseScaleX;
    133             offset.y *= 0.5f * inverseScaleY;
    134         } else {
    135             offset *= halfStrokeWidth;
    136         }
    137     }
    138 
    139     /**
    140      * NOTE: the input will not always be a normal, especially for sharp edges - it should be the
    141      * result of totalOffsetFromNormals (see documentation there)
    142      */
    143     inline Vector2 deriveAAOffset(const Vector2& offset) const {
    144         return (Vector2){offset.x * 0.5f * inverseScaleX, offset.y * 0.5f * inverseScaleY};
    145     }
    146 
    147     /**
    148      * Returns the number of cap divisions beyond the minimum 2 (kButt_Cap/kSquareCap will return 0)
    149      * Should only be used when stroking and drawing caps
    150      */
    151     inline int capExtraDivisions() const {
    152         if (cap == SkPaint::kRound_Cap) {
    153             if (halfStrokeWidth == 0.0f) return 2;
    154 
    155             // ROUND_CAP_THRESH is the maximum error for polygonal approximation of the round cap
    156             const float errConst = (-ROUND_CAP_THRESH / halfStrokeWidth + 1);
    157             const float targetCosVal = 2 * errConst * errConst - 1;
    158             int neededDivisions = (int)(ceilf(PI / acos(targetCosVal)/2)) * 2;
    159             return neededDivisions;
    160         }
    161         return 0;
    162     }
    163 
    164     /**
    165      * Outset the bounds of point data (for line endpoints or points) to account for stroke
    166      * geometry.
    167      *
    168      * bounds are in pre-scaled space.
    169      */
    170     void expandBoundsForStroke(Rect* bounds) const {
    171         if (halfStrokeWidth == 0) {
    172             // hairline, outset by (0.5f + fudge factor) in post-scaling space
    173             bounds->outset(fabs(inverseScaleX) * (0.5f + Vertex::GeometryFudgeFactor()),
    174                     fabs(inverseScaleY) * (0.5f + Vertex::GeometryFudgeFactor()));
    175         } else {
    176             // non hairline, outset by half stroke width pre-scaled, and fudge factor post scaled
    177             bounds->outset(halfStrokeWidth + fabs(inverseScaleX) * Vertex::GeometryFudgeFactor(),
    178                     halfStrokeWidth + fabs(inverseScaleY) * Vertex::GeometryFudgeFactor());
    179         }
    180     }
    181 };
    182 
    183 void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
    184     Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());
    185 
    186     int currentIndex = 0;
    187     // zig zag between all previous points on the inside of the hull to create a
    188     // triangle strip that fills the hull
    189     int srcAindex = 0;
    190     int srcBindex = perimeter.size() - 1;
    191     while (srcAindex <= srcBindex) {
    192         buffer[currentIndex++] = perimeter[srcAindex];
    193         if (srcAindex == srcBindex) break;
    194         buffer[currentIndex++] = perimeter[srcBindex];
    195         srcAindex++;
    196         srcBindex--;
    197     }
    198 }
    199 
    200 /*
    201  * Fills a vertexBuffer with non-alpha vertices, zig-zagging at each perimeter point to create a
    202  * tri-strip as wide as the stroke.
    203  *
    204  * Uses an additional 2 vertices at the end to wrap around, closing the tri-strip
    205  * (for a total of perimeter.size() * 2 + 2 vertices)
    206  */
    207 void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter,
    208         VertexBuffer& vertexBuffer) {
    209     Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2);
    210 
    211     int currentIndex = 0;
    212     const Vertex* last = &(perimeter[perimeter.size() - 1]);
    213     const Vertex* current = &(perimeter[0]);
    214     Vector2 lastNormal = {current->y - last->y, last->x - current->x};
    215     lastNormal.normalize();
    216     for (unsigned int i = 0; i < perimeter.size(); i++) {
    217         const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
    218         Vector2 nextNormal = {next->y - current->y, current->x - next->x};
    219         nextNormal.normalize();
    220 
    221         Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
    222         paintInfo.scaleOffsetForStrokeWidth(totalOffset);
    223 
    224         Vertex::set(&buffer[currentIndex++],
    225                 current->x + totalOffset.x,
    226                 current->y + totalOffset.y);
    227 
    228         Vertex::set(&buffer[currentIndex++],
    229                 current->x - totalOffset.x,
    230                 current->y - totalOffset.y);
    231 
    232         last = current;
    233         current = next;
    234         lastNormal = nextNormal;
    235     }
    236 
    237     // wrap around to beginning
    238     buffer[currentIndex++] = buffer[0];
    239     buffer[currentIndex++] = buffer[1];
    240 
    241     DEBUG_DUMP_BUFFER();
    242 }
    243 
    244 static inline void storeBeginEnd(const PaintInfo& paintInfo, const Vertex& center,
    245         const Vector2& normal, Vertex* buffer, int& currentIndex, bool begin) {
    246     Vector2 strokeOffset = normal;
    247     paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
    248 
    249     Vector2 referencePoint = {center.x, center.y};
    250     if (paintInfo.cap == SkPaint::kSquare_Cap) {
    251         Vector2 rotated = {-strokeOffset.y, strokeOffset.x};
    252         referencePoint += rotated * (begin ? -1 : 1);
    253     }
    254 
    255     Vertex::set(&buffer[currentIndex++], referencePoint + strokeOffset);
    256     Vertex::set(&buffer[currentIndex++], referencePoint - strokeOffset);
    257 }
    258 
    259 /**
    260  * Fills a vertexBuffer with non-alpha vertices similar to getStrokeVerticesFromPerimeter, except:
    261  *
    262  * 1 - Doesn't need to wrap around, since the input vertices are unclosed
    263  *
    264  * 2 - can zig-zag across 'extra' vertices at either end, to create round caps
    265  */
    266 void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo,
    267         const Vector<Vertex>& vertices, VertexBuffer& vertexBuffer) {
    268     const int extra = paintInfo.capExtraDivisions();
    269     const int allocSize = (vertices.size() + extra) * 2;
    270     Vertex* buffer = vertexBuffer.alloc<Vertex>(allocSize);
    271 
    272     const int lastIndex = vertices.size() - 1;
    273     if (extra > 0) {
    274         // tessellate both round caps
    275         float beginTheta = atan2(
    276                     - (vertices[0].x - vertices[1].x),
    277                     vertices[0].y - vertices[1].y);
    278         float endTheta = atan2(
    279                     - (vertices[lastIndex].x - vertices[lastIndex - 1].x),
    280                     vertices[lastIndex].y - vertices[lastIndex - 1].y);
    281         const float dTheta = PI / (extra + 1);
    282         const float radialScale = 2.0f / (1 + cos(dTheta));
    283 
    284         int capOffset;
    285         for (int i = 0; i < extra; i++) {
    286             if (i < extra / 2) {
    287                 capOffset = extra - 2 * i - 1;
    288             } else {
    289                 capOffset = 2 * i - extra;
    290             }
    291 
    292             beginTheta += dTheta;
    293             Vector2 beginRadialOffset = {cos(beginTheta), sin(beginTheta)};
    294             paintInfo.scaleOffsetForStrokeWidth(beginRadialOffset);
    295             Vertex::set(&buffer[capOffset],
    296                     vertices[0].x + beginRadialOffset.x,
    297                     vertices[0].y + beginRadialOffset.y);
    298 
    299             endTheta += dTheta;
    300             Vector2 endRadialOffset = {cos(endTheta), sin(endTheta)};
    301             paintInfo.scaleOffsetForStrokeWidth(endRadialOffset);
    302             Vertex::set(&buffer[allocSize - 1 - capOffset],
    303                     vertices[lastIndex].x + endRadialOffset.x,
    304                     vertices[lastIndex].y + endRadialOffset.y);
    305         }
    306     }
    307 
    308     int currentIndex = extra;
    309     const Vertex* last = &(vertices[0]);
    310     const Vertex* current = &(vertices[1]);
    311     Vector2 lastNormal = {current->y - last->y, last->x - current->x};
    312     lastNormal.normalize();
    313 
    314     storeBeginEnd(paintInfo, vertices[0], lastNormal, buffer, currentIndex, true);
    315 
    316     for (unsigned int i = 1; i < vertices.size() - 1; i++) {
    317         const Vertex* next = &(vertices[i + 1]);
    318         Vector2 nextNormal = {next->y - current->y, current->x - next->x};
    319         nextNormal.normalize();
    320 
    321         Vector2 strokeOffset  = totalOffsetFromNormals(lastNormal, nextNormal);
    322         paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
    323 
    324         Vector2 center = {current->x, current->y};
    325         Vertex::set(&buffer[currentIndex++], center + strokeOffset);
    326         Vertex::set(&buffer[currentIndex++], center - strokeOffset);
    327 
    328         current = next;
    329         lastNormal = nextNormal;
    330     }
    331 
    332     storeBeginEnd(paintInfo, vertices[lastIndex], lastNormal, buffer, currentIndex, false);
    333 
    334     DEBUG_DUMP_BUFFER();
    335 }
    336 
    337 /**
    338  * Populates a vertexBuffer with AlphaVertices to create an anti-aliased fill shape tessellation
    339  *
    340  * 1 - create the AA perimeter of unit width, by zig-zagging at each point around the perimeter of
    341  * the shape (using 2 * perimeter.size() vertices)
    342  *
    343  * 2 - wrap around to the beginning to complete the perimeter (2 vertices)
    344  *
    345  * 3 - zig zag back and forth inside the shape to fill it (using perimeter.size() vertices)
    346  */
    347 void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter,
    348         VertexBuffer& vertexBuffer, float maxAlpha = 1.0f) {
    349     AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
    350 
    351     // generate alpha points - fill Alpha vertex gaps in between each point with
    352     // alpha 0 vertex, offset by a scaled normal.
    353     int currentIndex = 0;
    354     const Vertex* last = &(perimeter[perimeter.size() - 1]);
    355     const Vertex* current = &(perimeter[0]);
    356     Vector2 lastNormal = {current->y - last->y, last->x - current->x};
    357     lastNormal.normalize();
    358     for (unsigned int i = 0; i < perimeter.size(); i++) {
    359         const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
    360         Vector2 nextNormal = {next->y - current->y, current->x - next->x};
    361         nextNormal.normalize();
    362 
    363         // AA point offset from original point is that point's normal, such that each side is offset
    364         // by .5 pixels
    365         Vector2 totalOffset = paintInfo.deriveAAOffset(totalOffsetFromNormals(lastNormal, nextNormal));
    366 
    367         AlphaVertex::set(&buffer[currentIndex++],
    368                 current->x + totalOffset.x,
    369                 current->y + totalOffset.y,
    370                 0.0f);
    371         AlphaVertex::set(&buffer[currentIndex++],
    372                 current->x - totalOffset.x,
    373                 current->y - totalOffset.y,
    374                 maxAlpha);
    375 
    376         last = current;
    377         current = next;
    378         lastNormal = nextNormal;
    379     }
    380 
    381     // wrap around to beginning
    382     buffer[currentIndex++] = buffer[0];
    383     buffer[currentIndex++] = buffer[1];
    384 
    385     // zig zag between all previous points on the inside of the hull to create a
    386     // triangle strip that fills the hull, repeating the first inner point to
    387     // create degenerate tris to start inside path
    388     int srcAindex = 0;
    389     int srcBindex = perimeter.size() - 1;
    390     while (srcAindex <= srcBindex) {
    391         buffer[currentIndex++] = buffer[srcAindex * 2 + 1];
    392         if (srcAindex == srcBindex) break;
    393         buffer[currentIndex++] = buffer[srcBindex * 2 + 1];
    394         srcAindex++;
    395         srcBindex--;
    396     }
    397 
    398     DEBUG_DUMP_BUFFER();
    399 }
    400 
    401 /**
    402  * Stores geometry for a single, AA-perimeter (potentially rounded) cap
    403  *
    404  * For explanation of constants and general methodoloyg, see comments for
    405  * getStrokeVerticesFromUnclosedVerticesAA() below.
    406  */
    407 inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& vertices,
    408         AlphaVertex* buffer, bool isFirst, Vector2 normal, int offset) {
    409     const int extra = paintInfo.capExtraDivisions();
    410     const int extraOffset = (extra + 1) / 2;
    411     const int capIndex = isFirst
    412             ? 2 * offset + 6 + 2 * (extra + extraOffset)
    413             : offset + 2 + 2 * extraOffset;
    414     if (isFirst) normal *= -1;
    415 
    416     // TODO: this normal should be scaled by radialScale if extra != 0, see totalOffsetFromNormals()
    417     Vector2 AAOffset = paintInfo.deriveAAOffset(normal);
    418 
    419     Vector2 strokeOffset = normal;
    420     paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
    421     Vector2 outerOffset = strokeOffset + AAOffset;
    422     Vector2 innerOffset = strokeOffset - AAOffset;
    423 
    424     Vector2 capAAOffset = {0, 0};
    425     if (paintInfo.cap != SkPaint::kRound_Cap) {
    426         // if the cap is square or butt, the inside primary cap vertices will be inset in two
    427         // directions - both normal to the stroke, and parallel to it.
    428         capAAOffset = (Vector2){-AAOffset.y, AAOffset.x};
    429     }
    430 
    431     // determine referencePoint, the center point for the 4 primary cap vertices
    432     const Vertex* point = isFirst ? vertices.begin() : (vertices.end() - 1);
    433     Vector2 referencePoint = {point->x, point->y};
    434     if (paintInfo.cap == SkPaint::kSquare_Cap) {
    435         // To account for square cap, move the primary cap vertices (that create the AA edge) by the
    436         // stroke offset vector (rotated to be parallel to the stroke)
    437         Vector2 rotated = {-strokeOffset.y, strokeOffset.x};
    438         referencePoint += rotated;
    439     }
    440 
    441     AlphaVertex::set(&buffer[capIndex + 0],
    442             referencePoint.x + outerOffset.x + capAAOffset.x,
    443             referencePoint.y + outerOffset.y + capAAOffset.y,
    444             0.0f);
    445     AlphaVertex::set(&buffer[capIndex + 1],
    446             referencePoint.x + innerOffset.x - capAAOffset.x,
    447             referencePoint.y + innerOffset.y - capAAOffset.y,
    448             paintInfo.maxAlpha);
    449 
    450     bool isRound = paintInfo.cap == SkPaint::kRound_Cap;
    451 
    452     const int postCapIndex = (isRound && isFirst) ? (2 * extraOffset - 2) : capIndex + (2 * extra);
    453     AlphaVertex::set(&buffer[postCapIndex + 2],
    454             referencePoint.x - outerOffset.x + capAAOffset.x,
    455             referencePoint.y - outerOffset.y + capAAOffset.y,
    456             0.0f);
    457     AlphaVertex::set(&buffer[postCapIndex + 3],
    458             referencePoint.x - innerOffset.x - capAAOffset.x,
    459             referencePoint.y - innerOffset.y - capAAOffset.y,
    460             paintInfo.maxAlpha);
    461 
    462     if (isRound) {
    463         const float dTheta = PI / (extra + 1);
    464         const float radialScale = 2.0f / (1 + cos(dTheta));
    465         float theta = atan2(normal.y, normal.x);
    466         int capPerimIndex = capIndex + 2;
    467 
    468         for (int i = 0; i < extra; i++) {
    469             theta += dTheta;
    470 
    471             Vector2 radialOffset = {cos(theta), sin(theta)};
    472 
    473             // scale to compensate for pinching at sharp angles, see totalOffsetFromNormals()
    474             radialOffset *= radialScale;
    475 
    476             AAOffset = paintInfo.deriveAAOffset(radialOffset);
    477             paintInfo.scaleOffsetForStrokeWidth(radialOffset);
    478             AlphaVertex::set(&buffer[capPerimIndex++],
    479                     referencePoint.x + radialOffset.x + AAOffset.x,
    480                     referencePoint.y + radialOffset.y + AAOffset.y,
    481                     0.0f);
    482             AlphaVertex::set(&buffer[capPerimIndex++],
    483                     referencePoint.x + radialOffset.x - AAOffset.x,
    484                     referencePoint.y + radialOffset.y - AAOffset.y,
    485                     paintInfo.maxAlpha);
    486 
    487             if (isFirst && i == extra - extraOffset) {
    488                 //copy most recent two points to first two points
    489                 buffer[0] = buffer[capPerimIndex - 2];
    490                 buffer[1] = buffer[capPerimIndex - 1];
    491 
    492                 capPerimIndex = 2; // start writing the rest of the round cap at index 2
    493             }
    494         }
    495 
    496         if (isFirst) {
    497             const int startCapFillIndex = capIndex + 2 * (extra - extraOffset) + 4;
    498             int capFillIndex = startCapFillIndex;
    499             for (int i = 0; i < extra + 2; i += 2) {
    500                 buffer[capFillIndex++] = buffer[1 + i];
    501                 // TODO: to support odd numbers of divisions, break here on the last iteration
    502                 buffer[capFillIndex++] = buffer[startCapFillIndex - 3 - i];
    503             }
    504         } else {
    505             int capFillIndex = 6 * vertices.size() + 2 + 6 * extra - (extra + 2);
    506             for (int i = 0; i < extra + 2; i += 2) {
    507                 buffer[capFillIndex++] = buffer[capIndex + 1 + i];
    508                 // TODO: to support odd numbers of divisions, break here on the last iteration
    509                 buffer[capFillIndex++] = buffer[capIndex + 3 + 2 * extra - i];
    510             }
    511         }
    512         return;
    513     }
    514     if (isFirst) {
    515         buffer[0] = buffer[postCapIndex + 2];
    516         buffer[1] = buffer[postCapIndex + 3];
    517         buffer[postCapIndex + 4] = buffer[1]; // degenerate tris (the only two!)
    518         buffer[postCapIndex + 5] = buffer[postCapIndex + 1];
    519     } else {
    520         buffer[6 * vertices.size()] = buffer[postCapIndex + 1];
    521         buffer[6 * vertices.size() + 1] = buffer[postCapIndex + 3];
    522     }
    523 }
    524 
    525 /*
    526 the geometry for an aa, capped stroke consists of the following:
    527 
    528        # vertices       |    function
    529 ----------------------------------------------------------------------
    530 a) 2                    | Start AA perimeter
    531 b) 2, 2 * roundDivOff   | First half of begin cap's perimeter
    532                         |
    533    2 * middlePts        | 'Outer' or 'Top' AA perimeter half (between caps)
    534                         |
    535 a) 4                    | End cap's
    536 b) 2, 2 * roundDivs, 2  |    AA perimeter
    537                         |
    538    2 * middlePts        | 'Inner' or 'bottom' AA perimeter half
    539                         |
    540 a) 6                    | Begin cap's perimeter
    541 b) 2, 2*(rD - rDO + 1), | Last half of begin cap's perimeter
    542        roundDivs, 2     |
    543                         |
    544    2 * middlePts        | Stroke's full opacity center strip
    545                         |
    546 a) 2                    | end stroke
    547 b) 2, roundDivs         |    (and end cap fill, for round)
    548 
    549 Notes:
    550 * rows starting with 'a)' denote the Butt or Square cap vertex use, 'b)' denote Round
    551 
    552 * 'middlePts' is (number of points in the unclosed input vertex list, minus 2) times two
    553 
    554 * 'roundDivs' or 'rD' is the number of extra vertices (beyond the minimum of 2) that define the
    555         round cap's shape, and is at least two. This will increase with cap size to sufficiently
    556         define the cap's level of tessellation.
    557 
    558 * 'roundDivOffset' or 'rDO' is the point about halfway along the start cap's round perimeter, where
    559         the stream of vertices for the AA perimeter starts. By starting and ending the perimeter at
    560         this offset, the fill of the stroke is drawn from this point with minimal extra vertices.
    561 
    562 This means the outer perimeter starts at:
    563     outerIndex = (2) OR (2 + 2 * roundDivOff)
    564 the inner perimeter (since it is filled in reverse) starts at:
    565     innerIndex = outerIndex + (4 * middlePts) + ((4) OR (4 + 2 * roundDivs)) - 1
    566 the stroke starts at:
    567     strokeIndex = innerIndex + 1 + ((6) OR (6 + 3 * roundDivs - 2 * roundDivOffset))
    568 
    569 The total needed allocated space is either:
    570     2 + 4 + 6 + 2 + 3 * (2 * middlePts) = 14 + 6 * middlePts = 2 + 6 * pts
    571 or, for rounded caps:
    572     (2 + 2 * rDO) + (4 + 2 * rD) + (2 * (rD - rDO + 1)
    573             + roundDivs + 4) + (2 + roundDivs) + 3 * (2 * middlePts)
    574     = 14 + 6 * middlePts + 6 * roundDivs
    575     = 2 + 6 * pts + 6 * roundDivs
    576  */
    577 void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo,
    578         const Vector<Vertex>& vertices, VertexBuffer& vertexBuffer) {
    579 
    580     const int extra = paintInfo.capExtraDivisions();
    581     const int allocSize = 6 * vertices.size() + 2 + 6 * extra;
    582 
    583     AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(allocSize);
    584 
    585     const int extraOffset = (extra + 1) / 2;
    586     int offset = 2 * (vertices.size() - 2);
    587     // there is no outer/inner here, using them for consistency with below approach
    588     int currentAAOuterIndex = 2 + 2 * extraOffset;
    589     int currentAAInnerIndex = currentAAOuterIndex + (2 * offset) + 3 + (2 * extra);
    590     int currentStrokeIndex = currentAAInnerIndex + 7 + (3 * extra - 2 * extraOffset);
    591 
    592     const Vertex* last = &(vertices[0]);
    593     const Vertex* current = &(vertices[1]);
    594     Vector2 lastNormal = {current->y - last->y, last->x - current->x};
    595     lastNormal.normalize();
    596 
    597     // TODO: use normal from bezier traversal for cap, instead of from vertices
    598     storeCapAA(paintInfo, vertices, buffer, true, lastNormal, offset);
    599 
    600     for (unsigned int i = 1; i < vertices.size() - 1; i++) {
    601         const Vertex* next = &(vertices[i + 1]);
    602         Vector2 nextNormal = {next->y - current->y, current->x - next->x};
    603         nextNormal.normalize();
    604 
    605         Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
    606         Vector2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
    607 
    608         Vector2 innerOffset = totalOffset;
    609         paintInfo.scaleOffsetForStrokeWidth(innerOffset);
    610         Vector2 outerOffset = innerOffset + AAOffset;
    611         innerOffset -= AAOffset;
    612 
    613         AlphaVertex::set(&buffer[currentAAOuterIndex++],
    614                 current->x + outerOffset.x,
    615                 current->y + outerOffset.y,
    616                 0.0f);
    617         AlphaVertex::set(&buffer[currentAAOuterIndex++],
    618                 current->x + innerOffset.x,
    619                 current->y + innerOffset.y,
    620                 paintInfo.maxAlpha);
    621 
    622         AlphaVertex::set(&buffer[currentStrokeIndex++],
    623                 current->x + innerOffset.x,
    624                 current->y + innerOffset.y,
    625                 paintInfo.maxAlpha);
    626         AlphaVertex::set(&buffer[currentStrokeIndex++],
    627                 current->x - innerOffset.x,
    628                 current->y - innerOffset.y,
    629                 paintInfo.maxAlpha);
    630 
    631         AlphaVertex::set(&buffer[currentAAInnerIndex--],
    632                 current->x - innerOffset.x,
    633                 current->y - innerOffset.y,
    634                 paintInfo.maxAlpha);
    635         AlphaVertex::set(&buffer[currentAAInnerIndex--],
    636                 current->x - outerOffset.x,
    637                 current->y - outerOffset.y,
    638                 0.0f);
    639 
    640         current = next;
    641         lastNormal = nextNormal;
    642     }
    643 
    644     // TODO: use normal from bezier traversal for cap, instead of from vertices
    645     storeCapAA(paintInfo, vertices, buffer, false, lastNormal, offset);
    646 
    647     DEBUG_DUMP_ALPHA_BUFFER();
    648 }
    649 
    650 
    651 void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter,
    652         VertexBuffer& vertexBuffer) {
    653     AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
    654 
    655     int offset = 2 * perimeter.size() + 3;
    656     int currentAAOuterIndex = 0;
    657     int currentStrokeIndex = offset;
    658     int currentAAInnerIndex = offset * 2;
    659 
    660     const Vertex* last = &(perimeter[perimeter.size() - 1]);
    661     const Vertex* current = &(perimeter[0]);
    662     Vector2 lastNormal = {current->y - last->y, last->x - current->x};
    663     lastNormal.normalize();
    664     for (unsigned int i = 0; i < perimeter.size(); i++) {
    665         const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
    666         Vector2 nextNormal = {next->y - current->y, current->x - next->x};
    667         nextNormal.normalize();
    668 
    669         Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
    670         Vector2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
    671 
    672         Vector2 innerOffset = totalOffset;
    673         paintInfo.scaleOffsetForStrokeWidth(innerOffset);
    674         Vector2 outerOffset = innerOffset + AAOffset;
    675         innerOffset -= AAOffset;
    676 
    677         AlphaVertex::set(&buffer[currentAAOuterIndex++],
    678                 current->x + outerOffset.x,
    679                 current->y + outerOffset.y,
    680                 0.0f);
    681         AlphaVertex::set(&buffer[currentAAOuterIndex++],
    682                 current->x + innerOffset.x,
    683                 current->y + innerOffset.y,
    684                 paintInfo.maxAlpha);
    685 
    686         AlphaVertex::set(&buffer[currentStrokeIndex++],
    687                 current->x + innerOffset.x,
    688                 current->y + innerOffset.y,
    689                 paintInfo.maxAlpha);
    690         AlphaVertex::set(&buffer[currentStrokeIndex++],
    691                 current->x - innerOffset.x,
    692                 current->y - innerOffset.y,
    693                 paintInfo.maxAlpha);
    694 
    695         AlphaVertex::set(&buffer[currentAAInnerIndex++],
    696                 current->x - innerOffset.x,
    697                 current->y - innerOffset.y,
    698                 paintInfo.maxAlpha);
    699         AlphaVertex::set(&buffer[currentAAInnerIndex++],
    700                 current->x - outerOffset.x,
    701                 current->y - outerOffset.y,
    702                 0.0f);
    703 
    704         last = current;
    705         current = next;
    706         lastNormal = nextNormal;
    707     }
    708 
    709     // wrap each strip around to beginning, creating degenerate tris to bridge strips
    710     buffer[currentAAOuterIndex++] = buffer[0];
    711     buffer[currentAAOuterIndex++] = buffer[1];
    712     buffer[currentAAOuterIndex++] = buffer[1];
    713 
    714     buffer[currentStrokeIndex++] = buffer[offset];
    715     buffer[currentStrokeIndex++] = buffer[offset + 1];
    716     buffer[currentStrokeIndex++] = buffer[offset + 1];
    717 
    718     buffer[currentAAInnerIndex++] = buffer[2 * offset];
    719     buffer[currentAAInnerIndex++] = buffer[2 * offset + 1];
    720     // don't need to create last degenerate tri
    721 
    722     DEBUG_DUMP_ALPHA_BUFFER();
    723 }
    724 
    725 void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint,
    726         const mat4& transform, VertexBuffer& vertexBuffer) {
    727     ATRACE_CALL();
    728 
    729     const PaintInfo paintInfo(paint, transform);
    730 
    731     Vector<Vertex> tempVertices;
    732     float threshInvScaleX = paintInfo.inverseScaleX;
    733     float threshInvScaleY = paintInfo.inverseScaleY;
    734     if (paintInfo.style == SkPaint::kStroke_Style) {
    735         // alter the bezier recursion threshold values we calculate in order to compensate for
    736         // expansion done after the path vertices are found
    737         SkRect bounds = path.getBounds();
    738         if (!bounds.isEmpty()) {
    739             threshInvScaleX *= bounds.width() / (bounds.width() + paint->getStrokeWidth());
    740             threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth());
    741         }
    742     }
    743 
    744     // force close if we're filling the path, since fill path expects closed perimeter.
    745     bool forceClose = paintInfo.style != SkPaint::kStroke_Style;
    746     bool wasClosed = approximatePathOutlineVertices(path, forceClose,
    747             threshInvScaleX * threshInvScaleX, threshInvScaleY * threshInvScaleY,
    748             OUTLINE_REFINE_THRESHOLD_SQUARED, tempVertices);
    749 
    750     if (!tempVertices.size()) {
    751         // path was empty, return without allocating vertex buffer
    752         return;
    753     }
    754 
    755 #if VERTEX_DEBUG
    756     for (unsigned int i = 0; i < tempVertices.size(); i++) {
    757         ALOGD("orig path: point at %f %f",
    758                 tempVertices[i].x, tempVertices[i].y);
    759     }
    760 #endif
    761 
    762     if (paintInfo.style == SkPaint::kStroke_Style) {
    763         if (!paintInfo.isAA) {
    764             if (wasClosed) {
    765                 getStrokeVerticesFromPerimeter(paintInfo, tempVertices, vertexBuffer);
    766             } else {
    767                 getStrokeVerticesFromUnclosedVertices(paintInfo, tempVertices, vertexBuffer);
    768             }
    769 
    770         } else {
    771             if (wasClosed) {
    772                 getStrokeVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer);
    773             } else {
    774                 getStrokeVerticesFromUnclosedVerticesAA(paintInfo, tempVertices, vertexBuffer);
    775             }
    776         }
    777     } else {
    778         // For kStrokeAndFill style, the path should be adjusted externally.
    779         // It will be treated as a fill here.
    780         if (!paintInfo.isAA) {
    781             getFillVerticesFromPerimeter(tempVertices, vertexBuffer);
    782         } else {
    783             getFillVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer);
    784         }
    785     }
    786 
    787     Rect bounds(path.getBounds());
    788     paintInfo.expandBoundsForStroke(&bounds);
    789     vertexBuffer.setBounds(bounds);
    790 }
    791 
    792 template <class TYPE>
    793 static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer,
    794         const float* points, int count, Rect& bounds) {
    795     bounds.set(points[0], points[1], points[0], points[1]);
    796 
    797     int numPoints = count / 2;
    798     int verticesPerPoint = srcBuffer.getVertexCount();
    799     dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2);
    800 
    801     for (int i = 0; i < count; i += 2) {
    802         bounds.expandToCoverVertex(points[i + 0], points[i + 1]);
    803         dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]);
    804     }
    805     dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint);
    806 }
    807 
    808 void PathTessellator::tessellatePoints(const float* points, int count, const SkPaint* paint,
    809         const mat4& transform, VertexBuffer& vertexBuffer) {
    810     const PaintInfo paintInfo(paint, transform);
    811 
    812     // determine point shape
    813     SkPath path;
    814     float radius = paintInfo.halfStrokeWidth;
    815     if (radius == 0.0f) radius = 0.5f;
    816 
    817     if (paintInfo.cap == SkPaint::kRound_Cap) {
    818         path.addCircle(0, 0, radius);
    819     } else {
    820         path.addRect(-radius, -radius, radius, radius);
    821     }
    822 
    823     // calculate outline
    824     Vector<Vertex> outlineVertices;
    825     approximatePathOutlineVertices(path, true,
    826             paintInfo.inverseScaleX * paintInfo.inverseScaleX,
    827             paintInfo.inverseScaleY * paintInfo.inverseScaleY,
    828             OUTLINE_REFINE_THRESHOLD_SQUARED, outlineVertices);
    829 
    830     if (!outlineVertices.size()) return;
    831 
    832     Rect bounds;
    833     // tessellate, then duplicate outline across points
    834     int numPoints = count / 2;
    835     VertexBuffer tempBuffer;
    836     if (!paintInfo.isAA) {
    837         getFillVerticesFromPerimeter(outlineVertices, tempBuffer);
    838         instanceVertices<Vertex>(tempBuffer, vertexBuffer, points, count, bounds);
    839     } else {
    840         // note: pass maxAlpha directly, since we want fill to be alpha modulated
    841         getFillVerticesFromPerimeterAA(paintInfo, outlineVertices, tempBuffer, paintInfo.maxAlpha);
    842         instanceVertices<AlphaVertex>(tempBuffer, vertexBuffer, points, count, bounds);
    843     }
    844 
    845     // expand bounds from vertex coords to pixel data
    846     paintInfo.expandBoundsForStroke(&bounds);
    847     vertexBuffer.setBounds(bounds);
    848 }
    849 
    850 void PathTessellator::tessellateLines(const float* points, int count, const SkPaint* paint,
    851         const mat4& transform, VertexBuffer& vertexBuffer) {
    852     ATRACE_CALL();
    853     const PaintInfo paintInfo(paint, transform);
    854 
    855     const int extra = paintInfo.capExtraDivisions();
    856     int numLines = count / 4;
    857     int lineAllocSize;
    858     // pre-allocate space for lines in the buffer, and degenerate tris in between
    859     if (paintInfo.isAA) {
    860         lineAllocSize = 6 * (2) + 2 + 6 * extra;
    861         vertexBuffer.alloc<AlphaVertex>(numLines * lineAllocSize + (numLines - 1) * 2);
    862     } else {
    863         lineAllocSize = 2 * ((2) + extra);
    864         vertexBuffer.alloc<Vertex>(numLines * lineAllocSize + (numLines - 1) * 2);
    865     }
    866 
    867     Vector<Vertex> tempVertices;
    868     tempVertices.push();
    869     tempVertices.push();
    870     Vertex* tempVerticesData = tempVertices.editArray();
    871     Rect bounds;
    872     bounds.set(points[0], points[1], points[0], points[1]);
    873     for (int i = 0; i < count; i += 4) {
    874         Vertex::set(&(tempVerticesData[0]), points[i + 0], points[i + 1]);
    875         Vertex::set(&(tempVerticesData[1]), points[i + 2], points[i + 3]);
    876 
    877         if (paintInfo.isAA) {
    878             getStrokeVerticesFromUnclosedVerticesAA(paintInfo, tempVertices, vertexBuffer);
    879         } else {
    880             getStrokeVerticesFromUnclosedVertices(paintInfo, tempVertices, vertexBuffer);
    881         }
    882 
    883         // calculate bounds
    884         bounds.expandToCoverVertex(tempVerticesData[0].x, tempVerticesData[0].y);
    885         bounds.expandToCoverVertex(tempVerticesData[1].x, tempVerticesData[1].y);
    886     }
    887 
    888     // since multiple objects tessellated into buffer, separate them with degen tris
    889     if (paintInfo.isAA) {
    890         vertexBuffer.createDegenerateSeparators<AlphaVertex>(lineAllocSize);
    891     } else {
    892         vertexBuffer.createDegenerateSeparators<Vertex>(lineAllocSize);
    893     }
    894 
    895     // expand bounds from vertex coords to pixel data
    896     paintInfo.expandBoundsForStroke(&bounds);
    897     vertexBuffer.setBounds(bounds);
    898 }
    899 
    900 ///////////////////////////////////////////////////////////////////////////////
    901 // Simple path line approximation
    902 ///////////////////////////////////////////////////////////////////////////////
    903 
    904 bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, float thresholdSquared,
    905         Vector<Vertex>& outputVertices) {
    906     return approximatePathOutlineVertices(path, true, 1.0f, 1.0f, thresholdSquared, outputVertices);
    907 }
    908 
    909 void pushToVector(Vector<Vertex>& vertices, float x, float y) {
    910     // TODO: make this not yuck
    911     vertices.push();
    912     Vertex* newVertex = &(vertices.editArray()[vertices.size() - 1]);
    913     Vertex::set(newVertex, x, y);
    914 }
    915 
    916 bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose,
    917         float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
    918         Vector<Vertex>& outputVertices) {
    919     ATRACE_CALL();
    920 
    921     // TODO: to support joins other than sharp miter, join vertices should be labelled in the
    922     // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case.
    923     SkPath::Iter iter(path, forceClose);
    924     SkPoint pts[4];
    925     SkPath::Verb v;
    926     while (SkPath::kDone_Verb != (v = iter.next(pts))) {
    927             switch (v) {
    928             case SkPath::kMove_Verb:
    929                 pushToVector(outputVertices, pts[0].x(), pts[0].y());
    930                 ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
    931                 break;
    932             case SkPath::kClose_Verb:
    933                 ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y());
    934                 break;
    935             case SkPath::kLine_Verb:
    936                 ALOGV("kLine_Verb %f %f -> %f %f", pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y());
    937                 pushToVector(outputVertices, pts[1].x(), pts[1].y());
    938                 break;
    939             case SkPath::kQuad_Verb:
    940                 ALOGV("kQuad_Verb");
    941                 recursiveQuadraticBezierVertices(
    942                         pts[0].x(), pts[0].y(),
    943                         pts[2].x(), pts[2].y(),
    944                         pts[1].x(), pts[1].y(),
    945                         sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices);
    946                 break;
    947             case SkPath::kCubic_Verb:
    948                 ALOGV("kCubic_Verb");
    949                 recursiveCubicBezierVertices(
    950                         pts[0].x(), pts[0].y(),
    951                         pts[1].x(), pts[1].y(),
    952                         pts[3].x(), pts[3].y(),
    953                         pts[2].x(), pts[2].y(),
    954                         sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices);
    955                 break;
    956             default:
    957                 break;
    958             }
    959     }
    960 
    961     int size = outputVertices.size();
    962     if (size >= 2 && outputVertices[0].x == outputVertices[size - 1].x &&
    963             outputVertices[0].y == outputVertices[size - 1].y) {
    964         outputVertices.pop();
    965         return true;
    966     }
    967     return false;
    968 }
    969 
    970 ///////////////////////////////////////////////////////////////////////////////
    971 // Bezier approximation
    972 ///////////////////////////////////////////////////////////////////////////////
    973 
    974 void PathTessellator::recursiveCubicBezierVertices(
    975         float p1x, float p1y, float c1x, float c1y,
    976         float p2x, float p2y, float c2x, float c2y,
    977         float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
    978         Vector<Vertex>& outputVertices, int depth) {
    979     float dx = p2x - p1x;
    980     float dy = p2y - p1y;
    981     float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
    982     float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx);
    983     float d = d1 + d2;
    984 
    985     // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
    986     if (depth >= MAX_DEPTH
    987             || d * d <= thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
    988         // below thresh, draw line by adding endpoint
    989         pushToVector(outputVertices, p2x, p2y);
    990     } else {
    991         float p1c1x = (p1x + c1x) * 0.5f;
    992         float p1c1y = (p1y + c1y) * 0.5f;
    993         float p2c2x = (p2x + c2x) * 0.5f;
    994         float p2c2y = (p2y + c2y) * 0.5f;
    995 
    996         float c1c2x = (c1x + c2x) * 0.5f;
    997         float c1c2y = (c1y + c2y) * 0.5f;
    998 
    999         float p1c1c2x = (p1c1x + c1c2x) * 0.5f;
   1000         float p1c1c2y = (p1c1y + c1c2y) * 0.5f;
   1001 
   1002         float p2c1c2x = (p2c2x + c1c2x) * 0.5f;
   1003         float p2c1c2y = (p2c2y + c1c2y) * 0.5f;
   1004 
   1005         float mx = (p1c1c2x + p2c1c2x) * 0.5f;
   1006         float my = (p1c1c2y + p2c1c2y) * 0.5f;
   1007 
   1008         recursiveCubicBezierVertices(
   1009                 p1x, p1y, p1c1x, p1c1y,
   1010                 mx, my, p1c1c2x, p1c1c2y,
   1011                 sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1);
   1012         recursiveCubicBezierVertices(
   1013                 mx, my, p2c1c2x, p2c1c2y,
   1014                 p2x, p2y, p2c2x, p2c2y,
   1015                 sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1);
   1016     }
   1017 }
   1018 
   1019 void PathTessellator::recursiveQuadraticBezierVertices(
   1020         float ax, float ay,
   1021         float bx, float by,
   1022         float cx, float cy,
   1023         float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
   1024         Vector<Vertex>& outputVertices, int depth) {
   1025     float dx = bx - ax;
   1026     float dy = by - ay;
   1027     float d = (cx - bx) * dy - (cy - by) * dx;
   1028 
   1029     // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
   1030     if (depth >= MAX_DEPTH
   1031             || d * d <= thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
   1032         // below thresh, draw line by adding endpoint
   1033         pushToVector(outputVertices, bx, by);
   1034     } else {
   1035         float acx = (ax + cx) * 0.5f;
   1036         float bcx = (bx + cx) * 0.5f;
   1037         float acy = (ay + cy) * 0.5f;
   1038         float bcy = (by + cy) * 0.5f;
   1039 
   1040         // midpoint
   1041         float mx = (acx + bcx) * 0.5f;
   1042         float my = (acy + bcy) * 0.5f;
   1043 
   1044         recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy,
   1045                 sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1);
   1046         recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy,
   1047                 sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1);
   1048     }
   1049 }
   1050 
   1051 }; // namespace uirenderer
   1052 }; // namespace android
   1053