• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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 "include/core/SkString.h"
9 #include "include/private/SkNx.h"
10 #include "src/core/SkArenaAlloc.h"
11 #include "src/core/SkAutoBlitterChoose.h"
12 #include "src/core/SkConvertPixels.h"
13 #include "src/core/SkCoreBlitters.h"
14 #include "src/core/SkDraw.h"
15 #include "src/core/SkMatrixProvider.h"
16 #include "src/core/SkRasterClip.h"
17 #include "src/core/SkRasterPipeline.h"
18 #include "src/core/SkScan.h"
19 #include "src/core/SkVM.h"
20 #include "src/core/SkVMBlitter.h"
21 #include "src/core/SkVertState.h"
22 #include "src/core/SkVerticesPriv.h"
23 #include "src/shaders/SkComposeShader.h"
24 #include "src/shaders/SkShaderBase.h"
25 
26 struct Matrix43 {
27     float fMat[12];    // column major
28 
mapMatrix4329     Sk4f map(float x, float y) const {
30         return Sk4f::Load(&fMat[0]) * x + Sk4f::Load(&fMat[4]) * y + Sk4f::Load(&fMat[8]);
31     }
32 
33     // Pass a by value, so we don't have to worry about aliasing with this
setConcatMatrix4334     void setConcat(const Matrix43 a, const SkMatrix& b) {
35         SkASSERT(!b.hasPerspective());
36 
37         fMat[ 0] = a.dot(0, b.getScaleX(), b.getSkewY());
38         fMat[ 1] = a.dot(1, b.getScaleX(), b.getSkewY());
39         fMat[ 2] = a.dot(2, b.getScaleX(), b.getSkewY());
40         fMat[ 3] = a.dot(3, b.getScaleX(), b.getSkewY());
41 
42         fMat[ 4] = a.dot(0, b.getSkewX(), b.getScaleY());
43         fMat[ 5] = a.dot(1, b.getSkewX(), b.getScaleY());
44         fMat[ 6] = a.dot(2, b.getSkewX(), b.getScaleY());
45         fMat[ 7] = a.dot(3, b.getSkewX(), b.getScaleY());
46 
47         fMat[ 8] = a.dot(0, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 8];
48         fMat[ 9] = a.dot(1, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 9];
49         fMat[10] = a.dot(2, b.getTranslateX(), b.getTranslateY()) + a.fMat[10];
50         fMat[11] = a.dot(3, b.getTranslateX(), b.getTranslateY()) + a.fMat[11];
51     }
52 
53 private:
dotMatrix4354     float dot(int index, float x, float y) const {
55         return fMat[index + 0] * x + fMat[index + 4] * y;
56     }
57 };
58 
59 static bool SK_WARN_UNUSED_RESULT
texture_to_matrix(const VertState & state,const SkPoint verts[],const SkPoint texs[],SkMatrix * matrix)60 texture_to_matrix(const VertState& state, const SkPoint verts[], const SkPoint texs[],
61                   SkMatrix* matrix) {
62     SkPoint src[3], dst[3];
63 
64     src[0] = texs[state.f0];
65     src[1] = texs[state.f1];
66     src[2] = texs[state.f2];
67     dst[0] = verts[state.f0];
68     dst[1] = verts[state.f1];
69     dst[2] = verts[state.f2];
70     return matrix->setPolyToPoly(src, dst, 3);
71 }
72 
73 class SkTriColorShader : public SkShaderBase {
74 public:
SkTriColorShader(bool isOpaque,bool usePersp)75     SkTriColorShader(bool isOpaque, bool usePersp) : fIsOpaque(isOpaque), fUsePersp(usePersp) {}
76 
77     // This gets called for each triangle, without re-calling onAppendStages.
78     bool update(const SkMatrix& ctmInv, const SkPoint pts[], const SkPMColor4f colors[],
79                 int index0, int index1, int index2);
80 
81 protected:
82 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
onMakeContext(const ContextRec & rec,SkArenaAlloc * alloc) const83     Context* onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const override {
84         return nullptr;
85     }
86 #endif
onAppendStages(const SkStageRec & rec) const87     bool onAppendStages(const SkStageRec& rec) const override {
88         rec.fPipeline->append(SkRasterPipeline::seed_shader);
89         if (fUsePersp) {
90             rec.fPipeline->append(SkRasterPipeline::matrix_perspective, &fM33);
91         }
92         rec.fPipeline->append(SkRasterPipeline::matrix_4x3, &fM43);
93         return true;
94     }
95 
96     skvm::Color onProgram(skvm::Builder*,
97                           skvm::Coord, skvm::Coord, skvm::Color,
98                           const SkMatrixProvider&, const SkMatrix*, const SkColorInfo&,
99                           skvm::Uniforms*, SkArenaAlloc*) const override;
100 
101 private:
isOpaque() const102     bool isOpaque() const override { return fIsOpaque; }
103     // For serialization.  This will never be called.
getFactory() const104     Factory getFactory() const override { return nullptr; }
getTypeName() const105     const char* getTypeName() const override { return nullptr; }
106 
107     // If fUsePersp, we need both of these matrices,
108     // otherwise we can combine them, and only use fM43
109 
110     Matrix43 fM43;
111     SkMatrix fM33;
112     const bool fIsOpaque;
113     const bool fUsePersp;   // controls our stages, and what we do in update()
114     mutable skvm::Uniform fColorMatrix;
115     mutable skvm::Uniform fCoordMatrix;
116 
117     using INHERITED = SkShaderBase;
118 };
119 
onProgram(skvm::Builder * b,skvm::Coord device,skvm::Coord local,skvm::Color,const SkMatrixProvider & matrices,const SkMatrix * localM,const SkColorInfo &,skvm::Uniforms * uniforms,SkArenaAlloc * alloc) const120 skvm::Color SkTriColorShader::onProgram(skvm::Builder* b,
121                                         skvm::Coord device, skvm::Coord local, skvm::Color,
122                                         const SkMatrixProvider& matrices, const SkMatrix* localM,
123                                         const SkColorInfo&, skvm::Uniforms* uniforms,
124                                         SkArenaAlloc* alloc) const {
125 
126     fColorMatrix = uniforms->pushPtr(&fM43);
127 
128     skvm::F32 x = local.x,
129               y = local.y;
130 
131     if (fUsePersp) {
132         fCoordMatrix = uniforms->pushPtr(&fM33);
133         auto dot = [&, x, y](int row) {
134             return b->mad(x, b->arrayF(fCoordMatrix, row),
135                              b->mad(y, b->arrayF(fCoordMatrix, row + 3),
136                                        b->arrayF(fCoordMatrix, row + 6)));
137         };
138 
139         x = dot(0);
140         y = dot(1);
141         x = x * (1.0f / dot(2));
142         y = y * (1.0f / dot(2));
143     }
144 
145     auto colorDot = [&, x, y](int row) {
146         return b->mad(x, b->arrayF(fColorMatrix, row),
147                          b->mad(y, b->arrayF(fColorMatrix, row + 4),
148                                    b->arrayF(fColorMatrix, row + 8)));
149     };
150 
151     skvm::Color color;
152     color.r = colorDot(0);
153     color.g = colorDot(1);
154     color.b = colorDot(2);
155     color.a = colorDot(3);
156     return color;
157 }
158 
update(const SkMatrix & ctmInv,const SkPoint pts[],const SkPMColor4f colors[],int index0,int index1,int index2)159 bool SkTriColorShader::update(const SkMatrix& ctmInv, const SkPoint pts[],
160                               const SkPMColor4f colors[], int index0, int index1, int index2) {
161     SkMatrix m, im;
162     m.reset();
163     m.set(0, pts[index1].fX - pts[index0].fX);
164     m.set(1, pts[index2].fX - pts[index0].fX);
165     m.set(2, pts[index0].fX);
166     m.set(3, pts[index1].fY - pts[index0].fY);
167     m.set(4, pts[index2].fY - pts[index0].fY);
168     m.set(5, pts[index0].fY);
169     if (!m.invert(&im)) {
170         return false;
171     }
172 
173     fM33.setConcat(im, ctmInv);
174 
175     Sk4f c0 = Sk4f::Load(colors[index0].vec()),
176          c1 = Sk4f::Load(colors[index1].vec()),
177          c2 = Sk4f::Load(colors[index2].vec());
178 
179     (c1 - c0).store(&fM43.fMat[0]);
180     (c2 - c0).store(&fM43.fMat[4]);
181     c0.store(&fM43.fMat[8]);
182 
183     if (!fUsePersp) {
184         fM43.setConcat(fM43, fM33);
185     }
186     return true;
187 }
188 
189 // Convert the SkColors into float colors. The conversion depends on some conditions:
190 // - If the pixmap has a dst colorspace, we have to be "color-correct".
191 //   Do we map into dst-colorspace before or after we interpolate?
192 // - We have to decide when to apply per-color alpha (before or after we interpolate)
193 //
194 // For now, we will take a simple approach, but recognize this is just a start:
195 // - convert colors into dst colorspace before interpolation (matches gradients)
196 // - apply per-color alpha before interpolation (matches old version of vertices)
197 //
convert_colors(const SkColor src[],int count,SkColorSpace * deviceCS,SkArenaAlloc * alloc)198 static SkPMColor4f* convert_colors(const SkColor src[], int count, SkColorSpace* deviceCS,
199                                    SkArenaAlloc* alloc) {
200     SkPMColor4f* dst = alloc->makeArray<SkPMColor4f>(count);
201     SkImageInfo srcInfo = SkImageInfo::Make(count, 1, kBGRA_8888_SkColorType,
202                                             kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB());
203     SkImageInfo dstInfo = SkImageInfo::Make(count, 1, kRGBA_F32_SkColorType,
204                                             kPremul_SkAlphaType, sk_ref_sp(deviceCS));
205     SkAssertResult(SkConvertPixels(dstInfo, dst, 0, srcInfo, src, 0));
206     return dst;
207 }
208 
compute_is_opaque(const SkColor colors[],int count)209 static bool compute_is_opaque(const SkColor colors[], int count) {
210     uint32_t c = ~0;
211     for (int i = 0; i < count; ++i) {
212         c &= colors[i];
213     }
214     return SkColorGetA(c) == 0xFF;
215 }
216 
fill_triangle_2(const VertState & state,SkBlitter * blitter,const SkRasterClip & rc,const SkPoint dev2[])217 static void fill_triangle_2(const VertState& state, SkBlitter* blitter, const SkRasterClip& rc,
218                             const SkPoint dev2[]) {
219     SkPoint tmp[] = {
220         dev2[state.f0], dev2[state.f1], dev2[state.f2]
221     };
222     SkScan::FillTriangle(tmp, rc, blitter);
223 }
224 
225 static constexpr int kMaxClippedTrianglePointCount = 4;
fill_triangle_3(const VertState & state,SkBlitter * blitter,const SkRasterClip & rc,const SkPoint3 dev3[])226 static void fill_triangle_3(const VertState& state, SkBlitter* blitter, const SkRasterClip& rc,
227                             const SkPoint3 dev3[]) {
228     // Compute the crossing point (across zero) for the two values, expressed as a
229     // normalized 0...1 value. If curr is 0, returns 0. If next is 0, returns 1.
230     auto computeT = [](float curr, float next) {
231         // Check that 0 is between next and curr.
232         SkASSERT((next <= 0 && 0 < curr) || (curr <= 0 && 0 < next));
233         float t = curr / (curr - next);
234         SkASSERT(0 <= t && t <= 1);
235         return t;
236     };
237 
238     auto lerp = [](SkPoint3 curr, SkPoint3 next, float t) {
239         return curr + t * (next - curr);
240     };
241 
242     constexpr float tol = 0.05f;
243     // tol is the nudge away from zero, to keep the numerics nice.
244     // Think of it as our near-clipping-plane (or w-plane).
245     auto clip = [&](SkPoint3 curr, SkPoint3 next) {
246         // Return the point between curr and next where the fZ value crosses tol.
247         // To be (really) perspective correct, we should be computing based on 1/Z, not Z.
248         // For now, this is close enough (and faster).
249         return lerp(curr, next, computeT(curr.fZ - tol, next.fZ - tol));
250     };
251 
252     // Clip a triangle (based on its homogeneous W values), and return the projected polygon.
253     // Since we only clip against one "edge"/plane, the max number of points in the clipped
254     // polygon is 4.
255     auto clipTriangle = [&](SkPoint dst[], const int idx[3], const SkPoint3 pts[]) -> int {
256         SkPoint3 outPoints[kMaxClippedTrianglePointCount];
257         SkPoint3* outP = outPoints;
258 
259         for (int i = 0; i < 3; ++i) {
260             int curr = idx[i];
261             int next = idx[(i + 1) % 3];
262             if (pts[curr].fZ > tol) {
263                 *outP++ = pts[curr];
264                 if (pts[next].fZ <= tol) { // curr is IN, next is OUT
265                     *outP++ = clip(pts[curr], pts[next]);
266                 }
267             } else {
268                 if (pts[next].fZ > tol) { // curr is OUT, next is IN
269                     *outP++ = clip(pts[curr], pts[next]);
270                 }
271             }
272         }
273 
274         const int count = SkTo<int>(outP - outPoints);
275         SkASSERT(count == 0 || count == 3 || count == 4);
276         for (int i = 0; i < count; ++i) {
277             float scale = 1.0f / outPoints[i].fZ;
278             dst[i].set(outPoints[i].fX * scale, outPoints[i].fY * scale);
279         }
280         return count;
281     };
282 
283     SkPoint tmp[kMaxClippedTrianglePointCount];
284     int idx[] = { state.f0, state.f1, state.f2 };
285     if (int n = clipTriangle(tmp, idx, dev3)) {
286         // TODO: SkScan::FillConvexPoly(tmp, n, ...);
287         SkASSERT(n == 3 || n == 4);
288         SkScan::FillTriangle(tmp, rc, blitter);
289         if (n == 4) {
290             tmp[1] = tmp[2];
291             tmp[2] = tmp[3];
292             SkScan::FillTriangle(tmp, rc, blitter);
293         }
294     }
295 }
296 
fill_triangle(const VertState & state,SkBlitter * blitter,const SkRasterClip & rc,const SkPoint dev2[],const SkPoint3 dev3[])297 static void fill_triangle(const VertState& state, SkBlitter* blitter, const SkRasterClip& rc,
298                           const SkPoint dev2[], const SkPoint3 dev3[]) {
299     if (dev3) {
300         fill_triangle_3(state, blitter, rc, dev3);
301     } else {
302         fill_triangle_2(state, blitter, rc, dev2);
303     }
304 }
305 
306 extern bool gUseSkVMBlitter;
307 
drawFixedVertices(const SkVertices * vertices,SkBlendMode blendMode,const SkPaint & paint,const SkMatrix & ctmInverse,const SkPoint * dev2,const SkPoint3 * dev3,SkArenaAlloc * outerAlloc) const308 void SkDraw::drawFixedVertices(const SkVertices* vertices, SkBlendMode blendMode,
309                                const SkPaint& paint, const SkMatrix& ctmInverse,
310                                const SkPoint* dev2, const SkPoint3* dev3,
311                                SkArenaAlloc* outerAlloc) const {
312     SkVerticesPriv info(vertices->priv());
313 
314     const int vertexCount = info.vertexCount();
315     const int indexCount = info.indexCount();
316     const SkPoint* positions = info.positions();
317     const SkPoint* texCoords = info.texCoords();
318     const uint16_t* indices = info.indices();
319     const SkColor* colors = info.colors();
320 
321     SkShader* paintShader = paint.getShader();
322 
323     if (paintShader) {
324         if (!texCoords) {
325             texCoords = positions;
326         }
327     } else {
328         texCoords = nullptr;
329     }
330 
331     // We can simplify things for certain blend modes. This is for speed, and SkComposeShader
332     // itself insists we don't pass kSrc or kDst to it.
333     if (colors && texCoords) {
334         switch (blendMode) {
335             case SkBlendMode::kSrc:
336                 colors = nullptr;
337                 break;
338             case SkBlendMode::kDst:
339                 texCoords = nullptr;
340                 paintShader = nullptr;
341                 break;
342             default: break;
343         }
344     }
345 
346     // There is a paintShader iff there is texCoords.
347     SkASSERT((texCoords != nullptr) == (paintShader != nullptr));
348 
349     SkMatrix ctm = fMatrixProvider->localToDevice();
350     const bool usePerspective = ctm.hasPerspective();
351 
352     auto rpblit = [&]() {
353         VertState state(vertexCount, indices, indexCount);
354         VertState::Proc vertProc = state.chooseProc(info.mode());
355 
356         SkShader* shader = paintShader;
357         SkTriColorShader* triShader = nullptr;
358         SkPMColor4f* dstColors = nullptr;
359 
360         if (colors) {
361             dstColors = convert_colors(colors, vertexCount, fDst.colorSpace(), outerAlloc);
362             triShader = outerAlloc->make<SkTriColorShader>(compute_is_opaque(colors, vertexCount),
363                                                            usePerspective);
364             if (shader) {
365                 shader = outerAlloc->make<SkShader_Blend>(
366                         blendMode, sk_ref_sp(triShader), sk_ref_sp(shader));
367             } else {
368                 shader = triShader;
369             }
370         }
371 
372         SkPaint shaderPaint(paint);
373         shaderPaint.setShader(sk_ref_sp(shader));
374 
375         if (!texCoords) {  // only tricolor shader
376             auto blitter = SkCreateRasterPipelineBlitter(
377                     fDst, shaderPaint, *fMatrixProvider, outerAlloc, this->fRC->clipShader());
378             if (!blitter) {
379                 return false;
380             }
381             while (vertProc(&state)) {
382                 if (triShader &&
383                     !triShader->update(ctmInverse, positions, dstColors,
384                                        state.f0, state.f1, state.f2)) {
385                     continue;
386                 }
387                 fill_triangle(state, blitter, *fRC, dev2, dev3);
388             }
389             return true;
390         }
391 
392         SkRasterPipeline pipeline(outerAlloc);
393         SkStageRec rec = {&pipeline,
394                           outerAlloc,
395                           fDst.colorType(),
396                           fDst.colorSpace(),
397                           shaderPaint,
398                           nullptr,
399                           *fMatrixProvider};
400         if (auto updater = as_SB(shader)->appendUpdatableStages(rec)) {
401             bool isOpaque = shader->isOpaque();
402             if (triShader) {
403                 isOpaque = false;  // unless we want to walk all the colors, and see if they are
404                                    // all opaque (and the blend mode will keep them that way
405             }
406 
407             // Positions as texCoords? The local matrix is always identity, so update once
408             if (texCoords == positions) {
409                 if (!updater->update(ctm)) {
410                     return true;
411                 }
412             }
413 
414             auto blitter = SkCreateRasterPipelineBlitter(
415                     fDst, shaderPaint, pipeline, isOpaque, outerAlloc, fRC->clipShader());
416             if (!blitter) {
417                 return false;
418             }
419             while (vertProc(&state)) {
420                 if (triShader && !triShader->update(ctmInverse, positions, dstColors,
421                                                     state.f0, state.f1, state.f2)) {
422                     continue;
423                 }
424 
425                 SkMatrix localM;
426                 if ((texCoords == positions) ||
427                     (texture_to_matrix(state, positions, texCoords, &localM) &&
428                      updater->update(SkMatrix::Concat(ctm, localM)))) {
429                     fill_triangle(state, blitter, *fRC, dev2, dev3);
430                 }
431             }
432         } else {
433             // must rebuild pipeline for each triangle, to pass in the computed ctm
434             while (vertProc(&state)) {
435                 if (triShader && !triShader->update(ctmInverse, positions, dstColors,
436                                                     state.f0, state.f1, state.f2)) {
437                     continue;
438                 }
439 
440                 SkSTArenaAlloc<2048> innerAlloc;
441 
442                 const SkMatrixProvider* matrixProvider = fMatrixProvider;
443                 SkTLazy<SkPreConcatMatrixProvider> preConcatMatrixProvider;
444                 if (texCoords && (texCoords != positions)) {
445                     SkMatrix localM;
446                     if (!texture_to_matrix(state, positions, texCoords, &localM)) {
447                         continue;
448                     }
449                     matrixProvider = preConcatMatrixProvider.init(*matrixProvider, localM);
450                 }
451 
452                 // It'd be nice if we could detect this will fail earlier.
453                 auto blitter = SkCreateRasterPipelineBlitter(
454                         fDst, shaderPaint, *matrixProvider, &innerAlloc, this->fRC->clipShader());
455                 if (!blitter) {
456                     return false;
457                 }
458                 fill_triangle(state, blitter, *fRC, dev2, dev3);
459             }
460         }
461         return true;
462     };
463 
464     if (gUseSkVMBlitter || !rpblit()) {
465         VertState state(vertexCount, indices, indexCount);
466         VertState::Proc vertProc = state.chooseProc(info.mode());
467 
468         // No colors are changing and no texture coordinates are changing, so no updates between
469         // triangles are needed. Use SkVM to blit the triangles.
470         SkShader* shader = paintShader;
471         SkUpdatableShader* texCoordShader = nullptr;
472         if (texCoords && texCoords != positions) {
473             texCoordShader = as_SB(shader)->updatableShader(outerAlloc);
474             shader = texCoordShader;
475         }
476 
477         SkTriColorShader* colorShader = nullptr;
478         SkPMColor4f* dstColors = nullptr;
479         if (colors) {
480             colorShader = outerAlloc->make<SkTriColorShader>(compute_is_opaque(colors, vertexCount),
481                                                              usePerspective);
482             dstColors = convert_colors(colors, vertexCount, fDst.colorSpace(), outerAlloc);
483             if (shader) {
484                 shader = outerAlloc->make<SkShader_Blend>(
485                         blendMode, sk_ref_sp(colorShader), sk_ref_sp(shader));
486             } else {
487                 shader = colorShader;
488             }
489         }
490 
491         SkPaint shaderPaint{paint};
492         shaderPaint.setShader(sk_ref_sp(shader));
493         auto blitter = SkVMBlitter::Make(
494                 fDst, shaderPaint, *fMatrixProvider, outerAlloc, this->fRC->clipShader());
495         if (!blitter) {
496             return;
497         }
498         while (vertProc(&state)) {
499             SkMatrix localM;
500             if (texCoordShader && !(texture_to_matrix(state, positions, texCoords, &localM) &&
501                                     texCoordShader->update(SkMatrix::Concat(ctm, localM)))) {
502                 continue;
503             }
504 
505             if (colorShader && !colorShader->update(ctmInverse, positions, dstColors,state.f0,
506                                                     state.f1, state.f2)) {
507                 continue;
508             }
509 
510             fill_triangle(state, blitter, *fRC, dev2, dev3);
511         }
512     }
513 }
514 
drawVertices(const SkVertices * vertices,SkBlendMode bmode,const SkPaint & paint) const515 void SkDraw::drawVertices(const SkVertices* vertices, SkBlendMode bmode,
516                           const SkPaint& paint) const {
517     SkVerticesPriv info(vertices->priv());
518     const int vertexCount = info.vertexCount();
519     const int indexCount = info.indexCount();
520 
521     // abort early if there is nothing to draw
522     if (vertexCount < 3 || (indexCount > 0 && indexCount < 3) || fRC->isEmpty()) {
523         return;
524     }
525     SkMatrix ctm = fMatrixProvider->localToDevice();
526     SkMatrix ctmInv;
527     if (!ctm.invert(&ctmInv)) {
528         return;
529     }
530 
531     constexpr size_t kDefVertexCount = 16;
532     constexpr size_t kOuterSize = sizeof(SkTriColorShader) +
533                                  sizeof(SkShader_Blend) +
534                                  (2 * sizeof(SkPoint) + sizeof(SkColor4f)) * kDefVertexCount;
535     SkSTArenaAlloc<kOuterSize> outerAlloc;
536 
537     SkPoint*  dev2 = nullptr;
538     SkPoint3* dev3 = nullptr;
539 
540     if (ctm.hasPerspective()) {
541         dev3 = outerAlloc.makeArray<SkPoint3>(vertexCount);
542         ctm.mapHomogeneousPoints(dev3, info.positions(), vertexCount);
543         // similar to the bounds check for 2d points (below)
544         if (!SkScalarsAreFinite((const SkScalar*)dev3, vertexCount * 3)) {
545             return;
546         }
547     } else {
548         dev2 = outerAlloc.makeArray<SkPoint>(vertexCount);
549         ctm.mapPoints(dev2, info.positions(), vertexCount);
550 
551         SkRect bounds;
552         // this also sets bounds to empty if we see a non-finite value
553         bounds.setBounds(dev2, vertexCount);
554         if (bounds.isEmpty()) {
555             return;
556         }
557     }
558 
559     this->drawFixedVertices(vertices, bmode, paint, ctmInv, dev2, dev3, &outerAlloc);
560 }
561