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