1 /*
2  * Copyright 2015 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 "GrNonAAStrokeRectOp.h"
9 
10 #include "GrColor.h"
11 #include "GrDefaultGeoProcFactory.h"
12 #include "GrDrawOpTest.h"
13 #include "GrMeshDrawOp.h"
14 #include "GrOpFlushState.h"
15 #include "SkStrokeRec.h"
16 #include "SkRandom.h"
17 
18 /*  create a triangle strip that strokes the specified rect. There are 8
19     unique vertices, but we repeat the last 2 to close up. Alternatively we
20     could use an indices array, and then only send 8 verts, but not sure that
21     would be faster.
22     */
init_stroke_rect_strip(SkPoint verts[10],const SkRect & rect,SkScalar width)23 static void init_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) {
24     const SkScalar rad = SkScalarHalf(width);
25     // TODO we should be able to enable this assert, but we'd have to filter these draws
26     // this is a bug
27     // SkASSERT(rad < rect.width() / 2 && rad < rect.height() / 2);
28 
29     verts[0].set(rect.fLeft + rad, rect.fTop + rad);
30     verts[1].set(rect.fLeft - rad, rect.fTop - rad);
31     verts[2].set(rect.fRight - rad, rect.fTop + rad);
32     verts[3].set(rect.fRight + rad, rect.fTop - rad);
33     verts[4].set(rect.fRight - rad, rect.fBottom - rad);
34     verts[5].set(rect.fRight + rad, rect.fBottom + rad);
35     verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
36     verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
37     verts[8] = verts[0];
38     verts[9] = verts[1];
39 }
40 
41 // Allow all hairlines and all miters, so long as the miter limit doesn't produce beveled corners.
allowed_stroke(const SkStrokeRec & stroke)42 inline static bool allowed_stroke(const SkStrokeRec& stroke) {
43     SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
44              stroke.getStyle() == SkStrokeRec::kHairline_Style);
45     return !stroke.getWidth() ||
46            (stroke.getJoin() == SkPaint::kMiter_Join && stroke.getMiter() > SK_ScalarSqrt2);
47 }
48 
49 class NonAAStrokeRectOp final : public GrMeshDrawOp {
50 public:
51     DEFINE_OP_CLASS_ID
52 
name() const53     const char* name() const override { return "NonAAStrokeRectOp"; }
54 
dumpInfo() const55     SkString dumpInfo() const override {
56         SkString string;
57         string.appendf(
58                 "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
59                 "StrokeWidth: %.2f\n",
60                 fColor, fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom, fStrokeWidth);
61         string.append(DumpPipelineInfo(*this->pipeline()));
62         string.append(INHERITED::dumpInfo());
63         return string;
64     }
65 
Make(GrColor color,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke,bool snapToPixelCenters)66     static std::unique_ptr<GrMeshDrawOp> Make(GrColor color, const SkMatrix& viewMatrix,
67                                               const SkRect& rect, const SkStrokeRec& stroke,
68                                               bool snapToPixelCenters) {
69         if (!allowed_stroke(stroke)) {
70             return nullptr;
71         }
72         NonAAStrokeRectOp* op = new NonAAStrokeRectOp();
73         op->fColor = color;
74         op->fViewMatrix = viewMatrix;
75         op->fRect = rect;
76         // Sort the rect for hairlines
77         op->fRect.sort();
78         op->fStrokeWidth = stroke.getWidth();
79 
80         SkScalar rad = SkScalarHalf(op->fStrokeWidth);
81         SkRect bounds = rect;
82         bounds.outset(rad, rad);
83 
84         // If our caller snaps to pixel centers then we have to round out the bounds
85         if (snapToPixelCenters) {
86             viewMatrix.mapRect(&bounds);
87             // We want to be consistent with how we snap non-aa lines. To match what we do in
88             // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
89             // pixel to force us to pixel centers.
90             bounds.set(SkScalarFloorToScalar(bounds.fLeft),
91                        SkScalarFloorToScalar(bounds.fTop),
92                        SkScalarFloorToScalar(bounds.fRight),
93                        SkScalarFloorToScalar(bounds.fBottom));
94             bounds.offset(0.5f, 0.5f);
95             op->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
96         } else {
97             op->setTransformedBounds(bounds, op->fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
98         }
99         return std::unique_ptr<GrMeshDrawOp>(op);
100     }
101 
102 private:
NonAAStrokeRectOp()103     NonAAStrokeRectOp() : INHERITED(ClassID()) {}
104 
getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor * color,GrPipelineAnalysisCoverage * coverage) const105     void getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor* color,
106                                             GrPipelineAnalysisCoverage* coverage) const override {
107         color->setToConstant(fColor);
108         *coverage = GrPipelineAnalysisCoverage::kNone;
109     }
110 
onPrepareDraws(Target * target) const111     void onPrepareDraws(Target* target) const override {
112         sk_sp<GrGeometryProcessor> gp;
113         {
114             using namespace GrDefaultGeoProcFactory;
115             Color color(fColor);
116             LocalCoords::Type localCoordsType = fNeedsLocalCoords
117                                                         ? LocalCoords::kUsePosition_Type
118                                                         : LocalCoords::kUnused_Type;
119             gp = GrDefaultGeoProcFactory::Make(color, Coverage::kSolid_Type, localCoordsType,
120                                                fViewMatrix);
121         }
122 
123         size_t vertexStride = gp->getVertexStride();
124 
125         SkASSERT(vertexStride == sizeof(GrDefaultGeoProcFactory::PositionAttr));
126 
127         int vertexCount = kVertsPerHairlineRect;
128         if (fStrokeWidth > 0) {
129             vertexCount = kVertsPerStrokeRect;
130         }
131 
132         const GrBuffer* vertexBuffer;
133         int firstVertex;
134 
135         void* verts =
136                 target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex);
137 
138         if (!verts) {
139             SkDebugf("Could not allocate vertices\n");
140             return;
141         }
142 
143         SkPoint* vertex = reinterpret_cast<SkPoint*>(verts);
144 
145         GrPrimitiveType primType;
146         if (fStrokeWidth > 0) {
147             primType = kTriangleStrip_GrPrimitiveType;
148             init_stroke_rect_strip(vertex, fRect, fStrokeWidth);
149         } else {
150             // hairline
151             primType = kLineStrip_GrPrimitiveType;
152             vertex[0].set(fRect.fLeft, fRect.fTop);
153             vertex[1].set(fRect.fRight, fRect.fTop);
154             vertex[2].set(fRect.fRight, fRect.fBottom);
155             vertex[3].set(fRect.fLeft, fRect.fBottom);
156             vertex[4].set(fRect.fLeft, fRect.fTop);
157         }
158 
159         GrMesh mesh;
160         mesh.init(primType, vertexBuffer, firstVertex, vertexCount);
161         target->draw(gp.get(), mesh);
162     }
163 
applyPipelineOptimizations(const GrPipelineOptimizations & optimizations)164     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
165         optimizations.getOverrideColorIfSet(&fColor);
166         fNeedsLocalCoords = optimizations.readsLocalCoords();
167     }
168 
onCombineIfPossible(GrOp * t,const GrCaps &)169     bool onCombineIfPossible(GrOp* t, const GrCaps&) override {
170         // NonAA stroke rects cannot combine right now
171         // TODO make these combinable.
172         return false;
173     }
174 
175     GrColor fColor;
176     SkMatrix fViewMatrix;
177     SkRect fRect;
178     SkScalar fStrokeWidth;
179     bool fNeedsLocalCoords;
180 
181     const static int kVertsPerHairlineRect = 5;
182     const static int kVertsPerStrokeRect = 10;
183 
184     typedef GrMeshDrawOp INHERITED;
185 };
186 
187 namespace GrNonAAStrokeRectOp {
188 
Make(GrColor color,const SkMatrix & viewMatrix,const SkRect & rect,const SkStrokeRec & stroke,bool snapToPixelCenters)189 std::unique_ptr<GrMeshDrawOp> Make(GrColor color,
190                                    const SkMatrix& viewMatrix,
191                                    const SkRect& rect,
192                                    const SkStrokeRec& stroke,
193                                    bool snapToPixelCenters) {
194     return NonAAStrokeRectOp::Make(color, viewMatrix, rect, stroke, snapToPixelCenters);
195 }
196 }
197 
198 #if GR_TEST_UTILS
199 
DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp)200 DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) {
201     SkMatrix viewMatrix = GrTest::TestMatrix(random);
202     GrColor color = GrRandomColor(random);
203     SkRect rect = GrTest::TestRect(random);
204     SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f;
205     SkPaint paint;
206     paint.setStrokeWidth(strokeWidth);
207     paint.setStyle(SkPaint::kStroke_Style);
208     paint.setStrokeJoin(SkPaint::kMiter_Join);
209     SkStrokeRec strokeRec(paint);
210     return GrNonAAStrokeRectOp::Make(color, viewMatrix, rect, strokeRec, random->nextBool());
211 }
212 
213 #endif
214