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/SkRasterClip.h"
16#include "src/core/SkRasterPipeline.h"
17#include "src/core/SkScan.h"
18#include "src/core/SkVertState.h"
19#include "src/core/SkVerticesPriv.h"
20#include "src/shaders/SkComposeShader.h"
21#include "src/shaders/SkShaderBase.h"
22
23// Compute the crossing point (across zero) for the two values, expressed as a
24// normalized 0...1 value. If curr is 0, returns 0. If next is 0, returns 1.
25//
26static float compute_t(float curr, float next) {
27 SkASSERT((curr > 0 && next <= 0) || (curr <= 0 && next > 0));
28 float t = curr / (curr - next);
29 SkASSERT(t >= 0 && t <= 1);
30 return t;
31}
32
33static SkPoint3 lerp(SkPoint3 curr, SkPoint3 next, float t) {
34 return curr + t * (next - curr);
35}
36
37// tol is the nudge away from zero, to keep the numerics nice.
38// Think of it as our near-clipping-plane (or w-plane).
39static SkPoint3 clip(SkPoint3 curr, SkPoint3 next, float tol) {
40 // Return the point between curr and next where the fZ value corses tol.
41 // To be (really) perspective correct, we should be computing baesd on 1/Z, not Z.
42 // For now, this is close enough (and faster).
43 return lerp(curr, next, compute_t(curr.fZ - tol, next.fZ - tol));
44}
45
46constexpr int kMaxClippedTrianglePointCount = 4;
47// Clip a triangle (based on its homogeneous W values), and return the projected polygon.
48// Since we only clip against one "edge"/plane, the max number of points in the clipped
49// polygon is 4.
50static int clip_triangle(SkPoint dst[], const int idx[3], const SkPoint3 pts[]) {
51 SkPoint3 outPoints[4];
52 SkPoint3* outP = outPoints;
53 const float tol = 0.05f;
54
55 for (int i = 0; i < 3; ++i) {
56 int curr = idx[i];
57 int next = idx[(i + 1) % 3];
58 if (pts[curr].fZ > tol) {
59 *outP++ = pts[curr];
60 if (pts[next].fZ <= tol) { // curr is IN, next is OUT
61 *outP++ = clip(pts[curr], pts[next], tol);
62 }
63 } else {
64 if (pts[next].fZ > tol) { // curr is OUT, next is IN
65 *outP++ = clip(pts[curr], pts[next], tol);
66 }
67 }
68 }
69
70 const int count = outP - outPoints;
71 SkASSERT(count == 0 || count == 3 || count == 4);
72 for (int i = 0; i < count; ++i) {
73 float scale = 1.0f / outPoints[i].fZ;
74 dst[i].set(outPoints[i].fX * scale, outPoints[i].fY * scale);
75 }
76 return count;
77}
78
79struct Matrix43 {
80 float fMat[12]; // column major
81
82 Sk4f map(float x, float y) const {
83 return Sk4f::Load(&fMat[0]) * x + Sk4f::Load(&fMat[4]) * y + Sk4f::Load(&fMat[8]);
84 }
85
86 // Pass a by value, so we don't have to worry about aliasing with this
87 void setConcat(const Matrix43 a, const SkMatrix& b) {
88 SkASSERT(!b.hasPerspective());
89
90 fMat[ 0] = a.dot(0, b.getScaleX(), b.getSkewY());
91 fMat[ 1] = a.dot(1, b.getScaleX(), b.getSkewY());
92 fMat[ 2] = a.dot(2, b.getScaleX(), b.getSkewY());
93 fMat[ 3] = a.dot(3, b.getScaleX(), b.getSkewY());
94
95 fMat[ 4] = a.dot(0, b.getSkewX(), b.getScaleY());
96 fMat[ 5] = a.dot(1, b.getSkewX(), b.getScaleY());
97 fMat[ 6] = a.dot(2, b.getSkewX(), b.getScaleY());
98 fMat[ 7] = a.dot(3, b.getSkewX(), b.getScaleY());
99
100 fMat[ 8] = a.dot(0, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 8];
101 fMat[ 9] = a.dot(1, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 9];
102 fMat[10] = a.dot(2, b.getTranslateX(), b.getTranslateY()) + a.fMat[10];
103 fMat[11] = a.dot(3, b.getTranslateX(), b.getTranslateY()) + a.fMat[11];
104 }
105
106private:
107 float dot(int index, float x, float y) const {
108 return fMat[index + 0] * x + fMat[index + 4] * y;
109 }
110};
111
112static bool SK_WARN_UNUSED_RESULT
113texture_to_matrix(const VertState& state, const SkPoint verts[], const SkPoint texs[],
114 SkMatrix* matrix) {
115 SkPoint src[3], dst[3];
116
117 src[0] = texs[state.f0];
118 src[1] = texs[state.f1];
119 src[2] = texs[state.f2];
120 dst[0] = verts[state.f0];
121 dst[1] = verts[state.f1];
122 dst[2] = verts[state.f2];
123 return matrix->setPolyToPoly(src, dst, 3);
124}
125
126class SkTriColorShader : public SkShaderBase {
127public:
128 SkTriColorShader(bool isOpaque, bool usePersp) : fIsOpaque(isOpaque), fUsePersp(usePersp) {}
129
130 // This gets called for each triangle, without re-calling onAppendStages.
131 bool update(const SkMatrix& ctmInv, const SkPoint pts[], const SkPMColor4f colors[],
132 int index0, int index1, int index2);
133
134protected:
135#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
136 Context* onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const override {
137 return nullptr;
138 }
139#endif
140 bool onAppendStages(const SkStageRec& rec) const override {
141 rec.fPipeline->append(SkRasterPipeline::seed_shader);
142 if (fUsePersp) {
143 rec.fPipeline->append(SkRasterPipeline::matrix_perspective, &fM33);
144 }
145 rec.fPipeline->append(SkRasterPipeline::matrix_4x3, &fM43);
146 return true;
147 }
148
149private:
150 bool isOpaque() const override { return fIsOpaque; }
151 // For serialization. This will never be called.
152 Factory getFactory() const override { return nullptr; }
153 const char* getTypeName() const override { return nullptr; }
154
155 // If fUsePersp, we need both of these matrices,
156 // otherwise we can combine them, and only use fM43
157
158 Matrix43 fM43;
159 SkMatrix fM33;
160 const bool fIsOpaque;
161 const bool fUsePersp; // controls our stages, and what we do in update()
162
163 typedef SkShaderBase INHERITED;
164};
165
166bool SkTriColorShader::update(const SkMatrix& ctmInv, const SkPoint pts[],
167 const SkPMColor4f colors[], int index0, int index1, int index2) {
168 SkMatrix m, im;
169 m.reset();
170 m.set(0, pts[index1].fX - pts[index0].fX);
171 m.set(1, pts[index2].fX - pts[index0].fX);
172 m.set(2, pts[index0].fX);
173 m.set(3, pts[index1].fY - pts[index0].fY);
174 m.set(4, pts[index2].fY - pts[index0].fY);
175 m.set(5, pts[index0].fY);
176 if (!m.invert(&im)) {
177 return false;
178 }
179
180 fM33.setConcat(im, ctmInv);
181
182 Sk4f c0 = Sk4f::Load(colors[index0].vec()),
183 c1 = Sk4f::Load(colors[index1].vec()),
184 c2 = Sk4f::Load(colors[index2].vec());
185
186 (c1 - c0).store(&fM43.fMat[0]);
187 (c2 - c0).store(&fM43.fMat[4]);
188 c0.store(&fM43.fMat[8]);
189
190 if (!fUsePersp) {
191 fM43.setConcat(fM43, fM33);
192 }
193 return true;
194}
195
196// Convert the SkColors into float colors. The conversion depends on some conditions:
197// - If the pixmap has a dst colorspace, we have to be "color-correct".
198// Do we map into dst-colorspace before or after we interpolate?
199// - We have to decide when to apply per-color alpha (before or after we interpolate)
200//
201// For now, we will take a simple approach, but recognize this is just a start:
202// - convert colors into dst colorspace before interpolation (matches gradients)
203// - apply per-color alpha before interpolation (matches old version of vertices)
204//
205static SkPMColor4f* convert_colors(const SkColor src[], int count, SkColorSpace* deviceCS,
206 SkArenaAlloc* alloc) {
207 SkPMColor4f* dst = alloc->makeArray<SkPMColor4f>(count);
208 SkImageInfo srcInfo = SkImageInfo::Make(count, 1, kBGRA_8888_SkColorType,
209 kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB());
210 SkImageInfo dstInfo = SkImageInfo::Make(count, 1, kRGBA_F32_SkColorType,
211 kPremul_SkAlphaType, sk_ref_sp(deviceCS));
212 SkConvertPixels(dstInfo, dst, 0, srcInfo, src, 0);
213 return dst;
214}
215
216static bool compute_is_opaque(const SkColor colors[], int count) {
217 uint32_t c = ~0;
218 for (int i = 0; i < count; ++i) {
219 c &= colors[i];
220 }
221 return SkColorGetA(c) == 0xFF;
222}
223
224static void fill_triangle_2(const VertState& state, SkBlitter* blitter, const SkRasterClip& rc,
225 const SkPoint dev2[]) {
226 SkPoint tmp[] = {
227 dev2[state.f0], dev2[state.f1], dev2[state.f2]
228 };
229 SkScan::FillTriangle(tmp, rc, blitter);
230}
231
232static void fill_triangle_3(const VertState& state, SkBlitter* blitter, const SkRasterClip& rc,
233 const SkPoint3 dev3[]) {
234 SkPoint tmp[kMaxClippedTrianglePointCount];
235 int idx[] = { state.f0, state.f1, state.f2 };
236 if (int n = clip_triangle(tmp, idx, dev3)) {
237 // TODO: SkScan::FillConvexPoly(tmp, n, ...);
238 SkASSERT(n == 3 || n == 4);
239 SkScan::FillTriangle(tmp, rc, blitter);
240 if (n == 4) {
241 tmp[1] = tmp[2];
242 tmp[2] = tmp[3];
243 SkScan::FillTriangle(tmp, rc, blitter);
244 }
245 }
246}
247static void fill_triangle(const VertState& state, SkBlitter* blitter, const SkRasterClip& rc,
248 const SkPoint dev2[], const SkPoint3 dev3[]) {
249 if (dev3) {
250 fill_triangle_3(state, blitter, rc, dev3);
251 } else {
252 fill_triangle_2(state, blitter, rc, dev2);
253 }
254}
255
256void SkDraw::draw_fixed_vertices(const SkVertices* vertices, SkBlendMode bmode,
257 const SkPaint& paint, const SkMatrix& ctmInv,
258 const SkPoint dev2[], const SkPoint3 dev3[],
259 SkArenaAlloc* outerAlloc) const {
260 SkVerticesPriv info(vertices->priv());
261 SkASSERT(!info.hasCustomData());
262
263 const int vertexCount = info.vertexCount();
264 const int indexCount = info.indexCount();
265 const SkPoint* positions = info.positions();
266 const SkPoint* textures = info.texCoords();
267 const uint16_t* indices = info.indices();
268 const SkColor* colors = info.colors();
269
270 // make textures and shader mutually consistent
271 SkShader* shader = paint.getShader();
272 if (!(shader && textures)) {
273 shader = nullptr;
274 textures = nullptr;
275 }
276
277 // We can simplify things for certain blendmodes. This is for speed, and SkComposeShader
278 // itself insists we don't pass kSrc or kDst to it.
279 //
280 if (colors && textures) {
281 switch (bmode) {
282 case SkBlendMode::kSrc:
283 colors = nullptr;
284 break;
285 case SkBlendMode::kDst:
286 textures = nullptr;
287 break;
288 default: break;
289 }
290 }
291
292 // we don't use the shader if there are no textures
293 if (!textures) {
294 shader = nullptr;
295 }
296
297 /* We need to know if we have perspective or not, so we can know what stage(s) we will need,
298 and how to prep our "uniforms" before each triangle in the tricolorshader.
299
300 We could just check the matrix on each triangle to decide, but we have to be sure to always
301 make the same decision, since we create 1 or 2 stages only once for the entire patch.
302
303 To be safe, we just make that determination here, and pass it into the tricolorshader.
304 */
305 const bool usePerspective = fMatrix->hasPerspective();
306
307 VertState state(vertexCount, indices, indexCount);
308 VertState::Proc vertProc = state.chooseProc(info.mode());
309
310 SkTriColorShader* triShader = nullptr;
311 SkPMColor4f* dstColors = nullptr;
312
313 if (colors) {
314 dstColors = convert_colors(colors, vertexCount, fDst.colorSpace(), outerAlloc);
315 triShader = outerAlloc->make<SkTriColorShader>(compute_is_opaque(colors, vertexCount),
316 usePerspective);
317 if (shader) {
318 shader = outerAlloc->make<SkShader_Blend>(bmode,
319 sk_ref_sp(triShader), sk_ref_sp(shader));
320 } else {
321 shader = triShader;
322 }
323 }
324
325 SkPaint p(paint);
326 p.setShader(sk_ref_sp(shader));
327
328 if (!textures) { // only tricolor shader
329 auto blitter = SkCreateRasterPipelineBlitter(fDst, p, *fMatrix, outerAlloc,
330 this->fRC->clipShader());
331 while (vertProc(&state)) {
332 if (triShader &&
333 !triShader->update(ctmInv, positions, dstColors, state.f0, state.f1, state.f2)) {
334 continue;
335 }
336 fill_triangle(state, blitter, *fRC, dev2, dev3);
337 }
338 return;
339 }
340
341 SkRasterPipeline pipeline(outerAlloc);
342 SkStageRec rec = {
343 &pipeline, outerAlloc, fDst.colorType(), fDst.colorSpace(), p, nullptr, *fMatrix
344 };
345 if (auto updater = as_SB(shader)->appendUpdatableStages(rec)) {
346 bool isOpaque = shader->isOpaque();
347 if (triShader) {
348 isOpaque = false; // unless we want to walk all the colors, and see if they are
349 // all opaque (and the blendmode will keep them that way
350 }
351
352 auto blitter = SkCreateRasterPipelineBlitter(fDst, p, pipeline, isOpaque, outerAlloc,
353 fRC->clipShader());
354 while (vertProc(&state)) {
355 if (triShader && !triShader->update(ctmInv, positions, dstColors,
356 state.f0, state.f1, state.f2)) {
357 continue;
358 }
359
360 SkMatrix localM;
361 if (texture_to_matrix(state, positions, textures, &localM) &&
362 updater->update(*fMatrix, &localM))
363 {
364 fill_triangle(state, blitter, *fRC, dev2, dev3);
365 }
366 }
367 } else {
368 // must rebuild pipeline for each triangle, to pass in the computed ctm
369 while (vertProc(&state)) {
370 if (triShader && !triShader->update(ctmInv, positions, dstColors,
371 state.f0, state.f1, state.f2)) {
372 continue;
373 }
374
375 SkSTArenaAlloc<2048> innerAlloc;
376
377 const SkMatrix* ctm = fMatrix;
378 SkMatrix tmpCtm;
379 if (textures) {
380 SkMatrix localM;
381 if (!texture_to_matrix(state, positions, textures, &localM)) {
382 continue;
383 }
384 tmpCtm = SkMatrix::Concat(*fMatrix, localM);
385 ctm = &tmpCtm;
386 }
387
388 auto blitter = SkCreateRasterPipelineBlitter(fDst, p, *ctm, &innerAlloc,
389 this->fRC->clipShader());
390 fill_triangle(state, blitter, *fRC, dev2, dev3);
391 }
392 }
393}
394
395void SkDraw::draw_vdata_vertices(const SkVertices* vt, const SkPaint& paint,
396 const SkMatrix& ctmInv,
397 const SkPoint dev2[], const SkPoint3 dev3[],
398 SkArenaAlloc* outerAlloc) const {
399 // TODO: Handle custom attributes
400}
401
402void SkDraw::drawVertices(const SkVertices* vertices, SkBlendMode bmode,
403 const SkPaint& paint) const {
404 SkVerticesPriv info(vertices->priv());
405 const int vertexCount = info.vertexCount();
406 const int indexCount = info.indexCount();
407
408 // abort early if there is nothing to draw
409 if (vertexCount < 3 || (indexCount > 0 && indexCount < 3) || fRC->isEmpty()) {
410 return;
411 }
412 SkMatrix ctmInv;
413 if (!fMatrix->invert(&ctmInv)) {
414 return;
415 }
416
417 constexpr size_t kDefVertexCount = 16;
418 constexpr size_t kOuterSize = sizeof(SkTriColorShader) +
419 sizeof(SkShader_Blend) +
420 (2 * sizeof(SkPoint) + sizeof(SkColor4f)) * kDefVertexCount;
421 SkSTArenaAlloc<kOuterSize> outerAlloc;
422
423 SkPoint* dev2 = nullptr;
424 SkPoint3* dev3 = nullptr;
425
426 if (fMatrix->hasPerspective()) {
427 dev3 = outerAlloc.makeArray<SkPoint3>(vertexCount);
428 fMatrix->mapHomogeneousPoints(dev3, info.positions(), vertexCount);
429 // similar to the bounds check for 2d points (below)
430 if (!SkScalarsAreFinite((const SkScalar*)dev3, vertexCount * 3)) {
431 return;
432 }
433 } else {
434 dev2 = outerAlloc.makeArray<SkPoint>(vertexCount);
435 fMatrix->mapPoints(dev2, info.positions(), vertexCount);
436
437 SkRect bounds;
438 // this also sets bounds to empty if we see a non-finite value
439 bounds.setBounds(dev2, vertexCount);
440 if (bounds.isEmpty()) {
441 return;
442 }
443 }
444
445 if (!info.hasCustomData()) {
446 this->draw_fixed_vertices(vertices, bmode, paint, ctmInv, dev2, dev3, &outerAlloc);
447 } else {
448 this->draw_vdata_vertices(vertices, paint, ctmInv, dev2, dev3, &outerAlloc);
449 }
450}
451