• 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 "src/shaders/gradients/SkTwoPointConicalGradient.h"
9 
10 #include "include/private/SkFloatingPoint.h"
11 #include "src/core/SkKeyHelpers.h"
12 #include "src/core/SkRasterPipeline.h"
13 #include "src/core/SkReadBuffer.h"
14 #include "src/core/SkWriteBuffer.h"
15 
16 #include <utility>
17 
18 // Please see https://skia.org/dev/design/conical for how our shader works.
19 
set(SkScalar r0,SkScalar r1,SkMatrix * matrix)20 bool SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) {
21     fIsSwapped = false;
22     fFocalX = sk_ieee_float_divide(r0, (r0 - r1));
23     if (SkScalarNearlyZero(fFocalX - 1)) {
24         // swap r0, r1
25         matrix->postTranslate(-1, 0);
26         matrix->postScale(-1, 1);
27         std::swap(r0, r1);
28         fFocalX = 0; // because r0 is now 0
29         fIsSwapped = true;
30     }
31 
32     // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
33     const SkPoint from[2]   = { {fFocalX, 0}, {1, 0} };
34     const SkPoint to[2]     = { {0, 0}, {1, 0} };
35     SkMatrix focalMatrix;
36     if (!focalMatrix.setPolyToPoly(from, to, 2)) {
37         return false;
38     }
39     matrix->postConcat(focalMatrix);
40     fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
41 
42     // The following transformations are just to accelerate the shader computation by saving
43     // some arithmatic operations.
44     if (this->isFocalOnCircle()) {
45         matrix->postScale(0.5, 0.5);
46     } else {
47         matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
48     }
49     matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
50     return true;
51 }
52 
Create(const SkPoint & c0,SkScalar r0,const SkPoint & c1,SkScalar r1,const Descriptor & desc)53 sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0,
54                                                   const SkPoint& c1, SkScalar r1,
55                                                   const Descriptor& desc) {
56     SkMatrix gradientMatrix;
57     Type     gradientType;
58 
59     if (SkScalarNearlyZero((c0 - c1).length())) {
60         if (SkScalarNearlyZero(std::max(r0, r1)) || SkScalarNearlyEqual(r0, r1)) {
61             // Degenerate case; avoid dividing by zero. Should have been caught by caller but
62             // just in case, recheck here.
63             return nullptr;
64         }
65         // Concentric case: we can pretend we're radial (with a tiny twist).
66         const SkScalar scale = sk_ieee_float_divide(1, std::max(r0, r1));
67         gradientMatrix = SkMatrix::Translate(-c1.x(), -c1.y());
68         gradientMatrix.postScale(scale, scale);
69 
70         gradientType = Type::kRadial;
71     } else {
72         const SkPoint centers[2] = { c0    , c1     };
73         const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
74 
75         if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) {
76             // Degenerate case.
77             return nullptr;
78         }
79 
80         gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
81     }
82 
83     FocalData focalData;
84     if (gradientType == Type::kFocal) {
85         const auto dCenter = (c0 - c1).length();
86         if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) {
87             return nullptr;
88         }
89     }
90     return sk_sp<SkShader>(new SkTwoPointConicalGradient(c0, r0, c1, r1, desc,
91                                                          gradientType, gradientMatrix, focalData));
92 }
93 
SkTwoPointConicalGradient(const SkPoint & start,SkScalar startRadius,const SkPoint & end,SkScalar endRadius,const Descriptor & desc,Type type,const SkMatrix & gradientMatrix,const FocalData & data)94 SkTwoPointConicalGradient::SkTwoPointConicalGradient(
95         const SkPoint& start, SkScalar startRadius,
96         const SkPoint& end, SkScalar endRadius,
97         const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data)
98     : SkGradientShaderBase(desc, gradientMatrix)
99     , fCenter1(start)
100     , fCenter2(end)
101     , fRadius1(startRadius)
102     , fRadius2(endRadius)
103     , fType(type)
104 {
105     // this is degenerate, and should be caught by our caller
106     SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
107     if (type == Type::kFocal) {
108         fFocalData = data;
109     }
110 }
111 
isOpaque() const112 bool SkTwoPointConicalGradient::isOpaque() const {
113     // Because areas outside the cone are left untouched, we cannot treat the
114     // shader as opaque even if the gradient itself is opaque.
115     // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
116     return false;
117 }
118 
119 // Returns the original non-sorted version of the gradient
asAGradient(GradientInfo * info) const120 SkShader::GradientType SkTwoPointConicalGradient::asAGradient(GradientInfo* info) const {
121     if (info) {
122         commonAsAGradient(info);
123         info->fPoint[0] = fCenter1;
124         info->fPoint[1] = fCenter2;
125         info->fRadius[0] = fRadius1;
126         info->fRadius[1] = fRadius2;
127     }
128     return kConical_GradientType;
129 }
130 
CreateProc(SkReadBuffer & buffer)131 sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
132     DescriptorScope desc;
133     if (!desc.unflatten(buffer)) {
134         return nullptr;
135     }
136     SkPoint c1 = buffer.readPoint();
137     SkPoint c2 = buffer.readPoint();
138     SkScalar r1 = buffer.readScalar();
139     SkScalar r2 = buffer.readScalar();
140 
141     if (!buffer.isValid()) {
142         return nullptr;
143     }
144     return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors,
145                                                  std::move(desc.fColorSpace), desc.fPos,
146                                                  desc.fCount, desc.fTileMode, desc.fGradFlags,
147                                                  desc.fLocalMatrix);
148 }
149 
flatten(SkWriteBuffer & buffer) const150 void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
151     this->INHERITED::flatten(buffer);
152     buffer.writePoint(fCenter1);
153     buffer.writePoint(fCenter2);
154     buffer.writeScalar(fRadius1);
155     buffer.writeScalar(fRadius2);
156 }
157 
appendGradientStages(SkArenaAlloc * alloc,SkRasterPipeline * p,SkRasterPipeline * postPipeline) const158 void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
159                                                      SkRasterPipeline* postPipeline) const {
160     const auto dRadius = fRadius2 - fRadius1;
161 
162     if (fType == Type::kRadial) {
163         p->append(SkRasterPipeline::xy_to_radius);
164 
165         // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
166         auto scale =  std::max(fRadius1, fRadius2) / dRadius;
167         auto bias  = -fRadius1 / dRadius;
168 
169         p->append_matrix(alloc, SkMatrix::Translate(bias, 0) * SkMatrix::Scale(scale, 1));
170         return;
171     }
172 
173     if (fType == Type::kStrip) {
174         auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
175         SkScalar scaledR0 = fRadius1 / this->getCenterX1();
176         ctx->fP0 = scaledR0 * scaledR0;
177         p->append(SkRasterPipeline::xy_to_2pt_conical_strip, ctx);
178         p->append(SkRasterPipeline::mask_2pt_conical_nan, ctx);
179         postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
180         return;
181     }
182 
183     auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
184     ctx->fP0 = 1/fFocalData.fR1;
185     ctx->fP1 = fFocalData.fFocalX;
186 
187     if (fFocalData.isFocalOnCircle()) {
188         p->append(SkRasterPipeline::xy_to_2pt_conical_focal_on_circle);
189     } else if (fFocalData.isWellBehaved()) {
190         p->append(SkRasterPipeline::xy_to_2pt_conical_well_behaved, ctx);
191     } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
192         p->append(SkRasterPipeline::xy_to_2pt_conical_smaller, ctx);
193     } else {
194         p->append(SkRasterPipeline::xy_to_2pt_conical_greater, ctx);
195     }
196 
197     if (!fFocalData.isWellBehaved()) {
198         p->append(SkRasterPipeline::mask_2pt_conical_degenerates, ctx);
199     }
200     if (1 - fFocalData.fFocalX < 0) {
201         p->append(SkRasterPipeline::negate_x);
202     }
203     if (!fFocalData.isNativelyFocal()) {
204         p->append(SkRasterPipeline::alter_2pt_conical_compensate_focal, ctx);
205     }
206     if (fFocalData.isSwapped()) {
207         p->append(SkRasterPipeline::alter_2pt_conical_unswap);
208     }
209     if (!fFocalData.isWellBehaved()) {
210         postPipeline->append(SkRasterPipeline::apply_vector_mask, &ctx->fMask);
211     }
212 }
213 
transformT(skvm::Builder * p,skvm::Uniforms * uniforms,skvm::Coord coord,skvm::I32 * mask) const214 skvm::F32 SkTwoPointConicalGradient::transformT(skvm::Builder* p, skvm::Uniforms* uniforms,
215                                                 skvm::Coord coord, skvm::I32* mask) const {
216     auto mag = [](skvm::F32 x, skvm::F32 y) { return sqrt(x*x + y*y); };
217 
218     // See https://skia.org/dev/design/conical, and onAppendStages() above.
219     // There's a lot going on here, and I'm not really sure what's independent
220     // or disjoint, what can be reordered, simplified, etc.  Tweak carefully.
221 
222     const skvm::F32 x = coord.x,
223                     y = coord.y;
224     if (fType == Type::kRadial) {
225         float denom = 1.0f / (fRadius2 - fRadius1),
226               scale = std::max(fRadius1, fRadius2) * denom,
227                bias =                  -fRadius1 * denom;
228         return mag(x,y) * p->uniformF(uniforms->pushF(scale))
229                         + p->uniformF(uniforms->pushF(bias ));
230     }
231 
232     if (fType == Type::kStrip) {
233         float r = fRadius1 / this->getCenterX1();
234         skvm::F32 t = x + sqrt(p->uniformF(uniforms->pushF(r*r)) - y*y);
235 
236         *mask = (t == t);   // t != NaN
237         return t;
238     }
239 
240     const skvm::F32 invR1 = p->uniformF(uniforms->pushF(1 / fFocalData.fR1));
241 
242     skvm::F32 t;
243     if (fFocalData.isFocalOnCircle()) {
244         t = (y/x) * y + x;       // (x^2 + y^2) / x  ~~>  x + y^2/x  ~~>  y/x * y + x
245     } else if (fFocalData.isWellBehaved()) {
246         t = mag(x,y) - x*invR1;
247     } else {
248         skvm::F32 k = sqrt(x*x - y*y);
249         if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
250             k = -k;
251         }
252         t = k - x*invR1;
253     }
254 
255     if (!fFocalData.isWellBehaved()) {
256         // TODO: not sure why we consider t == 0 degenerate
257         *mask = (t > 0.0f);  // and implicitly, t != NaN
258     }
259 
260     const skvm::F32 focalX = p->uniformF(uniforms->pushF(fFocalData.fFocalX));
261     if (1 - fFocalData.fFocalX < 0)    { t = -t; }
262     if (!fFocalData.isNativelyFocal()) { t += focalX; }
263     if ( fFocalData.isSwapped())       { t = 1.0f - t; }
264     return t;
265 }
266 
267 /////////////////////////////////////////////////////////////////////
268 
269 #if SK_SUPPORT_GPU
270 
271 #include "src/gpu/gradients/GrGradientShader.h"
272 
asFragmentProcessor(const GrFPArgs & args) const273 std::unique_ptr<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor(
274         const GrFPArgs& args) const {
275     return GrGradientShader::MakeConical(*this, args);
276 }
277 
278 #endif
279 
addToKey(SkShaderCodeDictionary * dict,SkBackend backend,SkPaintParamsKeyBuilder * builder,SkUniformBlock * uniformBlock) const280 void SkTwoPointConicalGradient::addToKey(SkShaderCodeDictionary* dict,
281                                          SkBackend backend,
282                                          SkPaintParamsKeyBuilder* builder,
283                                          SkUniformBlock* uniformBlock) const {
284     GradientShaderBlocks::GradientData data(kConical_GradientType,
285                                             fCenter1, fCenter2,
286                                             fRadius1, fRadius2,
287                                             fTileMode,
288                                             fColorCount,
289                                             fOrigColors4f,
290                                             fOrigPos);
291 
292     GradientShaderBlocks::AddToKey(dict, backend, builder, uniformBlock, data);
293 }
294