blob: 7a6554b88f21f2c0d8a17065e2063fd253debc79 [file] [log] [blame]
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +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 */
7
8#include "GrRRectEffect.h"
9
10#include "gl/GrGLEffect.h"
11#include "gl/GrGLSL.h"
12#include "GrTBackendEffectFactory.h"
13
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000014#include "SkRRect.h"
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +000015
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000016class GLRRectEffect;
17
18class RRectEffect : public GrEffect {
19public:
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +000020 // This effect only supports circular corner rrects where the radius is >= kRadiusMin.
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000021 static const SkScalar kRadiusMin;
22
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +000023 /// The types of circular corner rrects supported
24 enum Type {
25 kCircleCorner_Type, //<! All four corners have the same circular radius.
26 kLeftCircleTab_Type, //<! The left side has circular corners and the right is a rect.
27 kTopCircleTab_Type, //<! etc
28 kRightCircleTab_Type,
29 kBottomCircleTab_Type,
30 };
31
32 static GrEffectRef* Create(const SkRRect&, Type);
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000033
34 virtual ~RRectEffect() {};
35 static const char* Name() { return "RRect"; }
36
37 const SkRRect& getRRect() const { return fRRect; }
38
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +000039 Type getType() const { return fType; }
40
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000041 typedef GLRRectEffect GLEffect;
42
43 virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
44
45 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
46
47private:
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +000048 RRectEffect(const SkRRect&, Type);
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000049
50 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE;
51
52 SkRRect fRRect;
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +000053 Type fType;
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000054
55 GR_DECLARE_EFFECT_TEST;
56
57 typedef GrEffect INHERITED;
58};
59
60const SkScalar RRectEffect::kRadiusMin = 0.5f;
61
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +000062GrEffectRef* RRectEffect::Create(const SkRRect& rrect, Type type) {
63 return CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(RRectEffect, (rrect, type))));
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000064}
65
66void RRectEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
67 *validFlags = 0;
68}
69
70const GrBackendEffectFactory& RRectEffect::getFactory() const {
71 return GrTBackendEffectFactory<RRectEffect>::getInstance();
72}
73
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +000074RRectEffect::RRectEffect(const SkRRect& rrect, Type type)
75 : fRRect(rrect)
76 , fType(type) {
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000077 this->setWillReadFragmentPosition();
78}
79
80bool RRectEffect::onIsEqual(const GrEffect& other) const {
81 const RRectEffect& rre = CastEffect<RRectEffect>(other);
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +000082 // type is derived from fRRect, so no need to check it.
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000083 return fRRect == rre.fRRect;
84}
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +000085
86//////////////////////////////////////////////////////////////////////////////
commit-bot@chromium.orge5280892014-02-21 17:52:29 +000087
88GR_DEFINE_EFFECT_TEST(RRectEffect);
89
90GrEffectRef* RRectEffect::TestCreate(SkRandom* random,
91 GrContext*,
92 const GrDrawTargetCaps& caps,
93 GrTexture*[]) {
94 SkScalar w = random->nextRangeScalar(20.f, 1000.f);
95 SkScalar h = random->nextRangeScalar(20.f, 1000.f);
96 SkScalar r = random->nextRangeF(kRadiusMin, 9.f);
97 SkRRect rrect;
98 rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
99
100 return GrRRectEffect::Create(rrect);
101}
102
103//////////////////////////////////////////////////////////////////////////////
104
105class GLRRectEffect : public GrGLEffect {
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000106public:
commit-bot@chromium.orge5280892014-02-21 17:52:29 +0000107 GLRRectEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000108
109 virtual void emitCode(GrGLShaderBuilder* builder,
110 const GrDrawEffect& drawEffect,
111 EffectKey key,
112 const char* outputColor,
113 const char* inputColor,
114 const TransformedCoordsArray&,
115 const TextureSamplerArray&) SK_OVERRIDE;
116
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +0000117 static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000118
119 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
120
121private:
122 GrGLUniformManager::UniformHandle fInnerRectUniform;
123 GrGLUniformManager::UniformHandle fRadiusPlusHalfUniform;
124 SkRRect fPrevRRect;
125 typedef GrGLEffect INHERITED;
126};
127
commit-bot@chromium.orge5280892014-02-21 17:52:29 +0000128GLRRectEffect::GLRRectEffect(const GrBackendEffectFactory& factory,
129 const GrDrawEffect& drawEffect)
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000130 : INHERITED (factory) {
131 fPrevRRect.setEmpty();
132}
133
commit-bot@chromium.orge5280892014-02-21 17:52:29 +0000134void GLRRectEffect::emitCode(GrGLShaderBuilder* builder,
135 const GrDrawEffect& drawEffect,
136 EffectKey key,
137 const char* outputColor,
138 const char* inputColor,
139 const TransformedCoordsArray&,
140 const TextureSamplerArray& samplers) {
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +0000141 const RRectEffect& rre = drawEffect.castEffect<RRectEffect>();
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000142 const char *rectName;
143 const char *radiusPlusHalfName;
144 // The inner rect is the rrect bounds inset by the radius. Its top, left, right, and bottom
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +0000145 // edges correspond to components x, y, z, and w, respectively. When one side of the rrect has
146 // rectangular corners, that side's value corresponds to the rect edge's value outset by half a
147 // pixel.
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000148 fInnerRectUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
149 kVec4f_GrSLType,
150 "innerRect",
151 &rectName);
152 fRadiusPlusHalfUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
153 kFloat_GrSLType,
154 "radiusPlusHalf",
155 &radiusPlusHalfName);
156 const char* fragmentPos = builder->fragmentPosition();
157 // At each quarter-circle corner we compute a vector that is the offset of the fragment position
158 // from the circle center. The vector is pinned in x and y to be in the quarter-plane relevant
159 // to that corner. This means that points near the interior near the rrect top edge will have
160 // a vector that points straight up for both the TL left and TR corners. Computing an
161 // alpha from this vector at either the TR or TL corner will give the correct result. Similarly,
162 // fragments near the other three edges will get the correct AA. Fragments in the interior of
163 // the rrect will have a (0,0) vector at all four corners. So long as the radius > 0.5 they will
164 // correctly produce an alpha value of 1 at all four corners. We take the min of all the alphas.
165 // The code below is a simplified version of the above that performs maxs on the vector
166 // components before computing distances and alpha values so that only one distance computation
167 // need be computed to determine the min alpha.
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +0000168 //
169 // For the cases where one half of the rrect is rectangular we drop one of the x or y
170 // computations, compute a separate rect edge alpha for the rect side, and mul the two computed
171 // alphas together.
172 switch (rre.getType()) {
173 case RRectEffect::kCircleCorner_Type:
174 builder->fsCodeAppendf("\t\tvec2 dxy0 = %s.xy - %s.xy;\n", rectName, fragmentPos);
175 builder->fsCodeAppendf("\t\tvec2 dxy1 = %s.xy - %s.zw;\n", fragmentPos, rectName);
176 builder->fsCodeAppend("\t\tvec2 dxy = max(max(dxy0, dxy1), 0.0);\n");
177 builder->fsCodeAppendf("\t\tfloat alpha = clamp(%s - length(dxy), 0.0, 1.0);\n",
178 radiusPlusHalfName);
179 break;
180 case RRectEffect::kLeftCircleTab_Type:
181 builder->fsCodeAppendf("\t\tvec2 dxy0 = %s.xy - %s.xy;\n", rectName, fragmentPos);
182 builder->fsCodeAppendf("\t\tfloat dy1 = %s.y - %s.w;\n", fragmentPos, rectName);
183 builder->fsCodeAppend("\t\tvec2 dxy = max(vec2(dxy0.x, max(dxy0.y, dy1)), 0.0);\n");
184 builder->fsCodeAppendf("\t\tfloat rightAlpha = clamp(%s.z - %s.x, 0.0, 1.0);\n",
185 rectName, fragmentPos);
186 builder->fsCodeAppendf("\t\tfloat alpha = rightAlpha * clamp(%s - length(dxy), 0.0, 1.0);\n",
187 radiusPlusHalfName);
188 break;
189 case RRectEffect::kTopCircleTab_Type:
190 builder->fsCodeAppendf("\t\tvec2 dxy0 = %s.xy - %s.xy;\n", rectName, fragmentPos);
191 builder->fsCodeAppendf("\t\tfloat dx1 = %s.x - %s.z;\n", fragmentPos, rectName);
192 builder->fsCodeAppend("\t\tvec2 dxy = max(vec2(max(dxy0.x, dx1), dxy0.y), 0.0);\n");
193 builder->fsCodeAppendf("\t\tfloat bottomAlpha = clamp(%s.w - %s.y, 0.0, 1.0);\n",
194 rectName, fragmentPos);
195 builder->fsCodeAppendf("\t\tfloat alpha = bottomAlpha * clamp(%s - length(dxy), 0.0, 1.0);\n",
196 radiusPlusHalfName);
197 break;
198 case RRectEffect::kRightCircleTab_Type:
199 builder->fsCodeAppendf("\t\tfloat dy0 = %s.y - %s.y;\n", rectName, fragmentPos);
200 builder->fsCodeAppendf("\t\tvec2 dxy1 = %s.xy - %s.zw;\n", fragmentPos, rectName);
201 builder->fsCodeAppend("\t\tvec2 dxy = max(vec2(dxy1.x, max(dy0, dxy1.y)), 0.0);\n");
202 builder->fsCodeAppendf("\t\tfloat leftAlpha = clamp(%s.x - %s.x, 0.0, 1.0);\n",
203 fragmentPos, rectName);
204 builder->fsCodeAppendf("\t\tfloat alpha = leftAlpha * clamp(%s - length(dxy), 0.0, 1.0);\n",
205 radiusPlusHalfName);
206 break;
207 case RRectEffect::kBottomCircleTab_Type:
208 builder->fsCodeAppendf("\t\tfloat dx0 = %s.x - %s.x;\n", rectName, fragmentPos);
209 builder->fsCodeAppendf("\t\tvec2 dxy1 = %s.xy - %s.zw;\n", fragmentPos, rectName);
210 builder->fsCodeAppend("\t\tvec2 dxy = max(vec2(max(dx0, dxy1.x), dxy1.y), 0.0);\n");
211 builder->fsCodeAppendf("\t\tfloat topAlpha = clamp(%s.y - %s.y, 0.0, 1.0);\n",
212 fragmentPos, rectName);
213 builder->fsCodeAppendf("\t\tfloat alpha = topAlpha * clamp(%s - length(dxy), 0.0, 1.0);\n",
214 radiusPlusHalfName);
215 break;
216 }
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000217
218 builder->fsCodeAppendf("\t\t%s = %s;\n", outputColor,
219 (GrGLSLExpr4(inputColor) * GrGLSLExpr1("alpha")).c_str());
220}
221
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +0000222GrGLEffect::EffectKey GLRRectEffect::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
223 const RRectEffect& rre = drawEffect.castEffect<RRectEffect>();
224 return rre.getType();
225}
226
commit-bot@chromium.orge5280892014-02-21 17:52:29 +0000227void GLRRectEffect::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
228 const RRectEffect& rre = drawEffect.castEffect<RRectEffect>();
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000229 const SkRRect& rrect = rre.getRRect();
230 if (rrect != fPrevRRect) {
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000231 SkRect rect = rrect.getBounds();
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +0000232 SkScalar radius = 0;
233 switch (rre.getType()) {
234 case RRectEffect::kCircleCorner_Type:
235 SkASSERT(rrect.isSimpleCircular());
236 radius = rrect.getSimpleRadii().fX;
237 SkASSERT(radius >= RRectEffect::kRadiusMin);
238 rect.inset(radius, radius);
239 break;
240 case RRectEffect::kLeftCircleTab_Type:
241 radius = rrect.radii(SkRRect::kUpperLeft_Corner).fX;
242 rect.fLeft += radius;
243 rect.fTop += radius;
244 rect.fRight += 0.5f;
245 rect.fBottom -= radius;
246 break;
247 case RRectEffect::kTopCircleTab_Type:
248 radius = rrect.radii(SkRRect::kUpperLeft_Corner).fX;
249 rect.fLeft += radius;
250 rect.fTop += radius;
251 rect.fRight -= radius;
252 rect.fBottom += 0.5f;
253 break;
254 case RRectEffect::kRightCircleTab_Type:
255 radius = rrect.radii(SkRRect::kUpperRight_Corner).fX;
256 rect.fLeft -= 0.5f;
257 rect.fTop += radius;
258 rect.fRight -= radius;
259 rect.fBottom -= radius;
260 break;
261 case RRectEffect::kBottomCircleTab_Type:
262 radius = rrect.radii(SkRRect::kLowerLeft_Corner).fX;
263 rect.fLeft += radius;
264 rect.fTop -= 0.5f;
265 rect.fRight -= radius;
266 rect.fBottom -= radius;
267 break;
268 }
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000269 uman.set4f(fInnerRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
270 uman.set1f(fRadiusPlusHalfUniform, radius + 0.5f);
271 fPrevRRect = rrect;
272 }
273}
274
275//////////////////////////////////////////////////////////////////////////////
276
277GrEffectRef* GrRRectEffect::Create(const SkRRect& rrect) {
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +0000278 RRectEffect::Type type;
279 if (rrect.isSimpleCircular()) {
280 if (rrect.getSimpleRadii().fX < RRectEffect::kRadiusMin) {
281 return NULL;
282 }
283 type = RRectEffect::kCircleCorner_Type;
284 } else if (rrect.isComplex()) {
285 // Check for the "tab" cases - two adjacent circular corners and two square corners.
286 SkScalar radius = 0;
287 int circleCornerBitfield = 0;
288 for (int c = 0; c < 4; ++c) {
289 const SkVector& r = rrect.radii((SkRRect::Corner)c);
290 SkASSERT((0 == r.fX) == (0 == r.fY));
291 if (0 == r.fX) {
292 continue;
293 }
294 if (r.fX != r.fY) {
295 return NULL;
296 }
297 if (!circleCornerBitfield) {
298 radius = r.fX;
299 if (radius < RRectEffect::kRadiusMin) {
300 return NULL;
301 }
302 circleCornerBitfield = 1 << c;
303 } else {
304 if (r.fX != radius) {
305 return NULL;
306 }
307 circleCornerBitfield |= 1 << c;
308 }
309 }
310
311 GR_STATIC_ASSERT(SkRRect::kUpperLeft_Corner == 0);
312 GR_STATIC_ASSERT(SkRRect::kUpperRight_Corner == 1);
313 GR_STATIC_ASSERT(SkRRect::kLowerRight_Corner == 2);
314 GR_STATIC_ASSERT(SkRRect::kLowerLeft_Corner == 3);
315 switch (circleCornerBitfield) {
316 case 3:
317 type = RRectEffect::kTopCircleTab_Type;
318 break;
319 case 6:
320 type = RRectEffect::kRightCircleTab_Type;
321 break;
322 case 9:
323 type = RRectEffect::kLeftCircleTab_Type;
324 break;
325 case 12:
326 type = RRectEffect::kBottomCircleTab_Type;
327 break;
328 default:
329 return NULL;
330 }
331 } else {
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000332 return NULL;
333 }
commit-bot@chromium.orgcb3672e2014-02-21 22:41:56 +0000334 return RRectEffect::Create(rrect, type);
commit-bot@chromium.orgc2f78242014-02-19 15:18:05 +0000335}