1/*
2 * Copyright 2014 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/SkTLazy.h"
10#include "src/core/SkVM.h"
11#include "src/shaders/SkLocalMatrixShader.h"
12
13#if SK_SUPPORT_GPU
14#include "src/gpu/GrFragmentProcessor.h"
15#include "src/gpu/effects/GrMatrixEffect.h"
16#include "src/gpu/effects/generated/GrDeviceSpaceEffect.h"
17#endif
18
19#if SK_SUPPORT_GPU
20std::unique_ptr<GrFragmentProcessor> SkLocalMatrixShader::asFragmentProcessor(
21 const GrFPArgs& args) const {
22 return as_SB(fProxyShader)->asFragmentProcessor(
23 GrFPArgs::WithPreLocalMatrix(args, this->getLocalMatrix()));
24}
25#endif
26
27sk_sp<SkFlattenable> SkLocalMatrixShader::CreateProc(SkReadBuffer& buffer) {
28 SkMatrix lm;
29 buffer.readMatrix(&lm);
30 auto baseShader(buffer.readShader());
31 if (!baseShader) {
32 return nullptr;
33 }
34 return baseShader->makeWithLocalMatrix(lm);
35}
36
37void SkLocalMatrixShader::flatten(SkWriteBuffer& buffer) const {
38 buffer.writeMatrix(this->getLocalMatrix());
39 buffer.writeFlattenable(fProxyShader.get());
40}
41
42#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
43SkShaderBase::Context* SkLocalMatrixShader::onMakeContext(
44 const ContextRec& rec, SkArenaAlloc* alloc) const
45{
46 SkTCopyOnFirstWrite<SkMatrix> lm(this->getLocalMatrix());
47 if (rec.fLocalMatrix) {
48 lm.writable()->preConcat(*rec.fLocalMatrix);
49 }
50
51 ContextRec newRec(rec);
52 newRec.fLocalMatrix = lm;
53
54 return as_SB(fProxyShader)->makeContext(newRec, alloc);
55}
56#endif
57
58SkImage* SkLocalMatrixShader::onIsAImage(SkMatrix* outMatrix, SkTileMode* mode) const {
59 SkMatrix imageMatrix;
60 SkImage* image = fProxyShader->isAImage(&imageMatrix, mode);
61 if (image && outMatrix) {
62 // Local matrix must be applied first so it is on the right side of the concat.
63 *outMatrix = SkMatrix::Concat(imageMatrix, this->getLocalMatrix());
64 }
65
66 return image;
67}
68
69bool SkLocalMatrixShader::onAppendStages(const SkStageRec& rec) const {
70 SkTCopyOnFirstWrite<SkMatrix> lm(this->getLocalMatrix());
71 if (rec.fLocalM) {
72 lm.writable()->preConcat(*rec.fLocalM);
73 }
74
75 SkStageRec newRec = rec;
76 newRec.fLocalM = lm;
77 return as_SB(fProxyShader)->appendStages(newRec);
78}
79
80
81skvm::Color SkLocalMatrixShader::onProgram(skvm::Builder* p,
82 skvm::Coord device, skvm::Coord local, skvm::Color paint,
83 const SkMatrixProvider& matrices, const SkMatrix* localM,
84 SkFilterQuality quality, const SkColorInfo& dst,
85 skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
86 SkTCopyOnFirstWrite<SkMatrix> lm(this->getLocalMatrix());
87 if (localM) {
88 lm.writable()->preConcat(*localM);
89 }
90 return as_SB(fProxyShader)->program(p, device,local, paint,
91 matrices,lm.get(),
92 quality,dst,
93 uniforms,alloc);
94}
95
96sk_sp<SkShader> SkShader::makeWithLocalMatrix(const SkMatrix& localMatrix) const {
97 if (localMatrix.isIdentity()) {
98 return sk_ref_sp(const_cast<SkShader*>(this));
99 }
100
101 const SkMatrix* lm = &localMatrix;
102
103 sk_sp<SkShader> baseShader;
104 SkMatrix otherLocalMatrix;
105 sk_sp<SkShader> proxy(as_SB(this)->makeAsALocalMatrixShader(&otherLocalMatrix));
106 if (proxy) {
107 otherLocalMatrix.preConcat(localMatrix);
108 lm = &otherLocalMatrix;
109 baseShader = proxy;
110 } else {
111 baseShader = sk_ref_sp(const_cast<SkShader*>(this));
112 }
113
114 return sk_make_sp<SkLocalMatrixShader>(std::move(baseShader), *lm);
115}
116
117////////////////////////////////////////////////////////////////////
118
119/**
120 * Replaces the CTM when used. Created to support clipShaders, which have to be evaluated
121 * using the CTM that was present at the time they were specified (which may be different
122 * from the CTM at the time something is drawn through the clip.
123 */
124class SkCTMShader final : public SkShaderBase {
125public:
126 SkCTMShader(sk_sp<SkShader> proxy, const SkMatrix& ctm)
127 : fProxyShader(std::move(proxy))
128 , fCTM(ctm)
129 {}
130
131 GradientType asAGradient(GradientInfo* info) const override {
132 return fProxyShader->asAGradient(info);
133 }
134
135#if SK_SUPPORT_GPU
136 std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
137#endif
138
139protected:
140 void flatten(SkWriteBuffer&) const override { SkASSERT(false); }
141
142#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
143 Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override { return nullptr; }
144#endif
145
146 bool onAppendStages(const SkStageRec& rec) const override {
147 SkOverrideDeviceMatrixProvider matrixProvider(rec.fMatrixProvider, fCTM);
148 SkStageRec newRec = {
149 rec.fPipeline,
150 rec.fAlloc,
151 rec.fDstColorType,
152 rec.fDstCS,
153 rec.fPaint,
154 rec.fLocalM,
155 matrixProvider,
156 };
157 return as_SB(fProxyShader)->appendStages(newRec);
158 }
159
160 skvm::Color onProgram(skvm::Builder* p,
161 skvm::Coord device, skvm::Coord local, skvm::Color paint,
162 const SkMatrixProvider& matrices, const SkMatrix* localM,
163 SkFilterQuality quality, const SkColorInfo& dst,
164 skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const override {
165 SkOverrideDeviceMatrixProvider matrixProvider(matrices, fCTM);
166 return as_SB(fProxyShader)->program(p, device,local, paint,
167 matrixProvider,localM,
168 quality,dst,
169 uniforms,alloc);
170 }
171
172private:
173 SK_FLATTENABLE_HOOKS(SkCTMShader)
174
175 sk_sp<SkShader> fProxyShader;
176 SkMatrix fCTM;
177
178 typedef SkShaderBase INHERITED;
179};
180
181
182#if SK_SUPPORT_GPU
183std::unique_ptr<GrFragmentProcessor> SkCTMShader::asFragmentProcessor(
184 const GrFPArgs& args) const {
185 SkMatrix ctmInv;
186 if (!fCTM.invert(&ctmInv)) {
187 return nullptr;
188 }
189
190 auto ctmProvider = SkOverrideDeviceMatrixProvider(args.fMatrixProvider, fCTM);
191 auto base = as_SB(fProxyShader)->asFragmentProcessor(
192 GrFPArgs::WithPreLocalMatrix(args.withNewMatrixProvider(ctmProvider),
193 this->getLocalMatrix()));
194 if (!base) {
195 return nullptr;
196 }
197
198 // In order for the shader to be evaluated with the original CTM, we explicitly evaluate it
199 // at sk_FragCoord, and pass that through the inverse of the original CTM. This avoids requiring
200 // local coords for the shader and mapping from the draw's local to device and then back.
201 return GrDeviceSpaceEffect::Make(GrMatrixEffect::Make(ctmInv, std::move(base)));
202}
203#endif
204
205sk_sp<SkFlattenable> SkCTMShader::CreateProc(SkReadBuffer& buffer) {
206 SkASSERT(false);
207 return nullptr;
208}
209
210sk_sp<SkShader> SkShaderBase::makeWithCTM(const SkMatrix& postM) const {
211 return sk_sp<SkShader>(new SkCTMShader(sk_ref_sp(this), postM));
212}
213