• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2012 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 "include/private/base/SkFloatingPoint.h"
9 #include "src/core/SkRasterPipeline.h"
10 #include "src/core/SkReadBuffer.h"
11 #include "src/core/SkWriteBuffer.h"
12 #include "src/shaders/SkLocalMatrixShader.h"
13 #include "src/shaders/gradients/SkGradientShaderBase.h"
14 
15 #include <utility>
16 
17 #if defined(SK_GRAPHITE)
18 #include "src/gpu/graphite/KeyContext.h"
19 #include "src/gpu/graphite/KeyHelpers.h"
20 #include "src/gpu/graphite/PaintParamsKey.h"
21 #endif
22 
23 // Please see https://skia.org/dev/design/conical for how our shader works.
24 
25 class SkTwoPointConicalGradient final : public SkGradientShaderBase {
26 public:
27     // See https://skia.org/dev/design/conical for what focal data means and how our shader works.
28     // We make it public so the GPU shader can also use it.
29     struct FocalData {
30         SkScalar    fR1;        // r1 after mapping focal point to (0, 0)
31         SkScalar    fFocalX;    // f
32         bool        fIsSwapped; // whether we swapped r0, r1
33 
34         // The input r0, r1 are the radii when we map centers to {(0, 0), (1, 0)}.
35         // We'll post concat matrix with our transformation matrix that maps focal point to (0, 0).
36         // Returns true if the set succeeded
37         bool set(SkScalar r0, SkScalar r1, SkMatrix* matrix);
38 
39         // Whether the focal point (0, 0) is on the end circle with center (1, 0) and radius r1. If
40         // this is true, it's as if an aircraft is flying at Mach 1 and all circles (soundwaves)
41         // will go through the focal point (aircraft). In our previous implementations, this was
42         // known as the edge case where the inside circle touches the outside circle (on the focal
43         // point). If we were to solve for t bruteforcely using a quadratic equation, this case
44         // implies that the quadratic equation degenerates to a linear equation.
isFocalOnCircleSkTwoPointConicalGradient::FocalData45         bool isFocalOnCircle() const { return SkScalarNearlyZero(1 - fR1); }
46 
isSwappedSkTwoPointConicalGradient::FocalData47         bool isSwapped() const { return fIsSwapped; }
isWellBehavedSkTwoPointConicalGradient::FocalData48         bool isWellBehaved() const { return !this->isFocalOnCircle() && fR1 > 1; }
isNativelyFocalSkTwoPointConicalGradient::FocalData49         bool isNativelyFocal() const { return SkScalarNearlyZero(fFocalX); }
50     };
51 
52     enum class Type {
53         kRadial,
54         kStrip,
55         kFocal
56     };
57 
58     static sk_sp<SkShader> Create(const SkPoint& start, SkScalar startRadius,
59                                   const SkPoint& end,   SkScalar endRadius,
60                                   const Descriptor&, const SkMatrix* localMatrix);
61 
62     GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
63 #if defined(SK_GANESH)
64     std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
65                                                              const MatrixRec&) const override;
66 #endif
67 #if defined(SK_GRAPHITE)
68     void addToKey(const skgpu::graphite::KeyContext&,
69                   skgpu::graphite::PaintParamsKeyBuilder*,
70                   skgpu::graphite::PipelineDataGatherer*) const override;
71 #endif
72     bool isOpaque() const override;
73 
getCenterX1() const74     SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); }
getStartRadius() const75     SkScalar getStartRadius() const { return fRadius1; }
getDiffRadius() const76     SkScalar getDiffRadius() const { return fRadius2 - fRadius1; }
getStartCenter() const77     const SkPoint& getStartCenter() const { return fCenter1; }
getEndCenter() const78     const SkPoint& getEndCenter() const { return fCenter2; }
getEndRadius() const79     SkScalar getEndRadius() const { return fRadius2; }
80 
getType() const81     Type getType() const { return fType; }
getFocalData() const82     const FocalData& getFocalData() const { return fFocalData; }
83 
84     SkTwoPointConicalGradient(const SkPoint& c0, SkScalar r0,
85                               const SkPoint& c1, SkScalar r1,
86                               const Descriptor&, Type, const SkMatrix&, const FocalData&);
87 
88 protected:
89     void flatten(SkWriteBuffer& buffer) const override;
90 
91     void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
92                               SkRasterPipeline* postPipeline) const override;
93 
94     skvm::F32 transformT(skvm::Builder*, skvm::Uniforms*,
95                          skvm::Coord coord, skvm::I32* mask) const final;
96 
97 private:
98     friend void ::SkRegisterTwoPointConicalGradientShaderFlattenable();
99     SK_FLATTENABLE_HOOKS(SkTwoPointConicalGradient)
100 
101     SkPoint  fCenter1;
102     SkPoint  fCenter2;
103     SkScalar fRadius1;
104     SkScalar fRadius2;
105     Type     fType;
106 
107     FocalData fFocalData;
108 };
109 
set(SkScalar r0,SkScalar r1,SkMatrix * matrix)110 bool SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) {
111     fIsSwapped = false;
112     fFocalX = sk_ieee_float_divide(r0, (r0 - r1));
113     if (SkScalarNearlyZero(fFocalX - 1)) {
114         // swap r0, r1
115         matrix->postTranslate(-1, 0);
116         matrix->postScale(-1, 1);
117         std::swap(r0, r1);
118         fFocalX = 0; // because r0 is now 0
119         fIsSwapped = true;
120     }
121 
122     // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
123     const SkPoint from[2]   = { {fFocalX, 0}, {1, 0} };
124     const SkPoint to[2]     = { {0, 0}, {1, 0} };
125     SkMatrix focalMatrix;
126     if (!focalMatrix.setPolyToPoly(from, to, 2)) {
127         return false;
128     }
129     matrix->postConcat(focalMatrix);
130     fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
131 
132     // The following transformations are just to accelerate the shader computation by saving
133     // some arithmatic operations.
134     if (this->isFocalOnCircle()) {
135         matrix->postScale(0.5, 0.5);
136     } else {
137         matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
138     }
139     matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
140     return true;
141 }
142 
Create(const SkPoint & c0,SkScalar r0,const SkPoint & c1,SkScalar r1,const Descriptor & desc,const SkMatrix * localMatrix)143 sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0,
144                                                   const SkPoint& c1, SkScalar r1,
145                                                   const Descriptor& desc,
146                                                   const SkMatrix* localMatrix) {
147     SkMatrix gradientMatrix;
148     Type     gradientType;
149 
150     if (SkScalarNearlyZero((c0 - c1).length())) {
151         if (SkScalarNearlyZero(std::max(r0, r1)) || SkScalarNearlyEqual(r0, r1)) {
152             // Degenerate case; avoid dividing by zero. Should have been caught by caller but
153             // just in case, recheck here.
154             return nullptr;
155         }
156         // Concentric case: we can pretend we're radial (with a tiny twist).
157         const SkScalar scale = sk_ieee_float_divide(1, std::max(r0, r1));
158         gradientMatrix = SkMatrix::Translate(-c1.x(), -c1.y());
159         gradientMatrix.postScale(scale, scale);
160 
161         gradientType = Type::kRadial;
162     } else {
163         const SkPoint centers[2] = { c0    , c1     };
164         const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
165 
166         if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) {
167             // Degenerate case.
168             return nullptr;
169         }
170 
171         gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
172     }
173 
174     FocalData focalData;
175     if (gradientType == Type::kFocal) {
176         const auto dCenter = (c0 - c1).length();
177         if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) {
178             return nullptr;
179         }
180     }
181     return SkLocalMatrixShader::MakeWrapped<SkTwoPointConicalGradient>(localMatrix,
182                                                                        c0, r0,
183                                                                        c1, r1,
184                                                                        desc,
185                                                                        gradientType,
186                                                                        gradientMatrix,
187                                                                        focalData);
188 }
189 
SkTwoPointConicalGradient(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,const Descriptor & desc,Type type,const SkMatrix & gradientMatrix,const FocalData & data)190 SkTwoPointConicalGradient::SkTwoPointConicalGradient(
191         const SkPoint& start, SkScalar startRadius,
192         const SkPoint& end, SkScalar endRadius,
193         const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data)
194     : SkGradientShaderBase(desc, gradientMatrix)
195     , fCenter1(start)
196     , fCenter2(end)
197     , fRadius1(startRadius)
198     , fRadius2(endRadius)
199     , fType(type)
200 {
201     // this is degenerate, and should be caught by our caller
202     SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
203     if (type == Type::kFocal) {
204         fFocalData = data;
205     }
206 }
207 
isOpaque() const208 bool SkTwoPointConicalGradient::isOpaque() const {
209     // Because areas outside the cone are left untouched, we cannot treat the
210     // shader as opaque even if the gradient itself is opaque.
211     // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
212     return false;
213 }
214 
215 // Returns the original non-sorted version of the gradient
asGradient(GradientInfo * info,SkMatrix * localMatrix) const216 SkShaderBase::GradientType SkTwoPointConicalGradient::asGradient(GradientInfo* info,
217                                                                  SkMatrix* localMatrix) const {
218     if (info) {
219         commonAsAGradient(info);
220         info->fPoint[0] = fCenter1;
221         info->fPoint[1] = fCenter2;
222         info->fRadius[0] = fRadius1;
223         info->fRadius[1] = fRadius2;
224     }
225     if (localMatrix) {
226         *localMatrix = SkMatrix::I();
227     }
228     return GradientType::kConical;
229 }
230 
CreateProc(SkReadBuffer & buffer)231 sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
232     DescriptorScope desc;
233     SkMatrix legacyLocalMatrix;
234     if (!desc.unflatten(buffer, &legacyLocalMatrix)) {
235         return nullptr;
236     }
237     SkPoint c1 = buffer.readPoint();
238     SkPoint c2 = buffer.readPoint();
239     SkScalar r1 = buffer.readScalar();
240     SkScalar r2 = buffer.readScalar();
241 
242     if (!buffer.isValid()) {
243         return nullptr;
244     }
245     return SkGradientShader::MakeTwoPointConical(c1, r1,
246                                                  c2, r2,
247                                                  desc.fColors,
248                                                  std::move(desc.fColorSpace),
249                                                  desc.fPositions,
250                                                  desc.fColorCount,
251                                                  desc.fTileMode,
252                                                  desc.fInterpolation,
253                                                  &legacyLocalMatrix);
254 }
255 
flatten(SkWriteBuffer & buffer) const256 void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
257     this->SkGradientShaderBase::flatten(buffer);
258     buffer.writePoint(fCenter1);
259     buffer.writePoint(fCenter2);
260     buffer.writeScalar(fRadius1);
261     buffer.writeScalar(fRadius2);
262 }
263 
appendGradientStages(SkArenaAlloc * alloc,SkRasterPipeline * p,SkRasterPipeline * postPipeline) const264 void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
265                                                      SkRasterPipeline* postPipeline) const {
266     const auto dRadius = fRadius2 - fRadius1;
267 
268     if (fType == Type::kRadial) {
269         p->append(SkRasterPipelineOp::xy_to_radius);
270 
271         // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
272         auto scale =  std::max(fRadius1, fRadius2) / dRadius;
273         auto bias  = -fRadius1 / dRadius;
274 
275         p->append_matrix(alloc, SkMatrix::Translate(bias, 0) * SkMatrix::Scale(scale, 1));
276         return;
277     }
278 
279     if (fType == Type::kStrip) {
280         auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
281         SkScalar scaledR0 = fRadius1 / this->getCenterX1();
282         ctx->fP0 = scaledR0 * scaledR0;
283         p->append(SkRasterPipelineOp::xy_to_2pt_conical_strip, ctx);
284         p->append(SkRasterPipelineOp::mask_2pt_conical_nan, ctx);
285         postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
286         return;
287     }
288 
289     auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
290     ctx->fP0 = 1/fFocalData.fR1;
291     ctx->fP1 = fFocalData.fFocalX;
292 
293     if (fFocalData.isFocalOnCircle()) {
294         p->append(SkRasterPipelineOp::xy_to_2pt_conical_focal_on_circle);
295     } else if (fFocalData.isWellBehaved()) {
296         p->append(SkRasterPipelineOp::xy_to_2pt_conical_well_behaved, ctx);
297     } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
298         p->append(SkRasterPipelineOp::xy_to_2pt_conical_smaller, ctx);
299     } else {
300         p->append(SkRasterPipelineOp::xy_to_2pt_conical_greater, ctx);
301     }
302 
303     if (!fFocalData.isWellBehaved()) {
304         p->append(SkRasterPipelineOp::mask_2pt_conical_degenerates, ctx);
305     }
306     if (1 - fFocalData.fFocalX < 0) {
307         p->append(SkRasterPipelineOp::negate_x);
308     }
309     if (!fFocalData.isNativelyFocal()) {
310         p->append(SkRasterPipelineOp::alter_2pt_conical_compensate_focal, ctx);
311     }
312     if (fFocalData.isSwapped()) {
313         p->append(SkRasterPipelineOp::alter_2pt_conical_unswap);
314     }
315     if (!fFocalData.isWellBehaved()) {
316         postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
317     }
318 }
319 
transformT(skvm::Builder * p,skvm::Uniforms * uniforms,skvm::Coord coord,skvm::I32 * mask) const320 skvm::F32 SkTwoPointConicalGradient::transformT(skvm::Builder* p, skvm::Uniforms* uniforms,
321                                                 skvm::Coord coord, skvm::I32* mask) const {
322     auto mag = [](skvm::F32 x, skvm::F32 y) { return sqrt(x*x + y*y); };
323 
324     // See https://skia.org/dev/design/conical, and appendStages() above.
325     // There's a lot going on here, and I'm not really sure what's independent
326     // or disjoint, what can be reordered, simplified, etc.  Tweak carefully.
327 
328     const skvm::F32 x = coord.x,
329                     y = coord.y;
330     if (fType == Type::kRadial) {
331         float denom = 1.0f / (fRadius2 - fRadius1),
332               scale = std::max(fRadius1, fRadius2) * denom,
333                bias =                  -fRadius1 * denom;
334         return mag(x,y) * p->uniformF(uniforms->pushF(scale))
335                         + p->uniformF(uniforms->pushF(bias ));
336     }
337 
338     if (fType == Type::kStrip) {
339         float r = fRadius1 / this->getCenterX1();
340         skvm::F32 t = x + sqrt(p->uniformF(uniforms->pushF(r*r)) - y*y);
341 
342         *mask = (t == t);   // t != NaN
343         return t;
344     }
345 
346     const skvm::F32 invR1 = p->uniformF(uniforms->pushF(1 / fFocalData.fR1));
347 
348     skvm::F32 t;
349     if (fFocalData.isFocalOnCircle()) {
350         t = (y/x) * y + x;       // (x^2 + y^2) / x  ~~>  x + y^2/x  ~~>  y/x * y + x
351     } else if (fFocalData.isWellBehaved()) {
352         t = mag(x,y) - x*invR1;
353     } else {
354         skvm::F32 k = sqrt(x*x - y*y);
355         if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
356             k = -k;
357         }
358         t = k - x*invR1;
359     }
360 
361     if (!fFocalData.isWellBehaved()) {
362         // TODO: not sure why we consider t == 0 degenerate
363         *mask = (t > 0.0f);  // and implicitly, t != NaN
364     }
365 
366     const skvm::F32 focalX = p->uniformF(uniforms->pushF(fFocalData.fFocalX));
367     if (1 - fFocalData.fFocalX < 0)    { t = -t; }
368     if (!fFocalData.isNativelyFocal()) { t += focalX; }
369     if ( fFocalData.isSwapped())       { t = 1.0f - t; }
370     return t;
371 }
372 
373 /////////////////////////////////////////////////////////////////////
374 
375 #if defined(SK_GANESH)
376 
377 #include "src/core/SkRuntimeEffectPriv.h"
378 #include "src/gpu/ganesh/effects/GrSkSLFP.h"
379 #include "src/gpu/ganesh/gradients/GrGradientShader.h"
380 
381 std::unique_ptr<GrFragmentProcessor>
asFragmentProcessor(const GrFPArgs & args,const MatrixRec & mRec) const382 SkTwoPointConicalGradient::asFragmentProcessor(const GrFPArgs& args, const MatrixRec& mRec) const {
383     // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
384     // was opaque. Thus, all of these layout FPs disable that optimization.
385     std::unique_ptr<GrFragmentProcessor> fp;
386     SkTLazy<SkMatrix> matrix;
387     switch (this->getType()) {
388         case SkTwoPointConicalGradient::Type::kStrip: {
389             static const SkRuntimeEffect* kEffect =
390                 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
391                         "uniform half r0_2;"
392                         "half4 main(float2 p) {"
393                             "half v = 1;" // validation flag,set to negative to discard fragment later
394                             "float t = r0_2 - p.y * p.y;"
395                             "if (t >= 0) {"
396                                 "t = p.x + sqrt(t);"
397                             "} else {"
398                                 "v = -1;"
399                             "}"
400                             "return half4(half(t), v, 0, 0);"
401                         "}"
402                     );
403             float r0 = this->getStartRadius() / this->getCenterX1();
404             fp = GrSkSLFP::Make(kEffect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr,
405                                 GrSkSLFP::OptFlags::kNone,
406                                 "r0_2", r0 * r0);
407         } break;
408 
409         case SkTwoPointConicalGradient::Type::kRadial: {
410             static const SkRuntimeEffect* kEffect =
411                 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
412                         "uniform half r0;"
413                         "uniform half lengthScale;"
414                         "half4 main(float2 p) {"
415                             "half v = 1;" // validation flag,set to negative to discard fragment later
416                             "float t = length(p) * lengthScale - r0;"
417                             "return half4(half(t), v, 0, 0);"
418                         "}"
419                     );
420             float dr = this->getDiffRadius();
421             float r0 = this->getStartRadius() / dr;
422             bool isRadiusIncreasing = dr >= 0;
423             fp = GrSkSLFP::Make(kEffect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr,
424                                 GrSkSLFP::OptFlags::kNone,
425                                 "r0", r0,
426                                 "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f);
427 
428             // GPU radial matrix is different from the original matrix, since we map the diff radius
429             // to have |dr| = 1, so manually compute the final gradient matrix here.
430 
431             // Map center to (0, 0)
432             matrix.set(SkMatrix::Translate(-this->getStartCenter().fX,
433                                            -this->getStartCenter().fY));
434             // scale |diffRadius| to 1
435             matrix->postScale(1 / dr, 1 / dr);
436         } break;
437 
438         case SkTwoPointConicalGradient::Type::kFocal: {
439             static const SkRuntimeEffect* kEffect =
440                 SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
441                         // Optimization flags, all specialized:
442                         "uniform int isRadiusIncreasing;"
443                         "uniform int isFocalOnCircle;"
444                         "uniform int isWellBehaved;"
445                         "uniform int isSwapped;"
446                         "uniform int isNativelyFocal;"
447 
448                         "uniform half invR1;"  // 1/r1
449                         "uniform half fx;"     // focalX = r0/(r0-r1)
450 
451                         "half4 main(float2 p) {"
452                             "float t = -1;"
453                             "half v = 1;" // validation flag,set to negative to discard fragment later
454 
455                             "float x_t = -1;"
456                             "if (bool(isFocalOnCircle)) {"
457                                 "x_t = dot(p, p) / p.x;"
458                             "} else if (bool(isWellBehaved)) {"
459                                 "x_t = length(p) - p.x * invR1;"
460                             "} else {"
461                                 "float temp = p.x * p.x - p.y * p.y;"
462 
463                                 // Only do sqrt if temp >= 0; this is significantly slower than
464                                 // checking temp >= 0 in the if statement that checks r(t) >= 0.
465                                 // But GPU may break if we sqrt a negative float. (Although I
466                                 // haven't observed that on any devices so far, and the old
467                                 // approach also does sqrt negative value without a check.) If
468                                 // the performance is really critical, maybe we should just
469                                 // compute the area where temp and x_t are always valid and drop
470                                 // all these ifs.
471                                 "if (temp >= 0) {"
472                                     "if (bool(isSwapped) || !bool(isRadiusIncreasing)) {"
473                                         "x_t = -sqrt(temp) - p.x * invR1;"
474                                     "} else {"
475                                         "x_t = sqrt(temp) - p.x * invR1;"
476                                     "}"
477                                 "}"
478                             "}"
479 
480                             // The final calculation of t from x_t has lots of static
481                             // optimizations but only do them when x_t is positive (which
482                             // can be assumed true if isWellBehaved is true)
483                             "if (!bool(isWellBehaved)) {"
484                                 // This will still calculate t even though it will be ignored
485                                 // later in the pipeline to avoid a branch
486                                 "if (x_t <= 0.0) {"
487                                     "v = -1;"
488                                 "}"
489                             "}"
490                             "if (bool(isRadiusIncreasing)) {"
491                                 "if (bool(isNativelyFocal)) {"
492                                     "t = x_t;"
493                                 "} else {"
494                                     "t = x_t + fx;"
495                                 "}"
496                             "} else {"
497                                 "if (bool(isNativelyFocal)) {"
498                                     "t = -x_t;"
499                                 "} else {"
500                                     "t = -x_t + fx;"
501                                 "}"
502                             "}"
503 
504                             "if (bool(isSwapped)) {"
505                                 "t = 1 - t;"
506                             "}"
507 
508                             "return half4(half(t), v, 0, 0);"
509                         "}"
510                     );
511 
512             const SkTwoPointConicalGradient::FocalData& focalData = this->getFocalData();
513             bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
514                     isFocalOnCircle    = focalData.isFocalOnCircle(),
515                     isWellBehaved      = focalData.isWellBehaved(),
516                     isSwapped          = focalData.isSwapped(),
517                     isNativelyFocal    = focalData.isNativelyFocal();
518 
519             fp = GrSkSLFP::Make(kEffect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
520                                 GrSkSLFP::OptFlags::kNone,
521                                 "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
522                                 "isFocalOnCircle",    GrSkSLFP::Specialize<int>(isFocalOnCircle),
523                                 "isWellBehaved",      GrSkSLFP::Specialize<int>(isWellBehaved),
524                                 "isSwapped",          GrSkSLFP::Specialize<int>(isSwapped),
525                                 "isNativelyFocal",    GrSkSLFP::Specialize<int>(isNativelyFocal),
526                                 "invR1", 1.0f / focalData.fR1,
527                                 "fx", focalData.fFocalX);
528         } break;
529     }
530     return GrGradientShader::MakeGradientFP(*this,
531                                             args,
532                                             mRec,
533                                             std::move(fp),
534                                             matrix.getMaybeNull());
535 }
536 
537 #endif
538 
539 #if defined(SK_GRAPHITE)
addToKey(const skgpu::graphite::KeyContext & keyContext,skgpu::graphite::PaintParamsKeyBuilder * builder,skgpu::graphite::PipelineDataGatherer * gatherer) const540 void SkTwoPointConicalGradient::addToKey(const skgpu::graphite::KeyContext& keyContext,
541                                          skgpu::graphite::PaintParamsKeyBuilder* builder,
542                                          skgpu::graphite::PipelineDataGatherer* gatherer) const {
543     using namespace skgpu::graphite;
544 
545     SkColor4fXformer xformedColors(this, keyContext.dstColorInfo().colorSpace());
546     const SkPMColor4f* colors = xformedColors.fColors.begin();
547 
548     GradientShaderBlocks::GradientData data(GradientType::kConical,
549                                             fCenter1, fCenter2,
550                                             fRadius1, fRadius2,
551                                             0.0f, 0.0f,
552                                             fTileMode,
553                                             fColorCount,
554                                             colors,
555                                             fPositions,
556                                             fInterpolation);
557 
558     MakeInterpolatedToDst(keyContext, builder, gatherer,
559                           data, fInterpolation,
560                           xformedColors.fIntermediateColorSpace.get());
561 }
562 #endif
563 
564 // assumes colors is SkColor4f* and pos is SkScalar*
565 #define EXPAND_1_COLOR(count)                \
566      SkColor4f tmp[2];                       \
567      do {                                    \
568          if (1 == count) {                   \
569              tmp[0] = tmp[1] = colors[0];    \
570              colors = tmp;                   \
571              pos = nullptr;                  \
572              count = 2;                      \
573          }                                   \
574      } while (0)
575 
MakeTwoPointConical(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,const SkColor4f colors[],sk_sp<SkColorSpace> colorSpace,const SkScalar pos[],int colorCount,SkTileMode mode,const Interpolation & interpolation,const SkMatrix * localMatrix)576 sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
577                                                       SkScalar startRadius,
578                                                       const SkPoint& end,
579                                                       SkScalar endRadius,
580                                                       const SkColor4f colors[],
581                                                       sk_sp<SkColorSpace> colorSpace,
582                                                       const SkScalar pos[],
583                                                       int colorCount,
584                                                       SkTileMode mode,
585                                                       const Interpolation& interpolation,
586                                                       const SkMatrix* localMatrix) {
587     if (startRadius < 0 || endRadius < 0) {
588         return nullptr;
589     }
590     if (!SkGradientShaderBase::ValidGradient(colors, colorCount, mode, interpolation)) {
591         return nullptr;
592     }
593     if (SkScalarNearlyZero((start - end).length(), SkGradientShaderBase::kDegenerateThreshold)) {
594         // If the center positions are the same, then the gradient is the radial variant of a 2 pt
595         // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate
596         // (startRadius == endRadius).
597         if (SkScalarNearlyEqual(startRadius, endRadius,
598                                 SkGradientShaderBase::kDegenerateThreshold)) {
599             // Degenerate case, where the interpolation region area approaches zero. The proper
600             // behavior depends on the tile mode, which is consistent with the default degenerate
601             // gradient behavior, except when mode = clamp and the radii > 0.
602             if (mode == SkTileMode::kClamp &&
603                 endRadius > SkGradientShaderBase::kDegenerateThreshold) {
604                 // The interpolation region becomes an infinitely thin ring at the radius, so the
605                 // final gradient will be the first color repeated from p=0 to 1, and then a hard
606                 // stop switching to the last color at p=1.
607                 static constexpr SkScalar circlePos[3] = {0, 1, 1};
608                 SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
609                 return MakeRadial(start, endRadius, reColors, std::move(colorSpace),
610                                   circlePos, 3, mode, interpolation, localMatrix);
611             } else {
612                 // Otherwise use the default degenerate case
613                 return SkGradientShaderBase::MakeDegenerateGradient(colors, pos, colorCount,
614                                                                     std::move(colorSpace), mode);
615             }
616         } else if (SkScalarNearlyZero(startRadius, SkGradientShaderBase::kDegenerateThreshold)) {
617             // We can treat this gradient as radial, which is faster. If we got here, we know
618             // that endRadius is not equal to 0, so this produces a meaningful gradient
619             return MakeRadial(start, endRadius, colors, std::move(colorSpace), pos, colorCount,
620                               mode, interpolation, localMatrix);
621         }
622         // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the
623         // regular 2pt constructor.
624     }
625 
626     if (localMatrix && !localMatrix->invert(nullptr)) {
627         return nullptr;
628     }
629     EXPAND_1_COLOR(colorCount);
630 
631     SkGradientShaderBase::ColorStopOptimizer opt(colors, pos, colorCount, mode);
632 
633     SkGradientShaderBase::Descriptor desc(opt.fColors, std::move(colorSpace), opt.fPos,
634                                           opt.fCount, mode, interpolation);
635     return SkTwoPointConicalGradient::Create(start, startRadius, end, endRadius, desc, localMatrix);
636 }
637 
638 #undef EXPAND_1_COLOR
639 
MakeTwoPointConical(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,const SkColor colors[],const SkScalar pos[],int colorCount,SkTileMode mode,uint32_t flags,const SkMatrix * localMatrix)640 sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
641                                                       SkScalar startRadius,
642                                                       const SkPoint& end,
643                                                       SkScalar endRadius,
644                                                       const SkColor colors[],
645                                                       const SkScalar pos[],
646                                                       int colorCount,
647                                                       SkTileMode mode,
648                                                       uint32_t flags,
649                                                       const SkMatrix* localMatrix) {
650     SkColorConverter converter(colors, colorCount);
651     return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(),
652                                nullptr, pos, colorCount, mode, flags, localMatrix);
653 }
654 
SkRegisterTwoPointConicalGradientShaderFlattenable()655 void SkRegisterTwoPointConicalGradientShaderFlattenable() {
656     SK_REGISTER_FLATTENABLE(SkTwoPointConicalGradient);
657 }
658