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 
9 #include "SkTwoPointConicalGradient.h"
10 
11 #if SK_SUPPORT_GPU
12 #include "glsl/GrGLSLFragmentShaderBuilder.h"
13 #include "glsl/GrGLSLProgramDataManager.h"
14 #include "glsl/GrGLSLUniformHandler.h"
15 #include "SkTwoPointConicalGradient_gpu.h"
16 
17 // For brevity
18 typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
19 
20 // Please see https://skia.org/dev/design/conical for how our shader works.
21 class TwoPointConicalEffect : public GrGradientEffect {
22 public:
23     using Type = SkTwoPointConicalGradient::Type;
24     class DegeneratedGLSLProcessor; // radial (center0 == center1) or strip (r0 == r1) case
25     class FocalGLSLProcessor; // all other cases where we can derive a focal point
26 
27     struct Data {
28         Type        fType;
29         SkScalar    fRadius0;
30         SkScalar    fDiffRadius;
31         SkTwoPointConicalGradient::FocalData fFocalData;
32 
33         // Construct from the shader, and set the matrix accordingly
34         Data(const SkTwoPointConicalGradient& shader, SkMatrix& matrix);
35 
operator ==TwoPointConicalEffect::Data36         bool operator== (const Data& d) const {
37             if (fType != d.fType) {
38                 return false;
39             }
40             switch (fType) {
41                 case Type::kRadial:
42                 case Type::kStrip:
43                     return fRadius0 == d.fRadius0 && fDiffRadius == d.fDiffRadius;
44                 case Type::kFocal:
45                     return fFocalData.fR1 == d.fFocalData.fR1 &&
46                             fFocalData.fFocalX == d.fFocalData.fFocalX &&
47                             fFocalData.fIsSwapped == d.fFocalData.fIsSwapped;
48             }
49             SkDEBUGFAIL("This return should be unreachable; it's here just for compile warning");
50             return false;
51         }
52     };
53 
54     static std::unique_ptr<GrFragmentProcessor> Make(const CreateArgs& args, const Data& data);
55 
diffRadius() const56     SkScalar diffRadius() const {
57         SkASSERT(!this->isFocal()); // fDiffRadius is uninitialized for focal cases
58         return fData.fDiffRadius;
59     }
r0() const60     SkScalar r0() const {
61         SkASSERT(!this->isFocal()); // fRadius0 is uninitialized for focal cases
62         return fData.fRadius0;
63     }
64 
r1() const65     SkScalar r1() const {
66         SkASSERT(this->isFocal()); // fFocalData is uninitialized for non-focal cases
67         return fData.fFocalData.fR1;
68     }
focalX() const69     SkScalar focalX() const {
70         SkASSERT(this->isFocal()); // fFocalData is uninitialized for non-focal cases
71         return fData.fFocalData.fFocalX;
72     }
73 
name() const74     const char* name() const override { return "Two-Point Conical Gradient"; }
75 
76     // Whether the focal point (0, 0) is on the end circle with center (1, 0) and radius r1. If this
77     // is true, it's as if an aircraft is flying at Mach 1 and all circles (soundwaves) will go
78     // through the focal point (aircraft). In our previous implementations, this was known as the
79     // edge case where the inside circle touches the outside circle (on the focal point). If we were
80     // to solve for t bruteforcely using a quadratic equation, this case implies that the quadratic
81     // equation degenerates to a linear equation.
isFocalOnCircle() const82     bool isFocalOnCircle() const { return this->isFocal() && fData.fFocalData.isFocalOnCircle(); }
isSwapped() const83     bool isSwapped() const { return this->isFocal() && fData.fFocalData.isSwapped(); }
84 
getType() const85     Type getType() const { return fData.fType; }
isFocal() const86     bool isFocal() const { return fData.fType == Type::kFocal; }
87 
88     // Whether the t we solved is always valid (so we don't need to check r(t) > 0).
isWellBehaved() const89     bool isWellBehaved() const { return this->isFocal() && fData.fFocalData.isWellBehaved(); }
90 
91     // Whether r0 == 0 so it's focal without any transformation
isNativelyFocal() const92     bool isNativelyFocal() const { return this->isFocal() && fData.fFocalData.isNativelyFocal(); }
93 
94     // Note that focalX = f = r0 / (r0 - r1), so 1 - focalX > 0 == r0 < r1
isRadiusIncreasing() const95     bool isRadiusIncreasing() const { return this->isFocal() && 1 - fData.fFocalData.fFocalX > 0; }
96 
97 protected:
onGetGLSLProcessorKey(const GrShaderCaps & c,GrProcessorKeyBuilder * b) const98     void onGetGLSLProcessorKey(const GrShaderCaps& c, GrProcessorKeyBuilder* b) const override {
99         INHERITED::onGetGLSLProcessorKey(c, b);
100         uint32_t key = 0;
101         key |= static_cast<int>(fData.fType);
102         SkASSERT(key < (1 << 2));
103         key |= (this->isFocalOnCircle() << 2);
104         key |= (this->isWellBehaved() << 3);
105         key |= (this->isRadiusIncreasing() << 4);
106         key |= (this->isNativelyFocal() << 5);
107         key |= (this->isSwapped() << 6);
108         SkASSERT(key < (1 << 7));
109         b->add32(key);
110     }
111 
112 
113     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
114 
clone() const115     std::unique_ptr<GrFragmentProcessor> clone() const override {
116         return std::unique_ptr<GrFragmentProcessor>(new TwoPointConicalEffect(*this));
117     }
118 
onIsEqual(const GrFragmentProcessor & sBase) const119     bool onIsEqual(const GrFragmentProcessor& sBase) const override {
120         const TwoPointConicalEffect& s = sBase.cast<TwoPointConicalEffect>();
121         return (INHERITED::onIsEqual(sBase) && fData == s.fData);
122     }
123 
TwoPointConicalEffect(const CreateArgs & args,const Data data)124     explicit TwoPointConicalEffect(const CreateArgs& args, const Data data)
125         : INHERITED(kTwoPointConicalEffect_ClassID, args,
126             false /* opaque: draws transparent black outside of the cone. */)
127         , fData(data) {}
128 
TwoPointConicalEffect(const TwoPointConicalEffect & that)129     explicit TwoPointConicalEffect(const TwoPointConicalEffect& that)
130             : INHERITED(that)
131             , fData(that.fData) {}
132 
133     GR_DECLARE_FRAGMENT_PROCESSOR_TEST
134 
135     Data fData;
136 
137     typedef GrGradientEffect INHERITED;
138 };
139 
140 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(TwoPointConicalEffect);
141 
142 #if GR_TEST_UTILS
143 
TestCreate(GrProcessorTestData * d)144 std::unique_ptr<GrFragmentProcessor> TwoPointConicalEffect::TestCreate(
145         GrProcessorTestData* d) {
146     SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()};
147     SkPoint center2 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()};
148     SkScalar radius1 = d->fRandom->nextUScalar1();
149     SkScalar radius2 = d->fRandom->nextUScalar1();
150 
151     constexpr int   kTestTypeMask           = (1 << 2) - 1,
152                     kTestNativelyFocalBit   = (1 << 2),
153                     kTestFocalOnCircleBit   = (1 << 3),
154                     kTestSwappedBit         = (1 << 4);
155                     // We won't treat isWellDefined and isRadiusIncreasing specially beacuse they
156                     // should have high probability to be turned on and off as we're getting random
157                     // radii and centers.
158 
159     int mask = d->fRandom->nextU();
160     int type = mask & kTestTypeMask;
161     if (type == static_cast<int>(TwoPointConicalEffect::Type::kRadial)) {
162         center2 = center1;
163         // Make sure that the radii are different
164         if (SkScalarNearlyZero(radius1 - radius2)) {
165             radius2 += .1f;
166         }
167     } else if (type == static_cast<int>(TwoPointConicalEffect::Type::kStrip)) {
168         radius1 = SkTMax(radius1, .1f); // Make sure that the radius is non-zero
169         radius2 = radius1;
170         // Make sure that the centers are different
171         if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
172             center2.fX += .1f;
173         }
174     } else { // kFocal_Type
175         // Make sure that the centers are different
176         if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
177             center2.fX += .1f;
178         }
179 
180         if (kTestNativelyFocalBit & mask) {
181             radius1 = 0;
182         }
183         if (kTestFocalOnCircleBit & mask) {
184             radius2 = radius1 + SkPoint::Distance(center1, center2);
185         }
186         if (kTestSwappedBit & mask) {
187             std::swap(radius1, radius2);
188             radius2 = 0;
189         }
190 
191         // Make sure that the radii are different
192         if (SkScalarNearlyZero(radius1 - radius2)) {
193             radius2 += .1f;
194         }
195     }
196 
197     if (SkScalarNearlyZero(radius1 - radius2) &&
198             SkScalarNearlyZero(SkPoint::Distance(center1, center2))) {
199         radius2 += .1f; // make sure that we're not degenerated
200     }
201 
202     RandomGradientParams params(d->fRandom);
203     auto shader = params.fUseColors4f ?
204         SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2,
205                                               params.fColors4f, params.fColorSpace, params.fStops,
206                                               params.fColorCount, params.fTileMode) :
207         SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2,
208                                               params.fColors, params.fStops,
209                                               params.fColorCount, params.fTileMode);
210     GrTest::TestAsFPArgs asFPArgs(d);
211     std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
212 
213     GrAlwaysAssert(fp);
214     return fp;
215 }
216 #endif
217 
218 //////////////////////////////////////////////////////////////////////////////
219 // DegeneratedGLSLProcessor
220 //////////////////////////////////////////////////////////////////////////////
221 
222 class TwoPointConicalEffect::DegeneratedGLSLProcessor : public GrGradientEffect::GLSLProcessor {
223 protected:
emitCode(EmitArgs & args)224     void emitCode(EmitArgs& args) override {
225         const TwoPointConicalEffect& effect = args.fFp.cast<TwoPointConicalEffect>();
226         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
227         this->emitUniforms(uniformHandler, effect);
228         fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
229                                                "Conical2FSParams");
230 
231         SkString p0; // r0 for radial case, r0^2 for strip case
232         p0.appendf("%s", uniformHandler->getUniformVariable(fParamUni).getName().c_str());
233         const char* tName = "t"; // the gradient
234 
235         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
236         SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
237         const char* p = coords2D.c_str();
238 
239         if (effect.getType() == Type::kRadial) {
240             fragBuilder->codeAppendf("half %s = length(%s) - %s;", tName, p, p0.c_str());
241         } else {
242             // output will default to transparent black (we simply won't write anything
243             // else to it if invalid, instead of discarding or returning prematurely)
244             fragBuilder->codeAppendf("%s = half4(0.0,0.0,0.0,0.0);", args.fOutputColor);
245             fragBuilder->codeAppendf("half temp = %s - %s.y * %s.y;", p0.c_str(), p, p);
246             fragBuilder->codeAppendf("if (temp >= 0) {");
247             fragBuilder->codeAppendf("half %s = %s.x + sqrt(temp);", tName, p);
248         }
249         this->emitColor(fragBuilder,
250                         uniformHandler,
251                         args.fShaderCaps,
252                         effect,
253                         tName,
254                         args.fOutputColor,
255                         args.fInputColor,
256                         args.fTexSamplers);
257 
258         if (effect.getType() != Type::kRadial) {
259             fragBuilder->codeAppendf("}");
260         }
261     }
262 
onSetData(const GrGLSLProgramDataManager & pdman,const GrFragmentProcessor & p)263     void onSetData(const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& p) override {
264         INHERITED::onSetData(pdman, p);
265         const TwoPointConicalEffect& effect = p.cast<TwoPointConicalEffect>();
266         // kRadialType should imply r1 - r0 = 1 (after our transformation) so r0 = r0 / (r1 - r0)
267         SkASSERT(effect.getType() == Type::kStrip || SkScalarNearlyZero(effect.diffRadius() - 1));
268         pdman.set1f(fParamUni, effect.getType() == Type::kRadial ? effect.r0()
269                                                                  : effect.r0() * effect.r0());
270     }
271 
272     UniformHandle fParamUni;
273 
274 private:
275     typedef GrGradientEffect::GLSLProcessor INHERITED;
276 };
277 
278 //////////////////////////////////////////////////////////////////////////////
279 // FocalGLSLProcessor
280 //////////////////////////////////////////////////////////////////////////////
281 
282 // Please see https://skia.org/dev/design/conical for how our shader works.
283 class TwoPointConicalEffect::FocalGLSLProcessor : public GrGradientEffect::GLSLProcessor {
284 protected:
emitCode(EmitArgs & args)285     void emitCode(EmitArgs& args) override {
286         const TwoPointConicalEffect& effect = args.fFp.cast<TwoPointConicalEffect>();
287         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
288         this->emitUniforms(uniformHandler, effect);
289         fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
290                                                "Conical2FSParams");
291 
292         SkString p0; // 1 / r1
293         SkString p1; // f = focalX = r0 / (r0 - r1)
294         p0.appendf("%s.x", uniformHandler->getUniformVariable(fParamUni).getName().c_str());
295         p1.appendf("%s.y", uniformHandler->getUniformVariable(fParamUni).getName().c_str());
296         const char* tName = "t"; // the gradient
297 
298         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
299         SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
300         const char* p = coords2D.c_str();
301 
302         if (effect.isFocalOnCircle()) {
303             fragBuilder->codeAppendf("half x_t = dot(%s, %s) / %s.x;", p, p, p);
304         } else if (effect.isWellBehaved()) {
305             fragBuilder->codeAppendf("half x_t = length(%s) - %s.x * %s;", p, p, p0.c_str());
306         } else {
307             char sign = (effect.isSwapped() || !effect.isRadiusIncreasing()) ? '-' : ' ';
308             fragBuilder->codeAppendf("half temp = %s.x * %s.x - %s.y * %s.y;", p, p, p, p);
309             // Initialize x_t to illegal state
310             fragBuilder->codeAppendf("half x_t = -1;");
311 
312             // Only do sqrt if temp >= 0; this is significantly slower than checking temp >= 0 in
313             // the if statement that checks r(t) >= 0. But GPU may break if we sqrt a negative
314             // float. (Although I havevn't observed that on any devices so far, and the old approach
315             // also does sqrt negative value without a check.) If the performance is really
316             // critical, maybe we should just compute the area where temp and x_t are always
317             // valid and drop all these ifs.
318             fragBuilder->codeAppendf("if (temp >= 0) {");
319             fragBuilder->codeAppendf("x_t = (%csqrt(temp) - %s.x * %s);", sign, p, p0.c_str());
320             fragBuilder->codeAppendf("}");
321         }
322 
323         // empty sign is positive
324         char sign = effect.isRadiusIncreasing() ? ' ' : '-';
325 
326         // "+ 0" is much faster than "+ p1" so we specialize the natively focal case where p1 = 0.
327         fragBuilder->codeAppendf("half %s = %cx_t + %s;", tName, sign,
328                 effect.isNativelyFocal() ? "0" : p1.c_str());
329 
330         if (!effect.isWellBehaved()) {
331             // output will default to transparent black (we simply won't write anything
332             // else to it if invalid, instead of discarding or returning prematurely)
333             fragBuilder->codeAppendf("%s = half4(0.0,0.0,0.0,0.0);", args.fOutputColor);
334             fragBuilder->codeAppendf("if (x_t > 0.0) {");
335         }
336 
337         if (effect.isSwapped()) {
338             fragBuilder->codeAppendf("%s = 1 - %s;", tName, tName);
339         }
340 
341         this->emitColor(fragBuilder,
342                         uniformHandler,
343                         args.fShaderCaps,
344                         effect,
345                         tName,
346                         args.fOutputColor,
347                         args.fInputColor,
348                         args.fTexSamplers);
349         if (!effect.isWellBehaved()) {
350             fragBuilder->codeAppend("};");
351         }
352     }
353 
onSetData(const GrGLSLProgramDataManager & pdman,const GrFragmentProcessor & p)354     void onSetData(const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& p) override {
355         INHERITED::onSetData(pdman, p);
356         const TwoPointConicalEffect& effect = p.cast<TwoPointConicalEffect>();
357         pdman.set2f(fParamUni, 1 / effect.r1(), effect.focalX());
358     }
359 
360     UniformHandle fParamUni;
361 
362 private:
363     typedef GrGradientEffect::GLSLProcessor INHERITED;
364 };
365 
366 //////////////////////////////////////////////////////////////////////////////
367 
onCreateGLSLInstance() const368 GrGLSLFragmentProcessor* TwoPointConicalEffect::onCreateGLSLInstance() const {
369     if (fData.fType == Type::kRadial || fData.fType == Type::kStrip) {
370         return new DegeneratedGLSLProcessor;
371     }
372     return new FocalGLSLProcessor;
373 }
374 
Make(const GrGradientEffect::CreateArgs & args,const Data & data)375 std::unique_ptr<GrFragmentProcessor> TwoPointConicalEffect::Make(
376         const GrGradientEffect::CreateArgs& args, const Data& data) {
377     return GrGradientEffect::AdjustFP(
378             std::unique_ptr<TwoPointConicalEffect>(new TwoPointConicalEffect(args, data)),
379             args);
380 }
381 
Make(const GrGradientEffect::CreateArgs & args)382 std::unique_ptr<GrFragmentProcessor> Gr2PtConicalGradientEffect::Make(
383         const GrGradientEffect::CreateArgs& args) {
384     const SkTwoPointConicalGradient& shader =
385         *static_cast<const SkTwoPointConicalGradient*>(args.fShader);
386 
387     SkMatrix matrix;
388     if (!shader.getLocalMatrix().invert(&matrix)) {
389         return nullptr;
390     }
391     if (args.fMatrix) {
392         SkMatrix inv;
393         if (!args.fMatrix->invert(&inv)) {
394             return nullptr;
395         }
396         matrix.postConcat(inv);
397     }
398 
399     GrGradientEffect::CreateArgs newArgs(args.fContext, args.fShader, &matrix, args.fWrapMode,
400         args.fDstColorSpace);
401     // Data and matrix has to be prepared before constructing TwoPointConicalEffect so its parent
402     // class can have the right matrix to work with during construction.
403     TwoPointConicalEffect::Data data(shader, matrix);
404     return TwoPointConicalEffect::Make(newArgs, data);
405 }
406 
Data(const SkTwoPointConicalGradient & shader,SkMatrix & matrix)407 TwoPointConicalEffect::Data::Data(const SkTwoPointConicalGradient& shader, SkMatrix& matrix) {
408     fType = shader.getType();
409     if (fType == Type::kRadial) {
410         SkScalar dr = shader.getDiffRadius();
411         // Map center to (0, 0) and scale dr to 1
412         matrix.postTranslate(-shader.getStartCenter().fX, -shader.getStartCenter().fY);
413         matrix.postScale(1 / dr, 1 / dr);
414         fRadius0 = shader.getStartRadius() / dr;
415         fDiffRadius = 1;
416     } else if (fType == Type::kStrip) {
417         fRadius0 = shader.getStartRadius() / shader.getCenterX1();
418         fDiffRadius = 0;
419         matrix.postConcat(shader.getGradientMatrix());
420     } else if (fType == Type::kFocal) {
421         fFocalData = shader.getFocalData();
422         matrix.postConcat(shader.getGradientMatrix());
423     }
424 }
425 
426 #endif
427