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 */ 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. 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 53 const char* name() const override { return "NonAAStrokeRectOp"; } 54 55 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 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: 103 NonAAStrokeRectOp() : INHERITED(ClassID()) {} 104 105 void getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor* color, 106 GrPipelineAnalysisCoverage* coverage) const override { 107 color->setToConstant(fColor); 108 *coverage = GrPipelineAnalysisCoverage::kNone; 109 } 110 111 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 164 void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override { 165 optimizations.getOverrideColorIfSet(&fColor); 166 fNeedsLocalCoords = optimizations.readsLocalCoords(); 167 } 168 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 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 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