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