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