• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2006 The Android Open Source Project
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/SkMallocPixelRef.h"
9 #include "include/core/SkPaint.h"
10 #include "include/core/SkScalar.h"
11 #include "src/base/SkArenaAlloc.h"
12 #include "src/base/SkTLazy.h"
13 #include "src/core/SkColorSpacePriv.h"
14 #include "src/core/SkColorSpaceXformSteps.h"
15 #include "src/core/SkMatrixProvider.h"
16 #include "src/core/SkRasterPipeline.h"
17 #include "src/core/SkReadBuffer.h"
18 #include "src/core/SkWriteBuffer.h"
19 #include "src/shaders/SkBitmapProcShader.h"
20 #include "src/shaders/SkImageShader.h"
21 #include "src/shaders/SkShaderBase.h"
22 #include "src/shaders/SkTransformShader.h"
23 
24 #if defined(SK_GANESH)
25 #include "src/gpu/ganesh/GrFragmentProcessor.h"
26 #include "src/gpu/ganesh/effects/GrMatrixEffect.h"
27 #endif
28 
29 #if defined(SK_GRAPHITE)
30 #include "src/gpu/graphite/KeyHelpers.h"
31 #include "src/gpu/graphite/PaintParamsKey.h"
32 #endif
33 
34 SkShaderBase::SkShaderBase() = default;
35 
36 SkShaderBase::~SkShaderBase() = default;
37 
MatrixRec(const SkMatrix & ctm)38 SkShaderBase::MatrixRec::MatrixRec(const SkMatrix& ctm) : fCTM(ctm) {}
39 
40 std::optional<SkShaderBase::MatrixRec>
apply(const SkStageRec & rec,const SkMatrix & postInv) const41 SkShaderBase::MatrixRec::apply(const SkStageRec& rec, const SkMatrix& postInv) const {
42     SkMatrix total = fPendingLocalMatrix;
43     if (!fCTMApplied) {
44         total = SkMatrix::Concat(fCTM, total);
45     }
46     if (!total.invert(&total)) {
47         return {};
48     }
49     total = SkMatrix::Concat(postInv, total);
50     if (!fCTMApplied) {
51         rec.fPipeline->append(SkRasterPipelineOp::seed_shader);
52     }
53     // append_matrix is a no-op if total worked out to identity.
54     rec.fPipeline->append_matrix(rec.fAlloc, total);
55     return MatrixRec{fCTM,
56                      fTotalLocalMatrix,
57                      /*pendingLocalMatrix=*/SkMatrix::I(),
58                      fTotalMatrixIsValid,
59                      /*ctmApplied=*/true};
60 }
61 
62 std::optional<SkShaderBase::MatrixRec>
apply(skvm::Builder * p,skvm::Coord * local,skvm::Uniforms * uniforms,const SkMatrix & postInv) const63 SkShaderBase::MatrixRec::apply(skvm::Builder* p,
64                                skvm::Coord* local,
65                                skvm::Uniforms* uniforms,
66                                const SkMatrix& postInv) const {
67     SkMatrix total = fPendingLocalMatrix;
68     if (!fCTMApplied) {
69         total = SkMatrix::Concat(fCTM, total);
70     }
71     if (!total.invert(&total)) {
72         return {};
73     }
74     total = SkMatrix::Concat(postInv, total);
75     // ApplyMatrix is a no-op if total worked out to identity.
76     *local = SkShaderBase::ApplyMatrix(p, total, *local, uniforms);
77     return MatrixRec{fCTM,
78                      fTotalLocalMatrix,
79                      /*pendingLocalMatrix=*/SkMatrix::I(),
80                      fTotalMatrixIsValid,
81                      /*ctmApplied=*/true};
82 }
83 
84 #if defined(SK_GANESH)
apply(std::unique_ptr<GrFragmentProcessor> fp,const SkMatrix & postInv) const85 GrFPResult SkShaderBase::MatrixRec::apply(std::unique_ptr<GrFragmentProcessor> fp,
86                                           const SkMatrix& postInv) const {
87     // FP matrices work differently than SkRasterPipeline and SkVM. The starting coordinates
88     // provided to the root SkShader's FP are already in local space. So we never apply the inverse
89     // CTM.
90     SkASSERT(!fCTMApplied);
91     SkMatrix total;
92     if (!fPendingLocalMatrix.invert(&total)) {
93         return {false, std::move(fp)};
94     }
95     total = SkMatrix::Concat(postInv, total);
96     // GrMatrixEffect returns 'fp' if total worked out to identity.
97     return {true, GrMatrixEffect::Make(total, std::move(fp))};
98 }
99 
applied() const100 SkShaderBase::MatrixRec SkShaderBase::MatrixRec::applied() const {
101     // We mark the CTM as "not applied" because we *never* apply the CTM for FPs. Their starting
102     // coords are local, not device, coords.
103     return MatrixRec{fCTM,
104                      fTotalLocalMatrix,
105                      /*pendingLocalMatrix=*/SkMatrix::I(),
106                      fTotalMatrixIsValid,
107                      /*ctmApplied=*/false};
108 }
109 #endif
110 
concat(const SkMatrix & m) const111 SkShaderBase::MatrixRec SkShaderBase::MatrixRec::concat(const SkMatrix& m) const {
112     return {fCTM,
113             SkShaderBase::ConcatLocalMatrices(fTotalLocalMatrix, m),
114             SkShaderBase::ConcatLocalMatrices(fPendingLocalMatrix, m),
115             fTotalMatrixIsValid,
116             fCTMApplied};
117 }
118 
flatten(SkWriteBuffer & buffer) const119 void SkShaderBase::flatten(SkWriteBuffer& buffer) const { this->INHERITED::flatten(buffer); }
120 
computeTotalInverse(const SkMatrix & ctm,const SkMatrix * localMatrix,SkMatrix * totalInverse) const121 bool SkShaderBase::computeTotalInverse(const SkMatrix& ctm,
122                                        const SkMatrix* localMatrix,
123                                        SkMatrix* totalInverse) const {
124     return (localMatrix ? SkMatrix::Concat(ctm, *localMatrix) : ctm).invert(totalInverse);
125 }
126 
asLuminanceColor(SkColor * colorPtr) const127 bool SkShaderBase::asLuminanceColor(SkColor* colorPtr) const {
128     SkColor storage;
129     if (nullptr == colorPtr) {
130         colorPtr = &storage;
131     }
132     if (this->onAsLuminanceColor(colorPtr)) {
133         *colorPtr = SkColorSetA(*colorPtr, 0xFF);   // we only return opaque
134         return true;
135     }
136     return false;
137 }
138 
makeContext(const ContextRec & rec,SkArenaAlloc * alloc) const139 SkShaderBase::Context* SkShaderBase::makeContext(const ContextRec& rec, SkArenaAlloc* alloc) const {
140 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
141     // We always fall back to raster pipeline when perspective is present.
142     if (rec.fMatrix->hasPerspective() || (rec.fLocalMatrix && rec.fLocalMatrix->hasPerspective()) ||
143         !this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, nullptr)) {
144         return nullptr;
145     }
146 
147     return this->onMakeContext(rec, alloc);
148 #else
149     return nullptr;
150 #endif
151 }
152 
Context(const SkShaderBase & shader,const ContextRec & rec)153 SkShaderBase::Context::Context(const SkShaderBase& shader, const ContextRec& rec)
154     : fShader(shader), fCTM(*rec.fMatrix)
155 {
156     // We should never use a context with perspective.
157     SkASSERT(!rec.fMatrix->hasPerspective());
158     SkASSERT(!rec.fLocalMatrix || !rec.fLocalMatrix->hasPerspective());
159 
160     // Because the context parameters must be valid at this point, we know that the matrix is
161     // invertible.
162     SkAssertResult(fShader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &fTotalInverse));
163 
164     fPaintAlpha = rec.fPaintAlpha;
165 }
166 
~Context()167 SkShaderBase::Context::~Context() {}
168 
isLegacyCompatible(SkColorSpace * shaderColorSpace) const169 bool SkShaderBase::ContextRec::isLegacyCompatible(SkColorSpace* shaderColorSpace) const {
170     // In legacy pipelines, shaders always produce premul (or opaque) and the destination is also
171     // always premul (or opaque).  (And those "or opaque" caveats won't make any difference here.)
172     SkAlphaType shaderAT = kPremul_SkAlphaType,
173                    dstAT = kPremul_SkAlphaType;
174     return 0 == SkColorSpaceXformSteps{shaderColorSpace, shaderAT,
175                                          fDstColorSpace,    dstAT}.flags.mask();
176 }
177 
isAImage(SkMatrix * localMatrix,SkTileMode xy[2]) const178 SkImage* SkShader::isAImage(SkMatrix* localMatrix, SkTileMode xy[2]) const {
179     return as_SB(this)->onIsAImage(localMatrix, xy);
180 }
181 
182 #if defined(SK_GANESH)
183 std::unique_ptr<GrFragmentProcessor>
asRootFragmentProcessor(const GrFPArgs & args,const SkMatrix & ctm) const184 SkShaderBase::asRootFragmentProcessor(const GrFPArgs& args, const SkMatrix& ctm) const {
185     return this->asFragmentProcessor(args, MatrixRec(ctm));
186 }
187 
asFragmentProcessor(const GrFPArgs &,const MatrixRec &) const188 std::unique_ptr<GrFragmentProcessor> SkShaderBase::asFragmentProcessor(const GrFPArgs&,
189                                                                        const MatrixRec&) const {
190     return nullptr;
191 }
192 #endif
193 
makeAsALocalMatrixShader(SkMatrix *) const194 sk_sp<SkShader> SkShaderBase::makeAsALocalMatrixShader(SkMatrix*) const {
195     return nullptr;
196 }
197 
198 #if defined(SK_GRAPHITE)
199 // TODO: add implementations for derived classes
addToKey(const skgpu::graphite::KeyContext & keyContext,skgpu::graphite::PaintParamsKeyBuilder * builder,skgpu::graphite::PipelineDataGatherer * gatherer) const200 void SkShaderBase::addToKey(const skgpu::graphite::KeyContext& keyContext,
201                             skgpu::graphite::PaintParamsKeyBuilder* builder,
202                             skgpu::graphite::PipelineDataGatherer* gatherer) const {
203     using namespace skgpu::graphite;
204 
205     SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
206     builder->endBlock();
207 }
208 #endif
209 
appendRootStages(const SkStageRec & rec,const SkMatrix & ctm) const210 bool SkShaderBase::appendRootStages(const SkStageRec& rec, const SkMatrix& ctm) const {
211     return this->appendStages(rec, MatrixRec(ctm));
212 }
213 
appendStages(const SkStageRec & rec,const MatrixRec & mRec) const214 bool SkShaderBase::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
215     // SkShader::Context::shadeSpan() handles the paint opacity internally,
216     // but SkRasterPipelineBlitter applies it as a separate stage.
217     // We skip the internal shadeSpan() step by forcing the paint opaque.
218     SkTCopyOnFirstWrite<SkPaint> opaquePaint(rec.fPaint);
219     if (rec.fPaint.getAlpha() != SK_AlphaOPAQUE) {
220         opaquePaint.writable()->setAlpha(SK_AlphaOPAQUE);
221     }
222 
223     // We don't have a separate ctm and local matrix at this point. Just pass the combined matrix
224     // as the CTM. TODO: thread the MatrixRec through the legacy context system.
225     auto tm = mRec.totalMatrix();
226     ContextRec cr(*opaquePaint,
227                   tm,
228                   nullptr,
229                   rec.fDstColorType,
230                   sk_srgb_singleton(),
231                   rec.fSurfaceProps);
232 
233     struct CallbackCtx : SkRasterPipeline_CallbackCtx {
234         sk_sp<const SkShader> shader;
235         Context*              ctx;
236     };
237     auto cb = rec.fAlloc->make<CallbackCtx>();
238     cb->shader = sk_ref_sp(this);
239     cb->ctx = as_SB(this)->makeContext(cr, rec.fAlloc);
240     cb->fn  = [](SkRasterPipeline_CallbackCtx* self, int active_pixels) {
241         auto c = (CallbackCtx*)self;
242         int x = (int)c->rgba[0],
243             y = (int)c->rgba[1];
244         SkPMColor tmp[SkRasterPipeline_kMaxStride_highp];
245         c->ctx->shadeSpan(x,y, tmp, active_pixels);
246 
247         for (int i = 0; i < active_pixels; i++) {
248             auto rgba_4f = SkPMColor4f::FromPMColor(tmp[i]);
249             memcpy(c->rgba + 4*i, rgba_4f.vec(), 4*sizeof(float));
250         }
251     };
252 
253     if (cb->ctx) {
254         rec.fPipeline->append(SkRasterPipelineOp::seed_shader);
255         rec.fPipeline->append(SkRasterPipelineOp::callback, cb);
256         rec.fAlloc->make<SkColorSpaceXformSteps>(sk_srgb_singleton(), kPremul_SkAlphaType,
257                                                  rec.fDstCS,          kPremul_SkAlphaType)
258             ->apply(rec.fPipeline);
259         return true;
260     }
261     return false;
262 }
263 
rootProgram(skvm::Builder * p,skvm::Coord device,skvm::Color paint,const SkMatrix & ctm,const SkColorInfo & dst,skvm::Uniforms * uniforms,SkArenaAlloc * alloc) const264 skvm::Color SkShaderBase::rootProgram(skvm::Builder* p,
265                                       skvm::Coord device,
266                                       skvm::Color paint,
267                                       const SkMatrix& ctm,
268                                       const SkColorInfo& dst,
269                                       skvm::Uniforms* uniforms,
270                                       SkArenaAlloc* alloc) const {
271     // Shader subclasses should always act as if the destination were premul or opaque.
272     // SkVMBlitter handles all the coordination of unpremul itself, via premul.
273     SkColorInfo tweaked = dst.alphaType() == kUnpremul_SkAlphaType
274                            ? dst.makeAlphaType(kPremul_SkAlphaType)
275                            : dst;
276 
277     // Force opaque alpha for all opaque shaders.
278     //
279     // This is primarily nice in that we usually have a 1.0f constant splat
280     // somewhere in the program anyway, and this will let us drop the work the
281     // shader notionally does to produce alpha, p->extract(...), etc. in favor
282     // of that simple hoistable splat.
283     //
284     // More subtly, it makes isOpaque() a parameter to all shader program
285     // generation, guaranteeing that is-opaque bit is mixed into the overall
286     // shader program hash and blitter Key.  This makes it safe for us to use
287     // that bit to make decisions when constructing an SkVMBlitter, like doing
288     // SrcOver -> Src strength reduction.
289     if (auto color = this->program(p,
290                                    device,
291                                    /*local=*/device,
292                                    paint,
293                                    MatrixRec(ctm),
294                                    tweaked,
295                                    uniforms,
296                                    alloc)) {
297         if (this->isOpaque()) {
298             color.a = p->splat(1.0f);
299         }
300         return color;
301     }
302     return {};
303 }
304 
305 // need a cheap way to invert the alpha channel of a shader (i.e. 1 - a)
makeInvertAlpha() const306 sk_sp<SkShader> SkShaderBase::makeInvertAlpha() const {
307     return this->makeWithColorFilter(SkColorFilters::Blend(0xFFFFFFFF, SkBlendMode::kSrcOut));
308 }
309 
310 
ApplyMatrix(skvm::Builder * p,const SkMatrix & m,skvm::Coord coord,skvm::Uniforms * uniforms)311 skvm::Coord SkShaderBase::ApplyMatrix(skvm::Builder* p, const SkMatrix& m,
312                                       skvm::Coord coord, skvm::Uniforms* uniforms) {
313     skvm::F32 x = coord.x,
314               y = coord.y;
315     if (m.isIdentity()) {
316         // That was easy.
317     } else if (m.isTranslate()) {
318         x = p->add(x, p->uniformF(uniforms->pushF(m[2])));
319         y = p->add(y, p->uniformF(uniforms->pushF(m[5])));
320     } else if (m.isScaleTranslate()) {
321         x = p->mad(x, p->uniformF(uniforms->pushF(m[0])), p->uniformF(uniforms->pushF(m[2])));
322         y = p->mad(y, p->uniformF(uniforms->pushF(m[4])), p->uniformF(uniforms->pushF(m[5])));
323     } else {  // Affine or perspective.
324         auto dot = [&,x,y](int row) {
325             return p->mad(x, p->uniformF(uniforms->pushF(m[3*row+0])),
326                    p->mad(y, p->uniformF(uniforms->pushF(m[3*row+1])),
327                              p->uniformF(uniforms->pushF(m[3*row+2]))));
328         };
329         x = dot(0);
330         y = dot(1);
331         if (m.hasPerspective()) {
332             x = x * (1.0f / dot(2));
333             y = y * (1.0f / dot(2));
334         }
335     }
336     return {x,y};
337 }
338