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 | // |
26 | static 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 | |
33 | static 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). |
39 | static 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 | |
46 | constexpr 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. |
50 | static 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 | |
79 | struct 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 | |
106 | private: |
107 | float dot(int index, float x, float y) const { |
108 | return fMat[index + 0] * x + fMat[index + 4] * y; |
109 | } |
110 | }; |
111 | |
112 | static bool SK_WARN_UNUSED_RESULT |
113 | texture_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 | |
126 | class SkTriColorShader : public SkShaderBase { |
127 | public: |
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 | |
134 | protected: |
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 | |
149 | private: |
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 | |
166 | bool 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 | // |
205 | static 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 | |
216 | static 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 | |
224 | static 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 | |
232 | static 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 | } |
247 | static 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 | |
256 | void 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 | |
395 | void 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 | |
402 | void 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 | |