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