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