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 | #ifndef SkShaderBase_DEFINED |
9 | #define SkShaderBase_DEFINED |
10 | |
11 | #include "include/core/SkColor.h" |
12 | #include "include/core/SkFlattenable.h" |
13 | #include "include/core/SkMatrix.h" |
14 | #include "include/core/SkPoint.h" |
15 | #include "include/core/SkRefCnt.h" |
16 | #include "include/core/SkScalar.h" |
17 | #include "include/core/SkShader.h" |
18 | #include "include/core/SkSurfaceProps.h" |
19 | #include "include/core/SkTypes.h" |
20 | #include "include/private/base/SkNoncopyable.h" |
21 | |
22 | #include <cstddef> |
23 | #include <cstdint> |
24 | #include <optional> |
25 | #include <tuple> |
26 | |
27 | class SkArenaAlloc; |
28 | class SkColorSpace; |
29 | class SkImage; |
30 | class SkRuntimeEffect; |
31 | class SkWriteBuffer; |
32 | enum SkColorType : int; |
33 | enum class SkTileMode; |
34 | struct SkDeserialProcs; |
35 | struct SkStageRec; |
36 | |
37 | namespace SkShaders { |
38 | /** |
39 | * This is used to accumulate matrices, starting with the CTM, when building up |
40 | * SkRasterPipeline or GrFragmentProcessor by walking the SkShader tree. It avoids |
41 | * adding a matrix multiply for each individual matrix. It also handles the reverse matrix |
42 | * concatenation order required by Android Framework, see b/256873449. |
43 | * |
44 | * This also tracks the dubious concept of a "total matrix", in the legacy Context/shadeSpan system. |
45 | * That includes all the matrices encountered during traversal to the current shader, including ones |
46 | * that have already been applied. The total matrix represents the transformation from the current |
47 | * shader's coordinate space to device space. It is dubious because it doesn't account for SkShaders |
48 | * that manipulate the coordinates passed to their children, which may not even be representable by |
49 | * a matrix. |
50 | * |
51 | * The total matrix is used for mipmap level selection and a filter downgrade optimizations in |
52 | * SkImageShader and sizing of the SkImage created by SkPictureShader. If we can remove usages |
53 | * of the "total matrix" and if Android Framework could be updated to not use backwards local |
54 | * matrix concatenation this could just be replaced by a simple SkMatrix or SkM44 passed down |
55 | * during traversal. |
56 | */ |
57 | class MatrixRec { |
58 | public: |
59 | MatrixRec() = default; |
60 | |
61 | explicit MatrixRec(const SkMatrix& ctm); |
62 | |
63 | /** |
64 | * Returns a new MatrixRec that represents the existing total and pending matrix |
65 | * pre-concat'ed with m. |
66 | */ |
67 | [[nodiscard]] MatrixRec concat(const SkMatrix& m) const; |
68 | |
69 | /** |
70 | * Appends a mul by the inverse of the pending local matrix to the pipeline. 'postInv' is an |
71 | * additional matrix to post-apply to the inverted pending matrix. If the pending matrix is |
72 | * not invertible the std::optional result won't have a value and the pipeline will be |
73 | * unmodified. |
74 | */ |
75 | [[nodiscard]] std::optional<MatrixRec> apply(const SkStageRec& rec, |
76 | const SkMatrix& postInv = {}) const; |
77 | |
78 | /** |
79 | * FP matrices work differently than SkRasterPipeline. The starting coordinates provided to the |
80 | * root SkShader's FP are already in local space. So we never apply the inverse CTM. This |
81 | * returns the inverted pending local matrix with the provided postInv matrix applied after it. |
82 | * If the pending local matrix cannot be inverted, the boolean is false. |
83 | */ |
84 | std::tuple<SkMatrix, bool> applyForFragmentProcessor(const SkMatrix& postInv) const; |
85 | |
86 | /** |
87 | * A parent FP may need to create a FP for its child by calling |
88 | * SkShaderBase::asFragmentProcessor() and then pass the result to the apply() above. |
89 | * This comes up when the parent needs to ensure pending matrices are applied before the |
90 | * child because the parent is going to manipulate the coordinates *after* any pending |
91 | * matrix and pass the resulting coords to the child. This function gets a MatrixRec that |
92 | * reflects the state after this MatrixRec has bee applied but it does not apply it! |
93 | * Example: |
94 | * auto childFP = fChild->asFragmentProcessor(args, mrec.applied()); |
95 | * childFP = MakeAWrappingFPThatModifiesChildsCoords(std::move(childFP)); |
96 | * auto [success, parentFP] = mrec.apply(std::move(childFP)); |
97 | */ |
98 | MatrixRec applied() const; |
99 | |
100 | /** Call to indicate that the mapping from shader to device space is not known. */ |
101 | void markTotalMatrixInvalid() { fTotalMatrixIsValid = false; } |
102 | |
103 | /** Marks the CTM as already applied; can avoid re-seeding the shader unnecessarily. */ |
104 | void markCTMApplied() { fCTMApplied = true; } |
105 | |
106 | /** |
107 | * Indicates whether the total matrix of a MatrixRec passed to a SkShader actually |
108 | * represents the full transform between that shader's coordinate space and device space. |
109 | */ |
110 | bool totalMatrixIsValid() const { return fTotalMatrixIsValid; } |
111 | |
112 | /** |
113 | * Gets the total transform from the current shader's space to device space. This may or |
114 | * may not be valid. Shaders should avoid making decisions based on this matrix if |
115 | * totalMatrixIsValid() is false. |
116 | */ |
117 | SkMatrix totalMatrix() const { return SkMatrix::Concat(a: fCTM, b: fTotalLocalMatrix); } |
118 | |
119 | /** Gets the inverse of totalMatrix(), if invertible. */ |
120 | [[nodiscard]] bool totalInverse(SkMatrix* out) const { |
121 | return this->totalMatrix().invert(inverse: out); |
122 | } |
123 | |
124 | /** Is there a transform that has not yet been applied by a parent shader? */ |
125 | bool hasPendingMatrix() const { |
126 | return (!fCTMApplied && !fCTM.isIdentity()) || !fPendingLocalMatrix.isIdentity(); |
127 | } |
128 | |
129 | /** When generating raster pipeline, have the device coordinates been seeded? */ |
130 | bool rasterPipelineCoordsAreSeeded() const { return fCTMApplied; } |
131 | |
132 | private: |
133 | MatrixRec(const SkMatrix& ctm, |
134 | const SkMatrix& totalLocalMatrix, |
135 | const SkMatrix& pendingLocalMatrix, |
136 | bool totalIsValid, |
137 | bool ctmApplied) |
138 | : fCTM(ctm) |
139 | , fTotalLocalMatrix(totalLocalMatrix) |
140 | , fPendingLocalMatrix(pendingLocalMatrix) |
141 | , fTotalMatrixIsValid(totalIsValid) |
142 | , fCTMApplied(ctmApplied) {} |
143 | |
144 | const SkMatrix fCTM; |
145 | |
146 | // Concatenation of all local matrices, including those already applied. |
147 | const SkMatrix fTotalLocalMatrix; |
148 | |
149 | // The accumulated local matrices from walking down the shader hierarchy that have NOT yet |
150 | // been incorporated into the SkRasterPipeline. |
151 | const SkMatrix fPendingLocalMatrix; |
152 | |
153 | bool fTotalMatrixIsValid = true; |
154 | |
155 | // Tracks whether the CTM has already been applied (and in raster pipeline whether the |
156 | // device coords have been seeded.) |
157 | bool fCTMApplied = false; |
158 | }; |
159 | |
160 | } // namespace SkShaders |
161 | |
162 | #define SK_ALL_SHADERS(M) \ |
163 | M(Blend) \ |
164 | M(CTM) \ |
165 | M(Color) \ |
166 | M(Color4) \ |
167 | M(ColorFilter) \ |
168 | M(CoordClamp) \ |
169 | M(Empty) \ |
170 | M(GradientBase) \ |
171 | M(Image) \ |
172 | M(LocalMatrix) \ |
173 | M(PerlinNoise) \ |
174 | M(Picture) \ |
175 | M(Runtime) \ |
176 | M(Transform) \ |
177 | M(TriColor) |
178 | |
179 | #define SK_ALL_GRADIENTS(M) \ |
180 | M(Conical) \ |
181 | M(Linear) \ |
182 | M(Radial) \ |
183 | M(Sweep) |
184 | |
185 | class SkShaderBase : public SkShader { |
186 | public: |
187 | ~SkShaderBase() override; |
188 | |
189 | sk_sp<SkShader> makeInvertAlpha() const; |
190 | sk_sp<SkShader> makeWithCTM(const SkMatrix&) const; // owns its own ctm |
191 | |
192 | /** |
193 | * Returns true if the shader is guaranteed to produce only a single color. |
194 | * Subclasses can override this to allow loop-hoisting optimization. |
195 | */ |
196 | virtual bool isConstant() const { return false; } |
197 | |
198 | enum class ShaderType { |
199 | #define M(type) k##type, |
200 | SK_ALL_SHADERS(M) |
201 | #undef M |
202 | }; |
203 | |
204 | virtual ShaderType type() const = 0; |
205 | |
206 | enum class GradientType { |
207 | kNone, |
208 | #define M(type) k##type, |
209 | SK_ALL_GRADIENTS(M) |
210 | #undef M |
211 | }; |
212 | |
213 | /** |
214 | * If the shader subclass can be represented as a gradient, asGradient |
215 | * returns the matching GradientType enum (or GradientType::kNone if it |
216 | * cannot). Also, if info is not null, asGradient populates info with |
217 | * the relevant (see below) parameters for the gradient. fColorCount |
218 | * is both an input and output parameter. On input, it indicates how |
219 | * many entries in fColors and fColorOffsets can be used, if they are |
220 | * non-NULL. After asGradient has run, fColorCount indicates how |
221 | * many color-offset pairs there are in the gradient. If there is |
222 | * insufficient space to store all of the color-offset pairs, fColors |
223 | * and fColorOffsets will not be altered. fColorOffsets specifies |
224 | * where on the range of 0 to 1 to transition to the given color. |
225 | * The meaning of fPoint and fRadius is dependent on the type of gradient. |
226 | * |
227 | * None: |
228 | * info is ignored. |
229 | * Color: |
230 | * fColorOffsets[0] is meaningless. |
231 | * Linear: |
232 | * fPoint[0] and fPoint[1] are the end-points of the gradient |
233 | * Radial: |
234 | * fPoint[0] and fRadius[0] are the center and radius |
235 | * Conical: |
236 | * fPoint[0] and fRadius[0] are the center and radius of the 1st circle |
237 | * fPoint[1] and fRadius[1] are the center and radius of the 2nd circle |
238 | * Sweep: |
239 | * fPoint[0] is the center of the sweep. |
240 | */ |
241 | struct GradientInfo { |
242 | int fColorCount = 0; //!< In-out parameter, specifies passed size |
243 | // of fColors/fColorOffsets on input, and |
244 | // actual number of colors/offsets on |
245 | // output. |
246 | SkColor* fColors = nullptr; //!< The colors in the gradient. |
247 | SkScalar* fColorOffsets = nullptr; //!< The unit offset for color transitions. |
248 | SkPoint fPoint[2]; //!< Type specific, see above. |
249 | SkScalar fRadius[2]; //!< Type specific, see above. |
250 | SkTileMode fTileMode; |
251 | uint32_t fGradientFlags = 0; //!< see SkGradientShader::Flags |
252 | }; |
253 | |
254 | virtual GradientType asGradient(GradientInfo* info = nullptr, |
255 | SkMatrix* localMatrix = nullptr) const { |
256 | return GradientType::kNone; |
257 | } |
258 | |
259 | enum Flags { |
260 | //!< set if all of the colors will be opaque |
261 | kOpaqueAlpha_Flag = 1 << 0, |
262 | }; |
263 | |
264 | /** |
265 | * ContextRec acts as a parameter bundle for creating Contexts. |
266 | */ |
267 | struct ContextRec { |
268 | ContextRec(SkAlpha paintAlpha, |
269 | const SkShaders::MatrixRec& matrixRec, |
270 | SkColorType dstColorType, |
271 | SkColorSpace* dstColorSpace, |
272 | SkSurfaceProps props) |
273 | : fMatrixRec(matrixRec) |
274 | , fDstColorType(dstColorType) |
275 | , fDstColorSpace(dstColorSpace) |
276 | , fProps(props) |
277 | , fPaintAlpha(paintAlpha) {} |
278 | |
279 | static ContextRec Concat(const ContextRec& parentRec, const SkMatrix& localM) { |
280 | return {parentRec.fPaintAlpha, |
281 | parentRec.fMatrixRec.concat(m: localM), |
282 | parentRec.fDstColorType, |
283 | parentRec.fDstColorSpace, |
284 | parentRec.fProps}; |
285 | } |
286 | |
287 | const SkShaders::MatrixRec fMatrixRec; |
288 | SkColorType fDstColorType; // the color type of the dest surface |
289 | SkColorSpace* fDstColorSpace; // the color space of the dest surface (if any) |
290 | SkSurfaceProps fProps; // props of the dest surface |
291 | SkAlpha fPaintAlpha; |
292 | |
293 | bool isLegacyCompatible(SkColorSpace* shadersColorSpace) const; |
294 | }; |
295 | |
296 | class Context : public ::SkNoncopyable { |
297 | public: |
298 | Context(const SkShaderBase& shader, const ContextRec&); |
299 | |
300 | virtual ~Context(); |
301 | |
302 | /** |
303 | * Called sometimes before drawing with this shader. Return the type of |
304 | * alpha your shader will return. The default implementation returns 0. |
305 | * Your subclass should override if it can (even sometimes) report a |
306 | * non-zero value, since that will enable various blitters to perform |
307 | * faster. |
308 | */ |
309 | virtual uint32_t getFlags() const { return 0; } |
310 | |
311 | /** |
312 | * Called for each span of the object being drawn. Your subclass should |
313 | * set the appropriate colors (with premultiplied alpha) that correspond |
314 | * to the specified device coordinates. |
315 | */ |
316 | virtual void shadeSpan(int x, int y, SkPMColor[], int count) = 0; |
317 | |
318 | protected: |
319 | // Reference to shader, so we don't have to dupe information. |
320 | const SkShaderBase& fShader; |
321 | |
322 | uint8_t getPaintAlpha() const { return fPaintAlpha; } |
323 | const SkMatrix& getTotalInverse() const { return fTotalInverse; } |
324 | |
325 | private: |
326 | SkMatrix fTotalInverse; |
327 | uint8_t fPaintAlpha; |
328 | }; |
329 | |
330 | /** |
331 | * Make a context using the memory provided by the arena. |
332 | * |
333 | * @return pointer to context or nullptr if can't be created |
334 | */ |
335 | Context* makeContext(const ContextRec&, SkArenaAlloc*) const; |
336 | |
337 | /** |
338 | * If the shader can represent its "average" luminance in a single color, return true and |
339 | * if color is not NULL, return that color. If it cannot, return false and ignore the color |
340 | * parameter. |
341 | * |
342 | * Note: if this returns true, the returned color will always be opaque, as only the RGB |
343 | * components are used to compute luminance. |
344 | */ |
345 | bool asLuminanceColor(SkColor*) const; |
346 | |
347 | /** |
348 | * If this returns false, then we draw nothing (do not fall back to shader context). This should |
349 | * only be called on a root-level effect. It assumes that the initial device coordinates have |
350 | * not yet been seeded. |
351 | */ |
352 | [[nodiscard]] bool appendRootStages(const SkStageRec& rec, const SkMatrix& ctm) const; |
353 | |
354 | /** |
355 | * Adds stages to implement this shader. To ensure that the correct input coords are present |
356 | * in r,g MatrixRec::apply() must be called (unless the shader doesn't require it's input |
357 | * coords). The default impl creates shadercontext and calls that (not very efficient). |
358 | */ |
359 | virtual bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const; |
360 | |
361 | virtual SkImage* onIsAImage(SkMatrix*, SkTileMode[2]) const { |
362 | return nullptr; |
363 | } |
364 | |
365 | virtual SkRuntimeEffect* asRuntimeEffect() const { return nullptr; } |
366 | |
367 | static Type GetFlattenableType() { return kSkShader_Type; } |
368 | Type getFlattenableType() const override { return GetFlattenableType(); } |
369 | |
370 | static sk_sp<SkShaderBase> Deserialize(const void* data, size_t size, |
371 | const SkDeserialProcs* procs = nullptr) { |
372 | return sk_sp<SkShaderBase>(static_cast<SkShaderBase*>( |
373 | SkFlattenable::Deserialize(GetFlattenableType(), data, length: size, procs).release())); |
374 | } |
375 | static void RegisterFlattenables(); |
376 | |
377 | /** DEPRECATED. skbug.com/8941 |
378 | * If this shader can be represented by another shader + a localMatrix, return that shader and |
379 | * the localMatrix. If not, return nullptr and ignore the localMatrix parameter. |
380 | */ |
381 | virtual sk_sp<SkShader> makeAsALocalMatrixShader(SkMatrix* localMatrix) const; |
382 | |
383 | static SkMatrix ConcatLocalMatrices(const SkMatrix& parentLM, const SkMatrix& childLM) { |
384 | #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) // b/256873449 |
385 | return SkMatrix::Concat(childLM, parentLM); |
386 | #endif |
387 | return SkMatrix::Concat(a: parentLM, b: childLM); |
388 | } |
389 | |
390 | protected: |
391 | SkShaderBase(); |
392 | |
393 | void flatten(SkWriteBuffer&) const override; |
394 | |
395 | #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT |
396 | /** |
397 | * Specialize creating a SkShader context using the supplied allocator. |
398 | * @return pointer to context owned by the arena allocator. |
399 | */ |
400 | virtual Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const { |
401 | return nullptr; |
402 | } |
403 | #endif |
404 | |
405 | virtual bool onAsLuminanceColor(SkColor*) const { |
406 | return false; |
407 | } |
408 | |
409 | friend class SkShaders::MatrixRec; |
410 | }; |
411 | inline SkShaderBase* as_SB(SkShader* shader) { |
412 | return static_cast<SkShaderBase*>(shader); |
413 | } |
414 | |
415 | inline const SkShaderBase* as_SB(const SkShader* shader) { |
416 | return static_cast<const SkShaderBase*>(shader); |
417 | } |
418 | |
419 | inline const SkShaderBase* as_SB(const sk_sp<SkShader>& shader) { |
420 | return static_cast<SkShaderBase*>(shader.get()); |
421 | } |
422 | |
423 | void SkRegisterBlendShaderFlattenable(); |
424 | void SkRegisterColor4ShaderFlattenable(); |
425 | void SkRegisterColorShaderFlattenable(); |
426 | void SkRegisterCoordClampShaderFlattenable(); |
427 | void SkRegisterEmptyShaderFlattenable(); |
428 | void SkRegisterPerlinNoiseShaderFlattenable(); |
429 | |
430 | #endif // SkShaderBase_DEFINED |
431 | |