blob: 7cdb62dc44c516154b24dabacb83c4cddb77172f [file] [log] [blame]
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +00001/*
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 */
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +00007
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +00008#include "SkTwoPointConicalGradient_gpu.h"
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +00009
10#include "SkTwoPointConicalGradient.h"
11
commit-bot@chromium.orgef93d292014-04-10 15:37:52 +000012#if SK_SUPPORT_GPU
commit-bot@chromium.org7f434932014-04-10 16:03:06 +000013#include "GrTBackendEffectFactory.h"
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +000014// For brevity
15typedef GrGLUniformManager::UniformHandle UniformHandle;
16
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +000017//////////////////////////////////////////////////////////////////////////////
18
19static void set_matrix_default_conical(const SkTwoPointConicalGradient& shader,
20 SkMatrix* invLMatrix) {
21 // Inverse of the current local matrix is passed in then,
22 // translate to center1, rotate so center2 is on x axis.
23 const SkPoint& center1 = shader.getStartCenter();
24 const SkPoint& center2 = shader.getEndCenter();
25
26 invLMatrix->postTranslate(-center1.fX, -center1.fY);
27
28 SkPoint diff = center2 - center1;
29 SkScalar diffLen = diff.length();
30 if (0 != diffLen) {
31 SkScalar invDiffLen = SkScalarInvert(diffLen);
32 SkMatrix rot;
33 rot.setSinCos(-SkScalarMul(invDiffLen, diff.fY),
34 SkScalarMul(invDiffLen, diff.fX));
35 invLMatrix->postConcat(rot);
36 }
37}
38
39class GLDefault2PtConicalEffect;
40
41class Default2PtConicalEffect : public GrGradientEffect {
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +000042public:
43
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +000044 static GrEffectRef* Create(GrContext* ctx,
45 const SkTwoPointConicalGradient& shader,
46 const SkMatrix& matrix,
47 SkShader::TileMode tm) {
48 AutoEffectUnref effect(SkNEW_ARGS(Default2PtConicalEffect, (ctx, shader, matrix, tm)));
49 return CreateEffectRef(effect);
50 }
51
52 virtual ~Default2PtConicalEffect() { }
53
54 static const char* Name() { return "Two-Point Conical Gradient"; }
55 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
56
57 // The radial gradient parameters can collapse to a linear (instead of quadratic) equation.
58 bool isDegenerate() const { return SkScalarAbs(fDiffRadius) == SkScalarAbs(fCenterX1); }
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +000059 bool isFlipped() const { return fIsFlipped; }
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +000060 SkScalar center() const { return fCenterX1; }
61 SkScalar diffRadius() const { return fDiffRadius; }
62 SkScalar radius() const { return fRadius0; }
63
64 typedef GLDefault2PtConicalEffect GLEffect;
65
66private:
67 virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
68 const Default2PtConicalEffect& s = CastEffect<Default2PtConicalEffect>(sBase);
69 return (INHERITED::onIsEqual(sBase) &&
70 this->fCenterX1 == s.fCenterX1 &&
71 this->fRadius0 == s.fRadius0 &&
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +000072 this->fDiffRadius == s.fDiffRadius &&
73 this->fIsFlipped == s.fIsFlipped);
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +000074 }
75
76 Default2PtConicalEffect(GrContext* ctx,
77 const SkTwoPointConicalGradient& shader,
78 const SkMatrix& matrix,
79 SkShader::TileMode tm)
80 : INHERITED(ctx, shader, matrix, tm),
81 fCenterX1(shader.getCenterX1()),
82 fRadius0(shader.getStartRadius()),
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +000083 fDiffRadius(shader.getDiffRadius()),
84 fIsFlipped(shader.isFlippedGrad()) {
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +000085 // We pass the linear part of the quadratic as a varying.
86 // float b = -2.0 * (fCenterX1 * x + fRadius0 * fDiffRadius * z)
87 fBTransform = this->getCoordTransform();
88 SkMatrix& bMatrix = *fBTransform.accessMatrix();
89 SkScalar r0dr = SkScalarMul(fRadius0, fDiffRadius);
90 bMatrix[SkMatrix::kMScaleX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMScaleX]) +
91 SkScalarMul(r0dr, bMatrix[SkMatrix::kMPersp0]));
92 bMatrix[SkMatrix::kMSkewX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMSkewX]) +
93 SkScalarMul(r0dr, bMatrix[SkMatrix::kMPersp1]));
94 bMatrix[SkMatrix::kMTransX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMatrix::kMTransX]) +
95 SkScalarMul(r0dr, bMatrix[SkMatrix::kMPersp2]));
96 this->addCoordTransform(&fBTransform);
97 }
98
99 GR_DECLARE_EFFECT_TEST;
100
101 // @{
102 // Cache of values - these can change arbitrarily, EXCEPT
103 // we shouldn't change between degenerate and non-degenerate?!
104
105 GrCoordTransform fBTransform;
106 SkScalar fCenterX1;
107 SkScalar fRadius0;
108 SkScalar fDiffRadius;
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000109 bool fIsFlipped;
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000110
111 // @}
112
113 typedef GrGradientEffect INHERITED;
114};
115
116class GLDefault2PtConicalEffect : public GrGLGradientEffect {
117public:
118 GLDefault2PtConicalEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&);
119 virtual ~GLDefault2PtConicalEffect() { }
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000120
121 virtual void emitCode(GrGLShaderBuilder*,
122 const GrDrawEffect&,
123 EffectKey,
124 const char* outputColor,
125 const char* inputColor,
126 const TransformedCoordsArray&,
127 const TextureSamplerArray&) SK_OVERRIDE;
128 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
129
130 static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps& caps);
131
132protected:
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000133 UniformHandle fParamUni;
134
135 const char* fVSVaryingName;
136 const char* fFSVaryingName;
137
138 bool fIsDegenerate;
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000139 bool fIsFlipped;
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000140
141 // @{
142 /// Values last uploaded as uniforms
143
144 SkScalar fCachedCenter;
145 SkScalar fCachedRadius;
146 SkScalar fCachedDiffRadius;
147
148 // @}
149
150private:
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000151 typedef GrGLGradientEffect INHERITED;
152
153};
154
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000155const GrBackendEffectFactory& Default2PtConicalEffect::getFactory() const {
156 return GrTBackendEffectFactory<Default2PtConicalEffect>::getInstance();
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000157}
skia.committer@gmail.com221b9112014-04-04 03:04:32 +0000158
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000159GR_DEFINE_EFFECT_TEST(Default2PtConicalEffect);
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000160
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000161GrEffectRef* Default2PtConicalEffect::TestCreate(SkRandom* random,
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000162 GrContext* context,
163 const GrDrawTargetCaps&,
164 GrTexture**) {
165 SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
166 SkScalar radius1 = random->nextUScalar1();
167 SkPoint center2;
168 SkScalar radius2;
169 do {
170 center2.set(random->nextUScalar1(), random->nextUScalar1());
171 radius2 = random->nextUScalar1 ();
172 // If the circles are identical the factory will give us an empty shader.
173 } while (radius1 == radius2 && center1 == center2);
174
175 SkColor colors[kMaxRandomGradientColors];
176 SkScalar stopsArray[kMaxRandomGradientColors];
177 SkScalar* stops = stopsArray;
178 SkShader::TileMode tm;
179 int colorCount = RandomGradientParams(random, colors, &stops, &tm);
180 SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointConical(center1, radius1,
181 center2, radius2,
182 colors, stops, colorCount,
183 tm));
184 SkPaint paint;
185 return shader->asNewEffect(context, paint);
186}
187
188
189/////////////////////////////////////////////////////////////////////
190
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000191GLDefault2PtConicalEffect::GLDefault2PtConicalEffect(const GrBackendEffectFactory& factory,
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000192 const GrDrawEffect& drawEffect)
193 : INHERITED(factory)
194 , fVSVaryingName(NULL)
195 , fFSVaryingName(NULL)
196 , fCachedCenter(SK_ScalarMax)
197 , fCachedRadius(-SK_ScalarMax)
198 , fCachedDiffRadius(-SK_ScalarMax) {
199
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000200 const Default2PtConicalEffect& data = drawEffect.castEffect<Default2PtConicalEffect>();
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000201 fIsDegenerate = data.isDegenerate();
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000202 fIsFlipped = data.isFlipped();
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000203}
204
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000205void GLDefault2PtConicalEffect::emitCode(GrGLShaderBuilder* builder,
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000206 const GrDrawEffect&,
207 EffectKey key,
208 const char* outputColor,
209 const char* inputColor,
210 const TransformedCoordsArray& coords,
211 const TextureSamplerArray& samplers) {
212 this->emitUniforms(builder, key);
213 fParamUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_Visibility,
214 kFloat_GrSLType, "Conical2FSParams", 6);
215
216 SkString cName("c");
217 SkString ac4Name("ac4");
218 SkString dName("d");
219 SkString qName("q");
220 SkString r0Name("r0");
221 SkString r1Name("r1");
222 SkString tName("t");
223 SkString p0; // 4a
224 SkString p1; // 1/a
225 SkString p2; // distance between centers
226 SkString p3; // start radius
227 SkString p4; // start radius squared
228 SkString p5; // difference in radii (r1 - r0)
229
230 builder->getUniformVariable(fParamUni).appendArrayAccess(0, &p0);
231 builder->getUniformVariable(fParamUni).appendArrayAccess(1, &p1);
232 builder->getUniformVariable(fParamUni).appendArrayAccess(2, &p2);
233 builder->getUniformVariable(fParamUni).appendArrayAccess(3, &p3);
234 builder->getUniformVariable(fParamUni).appendArrayAccess(4, &p4);
235 builder->getUniformVariable(fParamUni).appendArrayAccess(5, &p5);
236
237 // We interpolate the linear component in coords[1].
238 SkASSERT(coords[0].type() == coords[1].type());
239 const char* coords2D;
240 SkString bVar;
241 if (kVec3f_GrSLType == coords[0].type()) {
242 builder->fsCodeAppendf("\tvec3 interpolants = vec3(%s.xy, %s.x) / %s.z;\n",
243 coords[0].c_str(), coords[1].c_str(), coords[0].c_str());
244 coords2D = "interpolants.xy";
245 bVar = "interpolants.z";
246 } else {
247 coords2D = coords[0].c_str();
248 bVar.printf("%s.x", coords[1].c_str());
249 }
250
251 // output will default to transparent black (we simply won't write anything
252 // else to it if invalid, instead of discarding or returning prematurely)
253 builder->fsCodeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", outputColor);
254
255 // c = (x^2)+(y^2) - params[4]
256 builder->fsCodeAppendf("\tfloat %s = dot(%s, %s) - %s;\n",
257 cName.c_str(), coords2D, coords2D, p4.c_str());
258
259 // Non-degenerate case (quadratic)
260 if (!fIsDegenerate) {
261
262 // ac4 = params[0] * c
263 builder->fsCodeAppendf("\tfloat %s = %s * %s;\n", ac4Name.c_str(), p0.c_str(),
264 cName.c_str());
265
266 // d = b^2 - ac4
267 builder->fsCodeAppendf("\tfloat %s = %s * %s - %s;\n", dName.c_str(),
268 bVar.c_str(), bVar.c_str(), ac4Name.c_str());
269
270 // only proceed if discriminant is >= 0
271 builder->fsCodeAppendf("\tif (%s >= 0.0) {\n", dName.c_str());
272
273 // intermediate value we'll use to compute the roots
274 // q = -0.5 * (b +/- sqrt(d))
275 builder->fsCodeAppendf("\t\tfloat %s = -0.5 * (%s + (%s < 0.0 ? -1.0 : 1.0)"
276 " * sqrt(%s));\n", qName.c_str(), bVar.c_str(),
277 bVar.c_str(), dName.c_str());
278
279 // compute both roots
280 // r0 = q * params[1]
281 builder->fsCodeAppendf("\t\tfloat %s = %s * %s;\n", r0Name.c_str(),
282 qName.c_str(), p1.c_str());
283 // r1 = c / q
284 builder->fsCodeAppendf("\t\tfloat %s = %s / %s;\n", r1Name.c_str(),
285 cName.c_str(), qName.c_str());
286
287 // Note: If there are two roots that both generate radius(t) > 0, the
288 // Canvas spec says to choose the larger t.
289
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000290 // so we'll look at the larger one first (or smaller if flipped):
291 if (!fIsFlipped) {
292 builder->fsCodeAppendf("\t\tfloat %s = max(%s, %s);\n", tName.c_str(),
293 r0Name.c_str(), r1Name.c_str());
294 } else {
295 builder->fsCodeAppendf("\t\tfloat %s = min(%s, %s);\n", tName.c_str(),
296 r0Name.c_str(), r1Name.c_str());
297 }
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000298
299 // if r(t) > 0, then we're done; t will be our x coordinate
300 builder->fsCodeAppendf("\t\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
301 p5.c_str(), p3.c_str());
302
303 builder->fsCodeAppend("\t\t");
304 this->emitColor(builder, tName.c_str(), key, outputColor, inputColor, samplers);
305
306 // otherwise, if r(t) for the larger root was <= 0, try the other root
307 builder->fsCodeAppend("\t\t} else {\n");
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000308 if (!fIsFlipped) {
309 builder->fsCodeAppendf("\t\t\t%s = min(%s, %s);\n", tName.c_str(),
310 r0Name.c_str(), r1Name.c_str());
311 } else {
312 builder->fsCodeAppendf("\t\t\t%s = max(%s, %s);\n", tName.c_str(),
313 r0Name.c_str(), r1Name.c_str());
314 }
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000315
316 // if r(t) > 0 for the smaller root, then t will be our x coordinate
317 builder->fsCodeAppendf("\t\t\tif (%s * %s + %s > 0.0) {\n",
318 tName.c_str(), p5.c_str(), p3.c_str());
319
320 builder->fsCodeAppend("\t\t\t");
321 this->emitColor(builder, tName.c_str(), key, outputColor, inputColor, samplers);
322
323 // end if (r(t) > 0) for smaller root
324 builder->fsCodeAppend("\t\t\t}\n");
325 // end if (r(t) > 0), else, for larger root
326 builder->fsCodeAppend("\t\t}\n");
327 // end if (discriminant >= 0)
328 builder->fsCodeAppend("\t}\n");
329 } else {
330
331 // linear case: t = -c/b
332 builder->fsCodeAppendf("\tfloat %s = -(%s / %s);\n", tName.c_str(),
333 cName.c_str(), bVar.c_str());
334
335 // if r(t) > 0, then t will be the x coordinate
336 builder->fsCodeAppendf("\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
337 p5.c_str(), p3.c_str());
338 builder->fsCodeAppend("\t");
339 this->emitColor(builder, tName.c_str(), key, outputColor, inputColor, samplers);
340 builder->fsCodeAppend("\t}\n");
341 }
342}
343
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000344void GLDefault2PtConicalEffect::setData(const GrGLUniformManager& uman,
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000345 const GrDrawEffect& drawEffect) {
346 INHERITED::setData(uman, drawEffect);
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000347 const Default2PtConicalEffect& data = drawEffect.castEffect<Default2PtConicalEffect>();
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000348 SkASSERT(data.isDegenerate() == fIsDegenerate);
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000349 SkASSERT(data.isFlipped() == fIsFlipped);
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000350 SkScalar centerX1 = data.center();
351 SkScalar radius0 = data.radius();
352 SkScalar diffRadius = data.diffRadius();
353
354 if (fCachedCenter != centerX1 ||
355 fCachedRadius != radius0 ||
356 fCachedDiffRadius != diffRadius) {
357
358 SkScalar a = SkScalarMul(centerX1, centerX1) - diffRadius * diffRadius;
359
360 // When we're in the degenerate (linear) case, the second
361 // value will be INF but the program doesn't read it. (We
362 // use the same 6 uniforms even though we don't need them
363 // all in the linear case just to keep the code complexity
364 // down).
365 float values[6] = {
366 SkScalarToFloat(a * 4),
367 1.f / (SkScalarToFloat(a)),
368 SkScalarToFloat(centerX1),
369 SkScalarToFloat(radius0),
370 SkScalarToFloat(SkScalarMul(radius0, radius0)),
371 SkScalarToFloat(diffRadius)
372 };
373
374 uman.set1fv(fParamUni, 6, values);
375 fCachedCenter = centerX1;
376 fCachedRadius = radius0;
377 fCachedDiffRadius = diffRadius;
378 }
379}
380
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000381GrGLEffect::EffectKey GLDefault2PtConicalEffect::GenKey(const GrDrawEffect& drawEffect,
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000382 const GrGLCaps&) {
383 enum {
384 kIsDegenerate = 1 << kBaseKeyBitCnt,
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000385 kIsFlipped = 1 << (kBaseKeyBitCnt + 1),
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000386 };
387
388 EffectKey key = GenBaseGradientKey(drawEffect);
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000389 if (drawEffect.castEffect<Default2PtConicalEffect>().isDegenerate()) {
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000390 key |= kIsDegenerate;
391 }
commit-bot@chromium.org44d83c12014-04-21 13:10:25 +0000392 if (drawEffect.castEffect<Default2PtConicalEffect>().isFlipped()) {
393 key |= kIsFlipped;
394 }
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000395 return key;
396}
commit-bot@chromium.org2af1a2d2014-04-04 13:50:50 +0000397
398//////////////////////////////////////////////////////////////////////////////
399
400GrEffectRef* Gr2PtConicalGradientEffect::Create(GrContext* ctx,
401 const SkTwoPointConicalGradient& shader,
402 SkShader::TileMode tm) {
403
404 SkMatrix matrix;
405 if (!shader.getLocalMatrix().invert(&matrix)) {
406 return NULL;
407 }
408
409 set_matrix_default_conical(shader, &matrix);
410 return Default2PtConicalEffect::Create(ctx, shader, matrix, tm);
411}
412
commit-bot@chromium.orgaa64fbf2014-04-03 14:59:19 +0000413#endif