Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(402)

Side by Side Diff: src/gpu/GrAARectRenderer.cpp

Issue 678953002: Default geometry processor (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: fix Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/gpu/GrAARectRenderer.h ('k') | src/gpu/GrDefaultGeoProcFactory.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright 2012 Google Inc. 2 * Copyright 2012 Google Inc.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license that can be 4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file. 5 * found in the LICENSE file.
6 */ 6 */
7 7
8 #include "GrAARectRenderer.h" 8 #include "GrAARectRenderer.h"
9 #include "GrGpu.h" 9 #include "GrGpu.h"
10 #include "gl/builders/GrGLProgramBuilder.h" 10 #include "gl/builders/GrGLProgramBuilder.h"
11 #include "gl/GrGLProcessor.h" 11 #include "gl/GrGLProcessor.h"
12 #include "gl/GrGLGeometryProcessor.h" 12 #include "gl/GrGLGeometryProcessor.h"
13 #include "GrTBackendProcessorFactory.h" 13 #include "GrTBackendProcessorFactory.h"
14 #include "SkColorPriv.h" 14 #include "SkColorPriv.h"
15 #include "GrGeometryProcessor.h" 15 #include "GrGeometryProcessor.h"
16 16
17 /////////////////////////////////////////////////////////////////////////////// 17 ///////////////////////////////////////////////////////////////////////////////
18 class GrGLAlignedRectEffect;
19
20 // Axis Aligned special case
21 class GrAlignedRectEffect : public GrGeometryProcessor {
22 public:
23 static GrGeometryProcessor* Create() {
24 GR_CREATE_STATIC_PROCESSOR(gAlignedRectEffect, GrAlignedRectEffect, ());
25 gAlignedRectEffect->ref();
26 return gAlignedRectEffect;
27 }
28
29 virtual ~GrAlignedRectEffect() {}
30
31 static const char* Name() { return "AlignedRectEdge"; }
32
33 const GrShaderVar& inRect() const { return fInRect; }
34
35 virtual const GrBackendGeometryProcessorFactory& getFactory() const SK_OVERR IDE {
36 return GrTBackendGeometryProcessorFactory<GrAlignedRectEffect>::getInsta nce();
37 }
38
39 class GLProcessor : public GrGLGeometryProcessor {
40 public:
41 GLProcessor(const GrBackendProcessorFactory& factory, const GrProcessor& )
42 : INHERITED (factory) {}
43
44 virtual void emitCode(const EmitArgs& args) SK_OVERRIDE {
45 // setup the varying for the Axis aligned rect effect
46 // xy -> interpolated offset
47 // zw -> w/2+0.5, h/2+0.5
48 GrGLVertToFrag v(kVec4f_GrSLType);
49 args.fPB->addVarying("Rect", &v);
50
51 const GrShaderVar& inRect = args.fGP.cast<GrAlignedRectEffect>().inR ect();
52 GrGLVertexBuilder* vsBuilder = args.fPB->getVertexShaderBuilder();
53 vsBuilder->codeAppendf("\t%s = %s;\n", v.fsIn(), inRect.c_str());
54
55 GrGLGPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilde r();
56 // TODO: compute all these offsets, spans, and scales in the VS
57 fsBuilder->codeAppendf("\tfloat insetW = min(1.0, %s.z) - 0.5;\n", v .fsIn());
58 fsBuilder->codeAppendf("\tfloat insetH = min(1.0, %s.w) - 0.5;\n", v .fsIn());
59 fsBuilder->codeAppend("\tfloat outset = 0.5;\n");
60 // 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.
62 fsBuilder->codeAppend("\tfloat spanW = insetW + outset;\n");
63 fsBuilder->codeAppend("\tfloat spanH = insetH + outset;\n");
64 // 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.
67 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");
69
70 // Compute the coverage for the rect's width
71 fsBuilder->codeAppendf(
72 "\tfloat coverage = scaleW*clamp((%s.z-abs(%s.x))/spanW, 0.0, 1. 0);\n", v.fsIn(),
73 v.fsIn());
74 // Compute the coverage for the rect's height and merge with the wid th
75 fsBuilder->codeAppendf(
76 "\tcoverage = coverage*scaleH*clamp((%s.w-abs(%s.y))/spanH, 0.0, 1.0);\n",
77 v.fsIn(), v.fsIn());
78
79
80 fsBuilder->codeAppendf("\t%s = %s;\n", args.fOutput,
81 (GrGLSLExpr4(args.fInput) * GrGLSLExpr1("cove rage")).c_str());
82 }
83
84 static void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBu ilder*) {}
85
86 virtual void setData(const GrGLProgramDataManager& pdman, const GrProces sor&) SK_OVERRIDE {}
87
88 private:
89 typedef GrGLGeometryProcessor INHERITED;
90 };
91
92
93 private:
94 GrAlignedRectEffect()
95 : fInRect(this->addVertexAttrib(GrShaderVar("inRect",
96 kVec4f_GrSLType,
97 GrShaderVar::kAttribute_Type Modifier))) {
98 }
99
100 const GrShaderVar& fInRect;
101
102 virtual bool onIsEqual(const GrGeometryProcessor&) const SK_OVERRIDE { retur n true; }
103
104 virtual void onComputeInvariantOutput(InvariantOutput* inout) const SK_OVERR IDE {
105 inout->mulByUnknownAlpha();
106 }
107
108 GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
109
110 typedef GrGeometryProcessor INHERITED;
111 };
112
113
114 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrAlignedRectEffect);
115
116 GrGeometryProcessor* GrAlignedRectEffect::TestCreate(SkRandom* random,
117 GrContext* context,
118 const GrDrawTargetCaps&,
119 GrTexture* textures[]) {
120 return GrAlignedRectEffect::Create();
121 }
122
123 ///////////////////////////////////////////////////////////////////////////////
124 class GrGLRectEffect;
125
126 /**
127 * The output of this effect is a modulation of the input color and coverage
128 * 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 */
138
139 class GrRectEffect : public GrGeometryProcessor {
140 public:
141 static GrGeometryProcessor* Create() {
142 GR_CREATE_STATIC_PROCESSOR(gRectEffect, GrRectEffect, ());
143 gRectEffect->ref();
144 return gRectEffect;
145 }
146
147 virtual ~GrRectEffect() {}
148
149 static const char* Name() { return "RectEdge"; }
150
151 const GrShaderVar& inRectEdge() const { return fInRectEdge; }
152 const GrShaderVar& inWidthHeight() const { return fInWidthHeight; }
153
154 virtual const GrBackendGeometryProcessorFactory& getFactory() const SK_OVERR IDE {
155 return GrTBackendGeometryProcessorFactory<GrRectEffect>::getInstance();
156 }
157
158 class GLProcessor : public GrGLGeometryProcessor {
159 public:
160 GLProcessor(const GrBackendProcessorFactory& factory, const GrProcessor& )
161 : INHERITED (factory) {}
162
163 virtual void emitCode(const EmitArgs& args) SK_OVERRIDE {
164 // setup the varying for the center point and the unit vector
165 // that points down the height of the rect
166 GrGLVertToFrag rectEdge(kVec4f_GrSLType);
167 args.fPB->addVarying("RectEdge", &rectEdge);
168
169 const GrRectEffect& rectEffect = args.fGP.cast<GrRectEffect>();
170 GrGLVertexBuilder* vsBuilder = args.fPB->getVertexShaderBuilder();
171 vsBuilder->codeAppendf("%s = %s;", rectEdge.vsOut(), rectEffect.inRe ctEdge().c_str());
172
173 // setup the varying for width/2+.5 and height/2+.5
174 GrGLVertToFrag widthHeight(kVec2f_GrSLType);
175 args.fPB->addVarying("WidthHeight", &widthHeight);
176 vsBuilder->codeAppendf("%s = %s;",
177 widthHeight.vsOut(),
178 rectEffect.inWidthHeight().c_str());
179
180 GrGLGPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilde r();
181 // TODO: compute all these offsets, spans, and scales in the VS
182 fsBuilder->codeAppendf("\tfloat insetW = min(1.0, %s.x) - 0.5;\n", w idthHeight.fsIn());
183 fsBuilder->codeAppendf("\tfloat insetH = min(1.0, %s.y) - 0.5;\n", w idthHeight.fsIn());
184 fsBuilder->codeAppend("\tfloat outset = 0.5;\n");
185 // For rects > 1 pixel wide and tall the span's are noops (i.e., 1.0 ). For rects
186 // < 1 pixel wide or tall they serve to normalize the < 1 ramp to a 0 .. 1 range.
187 fsBuilder->codeAppend("\tfloat spanW = insetW + outset;\n");
188 fsBuilder->codeAppend("\tfloat spanH = insetH + outset;\n");
189 // For rects < 1 pixel wide or tall, these scale factors are used to cap the maximum
190 // value of coverage that is used. In other words it is the coverage that is
191 // used in the interior of the rect after the ramp.
192 fsBuilder->codeAppend("\tfloat scaleW = min(1.0, 2.0*insetW/spanW);\ n");
193 fsBuilder->codeAppend("\tfloat scaleH = min(1.0, 2.0*insetH/spanH);\ n");
194
195 // Compute the coverage for the rect's width
196 fsBuilder->codeAppendf("\tvec2 offset = %s.xy - %s.xy;\n",
197 fsBuilder->fragmentPosition(), rectEdge.fsIn( ));
198 fsBuilder->codeAppendf("\tfloat perpDot = abs(offset.x * %s.w - offs et.y * %s.z);\n",
199 rectEdge.fsIn(), rectEdge.fsIn());
200 fsBuilder->codeAppendf(
201 "\tfloat coverage = scaleW*clamp((%s.x-perpDot)/spanW, 0.0, 1.0) ;\n",
202 widthHeight.fsIn());
203
204 // Compute the coverage for the rect's height and merge with the wid th
205 fsBuilder->codeAppendf("\tperpDot = abs(dot(offset, %s.zw));\n",
206 rectEdge.fsIn());
207 fsBuilder->codeAppendf(
208 "\tcoverage = coverage*scaleH*clamp((%s.y-perpDot)/spanH, 0. 0, 1.0);\n",
209 widthHeight.fsIn());
210
211
212 fsBuilder->codeAppendf("\t%s = %s;\n", args.fOutput,
213 (GrGLSLExpr4(args.fInput) * GrGLSLExpr1("cove rage")).c_str());
214 }
215
216 static void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBu ilder*) {}
217
218 virtual void setData(const GrGLProgramDataManager& pdman, const GrProces sor&) SK_OVERRIDE {}
219
220 private:
221 typedef GrGLGeometryProcessor INHERITED;
222 };
223
224
225
226 private:
227 GrRectEffect()
228 : fInRectEdge(this->addVertexAttrib(GrShaderVar("inRectEdge",
229 kVec4f_GrSLType,
230 GrShaderVar::kAttribute_ TypeModifier)))
231 , fInWidthHeight(this->addVertexAttrib(
232 GrShaderVar("inWidthHeight",
233 kVec2f_GrSLType,
234 GrShaderVar::kAttribute_TypeModifier))) {
235 this->setWillReadFragmentPosition();
236 }
237
238 virtual bool onIsEqual(const GrGeometryProcessor&) const SK_OVERRIDE { retur n true; }
239
240 virtual void onComputeInvariantOutput(InvariantOutput* inout) const SK_OVERR IDE {
241 inout->mulByUnknownAlpha();
242 }
243
244 const GrShaderVar& fInRectEdge;
245 const GrShaderVar& fInWidthHeight;
246
247 GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
248
249 typedef GrGeometryProcessor INHERITED;
250 };
251
252
253 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrRectEffect);
254
255 GrGeometryProcessor* GrRectEffect::TestCreate(SkRandom* random,
256 GrContext* context,
257 const GrDrawTargetCaps&,
258 GrTexture* textures[]) {
259 return GrRectEffect::Create();
260 }
261
262 ///////////////////////////////////////////////////////////////////////////////
263 18
264 namespace { 19 namespace {
265 extern const GrVertexAttrib gAARectAttribs[] = { 20 extern const GrVertexAttrib gAARectAttribs[] = {
266 {kVec2f_GrVertexAttribType, 0, kPosition_Gr VertexAttribBinding}, 21 {kVec2f_GrVertexAttribType, 0, kPosition_Gr VertexAttribBinding},
267 {kVec4ub_GrVertexAttribType, sizeof(SkPoint), kColor_GrVer texAttribBinding}, 22 {kVec4ub_GrVertexAttribType, sizeof(SkPoint), kColor_GrVer texAttribBinding},
268 {kFloat_GrVertexAttribType, sizeof(SkPoint) + sizeof(SkColor), kCoverage_Gr VertexAttribBinding}, 23 {kFloat_GrVertexAttribType, sizeof(SkPoint) + sizeof(SkColor), kCoverage_Gr VertexAttribBinding},
269 }; 24 };
270 25
271 // Should the coverage be multiplied into the color attrib or use a separate att rib. 26 // Should the coverage be multiplied into the color attrib or use a separate att rib.
272 enum CoverageAttribType { 27 enum CoverageAttribType {
(...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after
541 } 296 }
542 } 297 }
543 298
544 target->setIndexSourceToBuffer(indexBuffer); 299 target->setIndexSourceToBuffer(indexBuffer);
545 target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 300 target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1,
546 kVertsPerAAFillRect, 301 kVertsPerAAFillRect,
547 kIndicesPerAAFillRect); 302 kIndicesPerAAFillRect);
548 target->resetIndexSource(); 303 target->resetIndexSource();
549 } 304 }
550 305
551 namespace {
552
553 // Rotated
554 struct RectVertex {
555 SkPoint fPos;
556 SkPoint fCenter;
557 SkPoint fDir;
558 SkPoint fWidthHeight;
559 };
560
561 // Rotated
562 extern const GrVertexAttrib gAARectVertexAttribs[] = {
563 { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBind ing },
564 { kVec4f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexA ttribBinding },
565 { kVec2f_GrVertexAttribType, 3*sizeof(SkPoint), kGeometryProcessor_GrVertexA ttribBinding }
566 };
567
568 // Axis Aligned
569 struct AARectVertex {
570 SkPoint fPos;
571 SkPoint fOffset;
572 SkPoint fWidthHeight;
573 };
574
575 // Axis Aligned
576 extern const GrVertexAttrib gAAAARectVertexAttribs[] = {
577 { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBind ing },
578 { kVec4f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexA ttribBinding },
579 };
580
581 };
582
583 void GrAARectRenderer::shaderFillAARect(GrDrawTarget* target,
584 const SkRect& rect,
585 const SkMatrix& combinedMatrix) {
586 GrDrawState* drawState = target->drawState();
587
588 SkPoint center = SkPoint::Make(rect.centerX(), rect.centerY());
589 combinedMatrix.mapPoints(&center, 1);
590
591 // compute transformed (0, 1) vector
592 SkVector dir = { combinedMatrix[SkMatrix::kMSkewX], combinedMatrix[SkMatrix: :kMScaleY] };
593 dir.normalize();
594
595 // compute transformed (width, 0) and (0, height) vectors
596 SkVector vec[2] = {
597 { combinedMatrix[SkMatrix::kMScaleX], combinedMatrix[SkMatrix::kMSkewY] },
598 { combinedMatrix[SkMatrix::kMSkewX], combinedMatrix[SkMatrix::kMScaleY] }
599 };
600
601 SkScalar newWidth = SkScalarHalf(rect.width() * vec[0].length()) + SK_Scalar Half;
602 SkScalar newHeight = SkScalarHalf(rect.height() * vec[1].length()) + SK_Scal arHalf;
603 drawState->setVertexAttribs<gAARectVertexAttribs>(SK_ARRAY_COUNT(gAARectVert exAttribs),
604 sizeof(RectVertex));
605
606 GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
607 if (!geo.succeeded()) {
608 SkDebugf("Failed to get space for vertices!\n");
609 return;
610 }
611
612 RectVertex* verts = reinterpret_cast<RectVertex*>(geo.vertices());
613
614 GrGeometryProcessor* gp = GrRectEffect::Create();
615 drawState->setGeometryProcessor(gp)->unref();
616
617 for (int i = 0; i < 4; ++i) {
618 verts[i].fCenter = center;
619 verts[i].fDir = dir;
620 verts[i].fWidthHeight.fX = newWidth;
621 verts[i].fWidthHeight.fY = newHeight;
622 }
623
624 SkRect devRect;
625 combinedMatrix.mapRect(&devRect, rect);
626
627 SkRect devBounds = {
628 devRect.fLeft - SK_ScalarHalf,
629 devRect.fTop - SK_ScalarHalf,
630 devRect.fRight + SK_ScalarHalf,
631 devRect.fBottom + SK_ScalarHalf
632 };
633
634 verts[0].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fTop);
635 verts[1].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fBottom);
636 verts[2].fPos = SkPoint::Make(devBounds.fRight, devBounds.fBottom);
637 verts[3].fPos = SkPoint::Make(devBounds.fRight, devBounds.fTop);
638
639 target->setIndexSourceToBuffer(fGpu->getContext()->getQuadIndexBuffer());
640 target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6);
641 target->resetIndexSource();
642 }
643
644 void GrAARectRenderer::shaderFillAlignedAARect(GrDrawTarget* target,
645 const SkRect& rect,
646 const SkMatrix& combinedMatrix) {
647 GrDrawState* drawState = target->drawState();
648 SkASSERT(combinedMatrix.rectStaysRect());
649
650 drawState->setVertexAttribs<gAAAARectVertexAttribs>(SK_ARRAY_COUNT(gAAAARect VertexAttribs),
651 sizeof(AARectVertex));
652
653 GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0);
654 if (!geo.succeeded()) {
655 SkDebugf("Failed to get space for vertices!\n");
656 return;
657 }
658
659 AARectVertex* verts = reinterpret_cast<AARectVertex*>(geo.vertices());
660
661 GrGeometryProcessor* gp = GrAlignedRectEffect::Create();
662 drawState->setGeometryProcessor(gp)->unref();
663
664 SkRect devRect;
665 combinedMatrix.mapRect(&devRect, rect);
666
667 SkRect devBounds = {
668 devRect.fLeft - SK_ScalarHalf,
669 devRect.fTop - SK_ScalarHalf,
670 devRect.fRight + SK_ScalarHalf,
671 devRect.fBottom + SK_ScalarHalf
672 };
673
674 SkPoint widthHeight = {
675 SkScalarHalf(devRect.width()) + SK_ScalarHalf,
676 SkScalarHalf(devRect.height()) + SK_ScalarHalf
677 };
678
679 verts[0].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fTop);
680 verts[0].fOffset = SkPoint::Make(-widthHeight.fX, -widthHeight.fY);
681 verts[0].fWidthHeight = widthHeight;
682
683 verts[1].fPos = SkPoint::Make(devBounds.fLeft, devBounds.fBottom);
684 verts[1].fOffset = SkPoint::Make(-widthHeight.fX, widthHeight.fY);
685 verts[1].fWidthHeight = widthHeight;
686
687 verts[2].fPos = SkPoint::Make(devBounds.fRight, devBounds.fBottom);
688 verts[2].fOffset = widthHeight;
689 verts[2].fWidthHeight = widthHeight;
690
691 verts[3].fPos = SkPoint::Make(devBounds.fRight, devBounds.fTop);
692 verts[3].fOffset = SkPoint::Make(widthHeight.fX, -widthHeight.fY);
693 verts[3].fWidthHeight = widthHeight;
694
695 target->setIndexSourceToBuffer(fGpu->getContext()->getQuadIndexBuffer());
696 target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6);
697 target->resetIndexSource();
698 }
699
700 void GrAARectRenderer::strokeAARect(GrDrawTarget* target, 306 void GrAARectRenderer::strokeAARect(GrDrawTarget* target,
701 const SkRect& rect, 307 const SkRect& rect,
702 const SkMatrix& combinedMatrix, 308 const SkMatrix& combinedMatrix,
703 const SkRect& devRect, 309 const SkRect& devRect,
704 const SkStrokeRec& stroke) { 310 const SkStrokeRec& stroke) {
705 SkVector devStrokeSize; 311 SkVector devStrokeSize;
706 SkScalar width = stroke.getWidth(); 312 SkScalar width = stroke.getWidth();
707 if (width > 0) { 313 if (width > 0) {
708 devStrokeSize.set(width, width); 314 devStrokeSize.set(width, width);
709 combinedMatrix.mapVectors(&devStrokeSize, 1); 315 combinedMatrix.mapVectors(&devStrokeSize, 1);
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after
905 // can't call mapRect for devInside since it calls sort 511 // can't call mapRect for devInside since it calls sort
906 combinedMatrix.mapPoints((SkPoint*)&devInside, (const SkPoint*)&rects[1], 2) ; 512 combinedMatrix.mapPoints((SkPoint*)&devInside, (const SkPoint*)&rects[1], 2) ;
907 513
908 if (devInside.isEmpty()) { 514 if (devInside.isEmpty()) {
909 this->fillAARect(target, devOutside, SkMatrix::I(), devOutside); 515 this->fillAARect(target, devOutside, SkMatrix::I(), devOutside);
910 return; 516 return;
911 } 517 }
912 518
913 this->geometryStrokeAARect(target, devOutside, devOutsideAssist, devInside, true); 519 this->geometryStrokeAARect(target, devOutside, devOutsideAssist, devInside, true);
914 } 520 }
OLDNEW
« no previous file with comments | « src/gpu/GrAARectRenderer.h ('k') | src/gpu/GrDefaultGeoProcFactory.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698