blob: 3b4fd63788286e38fdc7f9b601055d6c8d7d30fa [file] [log] [blame]
robertphillips@google.comf6747b02012-06-12 00:32:28 +00001/*
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 "GrAARectRenderer.h"
robertphillips@google.comf6747b02012-06-12 00:32:28 +00009#include "GrGpu.h"
joshualitt47bb3822014-10-07 16:43:25 -070010#include "gl/builders/GrGLProgramBuilder.h"
joshualittb0a8a372014-09-23 09:50:21 -070011#include "gl/GrGLProcessor.h"
joshualitt249af152014-09-15 11:41:13 -070012#include "gl/GrGLGeometryProcessor.h"
joshualittb0a8a372014-09-23 09:50:21 -070013#include "GrTBackendProcessorFactory.h"
robertphillips@google.com908aed82013-05-28 13:16:20 +000014#include "SkColorPriv.h"
joshualittb0a8a372014-09-23 09:50:21 -070015#include "GrGeometryProcessor.h"
robertphillips@google.comf6747b02012-06-12 00:32:28 +000016
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000017///////////////////////////////////////////////////////////////////////////////
18class GrGLAlignedRectEffect;
19
20// Axis Aligned special case
joshualitt249af152014-09-15 11:41:13 -070021class GrAlignedRectEffect : public GrGeometryProcessor {
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000022public:
joshualittb0a8a372014-09-23 09:50:21 -070023 static GrGeometryProcessor* Create() {
bsalomon98b33eb2014-10-15 11:05:26 -070024 GR_CREATE_STATIC_PROCESSOR(gAlignedRectEffect, GrAlignedRectEffect, ());
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000025 gAlignedRectEffect->ref();
26 return gAlignedRectEffect;
27 }
28
29 virtual ~GrAlignedRectEffect() {}
30
31 static const char* Name() { return "AlignedRectEdge"; }
32
joshualitt249af152014-09-15 11:41:13 -070033 const GrShaderVar& inRect() const { return fInRect; }
34
joshualittb0a8a372014-09-23 09:50:21 -070035 virtual const GrBackendGeometryProcessorFactory& getFactory() const SK_OVERRIDE {
36 return GrTBackendGeometryProcessorFactory<GrAlignedRectEffect>::getInstance();
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000037 }
38
joshualittb0a8a372014-09-23 09:50:21 -070039 class GLProcessor : public GrGLGeometryProcessor {
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000040 public:
joshualittb0a8a372014-09-23 09:50:21 -070041 GLProcessor(const GrBackendProcessorFactory& factory, const GrProcessor&)
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000042 : INHERITED (factory) {}
43
joshualittc369e7c2014-10-22 10:56:26 -070044 virtual void emitCode(const EmitArgs& args) SK_OVERRIDE {
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000045 // setup the varying for the Axis aligned rect effect
46 // xy -> interpolated offset
47 // zw -> w/2+0.5, h/2+0.5
48 const char *vsRectName, *fsRectName;
joshualittc369e7c2014-10-22 10:56:26 -070049 args.fPB->addVarying(kVec4f_GrSLType, "Rect", &vsRectName, &fsRectName);
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000050
joshualittc369e7c2014-10-22 10:56:26 -070051 const GrShaderVar& inRect = args.fGP.cast<GrAlignedRectEffect>().inRect();
52 GrGLVertexBuilder* vsBuilder = args.fPB->getVertexShaderBuilder();
joshualitt249af152014-09-15 11:41:13 -070053 vsBuilder->codeAppendf("\t%s = %s;\n", vsRectName, inRect.c_str());
joshualitt30ba4362014-08-21 20:18:45 -070054
joshualittc369e7c2014-10-22 10:56:26 -070055 GrGLGPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder();
commit-bot@chromium.org99e0d082013-06-14 14:58:50 +000056 // TODO: compute all these offsets, spans, and scales in the VS
joshualitt30ba4362014-08-21 20:18:45 -070057 fsBuilder->codeAppendf("\tfloat insetW = min(1.0, %s.z) - 0.5;\n", fsRectName);
58 fsBuilder->codeAppendf("\tfloat insetH = min(1.0, %s.w) - 0.5;\n", fsRectName);
59 fsBuilder->codeAppend("\tfloat outset = 0.5;\n");
commit-bot@chromium.org99e0d082013-06-14 14:58:50 +000060 // For rects > 1 pixel wide and tall the span's are noops (i.e., 1.0). For rects
61 // < 1 pixel wide or tall they serve to normalize the < 1 ramp to a 0 .. 1 range.
joshualitt30ba4362014-08-21 20:18:45 -070062 fsBuilder->codeAppend("\tfloat spanW = insetW + outset;\n");
63 fsBuilder->codeAppend("\tfloat spanH = insetH + outset;\n");
commit-bot@chromium.org99e0d082013-06-14 14:58:50 +000064 // For rects < 1 pixel wide or tall, these scale factors are used to cap the maximum
65 // value of coverage that is used. In other words it is the coverage that is
66 // used in the interior of the rect after the ramp.
joshualitt30ba4362014-08-21 20:18:45 -070067 fsBuilder->codeAppend("\tfloat scaleW = min(1.0, 2.0*insetW/spanW);\n");
68 fsBuilder->codeAppend("\tfloat scaleH = min(1.0, 2.0*insetH/spanH);\n");
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000069
70 // Compute the coverage for the rect's width
joshualitt30ba4362014-08-21 20:18:45 -070071 fsBuilder->codeAppendf(
commit-bot@chromium.org99e0d082013-06-14 14:58:50 +000072 "\tfloat coverage = scaleW*clamp((%s.z-abs(%s.x))/spanW, 0.0, 1.0);\n", fsRectName,
73 fsRectName);
74 // Compute the coverage for the rect's height and merge with the width
joshualitt30ba4362014-08-21 20:18:45 -070075 fsBuilder->codeAppendf(
egdaniel@google.comf1d7de72013-06-14 19:25:53 +000076 "\tcoverage = coverage*scaleH*clamp((%s.w-abs(%s.y))/spanH, 0.0, 1.0);\n",
77 fsRectName, fsRectName);
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000078
commit-bot@chromium.org824c3462013-10-10 06:30:18 +000079
joshualittc369e7c2014-10-22 10:56:26 -070080 fsBuilder->codeAppendf("\t%s = %s;\n", args.fOutput,
81 (GrGLSLExpr4(args.fInput) * GrGLSLExpr1("coverage")).c_str());
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000082 }
83
joshualittb0a8a372014-09-23 09:50:21 -070084 static void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder*) {}
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000085
joshualittb0a8a372014-09-23 09:50:21 -070086 virtual void setData(const GrGLProgramDataManager& pdman, const GrProcessor&) SK_OVERRIDE {}
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000087
88 private:
joshualitt249af152014-09-15 11:41:13 -070089 typedef GrGLGeometryProcessor INHERITED;
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000090 };
91
92
93private:
joshualitt249af152014-09-15 11:41:13 -070094 GrAlignedRectEffect()
95 : fInRect(this->addVertexAttrib(GrShaderVar("inRect",
96 kVec4f_GrSLType,
97 GrShaderVar::kAttribute_TypeModifier))) {
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +000098 }
99
joshualitt249af152014-09-15 11:41:13 -0700100 const GrShaderVar& fInRect;
101
bsalomon0e08fc12014-10-15 08:19:04 -0700102 virtual bool onIsEqual(const GrGeometryProcessor&) const SK_OVERRIDE { return true; }
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000103
egdaniel1a8ecdf2014-10-03 06:24:12 -0700104 virtual void onComputeInvariantOutput(InvariantOutput* inout) const SK_OVERRIDE {
egdanielccb2e382014-10-13 12:53:46 -0700105 inout->mulByUnknownAlpha();
egdaniel1a8ecdf2014-10-03 06:24:12 -0700106 }
107
joshualittb0a8a372014-09-23 09:50:21 -0700108 GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000109
joshualitt249af152014-09-15 11:41:13 -0700110 typedef GrGeometryProcessor INHERITED;
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000111};
112
113
joshualittb0a8a372014-09-23 09:50:21 -0700114GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrAlignedRectEffect);
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000115
joshualittb0a8a372014-09-23 09:50:21 -0700116GrGeometryProcessor* GrAlignedRectEffect::TestCreate(SkRandom* random,
117 GrContext* context,
118 const GrDrawTargetCaps&,
119 GrTexture* textures[]) {
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000120 return GrAlignedRectEffect::Create();
121}
122
123///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000124class GrGLRectEffect;
125
126/**
skia.committer@gmail.com07d3a652013-04-10 07:01:15 +0000127 * The output of this effect is a modulation of the input color and coverage
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000128 * for an arbitrarily oriented rect. The rect is specified as:
129 * Center of the rect
130 * Unit vector point down the height of the rect
131 * Half width + 0.5
132 * Half height + 0.5
133 * The center and vector are stored in a vec4 varying ("RectEdge") with the
134 * center in the xy components and the vector in the zw components.
135 * The munged width and height are stored in a vec2 varying ("WidthHeight")
136 * with the width in x and the height in y.
137 */
joshualitt249af152014-09-15 11:41:13 -0700138
139class GrRectEffect : public GrGeometryProcessor {
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000140public:
joshualittb0a8a372014-09-23 09:50:21 -0700141 static GrGeometryProcessor* Create() {
bsalomon98b33eb2014-10-15 11:05:26 -0700142 GR_CREATE_STATIC_PROCESSOR(gRectEffect, GrRectEffect, ());
bsalomon@google.comd42aca32013-04-23 15:37:27 +0000143 gRectEffect->ref();
144 return gRectEffect;
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000145 }
146
147 virtual ~GrRectEffect() {}
148
149 static const char* Name() { return "RectEdge"; }
150
joshualitt249af152014-09-15 11:41:13 -0700151 const GrShaderVar& inRectEdge() const { return fInRectEdge; }
152 const GrShaderVar& inWidthHeight() const { return fInWidthHeight; }
153
joshualittb0a8a372014-09-23 09:50:21 -0700154 virtual const GrBackendGeometryProcessorFactory& getFactory() const SK_OVERRIDE {
155 return GrTBackendGeometryProcessorFactory<GrRectEffect>::getInstance();
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000156 }
157
joshualittb0a8a372014-09-23 09:50:21 -0700158 class GLProcessor : public GrGLGeometryProcessor {
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000159 public:
joshualittb0a8a372014-09-23 09:50:21 -0700160 GLProcessor(const GrBackendProcessorFactory& factory, const GrProcessor&)
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000161 : INHERITED (factory) {}
162
joshualittc369e7c2014-10-22 10:56:26 -0700163 virtual void emitCode(const EmitArgs& args) SK_OVERRIDE {
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000164 // setup the varying for the center point and the unit vector
165 // that points down the height of the rect
166 const char *vsRectEdgeName, *fsRectEdgeName;
joshualittc369e7c2014-10-22 10:56:26 -0700167 args.fPB->addVarying(kVec4f_GrSLType, "RectEdge",
commit-bot@chromium.org261dc562013-10-04 15:42:56 +0000168 &vsRectEdgeName, &fsRectEdgeName);
joshualitt30ba4362014-08-21 20:18:45 -0700169
joshualittc369e7c2014-10-22 10:56:26 -0700170 const GrRectEffect& rectEffect = args.fGP.cast<GrRectEffect>();
171 GrGLVertexBuilder* vsBuilder = args.fPB->getVertexShaderBuilder();
joshualitt249af152014-09-15 11:41:13 -0700172 vsBuilder->codeAppendf("%s = %s;", vsRectEdgeName, rectEffect.inRectEdge().c_str());
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000173
174 // setup the varying for width/2+.5 and height/2+.5
175 const char *vsWidthHeightName, *fsWidthHeightName;
joshualittc369e7c2014-10-22 10:56:26 -0700176 args.fPB->addVarying(kVec2f_GrSLType, "WidthHeight",
commit-bot@chromium.org261dc562013-10-04 15:42:56 +0000177 &vsWidthHeightName, &fsWidthHeightName);
joshualitt249af152014-09-15 11:41:13 -0700178 vsBuilder->codeAppendf("%s = %s;",
179 vsWidthHeightName,
180 rectEffect.inWidthHeight().c_str());
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000181
joshualittc369e7c2014-10-22 10:56:26 -0700182 GrGLGPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder();
egdaniel@google.comf1d7de72013-06-14 19:25:53 +0000183 // TODO: compute all these offsets, spans, and scales in the VS
joshualitt30ba4362014-08-21 20:18:45 -0700184 fsBuilder->codeAppendf("\tfloat insetW = min(1.0, %s.x) - 0.5;\n", fsWidthHeightName);
185 fsBuilder->codeAppendf("\tfloat insetH = min(1.0, %s.y) - 0.5;\n", fsWidthHeightName);
186 fsBuilder->codeAppend("\tfloat outset = 0.5;\n");
egdaniel@google.comf1d7de72013-06-14 19:25:53 +0000187 // For rects > 1 pixel wide and tall the span's are noops (i.e., 1.0). For rects
188 // < 1 pixel wide or tall they serve to normalize the < 1 ramp to a 0 .. 1 range.
joshualitt30ba4362014-08-21 20:18:45 -0700189 fsBuilder->codeAppend("\tfloat spanW = insetW + outset;\n");
190 fsBuilder->codeAppend("\tfloat spanH = insetH + outset;\n");
egdaniel@google.comf1d7de72013-06-14 19:25:53 +0000191 // For rects < 1 pixel wide or tall, these scale factors are used to cap the maximum
192 // value of coverage that is used. In other words it is the coverage that is
193 // used in the interior of the rect after the ramp.
joshualitt30ba4362014-08-21 20:18:45 -0700194 fsBuilder->codeAppend("\tfloat scaleW = min(1.0, 2.0*insetW/spanW);\n");
195 fsBuilder->codeAppend("\tfloat scaleH = min(1.0, 2.0*insetH/spanH);\n");
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000196
197 // Compute the coverage for the rect's width
joshualitt30ba4362014-08-21 20:18:45 -0700198 fsBuilder->codeAppendf("\tvec2 offset = %s.xy - %s.xy;\n",
199 fsBuilder->fragmentPosition(), fsRectEdgeName);
200 fsBuilder->codeAppendf("\tfloat perpDot = abs(offset.x * %s.w - offset.y * %s.z);\n",
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000201 fsRectEdgeName, fsRectEdgeName);
joshualitt30ba4362014-08-21 20:18:45 -0700202 fsBuilder->codeAppendf(
egdaniel@google.comf1d7de72013-06-14 19:25:53 +0000203 "\tfloat coverage = scaleW*clamp((%s.x-perpDot)/spanW, 0.0, 1.0);\n",
204 fsWidthHeightName);
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000205
206 // Compute the coverage for the rect's height and merge with the width
joshualitt30ba4362014-08-21 20:18:45 -0700207 fsBuilder->codeAppendf("\tperpDot = abs(dot(offset, %s.zw));\n",
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000208 fsRectEdgeName);
joshualitt30ba4362014-08-21 20:18:45 -0700209 fsBuilder->codeAppendf(
egdaniel@google.comf1d7de72013-06-14 19:25:53 +0000210 "\tcoverage = coverage*scaleH*clamp((%s.y-perpDot)/spanH, 0.0, 1.0);\n",
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000211 fsWidthHeightName);
212
commit-bot@chromium.org824c3462013-10-10 06:30:18 +0000213
joshualittc369e7c2014-10-22 10:56:26 -0700214 fsBuilder->codeAppendf("\t%s = %s;\n", args.fOutput,
215 (GrGLSLExpr4(args.fInput) * GrGLSLExpr1("coverage")).c_str());
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000216 }
217
joshualittb0a8a372014-09-23 09:50:21 -0700218 static void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder*) {}
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000219
joshualittb0a8a372014-09-23 09:50:21 -0700220 virtual void setData(const GrGLProgramDataManager& pdman, const GrProcessor&) SK_OVERRIDE {}
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000221
222 private:
joshualitt249af152014-09-15 11:41:13 -0700223 typedef GrGLGeometryProcessor INHERITED;
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000224 };
225
226
joshualitt249af152014-09-15 11:41:13 -0700227
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000228private:
joshualitt249af152014-09-15 11:41:13 -0700229 GrRectEffect()
230 : fInRectEdge(this->addVertexAttrib(GrShaderVar("inRectEdge",
231 kVec4f_GrSLType,
232 GrShaderVar::kAttribute_TypeModifier)))
233 , fInWidthHeight(this->addVertexAttrib(
234 GrShaderVar("inWidthHeight",
235 kVec2f_GrSLType,
236 GrShaderVar::kAttribute_TypeModifier))) {
commit-bot@chromium.org8d47ddc2013-05-09 14:55:46 +0000237 this->setWillReadFragmentPosition();
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000238 }
239
bsalomon0e08fc12014-10-15 08:19:04 -0700240 virtual bool onIsEqual(const GrGeometryProcessor&) const SK_OVERRIDE { return true; }
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000241
egdaniel1a8ecdf2014-10-03 06:24:12 -0700242 virtual void onComputeInvariantOutput(InvariantOutput* inout) const SK_OVERRIDE {
egdanielccb2e382014-10-13 12:53:46 -0700243 inout->mulByUnknownAlpha();
egdaniel1a8ecdf2014-10-03 06:24:12 -0700244 }
245
joshualitt249af152014-09-15 11:41:13 -0700246 const GrShaderVar& fInRectEdge;
247 const GrShaderVar& fInWidthHeight;
248
joshualittb0a8a372014-09-23 09:50:21 -0700249 GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000250
joshualitt249af152014-09-15 11:41:13 -0700251 typedef GrGeometryProcessor INHERITED;
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000252};
253
254
joshualittb0a8a372014-09-23 09:50:21 -0700255GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrRectEffect);
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000256
joshualittb0a8a372014-09-23 09:50:21 -0700257GrGeometryProcessor* GrRectEffect::TestCreate(SkRandom* random,
258 GrContext* context,
259 const GrDrawTargetCaps&,
260 GrTexture* textures[]) {
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000261 return GrRectEffect::Create();
262}
263
264///////////////////////////////////////////////////////////////////////////////
265
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000266namespace {
bsalomon9c0822a2014-08-11 11:07:48 -0700267extern const GrVertexAttrib gAARectAttribs[] = {
268 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
269 {kVec4ub_GrVertexAttribType, sizeof(SkPoint), kColor_GrVertexAttribBinding},
270 {kVec4ub_GrVertexAttribType, sizeof(SkPoint) + sizeof(SkColor), kCoverage_GrVertexAttribBinding},
robertphillips@google.com42903302013-04-20 12:26:07 +0000271};
272
bsalomonc30aaa02014-08-13 07:15:29 -0700273// Should the coverage be multiplied into the color attrib or use a separate attrib.
274enum CoverageAttribType {
275 kUseColor_CoverageAttribType,
276 kUseCoverage_CoverageAttribType,
277};
278}
279
280static CoverageAttribType set_rect_attribs(GrDrawState* drawState) {
281 if (drawState->canTweakAlphaForCoverage()) {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700282 drawState->setVertexAttribs<gAARectAttribs>(2, sizeof(SkPoint) + sizeof(SkColor));
bsalomonc30aaa02014-08-13 07:15:29 -0700283 return kUseColor_CoverageAttribType;
284 } else {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700285 drawState->setVertexAttribs<gAARectAttribs>(3, sizeof(SkPoint) + 2 * sizeof(SkColor));
bsalomonc30aaa02014-08-13 07:15:29 -0700286 return kUseCoverage_CoverageAttribType;
287 }
288}
289
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000290static void set_inset_fan(SkPoint* pts, size_t stride,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000291 const SkRect& r, SkScalar dx, SkScalar dy) {
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000292 pts->setRectFan(r.fLeft + dx, r.fTop + dy,
293 r.fRight - dx, r.fBottom - dy, stride);
294}
295
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000296void GrAARectRenderer::reset() {
commit-bot@chromium.orga4de8c22013-09-09 13:38:37 +0000297 SkSafeSetNull(fAAFillRectIndexBuffer);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000298 SkSafeSetNull(fAAMiterStrokeRectIndexBuffer);
299 SkSafeSetNull(fAABevelStrokeRectIndexBuffer);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000300}
301
robertphillips@google.com6d067302012-12-18 21:47:47 +0000302static const uint16_t gFillAARectIdx[] = {
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000303 0, 1, 5, 5, 4, 0,
304 1, 2, 6, 6, 5, 1,
305 2, 3, 7, 7, 6, 2,
306 3, 0, 4, 4, 7, 3,
307 4, 5, 6, 6, 7, 4,
308};
309
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000310static const int kIndicesPerAAFillRect = SK_ARRAY_COUNT(gFillAARectIdx);
robertphillips@google.com6d067302012-12-18 21:47:47 +0000311static const int kVertsPerAAFillRect = 8;
312static const int kNumAAFillRectsInIndexBuffer = 256;
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000313
joshualitt67f7f742014-10-22 15:51:52 -0700314GrIndexBuffer* GrAARectRenderer::aaFillRectIndexBuffer(GrGpu* gpu) {
315 static const size_t kAAFillRectIndexBufferSize = kIndicesPerAAFillRect *
316 sizeof(uint16_t) *
317 kNumAAFillRectsInIndexBuffer;
318
319 if (NULL == fAAFillRectIndexBuffer) {
320 fAAFillRectIndexBuffer = gpu->createIndexBuffer(kAAFillRectIndexBufferSize, false);
321 if (fAAFillRectIndexBuffer) {
322 uint16_t* data = (uint16_t*) fAAFillRectIndexBuffer->map();
323 bool useTempData = (NULL == data);
324 if (useTempData) {
325 data = SkNEW_ARRAY(uint16_t, kNumAAFillRectsInIndexBuffer * kIndicesPerAAFillRect);
326 }
327 for (int i = 0; i < kNumAAFillRectsInIndexBuffer; ++i) {
328 // Each AA filled rect is drawn with 8 vertices and 10 triangles (8 around
329 // the inner rect (for AA) and 2 for the inner rect.
330 int baseIdx = i * kIndicesPerAAFillRect;
331 uint16_t baseVert = (uint16_t)(i * kVertsPerAAFillRect);
332 for (int j = 0; j < kIndicesPerAAFillRect; ++j) {
333 data[baseIdx+j] = baseVert + gFillAARectIdx[j];
334 }
335 }
336 if (useTempData) {
337 if (!fAAFillRectIndexBuffer->updateData(data, kAAFillRectIndexBufferSize)) {
338 SkFAIL("Can't get AA Fill Rect indices into buffer!");
339 }
340 SkDELETE_ARRAY(data);
341 } else {
342 fAAFillRectIndexBuffer->unmap();
343 }
344 }
345 }
346
347 return fAAFillRectIndexBuffer;
348}
349
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000350static const uint16_t gMiterStrokeAARectIdx[] = {
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000351 0 + 0, 1 + 0, 5 + 0, 5 + 0, 4 + 0, 0 + 0,
352 1 + 0, 2 + 0, 6 + 0, 6 + 0, 5 + 0, 1 + 0,
353 2 + 0, 3 + 0, 7 + 0, 7 + 0, 6 + 0, 2 + 0,
354 3 + 0, 0 + 0, 4 + 0, 4 + 0, 7 + 0, 3 + 0,
355
356 0 + 4, 1 + 4, 5 + 4, 5 + 4, 4 + 4, 0 + 4,
357 1 + 4, 2 + 4, 6 + 4, 6 + 4, 5 + 4, 1 + 4,
358 2 + 4, 3 + 4, 7 + 4, 7 + 4, 6 + 4, 2 + 4,
359 3 + 4, 0 + 4, 4 + 4, 4 + 4, 7 + 4, 3 + 4,
360
361 0 + 8, 1 + 8, 5 + 8, 5 + 8, 4 + 8, 0 + 8,
362 1 + 8, 2 + 8, 6 + 8, 6 + 8, 5 + 8, 1 + 8,
363 2 + 8, 3 + 8, 7 + 8, 7 + 8, 6 + 8, 2 + 8,
364 3 + 8, 0 + 8, 4 + 8, 4 + 8, 7 + 8, 3 + 8,
365};
366
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000367/**
368 * As in miter-stroke, index = a + b, and a is the current index, b is the shift
369 * from the first index. The index layout:
370 * outer AA line: 0~3, 4~7
371 * outer edge: 8~11, 12~15
372 * inner edge: 16~19
373 * inner AA line: 20~23
374 * Following comes a bevel-stroke rect and its indices:
375 *
376 * 4 7
skia.committer@gmail.com26144182013-11-07 07:02:19 +0000377 * *********************************
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000378 * * ______________________________ *
379 * * / 12 15 \ *
380 * * / \ *
381 * 0 * |8 16_____________________19 11 | * 3
382 * * | | | | *
383 * * | | **************** | | *
384 * * | | * 20 23 * | | *
385 * * | | * * | | *
386 * * | | * 21 22 * | | *
387 * * | | **************** | | *
388 * * | |____________________| | *
389 * 1 * |9 17 18 10| * 2
skia.committer@gmail.com26144182013-11-07 07:02:19 +0000390 * * \ / *
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000391 * * \13 __________________________14/ *
392 * * *
skia.committer@gmail.com26144182013-11-07 07:02:19 +0000393 * **********************************
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000394 * 5 6
395 */
396static const uint16_t gBevelStrokeAARectIdx[] = {
397 // Draw outer AA, from outer AA line to outer edge, shift is 0.
398 0 + 0, 1 + 0, 9 + 0, 9 + 0, 8 + 0, 0 + 0,
399 1 + 0, 5 + 0, 13 + 0, 13 + 0, 9 + 0, 1 + 0,
400 5 + 0, 6 + 0, 14 + 0, 14 + 0, 13 + 0, 5 + 0,
401 6 + 0, 2 + 0, 10 + 0, 10 + 0, 14 + 0, 6 + 0,
402 2 + 0, 3 + 0, 11 + 0, 11 + 0, 10 + 0, 2 + 0,
403 3 + 0, 7 + 0, 15 + 0, 15 + 0, 11 + 0, 3 + 0,
404 7 + 0, 4 + 0, 12 + 0, 12 + 0, 15 + 0, 7 + 0,
405 4 + 0, 0 + 0, 8 + 0, 8 + 0, 12 + 0, 4 + 0,
406
407 // Draw the stroke, from outer edge to inner edge, shift is 8.
408 0 + 8, 1 + 8, 9 + 8, 9 + 8, 8 + 8, 0 + 8,
409 1 + 8, 5 + 8, 9 + 8,
410 5 + 8, 6 + 8, 10 + 8, 10 + 8, 9 + 8, 5 + 8,
411 6 + 8, 2 + 8, 10 + 8,
412 2 + 8, 3 + 8, 11 + 8, 11 + 8, 10 + 8, 2 + 8,
413 3 + 8, 7 + 8, 11 + 8,
414 7 + 8, 4 + 8, 8 + 8, 8 + 8, 11 + 8, 7 + 8,
415 4 + 8, 0 + 8, 8 + 8,
416
417 // Draw the inner AA, from inner edge to inner AA line, shift is 16.
418 0 + 16, 1 + 16, 5 + 16, 5 + 16, 4 + 16, 0 + 16,
419 1 + 16, 2 + 16, 6 + 16, 6 + 16, 5 + 16, 1 + 16,
420 2 + 16, 3 + 16, 7 + 16, 7 + 16, 6 + 16, 2 + 16,
421 3 + 16, 0 + 16, 4 + 16, 4 + 16, 7 + 16, 3 + 16,
422};
423
424int GrAARectRenderer::aaStrokeRectIndexCount(bool miterStroke) {
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000425 return miterStroke ? SK_ARRAY_COUNT(gMiterStrokeAARectIdx) :
426 SK_ARRAY_COUNT(gBevelStrokeAARectIdx);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000427}
428
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000429GrIndexBuffer* GrAARectRenderer::aaStrokeRectIndexBuffer(GrGpu* gpu, bool miterStroke) {
430 if (miterStroke) {
431 if (NULL == fAAMiterStrokeRectIndexBuffer) {
432 fAAMiterStrokeRectIndexBuffer =
joshualitt67f7f742014-10-22 15:51:52 -0700433 gpu->createIndexBuffer(sizeof(gMiterStrokeAARectIdx), false);
434 if (fAAMiterStrokeRectIndexBuffer) {
435#ifdef SK_DEBUG
436 bool updated =
437#endif
438 fAAMiterStrokeRectIndexBuffer->updateData(gMiterStrokeAARectIdx,
439 sizeof(gMiterStrokeAARectIdx));
440 GR_DEBUGASSERT(updated);
441 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000442 }
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000443 return fAAMiterStrokeRectIndexBuffer;
444 } else {
445 if (NULL == fAABevelStrokeRectIndexBuffer) {
446 fAABevelStrokeRectIndexBuffer =
joshualitt67f7f742014-10-22 15:51:52 -0700447 gpu->createIndexBuffer(sizeof(gBevelStrokeAARectIdx), false);
448 if (fAABevelStrokeRectIndexBuffer) {
449#ifdef SK_DEBUG
450 bool updated =
451#endif
452 fAABevelStrokeRectIndexBuffer->updateData(gBevelStrokeAARectIdx,
453 sizeof(gBevelStrokeAARectIdx));
454 GR_DEBUGASSERT(updated);
455 }
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000456 }
457 return fAABevelStrokeRectIndexBuffer;
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000458 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000459}
460
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000461void GrAARectRenderer::geometryFillAARect(GrGpu* gpu,
462 GrDrawTarget* target,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000463 const SkRect& rect,
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000464 const SkMatrix& combinedMatrix,
bsalomon9c0822a2014-08-11 11:07:48 -0700465 const SkRect& devRect) {
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000466 GrDrawState* drawState = target->drawState();
467
bsalomon9c0822a2014-08-11 11:07:48 -0700468 GrColor color = drawState->getColor();
469
bsalomonc30aaa02014-08-13 07:15:29 -0700470 CoverageAttribType covAttribType = set_rect_attribs(drawState);
471 if (kUseCoverage_CoverageAttribType == covAttribType && GrColorIsOpaque(color)) {
bsalomon9c0822a2014-08-11 11:07:48 -0700472 drawState->setHint(GrDrawState::kVertexColorsAreOpaque_Hint, true);
473 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000474
jvanverth@google.comb75b0a02013-02-05 20:33:30 +0000475 GrDrawTarget::AutoReleaseGeometry geo(target, 8, 0);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000476 if (!geo.succeeded()) {
477 GrPrintf("Failed to get space for vertices!\n");
478 return;
479 }
robertphillips@google.com6d067302012-12-18 21:47:47 +0000480
joshualitt67f7f742014-10-22 15:51:52 -0700481 GrIndexBuffer* indexBuffer = this->aaFillRectIndexBuffer(gpu);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000482 if (NULL == indexBuffer) {
483 GrPrintf("Failed to create index buffer!\n");
484 return;
485 }
486
487 intptr_t verts = reinterpret_cast<intptr_t>(geo.vertices());
egdaniel7b3d5ee2014-08-28 05:41:14 -0700488 size_t vstride = drawState->getVertexStride();
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000489
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000490 SkPoint* fan0Pos = reinterpret_cast<SkPoint*>(verts);
egdaniel7b3d5ee2014-08-28 05:41:14 -0700491 SkPoint* fan1Pos = reinterpret_cast<SkPoint*>(verts + 4 * vstride);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000492
robertphillips@google.com908aed82013-05-28 13:16:20 +0000493 SkScalar inset = SkMinScalar(devRect.width(), SK_Scalar1);
494 inset = SK_ScalarHalf * SkMinScalar(inset, devRect.height());
495
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000496 if (combinedMatrix.rectStaysRect()) {
robertphillips@google.comafd1cba2013-05-14 19:47:47 +0000497 // Temporarily #if'ed out. We don't want to pass in the devRect but
498 // right now it is computed in GrContext::apply_aa_to_rect and we don't
499 // want to throw away the work
500#if 0
robertphillips@google.com91b71162013-05-10 14:09:54 +0000501 SkRect devRect;
502 combinedMatrix.mapRect(&devRect, rect);
robertphillips@google.comafd1cba2013-05-14 19:47:47 +0000503#endif
robertphillips@google.com91b71162013-05-10 14:09:54 +0000504
egdaniel7b3d5ee2014-08-28 05:41:14 -0700505 set_inset_fan(fan0Pos, vstride, devRect, -SK_ScalarHalf, -SK_ScalarHalf);
506 set_inset_fan(fan1Pos, vstride, devRect, inset, inset);
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000507 } else {
508 // compute transformed (1, 0) and (0, 1) vectors
509 SkVector vec[2] = {
510 { combinedMatrix[SkMatrix::kMScaleX], combinedMatrix[SkMatrix::kMSkewY] },
511 { combinedMatrix[SkMatrix::kMSkewX], combinedMatrix[SkMatrix::kMScaleY] }
512 };
513
514 vec[0].normalize();
515 vec[0].scale(SK_ScalarHalf);
516 vec[1].normalize();
517 vec[1].scale(SK_ScalarHalf);
518
robertphillips@google.com91b71162013-05-10 14:09:54 +0000519 // create the rotated rect
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000520 fan0Pos->setRectFan(rect.fLeft, rect.fTop,
egdaniel7b3d5ee2014-08-28 05:41:14 -0700521 rect.fRight, rect.fBottom, vstride);
522 combinedMatrix.mapPointsWithStride(fan0Pos, vstride, 4);
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000523
robertphillips@google.com91b71162013-05-10 14:09:54 +0000524 // Now create the inset points and then outset the original
525 // rotated points
526
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000527 // TL
egdaniel7b3d5ee2014-08-28 05:41:14 -0700528 *((SkPoint*)((intptr_t)fan1Pos + 0 * vstride)) =
529 *((SkPoint*)((intptr_t)fan0Pos + 0 * vstride)) + vec[0] + vec[1];
530 *((SkPoint*)((intptr_t)fan0Pos + 0 * vstride)) -= vec[0] + vec[1];
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000531 // BL
egdaniel7b3d5ee2014-08-28 05:41:14 -0700532 *((SkPoint*)((intptr_t)fan1Pos + 1 * vstride)) =
533 *((SkPoint*)((intptr_t)fan0Pos + 1 * vstride)) + vec[0] - vec[1];
534 *((SkPoint*)((intptr_t)fan0Pos + 1 * vstride)) -= vec[0] - vec[1];
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000535 // BR
egdaniel7b3d5ee2014-08-28 05:41:14 -0700536 *((SkPoint*)((intptr_t)fan1Pos + 2 * vstride)) =
537 *((SkPoint*)((intptr_t)fan0Pos + 2 * vstride)) - vec[0] - vec[1];
538 *((SkPoint*)((intptr_t)fan0Pos + 2 * vstride)) += vec[0] + vec[1];
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000539 // TR
egdaniel7b3d5ee2014-08-28 05:41:14 -0700540 *((SkPoint*)((intptr_t)fan1Pos + 3 * vstride)) =
541 *((SkPoint*)((intptr_t)fan0Pos + 3 * vstride)) - vec[0] + vec[1];
542 *((SkPoint*)((intptr_t)fan0Pos + 3 * vstride)) += vec[0] - vec[1];
robertphillips@google.com4b140b52013-05-02 17:13:13 +0000543 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000544
bsalomon9c0822a2014-08-11 11:07:48 -0700545 // Make verts point to vertex color and then set all the color and coverage vertex attrs values.
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000546 verts += sizeof(SkPoint);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000547 for (int i = 0; i < 4; ++i) {
bsalomonc30aaa02014-08-13 07:15:29 -0700548 if (kUseCoverage_CoverageAttribType == covAttribType) {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700549 *reinterpret_cast<GrColor*>(verts + i * vstride) = color;
550 *reinterpret_cast<GrColor*>(verts + i * vstride + sizeof(GrColor)) = 0;
bsalomonc30aaa02014-08-13 07:15:29 -0700551 } else {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700552 *reinterpret_cast<GrColor*>(verts + i * vstride) = 0;
bsalomonc30aaa02014-08-13 07:15:29 -0700553 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000554 }
555
robertphillips@google.com908aed82013-05-28 13:16:20 +0000556 int scale;
557 if (inset < SK_ScalarHalf) {
558 scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
559 SkASSERT(scale >= 0 && scale <= 255);
560 } else {
561 scale = 0xff;
562 }
563
bsalomon9c0822a2014-08-11 11:07:48 -0700564 GrColor innerCoverage;
bsalomonc30aaa02014-08-13 07:15:29 -0700565 if (kUseCoverage_CoverageAttribType == covAttribType) {
566 innerCoverage = GrColorPackRGBA(scale, scale, scale, scale);
567 } else {
568 innerCoverage = (0xff == scale) ? color : SkAlphaMulQ(color, scale);
569 }
egdaniel7b3d5ee2014-08-28 05:41:14 -0700570 verts += 4 * vstride;
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000571 for (int i = 0; i < 4; ++i) {
bsalomonc30aaa02014-08-13 07:15:29 -0700572 if (kUseCoverage_CoverageAttribType == covAttribType) {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700573 *reinterpret_cast<GrColor*>(verts + i * vstride) = color;
574 *reinterpret_cast<GrColor*>(verts + i * vstride + sizeof(GrColor)) = innerCoverage;
bsalomonc30aaa02014-08-13 07:15:29 -0700575 } else {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700576 *reinterpret_cast<GrColor*>(verts + i * vstride) = innerCoverage;
bsalomonc30aaa02014-08-13 07:15:29 -0700577 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000578 }
579
580 target->setIndexSourceToBuffer(indexBuffer);
robertphillips@google.com6d067302012-12-18 21:47:47 +0000581 target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1,
582 kVertsPerAAFillRect,
583 kIndicesPerAAFillRect);
bsalomon@google.com0406b9e2013-04-02 21:00:15 +0000584 target->resetIndexSource();
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000585}
586
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000587namespace {
588
589// Rotated
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000590struct RectVertex {
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000591 SkPoint fPos;
592 SkPoint fCenter;
593 SkPoint fDir;
594 SkPoint fWidthHeight;
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000595};
596
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000597// Rotated
robertphillips@google.com42903302013-04-20 12:26:07 +0000598extern const GrVertexAttrib gAARectVertexAttribs[] = {
599 { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding },
joshualittb0a8a372014-09-23 09:50:21 -0700600 { kVec4f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding },
601 { kVec2f_GrVertexAttribType, 3*sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding }
robertphillips@google.com42903302013-04-20 12:26:07 +0000602};
603
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000604// Axis Aligned
605struct AARectVertex {
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000606 SkPoint fPos;
607 SkPoint fOffset;
608 SkPoint fWidthHeight;
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000609};
610
611// Axis Aligned
612extern const GrVertexAttrib gAAAARectVertexAttribs[] = {
613 { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding },
joshualittb0a8a372014-09-23 09:50:21 -0700614 { kVec4f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding },
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000615};
616
robertphillips@google.com42903302013-04-20 12:26:07 +0000617};
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000618
619void GrAARectRenderer::shaderFillAARect(GrGpu* gpu,
620 GrDrawTarget* target,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000621 const SkRect& rect,
robertphillips@google.com114eb9e2013-05-10 13:16:13 +0000622 const SkMatrix& combinedMatrix) {
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000623 GrDrawState* drawState = target->drawState();
624
625 SkPoint center = SkPoint::Make(rect.centerX(), rect.centerY());
626 combinedMatrix.mapPoints(&center, 1);
627
628 // compute transformed (0, 1) vector
629 SkVector dir = { combinedMatrix[SkMatrix::kMSkewX], combinedMatrix[SkMatrix::kMScaleY] };
630 dir.normalize();
631
632 // compute transformed (width, 0) and (0, height) vectors
633 SkVector vec[2] = {
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000634 { combinedMatrix[SkMatrix::kMScaleX], combinedMatrix[SkMatrix::kMSkewY] },
635 { combinedMatrix[SkMatrix::kMSkewX], combinedMatrix[SkMatrix::kMScaleY] }
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000636 };
637
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000638 SkScalar newWidth = SkScalarHalf(rect.width() * vec[0].length()) + SK_ScalarHalf;
639 SkScalar newHeight = SkScalarHalf(rect.height() * vec[1].length()) + SK_ScalarHalf;
egdaniel7b3d5ee2014-08-28 05:41:14 -0700640 drawState->setVertexAttribs<gAARectVertexAttribs>(SK_ARRAY_COUNT(gAARectVertexAttribs),
641 sizeof(RectVertex));
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000642
643 GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
644 if (!geo.succeeded()) {
645 GrPrintf("Failed to get space for vertices!\n");
646 return;
647 }
648
649 RectVertex* verts = reinterpret_cast<RectVertex*>(geo.vertices());
650
joshualittb0a8a372014-09-23 09:50:21 -0700651 GrGeometryProcessor* gp = GrRectEffect::Create();
652 drawState->setGeometryProcessor(gp)->unref();
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000653
654 for (int i = 0; i < 4; ++i) {
655 verts[i].fCenter = center;
656 verts[i].fDir = dir;
657 verts[i].fWidthHeight.fX = newWidth;
658 verts[i].fWidthHeight.fY = newHeight;
659 }
660
robertphillips@google.com114eb9e2013-05-10 13:16:13 +0000661 SkRect devRect;
662 combinedMatrix.mapRect(&devRect, rect);
663
robertphillips@google.comdf3695e2013-04-09 14:01:44 +0000664 SkRect devBounds = {
665 devRect.fLeft - SK_ScalarHalf,
666 devRect.fTop - SK_ScalarHalf,
667 devRect.fRight + SK_ScalarHalf,
668 devRect.fBottom + SK_ScalarHalf
669 };
670
671 verts[0].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fTop);
672 verts[1].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fBottom);
673 verts[2].fPos = SkPoint::Make(devBounds.fRight, devBounds.fBottom);
674 verts[3].fPos = SkPoint::Make(devBounds.fRight, devBounds.fTop);
675
676 target->setIndexSourceToBuffer(gpu->getContext()->getQuadIndexBuffer());
677 target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6);
678 target->resetIndexSource();
679}
680
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000681void GrAARectRenderer::shaderFillAlignedAARect(GrGpu* gpu,
682 GrDrawTarget* target,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000683 const SkRect& rect,
robertphillips@google.com114eb9e2013-05-10 13:16:13 +0000684 const SkMatrix& combinedMatrix) {
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000685 GrDrawState* drawState = target->drawState();
686 SkASSERT(combinedMatrix.rectStaysRect());
687
egdaniel7b3d5ee2014-08-28 05:41:14 -0700688 drawState->setVertexAttribs<gAAAARectVertexAttribs>(SK_ARRAY_COUNT(gAAAARectVertexAttribs),
689 sizeof(AARectVertex));
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000690
691 GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
692 if (!geo.succeeded()) {
693 GrPrintf("Failed to get space for vertices!\n");
694 return;
695 }
696
697 AARectVertex* verts = reinterpret_cast<AARectVertex*>(geo.vertices());
698
joshualittb0a8a372014-09-23 09:50:21 -0700699 GrGeometryProcessor* gp = GrAlignedRectEffect::Create();
700 drawState->setGeometryProcessor(gp)->unref();
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000701
robertphillips@google.com114eb9e2013-05-10 13:16:13 +0000702 SkRect devRect;
703 combinedMatrix.mapRect(&devRect, rect);
704
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000705 SkRect devBounds = {
706 devRect.fLeft - SK_ScalarHalf,
707 devRect.fTop - SK_ScalarHalf,
708 devRect.fRight + SK_ScalarHalf,
709 devRect.fBottom + SK_ScalarHalf
710 };
711
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000712 SkPoint widthHeight = {
robertphillips@google.comb19cb7f2013-05-02 15:37:20 +0000713 SkScalarHalf(devRect.width()) + SK_ScalarHalf,
714 SkScalarHalf(devRect.height()) + SK_ScalarHalf
715 };
716
717 verts[0].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fTop);
718 verts[0].fOffset = SkPoint::Make(-widthHeight.fX, -widthHeight.fY);
719 verts[0].fWidthHeight = widthHeight;
720
721 verts[1].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fBottom);
722 verts[1].fOffset = SkPoint::Make(-widthHeight.fX, widthHeight.fY);
723 verts[1].fWidthHeight = widthHeight;
724
725 verts[2].fPos = SkPoint::Make(devBounds.fRight, devBounds.fBottom);
726 verts[2].fOffset = widthHeight;
727 verts[2].fWidthHeight = widthHeight;
728
729 verts[3].fPos = SkPoint::Make(devBounds.fRight, devBounds.fTop);
730 verts[3].fOffset = SkPoint::Make(widthHeight.fX, -widthHeight.fY);
731 verts[3].fWidthHeight = widthHeight;
732
733 target->setIndexSourceToBuffer(gpu->getContext()->getQuadIndexBuffer());
734 target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6);
735 target->resetIndexSource();
736}
737
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000738void GrAARectRenderer::strokeAARect(GrGpu* gpu,
bsalomon@google.come7249bd2012-08-16 15:28:54 +0000739 GrDrawTarget* target,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000740 const SkRect& rect,
robertphillips@google.com18136d12013-05-10 11:05:58 +0000741 const SkMatrix& combinedMatrix,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000742 const SkRect& devRect,
bsalomon9c0822a2014-08-11 11:07:48 -0700743 const SkStrokeRec& stroke) {
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000744 SkVector devStrokeSize;
egdanield58a0ba2014-06-11 10:30:05 -0700745 SkScalar width = stroke.getWidth();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000746 if (width > 0) {
747 devStrokeSize.set(width, width);
748 combinedMatrix.mapVectors(&devStrokeSize, 1);
749 devStrokeSize.setAbs(devStrokeSize);
750 } else {
751 devStrokeSize.set(SK_Scalar1, SK_Scalar1);
752 }
jvanverth@google.com9b855c72013-03-01 18:21:22 +0000753
robertphillips@google.com18136d12013-05-10 11:05:58 +0000754 const SkScalar dx = devStrokeSize.fX;
755 const SkScalar dy = devStrokeSize.fY;
bsalomon@google.com81712882012-11-01 17:12:34 +0000756 const SkScalar rx = SkScalarMul(dx, SK_ScalarHalf);
757 const SkScalar ry = SkScalarMul(dy, SK_ScalarHalf);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000758
robertphillips@google.comafd1cba2013-05-14 19:47:47 +0000759 // Temporarily #if'ed out. We don't want to pass in the devRect but
760 // right now it is computed in GrContext::apply_aa_to_rect and we don't
761 // want to throw away the work
762#if 0
robertphillips@google.com18136d12013-05-10 11:05:58 +0000763 SkRect devRect;
764 combinedMatrix.mapRect(&devRect, rect);
robertphillips@google.comafd1cba2013-05-14 19:47:47 +0000765#endif
robertphillips@google.com18136d12013-05-10 11:05:58 +0000766
bsalomon@google.com81712882012-11-01 17:12:34 +0000767 SkScalar spare;
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000768 {
bsalomon@google.com81712882012-11-01 17:12:34 +0000769 SkScalar w = devRect.width() - dx;
770 SkScalar h = devRect.height() - dy;
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000771 spare = SkTMin(w, h);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000772 }
773
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000774 SkRect devOutside(devRect);
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000775 devOutside.outset(rx, ry);
776
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000777 bool miterStroke = true;
yunchao.he2bff2302014-07-28 19:18:49 -0700778 // For hairlines, make bevel and round joins appear the same as mitered ones.
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000779 // small miter limit means right angles show bevel...
yunchao.he2bff2302014-07-28 19:18:49 -0700780 if ((width > 0) && (stroke.getJoin() != SkPaint::kMiter_Join ||
781 stroke.getMiter() < SK_ScalarSqrt2)) {
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000782 miterStroke = false;
783 }
784
785 if (spare <= 0 && miterStroke) {
bsalomon9c0822a2014-08-11 11:07:48 -0700786 this->fillAARect(gpu, target, devOutside, SkMatrix::I(), devOutside);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000787 return;
788 }
skia.committer@gmail.comf140f182013-03-02 07:01:56 +0000789
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000790 SkRect devInside(devRect);
791 devInside.inset(rx, ry);
792
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000793 SkRect devOutsideAssist(devRect);
794
795 // For bevel-stroke, use 2 SkRect instances(devOutside and devOutsideAssist)
796 // to draw the outer of the rect. Because there are 8 vertices on the outer
skia.committer@gmail.com26144182013-11-07 07:02:19 +0000797 // edge, while vertex number of inner edge is 4, the same as miter-stroke.
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000798 if (!miterStroke) {
799 devOutside.inset(0, ry);
800 devOutsideAssist.outset(0, ry);
801 }
802
bsalomon9c0822a2014-08-11 11:07:48 -0700803 this->geometryStrokeAARect(gpu, target, devOutside, devOutsideAssist, devInside, miterStroke);
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000804}
805
806void GrAARectRenderer::geometryStrokeAARect(GrGpu* gpu,
807 GrDrawTarget* target,
808 const SkRect& devOutside,
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000809 const SkRect& devOutsideAssist,
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000810 const SkRect& devInside,
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000811 bool miterStroke) {
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000812 GrDrawState* drawState = target->drawState();
813
bsalomonc30aaa02014-08-13 07:15:29 -0700814 CoverageAttribType covAttribType = set_rect_attribs(drawState);
bsalomon9c0822a2014-08-11 11:07:48 -0700815
816 GrColor color = drawState->getColor();
bsalomonc30aaa02014-08-13 07:15:29 -0700817 if (kUseCoverage_CoverageAttribType == covAttribType && GrColorIsOpaque(color)) {
bsalomon9c0822a2014-08-11 11:07:48 -0700818 drawState->setHint(GrDrawState::kVertexColorsAreOpaque_Hint, true);
819 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000820
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000821 int innerVertexNum = 4;
822 int outerVertexNum = miterStroke ? 4 : 8;
823 int totalVertexNum = (outerVertexNum + innerVertexNum) * 2;
824
825 GrDrawTarget::AutoReleaseGeometry geo(target, totalVertexNum, 0);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000826 if (!geo.succeeded()) {
827 GrPrintf("Failed to get space for vertices!\n");
828 return;
829 }
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000830 GrIndexBuffer* indexBuffer = this->aaStrokeRectIndexBuffer(gpu, miterStroke);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000831 if (NULL == indexBuffer) {
832 GrPrintf("Failed to create index buffer!\n");
833 return;
834 }
835
836 intptr_t verts = reinterpret_cast<intptr_t>(geo.vertices());
egdaniel7b3d5ee2014-08-28 05:41:14 -0700837 size_t vstride = drawState->getVertexStride();
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000838
bsalomon@google.come7249bd2012-08-16 15:28:54 +0000839 // We create vertices for four nested rectangles. There are two ramps from 0 to full
840 // coverage, one on the exterior of the stroke and the other on the interior.
841 // The following pointers refer to the four rects, from outermost to innermost.
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000842 SkPoint* fan0Pos = reinterpret_cast<SkPoint*>(verts);
egdaniel7b3d5ee2014-08-28 05:41:14 -0700843 SkPoint* fan1Pos = reinterpret_cast<SkPoint*>(verts + outerVertexNum * vstride);
844 SkPoint* fan2Pos = reinterpret_cast<SkPoint*>(verts + 2 * outerVertexNum * vstride);
845 SkPoint* fan3Pos = reinterpret_cast<SkPoint*>(verts + (2 * outerVertexNum + innerVertexNum) * vstride);
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000846
robertphillips@google.comc111ce22013-07-01 13:10:10 +0000847#ifndef SK_IGNORE_THIN_STROKED_RECT_FIX
robertphillips@google.com353f0972013-06-28 17:57:06 +0000848 // TODO: this only really works if the X & Y margins are the same all around
robertphillips183e9852014-10-21 11:25:37 -0700849 // the rect (or if they are all >= 1.0).
robertphillips@google.com353f0972013-06-28 17:57:06 +0000850 SkScalar inset = SkMinScalar(SK_Scalar1, devOutside.fRight - devInside.fRight);
851 inset = SkMinScalar(inset, devInside.fLeft - devOutside.fLeft);
852 inset = SkMinScalar(inset, devInside.fTop - devOutside.fTop);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000853 if (miterStroke) {
854 inset = SK_ScalarHalf * SkMinScalar(inset, devOutside.fBottom - devInside.fBottom);
855 } else {
856 inset = SK_ScalarHalf * SkMinScalar(inset, devOutsideAssist.fBottom - devInside.fBottom);
857 }
robertphillips@google.com353f0972013-06-28 17:57:06 +0000858 SkASSERT(inset >= 0);
robertphillips@google.comc111ce22013-07-01 13:10:10 +0000859#else
860 SkScalar inset = SK_ScalarHalf;
861#endif
robertphillips@google.com353f0972013-06-28 17:57:06 +0000862
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000863 if (miterStroke) {
864 // outermost
egdaniel7b3d5ee2014-08-28 05:41:14 -0700865 set_inset_fan(fan0Pos, vstride, devOutside, -SK_ScalarHalf, -SK_ScalarHalf);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000866 // inner two
egdaniel7b3d5ee2014-08-28 05:41:14 -0700867 set_inset_fan(fan1Pos, vstride, devOutside, inset, inset);
868 set_inset_fan(fan2Pos, vstride, devInside, -inset, -inset);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000869 // innermost
egdaniel7b3d5ee2014-08-28 05:41:14 -0700870 set_inset_fan(fan3Pos, vstride, devInside, SK_ScalarHalf, SK_ScalarHalf);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000871 } else {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700872 SkPoint* fan0AssistPos = reinterpret_cast<SkPoint*>(verts + 4 * vstride);
873 SkPoint* fan1AssistPos = reinterpret_cast<SkPoint*>(verts + (outerVertexNum + 4) * vstride);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000874 // outermost
egdaniel7b3d5ee2014-08-28 05:41:14 -0700875 set_inset_fan(fan0Pos, vstride, devOutside, -SK_ScalarHalf, -SK_ScalarHalf);
876 set_inset_fan(fan0AssistPos, vstride, devOutsideAssist, -SK_ScalarHalf, -SK_ScalarHalf);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000877 // outer one of the inner two
egdaniel7b3d5ee2014-08-28 05:41:14 -0700878 set_inset_fan(fan1Pos, vstride, devOutside, inset, inset);
879 set_inset_fan(fan1AssistPos, vstride, devOutsideAssist, inset, inset);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000880 // inner one of the inner two
egdaniel7b3d5ee2014-08-28 05:41:14 -0700881 set_inset_fan(fan2Pos, vstride, devInside, -inset, -inset);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000882 // innermost
egdaniel7b3d5ee2014-08-28 05:41:14 -0700883 set_inset_fan(fan3Pos, vstride, devInside, SK_ScalarHalf, SK_ScalarHalf);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000884 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000885
bsalomon9c0822a2014-08-11 11:07:48 -0700886 // Make verts point to vertex color and then set all the color and coverage vertex attrs values.
bsalomon@google.come7249bd2012-08-16 15:28:54 +0000887 // The outermost rect has 0 coverage
commit-bot@chromium.org972f9cd2014-03-28 17:58:28 +0000888 verts += sizeof(SkPoint);
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000889 for (int i = 0; i < outerVertexNum; ++i) {
bsalomonc30aaa02014-08-13 07:15:29 -0700890 if (kUseCoverage_CoverageAttribType == covAttribType) {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700891 *reinterpret_cast<GrColor*>(verts + i * vstride) = color;
892 *reinterpret_cast<GrColor*>(verts + i * vstride + sizeof(GrColor)) = 0;
bsalomonc30aaa02014-08-13 07:15:29 -0700893 } else {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700894 *reinterpret_cast<GrColor*>(verts + i * vstride) = 0;
bsalomonc30aaa02014-08-13 07:15:29 -0700895 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000896 }
897
bsalomon9c0822a2014-08-11 11:07:48 -0700898 // scale is the coverage for the the inner two rects.
robertphillips@google.com353f0972013-06-28 17:57:06 +0000899 int scale;
900 if (inset < SK_ScalarHalf) {
901 scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
902 SkASSERT(scale >= 0 && scale <= 255);
903 } else {
904 scale = 0xff;
905 }
906
egdaniel7b3d5ee2014-08-28 05:41:14 -0700907 verts += outerVertexNum * vstride;
bsalomonc30aaa02014-08-13 07:15:29 -0700908 GrColor innerCoverage;
909 if (kUseCoverage_CoverageAttribType == covAttribType) {
910 innerCoverage = GrColorPackRGBA(scale, scale, scale, scale);
911 } else {
912 innerCoverage = (0xff == scale) ? color : SkAlphaMulQ(color, scale);
913 }
914
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000915 for (int i = 0; i < outerVertexNum + innerVertexNum; ++i) {
bsalomonc30aaa02014-08-13 07:15:29 -0700916 if (kUseCoverage_CoverageAttribType == covAttribType) {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700917 *reinterpret_cast<GrColor*>(verts + i * vstride) = color;
918 *reinterpret_cast<GrColor*>(verts + i * vstride + sizeof(GrColor)) = innerCoverage;
bsalomonc30aaa02014-08-13 07:15:29 -0700919 } else {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700920 *reinterpret_cast<GrColor*>(verts + i * vstride) = innerCoverage;
bsalomonc30aaa02014-08-13 07:15:29 -0700921 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000922 }
923
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000924 // The innermost rect has 0 coverage
egdaniel7b3d5ee2014-08-28 05:41:14 -0700925 verts += (outerVertexNum + innerVertexNum) * vstride;
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000926 for (int i = 0; i < innerVertexNum; ++i) {
bsalomonc30aaa02014-08-13 07:15:29 -0700927 if (kUseCoverage_CoverageAttribType == covAttribType) {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700928 *reinterpret_cast<GrColor*>(verts + i * vstride) = color;
929 *reinterpret_cast<GrColor*>(verts + i * vstride + sizeof(GrColor)) = 0;
bsalomonc30aaa02014-08-13 07:15:29 -0700930 } else {
egdaniel7b3d5ee2014-08-28 05:41:14 -0700931 *reinterpret_cast<GrColor*>(verts + i * vstride) = 0;
bsalomonc30aaa02014-08-13 07:15:29 -0700932 }
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000933 }
934
935 target->setIndexSourceToBuffer(indexBuffer);
joshualitt67f7f742014-10-22 15:51:52 -0700936 target->drawIndexed(kTriangles_GrPrimitiveType, 0, 0,
937 totalVertexNum, aaStrokeRectIndexCount(miterStroke));
robertphillips@google.comf6747b02012-06-12 00:32:28 +0000938}
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000939
940void GrAARectRenderer::fillAANestedRects(GrGpu* gpu,
941 GrDrawTarget* target,
942 const SkRect rects[2],
bsalomon9c0822a2014-08-11 11:07:48 -0700943 const SkMatrix& combinedMatrix) {
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000944 SkASSERT(combinedMatrix.rectStaysRect());
945 SkASSERT(!rects[1].isEmpty());
946
commit-bot@chromium.org6006d0f2013-11-06 10:08:21 +0000947 SkRect devOutside, devOutsideAssist, devInside;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000948 combinedMatrix.mapRect(&devOutside, rects[0]);
949 // can't call mapRect for devInside since it calls sort
950 combinedMatrix.mapPoints((SkPoint*)&devInside, (const SkPoint*)&rects[1], 2);
951
952 if (devInside.isEmpty()) {
bsalomon9c0822a2014-08-11 11:07:48 -0700953 this->fillAARect(gpu, target, devOutside, SkMatrix::I(), devOutside);
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000954 return;
955 }
956
bsalomon9c0822a2014-08-11 11:07:48 -0700957 this->geometryStrokeAARect(gpu, target, devOutside, devOutsideAssist, devInside, true);
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000958}