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 "GrTessellatingPathRenderer.h" 9 10 #include "GrBatchFlushState.h" 11 #include "GrBatchTest.h" 12 #include "GrDefaultGeoProcFactory.h" 13 #include "GrPathUtils.h" 14 #include "GrVertices.h" 15 #include "GrResourceCache.h" 16 #include "GrResourceProvider.h" 17 #include "GrTessellator.h" 18 #include "SkGeometry.h" 19 20 #include "batches/GrVertexBatch.h" 21 22 #include <stdio.h> 23 24 /* 25 * This path renderer tessellates the path into triangles using GrTessellator, uploads the triangles 26 * to a vertex buffer, and renders them with a single draw call. It does not currently do 27 * antialiasing, so it must be used in conjunction with multisampling. 28 */ 29 namespace { 30 31 struct TessInfo { 32 SkScalar fTolerance; 33 int fCount; 34 }; 35 36 // When the SkPathRef genID changes, invalidate a corresponding GrResource described by key. 37 class PathInvalidator : public SkPathRef::GenIDChangeListener { 38 public: 39 explicit PathInvalidator(const GrUniqueKey& key) : fMsg(key) {} 40 private: 41 GrUniqueKeyInvalidatedMessage fMsg; 42 43 void onChange() override { 44 SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(fMsg); 45 } 46 }; 47 48 bool cache_match(GrVertexBuffer* vertexBuffer, SkScalar tol, int* actualCount) { 49 if (!vertexBuffer) { 50 return false; 51 } 52 const SkData* data = vertexBuffer->getUniqueKey().getCustomData(); 53 SkASSERT(data); 54 const TessInfo* info = static_cast<const TessInfo*>(data->data()); 55 if (info->fTolerance == 0 || info->fTolerance < 3.0f * tol) { 56 *actualCount = info->fCount; 57 return true; 58 } 59 return false; 60 } 61 62 } // namespace 63 64 GrTessellatingPathRenderer::GrTessellatingPathRenderer() { 65 } 66 67 bool GrTessellatingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { 68 // This path renderer can draw all fill styles, all stroke styles except hairlines, but does 69 // not do antialiasing. It can do convex and concave paths, but we'll leave the convex ones to 70 // simpler algorithms. 71 return !IsStrokeHairlineOrEquivalent(*args.fStroke, *args.fViewMatrix, nullptr) && 72 !args.fAntiAlias && !args.fPath->isConvex(); 73 } 74 75 class TessellatingPathBatch : public GrVertexBatch { 76 public: 77 DEFINE_BATCH_CLASS_ID 78 79 static GrDrawBatch* Create(const GrColor& color, 80 const SkPath& path, 81 const GrStrokeInfo& stroke, 82 const SkMatrix& viewMatrix, 83 SkRect clipBounds) { 84 return new TessellatingPathBatch(color, path, stroke, viewMatrix, clipBounds); 85 } 86 87 const char* name() const override { return "TessellatingPathBatch"; } 88 89 void computePipelineOptimizations(GrInitInvariantOutput* color, 90 GrInitInvariantOutput* coverage, 91 GrBatchToXPOverrides* overrides) const override { 92 color->setKnownFourComponents(fColor); 93 coverage->setUnknownSingleComponent(); 94 } 95 96 private: 97 void initBatchTracker(const GrXPOverridesForBatch& overrides) override { 98 // Handle any color overrides 99 if (!overrides.readsColor()) { 100 fColor = GrColor_ILLEGAL; 101 } 102 overrides.getOverrideColorIfSet(&fColor); 103 fPipelineInfo = overrides; 104 } 105 106 int tessellate(GrUniqueKey* key, 107 GrResourceProvider* resourceProvider, 108 SkAutoTUnref<GrVertexBuffer>& vertexBuffer, 109 bool canMapVB) const { 110 SkPath path; 111 GrStrokeInfo stroke(fStroke); 112 if (stroke.isDashed()) { 113 if (!stroke.applyDashToPath(&path, &stroke, fPath)) { 114 return 0; 115 } 116 } else { 117 path = fPath; 118 } 119 if (!stroke.isFillStyle()) { 120 stroke.setResScale(SkScalarAbs(fViewMatrix.getMaxScale())); 121 if (!stroke.applyToPath(&path, path)) { 122 return 0; 123 } 124 stroke.setFillStyle(); 125 } 126 SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance; 127 SkRect pathBounds = path.getBounds(); 128 SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, fViewMatrix, pathBounds); 129 130 bool isLinear; 131 int count = GrTessellator::PathToTriangles(path, tol, fClipBounds, resourceProvider, 132 vertexBuffer, canMapVB, &isLinear); 133 if (!fPath.isVolatile()) { 134 TessInfo info; 135 info.fTolerance = isLinear ? 0 : tol; 136 info.fCount = count; 137 SkAutoTUnref<SkData> data(SkData::NewWithCopy(&info, sizeof(info))); 138 key->setCustomData(data.get()); 139 resourceProvider->assignUniqueKeyToResource(*key, vertexBuffer.get()); 140 SkPathPriv::AddGenIDChangeListener(fPath, new PathInvalidator(*key)); 141 } 142 return count; 143 } 144 145 void onPrepareDraws(Target* target) const override { 146 // construct a cache key from the path's genID and the view matrix 147 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); 148 GrUniqueKey key; 149 int clipBoundsSize32 = 150 fPath.isInverseFillType() ? sizeof(fClipBounds) / sizeof(uint32_t) : 0; 151 int strokeDataSize32 = fStroke.computeUniqueKeyFragmentData32Cnt(); 152 GrUniqueKey::Builder builder(&key, kDomain, 2 + clipBoundsSize32 + strokeDataSize32); 153 builder[0] = fPath.getGenerationID(); 154 builder[1] = fPath.getFillType(); 155 // For inverse fills, the tessellation is dependent on clip bounds. 156 if (fPath.isInverseFillType()) { 157 memcpy(&builder[2], &fClipBounds, sizeof(fClipBounds)); 158 } 159 fStroke.asUniqueKeyFragment(&builder[2 + clipBoundsSize32]); 160 builder.finish(); 161 GrResourceProvider* rp = target->resourceProvider(); 162 SkAutoTUnref<GrVertexBuffer> vertexBuffer(rp->findAndRefTByUniqueKey<GrVertexBuffer>(key)); 163 int actualCount; 164 SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance; 165 SkScalar tol = GrPathUtils::scaleToleranceToSrc( 166 screenSpaceTol, fViewMatrix, fPath.getBounds()); 167 if (!cache_match(vertexBuffer.get(), tol, &actualCount)) { 168 bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags(); 169 actualCount = this->tessellate(&key, rp, vertexBuffer, canMapVB); 170 } 171 172 if (actualCount == 0) { 173 return; 174 } 175 176 SkAutoTUnref<const GrGeometryProcessor> gp; 177 { 178 using namespace GrDefaultGeoProcFactory; 179 180 Color color(fColor); 181 LocalCoords localCoords(fPipelineInfo.readsLocalCoords() ? 182 LocalCoords::kUsePosition_Type : 183 LocalCoords::kUnused_Type); 184 Coverage::Type coverageType; 185 if (fPipelineInfo.readsCoverage()) { 186 coverageType = Coverage::kSolid_Type; 187 } else { 188 coverageType = Coverage::kNone_Type; 189 } 190 Coverage coverage(coverageType); 191 gp.reset(GrDefaultGeoProcFactory::Create(color, coverage, localCoords, 192 fViewMatrix)); 193 } 194 195 target->initDraw(gp, this->pipeline()); 196 SkASSERT(gp->getVertexStride() == sizeof(SkPoint)); 197 198 GrPrimitiveType primitiveType = TESSELLATOR_WIREFRAME ? kLines_GrPrimitiveType 199 : kTriangles_GrPrimitiveType; 200 GrVertices vertices; 201 vertices.init(primitiveType, vertexBuffer.get(), 0, actualCount); 202 target->draw(vertices); 203 } 204 205 bool onCombineIfPossible(GrBatch*, const GrCaps&) override { return false; } 206 207 TessellatingPathBatch(const GrColor& color, 208 const SkPath& path, 209 const GrStrokeInfo& stroke, 210 const SkMatrix& viewMatrix, 211 const SkRect& clipBounds) 212 : INHERITED(ClassID()) 213 , fColor(color) 214 , fPath(path) 215 , fStroke(stroke) 216 , fViewMatrix(viewMatrix) { 217 const SkRect& pathBounds = path.getBounds(); 218 fClipBounds = clipBounds; 219 // Because the clip bounds are used to add a contour for inverse fills, they must also 220 // include the path bounds. 221 fClipBounds.join(pathBounds); 222 if (path.isInverseFillType()) { 223 fBounds = fClipBounds; 224 } else { 225 fBounds = path.getBounds(); 226 } 227 if (!stroke.isFillStyle()) { 228 SkScalar radius = SkScalarHalf(stroke.getWidth()); 229 if (stroke.getJoin() == SkPaint::kMiter_Join) { 230 SkScalar scale = stroke.getMiter(); 231 if (scale > SK_Scalar1) { 232 radius = SkScalarMul(radius, scale); 233 } 234 } 235 fBounds.outset(radius, radius); 236 } 237 viewMatrix.mapRect(&fBounds); 238 } 239 240 GrColor fColor; 241 SkPath fPath; 242 GrStrokeInfo fStroke; 243 SkMatrix fViewMatrix; 244 SkRect fClipBounds; // in source space 245 GrXPOverridesForBatch fPipelineInfo; 246 247 typedef GrVertexBatch INHERITED; 248 }; 249 250 bool GrTessellatingPathRenderer::onDrawPath(const DrawPathArgs& args) { 251 GR_AUDIT_TRAIL_AUTO_FRAME(args.fTarget->getAuditTrail(), 252 "GrTessellatingPathRenderer::onDrawPath"); 253 SkASSERT(!args.fAntiAlias); 254 const GrRenderTarget* rt = args.fPipelineBuilder->getRenderTarget(); 255 if (nullptr == rt) { 256 return false; 257 } 258 259 SkIRect clipBoundsI; 260 args.fPipelineBuilder->clip().getConservativeBounds(rt->width(), rt->height(), &clipBoundsI); 261 SkRect clipBounds = SkRect::Make(clipBoundsI); 262 SkMatrix vmi; 263 if (!args.fViewMatrix->invert(&vmi)) { 264 return false; 265 } 266 vmi.mapRect(&clipBounds); 267 SkAutoTUnref<GrDrawBatch> batch(TessellatingPathBatch::Create(args.fColor, *args.fPath, 268 *args.fStroke, *args.fViewMatrix, 269 clipBounds)); 270 args.fTarget->drawBatch(*args.fPipelineBuilder, batch); 271 272 return true; 273 } 274 275 /////////////////////////////////////////////////////////////////////////////////////////////////// 276 277 #ifdef GR_TEST_UTILS 278 279 DRAW_BATCH_TEST_DEFINE(TesselatingPathBatch) { 280 GrColor color = GrRandomColor(random); 281 SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); 282 SkPath path = GrTest::TestPath(random); 283 SkRect clipBounds = GrTest::TestRect(random); 284 SkMatrix vmi; 285 bool result = viewMatrix.invert(&vmi); 286 if (!result) { 287 SkFAIL("Cannot invert matrix\n"); 288 } 289 vmi.mapRect(&clipBounds); 290 GrStrokeInfo strokeInfo = GrTest::TestStrokeInfo(random); 291 return TessellatingPathBatch::Create(color, path, strokeInfo, viewMatrix, clipBounds); 292 } 293 294 #endif 295