1 /*
2 * Copyright 2021 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 "src/core/SkMatrixProvider.h"
9 #include "src/core/SkRasterPipeline.h"
10 #include "src/shaders/SkTransformShader.h"
11
SkTransformShader(const SkShaderBase & shader,bool allowPerspective)12 SkTransformShader::SkTransformShader(const SkShaderBase& shader, bool allowPerspective)
13 : fShader{shader}, fAllowPerspective{allowPerspective} {
14 SkMatrix::I().get9(fMatrixStorage);
15 }
16
program(skvm::Builder * b,skvm::Coord device,skvm::Coord local,skvm::Color color,const MatrixRec & mRec,const SkColorInfo & dst,skvm::Uniforms * uniforms,SkArenaAlloc * alloc) const17 skvm::Color SkTransformShader::program(skvm::Builder* b,
18 skvm::Coord device,
19 skvm::Coord local,
20 skvm::Color color,
21 const MatrixRec& mRec,
22 const SkColorInfo& dst,
23 skvm::Uniforms* uniforms,
24 SkArenaAlloc* alloc) const {
25 // We have to seed and apply any constant matrices before appending our matrix that may
26 // mutate. We could try to apply one matrix stage and then incorporate the parent matrix
27 // with the variable matrix in each call to update(). However, in practice our callers
28 // fold the CTM into the update() matrix and don't wrap the transform shader in local matrix
29 // shaders so the call to apply below should be no-op. If this assert fires it just indicates an
30 // optimization opportunity, not a correctness bug.
31 SkASSERT(!mRec.hasPendingMatrix());
32
33 std::optional<MatrixRec> childMRec = mRec.apply(b, &local, uniforms);
34 if (!childMRec.has_value()) {
35 return {};
36 }
37 // The matrix we're about to insert gets updated between uses of the VM so our children can't
38 // know the total transform when they add their stages. We don't incorporate this shader's
39 // matrix into the MatrixRec at all.
40 childMRec->markTotalMatrixInvalid();
41
42 auto matrix = uniforms->pushPtr(&fMatrixStorage);
43
44 skvm::F32 x = local.x,
45 y = local.y;
46
47 auto dot = [&, x, y](int row) {
48 return b->mad(x,
49 b->arrayF(matrix, 3 * row + 0),
50 b->mad(y, b->arrayF(matrix, 3 * row + 1), b->arrayF(matrix, 3 * row + 2)));
51 };
52
53 x = dot(0);
54 y = dot(1);
55 if (fAllowPerspective) {
56 x = x * (1.0f / dot(2));
57 y = y * (1.0f / dot(2));
58 }
59
60 skvm::Coord newLocal = {x, y};
61 return fShader.program(b, device, newLocal, color, *childMRec, dst, uniforms, alloc);
62 }
63
update(const SkMatrix & matrix)64 bool SkTransformShader::update(const SkMatrix& matrix) {
65 if (SkMatrix inv; matrix.invert(&inv)) {
66 if (!fAllowPerspective && inv.hasPerspective()) {
67 return false;
68 }
69
70 inv.get9(fMatrixStorage);
71 return true;
72 }
73 return false;
74 }
75
appendStages(const SkStageRec & rec,const MatrixRec & mRec) const76 bool SkTransformShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
77 // We have to seed and apply any constant matrices before appending our matrix that may
78 // mutate. We could try to add one matrix stage and then incorporate the parent matrix
79 // with the variable matrix in each call to update(). However, in practice our callers
80 // fold the CTM into the update() matrix and don't wrap the transform shader in local matrix
81 // shaders so the call to apply below should just seed the coordinates. If this assert fires
82 // it just indicates an optimization opportunity, not a correctness bug.
83 SkASSERT(!mRec.hasPendingMatrix());
84 std::optional<MatrixRec> childMRec = mRec.apply(rec);
85 if (!childMRec.has_value()) {
86 return false;
87 }
88 // The matrix we're about to insert gets updated between uses of the pipeline so our children
89 // can't know the total transform when they add their stages. We don't even incorporate this
90 // matrix into the MatrixRec at all.
91 childMRec->markTotalMatrixInvalid();
92
93 auto type = fAllowPerspective ? SkRasterPipelineOp::matrix_perspective
94 : SkRasterPipelineOp::matrix_2x3;
95 rec.fPipeline->append(type, fMatrixStorage);
96
97 fShader.appendStages(rec, *childMRec);
98 return true;
99 }
100