1/* 2 * Copyright 2014 Google Inc. 3 * Copyright 2017 ARM Ltd. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9#include "src/gpu/ops/SmallPathRenderer.h" 10 11#include "include/core/SkPaint.h" 12#include "src/core/SkAutoPixmapStorage.h" 13#include "src/core/SkDistanceFieldGen.h" 14#include "src/core/SkDraw.h" 15#include "src/core/SkMatrixPriv.h" 16#include "src/core/SkMatrixProvider.h" 17#include "src/core/SkPointPriv.h" 18#include "src/core/SkRasterClip.h" 19#include "src/gpu/BufferWriter.h" 20#include "src/gpu/GrBuffer.h" 21#include "src/gpu/GrCaps.h" 22#include "src/gpu/GrDistanceFieldGenFromVector.h" 23#include "src/gpu/GrDrawOpTest.h" 24#include "src/gpu/GrResourceProvider.h" 25#include "src/gpu/effects/GrBitmapTextGeoProc.h" 26#include "src/gpu/effects/GrDistanceFieldGeoProc.h" 27#include "src/gpu/geometry/GrQuad.h" 28#include "src/gpu/geometry/GrStyledShape.h" 29#include "src/gpu/ops/GrMeshDrawOp.h" 30#include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h" 31#include "src/gpu/ops/SmallPathAtlasMgr.h" 32#include "src/gpu/ops/SmallPathShapeData.h" 33#include "src/gpu/v1/SurfaceDrawContext_v1.h" 34 35namespace skgpu::v1 { 36 37namespace { 38 39// mip levels 40static constexpr SkScalar kIdealMinMIP = 12; 41static constexpr SkScalar kMaxMIP = 162; 42 43static constexpr SkScalar kMaxDim = 73; 44static constexpr SkScalar kMinSize = SK_ScalarHalf; 45static constexpr SkScalar kMaxSize = 2*kMaxMIP; 46 47//////////////////////////////////////////////////////////////////////////////// 48 49// padding around path bounds to allow for antialiased pixels 50static const int kAntiAliasPad = 1; 51 52class SmallPathOp final : public GrMeshDrawOp { 53private: 54 using Helper = GrSimpleMeshDrawOpHelperWithStencil; 55 56public: 57 DEFINE_OP_CLASS_ID 58 59 static GrOp::Owner Make(GrRecordingContext* context, 60 GrPaint&& paint, 61 const GrStyledShape& shape, 62 const SkMatrix& viewMatrix, 63 bool gammaCorrect, 64 const GrUserStencilSettings* stencilSettings) { 65 return Helper::FactoryHelper<SmallPathOp>(context, std::move(paint), shape, viewMatrix, 66 gammaCorrect, stencilSettings); 67 } 68 69 SmallPathOp(GrProcessorSet* processorSet, const SkPMColor4f& color, const GrStyledShape& shape, 70 const SkMatrix& viewMatrix, bool gammaCorrect, 71 const GrUserStencilSettings* stencilSettings) 72 : INHERITED(ClassID()) 73 , fHelper(processorSet, GrAAType::kCoverage, stencilSettings) { 74 SkASSERT(shape.hasUnstyledKey()); 75 // Compute bounds 76 this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsHairline::kNo); 77 78#if defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) 79 fUsesDistanceField = true; 80#else 81 // only use distance fields on desktop and Android framework to save space in the atlas 82 fUsesDistanceField = this->bounds().width() > kMaxMIP || this->bounds().height() > kMaxMIP; 83#endif 84 // always use distance fields if in perspective 85 fUsesDistanceField = fUsesDistanceField || viewMatrix.hasPerspective(); 86 87 fShapes.emplace_back(Entry{color, shape, viewMatrix}); 88 89 fGammaCorrect = gammaCorrect; 90 } 91 92 const char* name() const override { return "SmallPathOp"; } 93 94 void visitProxies(const GrVisitProxyFunc& func) const override { 95 fHelper.visitProxies(func); 96 } 97 98 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 99 100 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 101 GrClampType clampType) override { 102 return fHelper.finalizeProcessors(caps, clip, clampType, 103 GrProcessorAnalysisCoverage::kSingleChannel, 104 &fShapes.front().fColor, &fWideColor); 105 } 106 107private: 108 struct FlushInfo { 109 sk_sp<const GrBuffer> fVertexBuffer; 110 sk_sp<const GrBuffer> fIndexBuffer; 111 GrGeometryProcessor* fGeometryProcessor; 112 const GrSurfaceProxy** fPrimProcProxies; 113 int fVertexOffset; 114 int fInstancesToFlush; 115 }; 116 117 GrProgramInfo* programInfo() override { 118 // TODO [PI]: implement 119 return nullptr; 120 } 121 122 void onCreateProgramInfo(const GrCaps*, 123 SkArenaAlloc*, 124 const GrSurfaceProxyView& writeView, 125 bool usesMSAASurface, 126 GrAppliedClip&&, 127 const GrDstProxyView&, 128 GrXferBarrierFlags renderPassXferBarriers, 129 GrLoadOp colorLoadOp) override { 130 // We cannot surface the SmallPathOp's programInfo at record time. As currently 131 // implemented, the GP is modified at flush time based on the number of pages in the 132 // atlas. 133 } 134 135 void onPrePrepareDraws(GrRecordingContext*, 136 const GrSurfaceProxyView& writeView, 137 GrAppliedClip*, 138 const GrDstProxyView&, 139 GrXferBarrierFlags renderPassXferBarriers, 140 GrLoadOp colorLoadOp) override { 141 // TODO [PI]: implement 142 } 143 144 void onPrepareDraws(GrMeshDrawTarget* target) override { 145 int instanceCount = fShapes.count(); 146 147 auto atlasMgr = target->smallPathAtlasManager(); 148 if (!atlasMgr) { 149 return; 150 } 151 152 static constexpr int kMaxTextures = GrDistanceFieldPathGeoProc::kMaxTextures; 153 static_assert(GrBitmapTextGeoProc::kMaxTextures == kMaxTextures); 154 155 FlushInfo flushInfo; 156 flushInfo.fPrimProcProxies = target->allocPrimProcProxyPtrs(kMaxTextures); 157 158 int numActiveProxies; 159 const GrSurfaceProxyView* views = atlasMgr->getViews(&numActiveProxies); 160 for (int i = 0; i < numActiveProxies; ++i) { 161 // This op does not know its atlas proxies when it is added to a OpsTasks, so the 162 // proxies don't get added during the visitProxies call. Thus we add them here. 163 flushInfo.fPrimProcProxies[i] = views[i].proxy(); 164 target->sampledProxyArray()->push_back(views[i].proxy()); 165 } 166 167 // Setup GrGeometryProcessor 168 const SkMatrix& ctm = fShapes[0].fViewMatrix; 169 if (fUsesDistanceField) { 170 uint32_t flags = 0; 171 // Still need to key off of ctm to pick the right shader for the transformed quad 172 flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0; 173 flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; 174 flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0; 175 176 const SkMatrix* matrix; 177 SkMatrix invert; 178 if (ctm.hasPerspective()) { 179 matrix = &ctm; 180 } else if (fHelper.usesLocalCoords()) { 181 if (!ctm.invert(&invert)) { 182 return; 183 } 184 matrix = &invert; 185 } else { 186 matrix = &SkMatrix::I(); 187 } 188 flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make( 189 target->allocator(), *target->caps().shaderCaps(), *matrix, fWideColor, 190 views, numActiveProxies, GrSamplerState::Filter::kLinear, 191 flags); 192 } else { 193 SkMatrix invert; 194 if (fHelper.usesLocalCoords()) { 195 if (!ctm.invert(&invert)) { 196 return; 197 } 198 } 199 200 flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make( 201 target->allocator(), *target->caps().shaderCaps(), this->color(), fWideColor, 202 views, numActiveProxies, GrSamplerState::Filter::kNearest, 203 kA8_GrMaskFormat, invert, false); 204 } 205 206 // allocate vertices 207 const size_t kVertexStride = flushInfo.fGeometryProcessor->vertexStride(); 208 209 // We need to make sure we don't overflow a 32 bit int when we request space in the 210 // makeVertexSpace call below. 211 if (instanceCount > SK_MaxS32 / GrResourceProvider::NumVertsPerNonAAQuad()) { 212 return; 213 } 214 VertexWriter vertices{target->makeVertexSpace( 215 kVertexStride, GrResourceProvider::NumVertsPerNonAAQuad() * instanceCount, 216 &flushInfo.fVertexBuffer, &flushInfo.fVertexOffset)}; 217 218 flushInfo.fIndexBuffer = target->resourceProvider()->refNonAAQuadIndexBuffer(); 219 if (!vertices || !flushInfo.fIndexBuffer) { 220 SkDebugf("Could not allocate vertices\n"); 221 return; 222 } 223 224 flushInfo.fInstancesToFlush = 0; 225 for (int i = 0; i < instanceCount; i++) { 226 const Entry& args = fShapes[i]; 227 228 skgpu::v1::SmallPathShapeData* shapeData; 229 if (fUsesDistanceField) { 230 // get mip level 231 SkScalar maxScale; 232 const SkRect& bounds = args.fShape.bounds(); 233 if (args.fViewMatrix.hasPerspective()) { 234 // approximate the scale since we can't get it from the matrix 235 SkRect xformedBounds; 236 args.fViewMatrix.mapRect(&xformedBounds, bounds); 237 maxScale = SkScalarAbs(std::max(xformedBounds.width() / bounds.width(), 238 xformedBounds.height() / bounds.height())); 239 } else { 240 maxScale = SkScalarAbs(args.fViewMatrix.getMaxScale()); 241 } 242 SkScalar maxDim = std::max(bounds.width(), bounds.height()); 243 // We try to create the DF at a 2^n scaled path resolution (1/2, 1, 2, 4, etc.) 244 // In the majority of cases this will yield a crisper rendering. 245 SkScalar mipScale = 1.0f; 246 // Our mipscale is the maxScale clamped to the next highest power of 2 247 if (maxScale <= SK_ScalarHalf) { 248 SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale))); 249 mipScale = SkScalarPow(2, -log); 250 } else if (maxScale > SK_Scalar1) { 251 SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale)); 252 mipScale = SkScalarPow(2, log); 253 } 254 // Log2 isn't very precise at values close to a power of 2, 255 // so add a little tolerance here. A little bit of scaling up is fine. 256 SkASSERT(maxScale <= mipScale + SK_ScalarNearlyZero); 257 258 SkScalar mipSize = mipScale*SkScalarAbs(maxDim); 259 // For sizes less than kIdealMinMIP we want to use as large a distance field as we can 260 // so we can preserve as much detail as possible. However, we can't scale down more 261 // than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize 262 // just bigger than the ideal, and then scale down until we are no more than 4x the 263 // original mipsize. 264 if (mipSize < kIdealMinMIP) { 265 SkScalar newMipSize = mipSize; 266 do { 267 newMipSize *= 2; 268 } while (newMipSize < kIdealMinMIP); 269 while (newMipSize > 4 * mipSize) { 270 newMipSize *= 0.25f; 271 } 272 mipSize = newMipSize; 273 } 274 275 SkScalar desiredDimension = std::min(mipSize, kMaxMIP); 276 int ceilDesiredDimension = SkScalarCeilToInt(desiredDimension); 277 278 // check to see if df path is cached 279 shapeData = atlasMgr->findOrCreate(args.fShape, ceilDesiredDimension); 280 if (!shapeData->fAtlasLocator.plotLocator().isValid()) { 281 SkScalar scale = desiredDimension / maxDim; 282 283 if (!this->addDFPathToAtlas(target, 284 &flushInfo, 285 atlasMgr, 286 shapeData, 287 args.fShape, 288 ceilDesiredDimension, 289 scale)) { 290 atlasMgr->deleteCacheEntry(shapeData); 291 continue; 292 } 293 } 294 } else { 295 // check to see if bitmap path is cached 296 shapeData = atlasMgr->findOrCreate(args.fShape, args.fViewMatrix); 297 if (!shapeData->fAtlasLocator.plotLocator().isValid()) { 298 if (!this->addBMPathToAtlas(target, 299 &flushInfo, 300 atlasMgr, 301 shapeData, 302 args.fShape, 303 args.fViewMatrix)) { 304 atlasMgr->deleteCacheEntry(shapeData); 305 continue; 306 } 307 } 308 } 309 310 auto uploadTarget = target->deferredUploadTarget(); 311 atlasMgr->setUseToken(shapeData, uploadTarget->tokenTracker()->nextDrawToken()); 312 313 this->writePathVertices(vertices, GrVertexColor(args.fColor, fWideColor), 314 args.fViewMatrix, shapeData); 315 flushInfo.fInstancesToFlush++; 316 } 317 318 this->flush(target, &flushInfo); 319 } 320 321 bool addToAtlasWithRetry(GrMeshDrawTarget* target, 322 FlushInfo* flushInfo, 323 skgpu::v1::SmallPathAtlasMgr* atlasMgr, 324 int width, int height, const void* image, 325 const SkRect& bounds, int srcInset, 326 skgpu::v1::SmallPathShapeData* shapeData) const { 327 auto resourceProvider = target->resourceProvider(); 328 auto uploadTarget = target->deferredUploadTarget(); 329 330 auto code = atlasMgr->addToAtlas(resourceProvider, uploadTarget, width, height, 331 image, &shapeData->fAtlasLocator); 332 if (GrDrawOpAtlas::ErrorCode::kError == code) { 333 return false; 334 } 335 336 if (GrDrawOpAtlas::ErrorCode::kTryAgain == code) { 337 this->flush(target, flushInfo); 338 339 code = atlasMgr->addToAtlas(resourceProvider, uploadTarget, width, height, 340 image, &shapeData->fAtlasLocator); 341 } 342 343 shapeData->fAtlasLocator.insetSrc(srcInset); 344 shapeData->fBounds = bounds; 345 346 return GrDrawOpAtlas::ErrorCode::kSucceeded == code; 347 } 348 349 bool addDFPathToAtlas(GrMeshDrawTarget* target, 350 FlushInfo* flushInfo, 351 skgpu::v1::SmallPathAtlasMgr* atlasMgr, 352 skgpu::v1::SmallPathShapeData* shapeData, 353 const GrStyledShape& shape, 354 uint32_t dimension, 355 SkScalar scale) const { 356 357 const SkRect& bounds = shape.bounds(); 358 359 // generate bounding rect for bitmap draw 360 SkRect scaledBounds = bounds; 361 // scale to mip level size 362 scaledBounds.fLeft *= scale; 363 scaledBounds.fTop *= scale; 364 scaledBounds.fRight *= scale; 365 scaledBounds.fBottom *= scale; 366 // subtract out integer portion of origin 367 // (SDF created will be placed with fractional offset burnt in) 368 SkScalar dx = SkScalarFloorToScalar(scaledBounds.fLeft); 369 SkScalar dy = SkScalarFloorToScalar(scaledBounds.fTop); 370 scaledBounds.offset(-dx, -dy); 371 // get integer boundary 372 SkIRect devPathBounds; 373 scaledBounds.roundOut(&devPathBounds); 374 // place devBounds at origin with padding to allow room for antialiasing 375 int width = devPathBounds.width() + 2 * kAntiAliasPad; 376 int height = devPathBounds.height() + 2 * kAntiAliasPad; 377 devPathBounds = SkIRect::MakeWH(width, height); 378 SkScalar translateX = kAntiAliasPad - dx; 379 SkScalar translateY = kAntiAliasPad - dy; 380 381 // draw path to bitmap 382 SkMatrix drawMatrix; 383 drawMatrix.setScale(scale, scale); 384 drawMatrix.postTranslate(translateX, translateY); 385 386 SkASSERT(devPathBounds.fLeft == 0); 387 SkASSERT(devPathBounds.fTop == 0); 388 SkASSERT(devPathBounds.width() > 0); 389 SkASSERT(devPathBounds.height() > 0); 390 391 // setup signed distance field storage 392 SkIRect dfBounds = devPathBounds.makeOutset(SK_DistanceFieldPad, SK_DistanceFieldPad); 393 width = dfBounds.width(); 394 height = dfBounds.height(); 395 // TODO We should really generate this directly into the plot somehow 396 SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char)); 397 398 SkPath path; 399 shape.asPath(&path); 400 // Generate signed distance field directly from SkPath 401 bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dfStorage.get(), 402 path, drawMatrix, width, height, 403 width * sizeof(unsigned char)); 404 if (!succeed) { 405 // setup bitmap backing 406 SkAutoPixmapStorage dst; 407 if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(), devPathBounds.height()))) { 408 return false; 409 } 410 sk_bzero(dst.writable_addr(), dst.computeByteSize()); 411 412 // rasterize path 413 SkPaint paint; 414 paint.setStyle(SkPaint::kFill_Style); 415 paint.setAntiAlias(true); 416 417 SkDraw draw; 418 419 SkRasterClip rasterClip; 420 rasterClip.setRect(devPathBounds); 421 draw.fRC = &rasterClip; 422 SkSimpleMatrixProvider matrixProvider(drawMatrix); 423 draw.fMatrixProvider = &matrixProvider; 424 draw.fDst = dst; 425 426 draw.drawPathCoverage(path, paint); 427 428 // Generate signed distance field 429 SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(), 430 (const unsigned char*)dst.addr(), 431 dst.width(), dst.height(), dst.rowBytes()); 432 } 433 434 SkRect drawBounds = SkRect::Make(devPathBounds).makeOffset(-translateX, -translateY); 435 drawBounds.fLeft /= scale; 436 drawBounds.fTop /= scale; 437 drawBounds.fRight /= scale; 438 drawBounds.fBottom /= scale; 439 440 return this->addToAtlasWithRetry(target, flushInfo, atlasMgr, 441 width, height, dfStorage.get(), 442 drawBounds, SK_DistanceFieldPad, shapeData); 443 } 444 445 bool addBMPathToAtlas(GrMeshDrawTarget* target, 446 FlushInfo* flushInfo, 447 skgpu::v1::SmallPathAtlasMgr* atlasMgr, 448 skgpu::v1::SmallPathShapeData* shapeData, 449 const GrStyledShape& shape, 450 const SkMatrix& ctm) const { 451 const SkRect& bounds = shape.bounds(); 452 if (bounds.isEmpty()) { 453 return false; 454 } 455 SkMatrix drawMatrix(ctm); 456 SkScalar tx = ctm.getTranslateX(); 457 SkScalar ty = ctm.getTranslateY(); 458 tx -= SkScalarFloorToScalar(tx); 459 ty -= SkScalarFloorToScalar(ty); 460 drawMatrix.set(SkMatrix::kMTransX, tx); 461 drawMatrix.set(SkMatrix::kMTransY, ty); 462 SkRect shapeDevBounds; 463 drawMatrix.mapRect(&shapeDevBounds, bounds); 464 SkScalar dx = SkScalarFloorToScalar(shapeDevBounds.fLeft); 465 SkScalar dy = SkScalarFloorToScalar(shapeDevBounds.fTop); 466 467 // get integer boundary 468 SkIRect devPathBounds; 469 shapeDevBounds.roundOut(&devPathBounds); 470 // place devBounds at origin with padding to allow room for antialiasing 471 int width = devPathBounds.width() + 2 * kAntiAliasPad; 472 int height = devPathBounds.height() + 2 * kAntiAliasPad; 473 devPathBounds = SkIRect::MakeWH(width, height); 474 SkScalar translateX = kAntiAliasPad - dx; 475 SkScalar translateY = kAntiAliasPad - dy; 476 477 SkASSERT(devPathBounds.fLeft == 0); 478 SkASSERT(devPathBounds.fTop == 0); 479 SkASSERT(devPathBounds.width() > 0); 480 SkASSERT(devPathBounds.height() > 0); 481 482 SkPath path; 483 shape.asPath(&path); 484 // setup bitmap backing 485 SkAutoPixmapStorage dst; 486 if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(), devPathBounds.height()))) { 487 return false; 488 } 489 sk_bzero(dst.writable_addr(), dst.computeByteSize()); 490 491 // rasterize path 492 SkPaint paint; 493 paint.setStyle(SkPaint::kFill_Style); 494 paint.setAntiAlias(true); 495 496 SkDraw draw; 497 498 SkRasterClip rasterClip; 499 rasterClip.setRect(devPathBounds); 500 draw.fRC = &rasterClip; 501 drawMatrix.postTranslate(translateX, translateY); 502 SkSimpleMatrixProvider matrixProvider(drawMatrix); 503 draw.fMatrixProvider = &matrixProvider; 504 draw.fDst = dst; 505 506 draw.drawPathCoverage(path, paint); 507 508 SkRect drawBounds = SkRect::Make(devPathBounds).makeOffset(-translateX, -translateY); 509 510 return this->addToAtlasWithRetry(target, flushInfo, atlasMgr, 511 dst.width(), dst.height(), dst.addr(), 512 drawBounds, 0, shapeData); 513 } 514 515 void writePathVertices(VertexWriter& vertices, 516 const GrVertexColor& color, 517 const SkMatrix& ctm, 518 const skgpu::v1::SmallPathShapeData* shapeData) const { 519 SkRect translatedBounds(shapeData->fBounds); 520 if (!fUsesDistanceField) { 521 translatedBounds.offset(SkScalarFloorToScalar(ctm.get(SkMatrix::kMTransX)), 522 SkScalarFloorToScalar(ctm.get(SkMatrix::kMTransY))); 523 } 524 525 // set up texture coordinates 526 auto texCoords = VertexWriter::TriStripFromUVs(shapeData->fAtlasLocator.getUVs()); 527 528 if (fUsesDistanceField && !ctm.hasPerspective()) { 529 vertices.writeQuad(GrQuad::MakeFromRect(translatedBounds, ctm), 530 color, 531 texCoords); 532 } else { 533 vertices.writeQuad(VertexWriter::TriStripFromRect(translatedBounds), 534 color, 535 texCoords); 536 } 537 } 538 539 void flush(GrMeshDrawTarget* target, FlushInfo* flushInfo) const { 540 auto atlasMgr = target->smallPathAtlasManager(); 541 if (!atlasMgr) { 542 return; 543 } 544 545 int numActiveProxies; 546 const GrSurfaceProxyView* views = atlasMgr->getViews(&numActiveProxies); 547 548 GrGeometryProcessor* gp = flushInfo->fGeometryProcessor; 549 if (gp->numTextureSamplers() != numActiveProxies) { 550 for (int i = gp->numTextureSamplers(); i < numActiveProxies; ++i) { 551 flushInfo->fPrimProcProxies[i] = views[i].proxy(); 552 // This op does not know its atlas proxies when it is added to a OpsTasks, so the 553 // proxies don't get added during the visitProxies call. Thus we add them here. 554 target->sampledProxyArray()->push_back(views[i].proxy()); 555 } 556 // During preparation the number of atlas pages has increased. 557 // Update the proxies used in the GP to match. 558 if (fUsesDistanceField) { 559 reinterpret_cast<GrDistanceFieldPathGeoProc*>(gp)->addNewViews( 560 views, numActiveProxies, GrSamplerState::Filter::kLinear); 561 } else { 562 reinterpret_cast<GrBitmapTextGeoProc*>(gp)->addNewViews( 563 views, numActiveProxies, GrSamplerState::Filter::kNearest); 564 } 565 } 566 567 if (flushInfo->fInstancesToFlush) { 568 GrSimpleMesh* mesh = target->allocMesh(); 569 mesh->setIndexedPatterned(flushInfo->fIndexBuffer, 570 GrResourceProvider::NumIndicesPerNonAAQuad(), 571 flushInfo->fInstancesToFlush, 572 GrResourceProvider::MaxNumNonAAQuads(), 573 flushInfo->fVertexBuffer, 574 GrResourceProvider::NumVertsPerNonAAQuad(), 575 flushInfo->fVertexOffset); 576 target->recordDraw(flushInfo->fGeometryProcessor, mesh, 1, flushInfo->fPrimProcProxies, 577 GrPrimitiveType::kTriangles); 578 flushInfo->fVertexOffset += GrResourceProvider::NumVertsPerNonAAQuad() * 579 flushInfo->fInstancesToFlush; 580 flushInfo->fInstancesToFlush = 0; 581 } 582 } 583 584 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 585 auto pipeline = fHelper.createPipeline(flushState); 586 587 flushState->executeDrawsAndUploadsForMeshDrawOp(this, chainBounds, pipeline, 588 fHelper.stencilSettings()); 589 } 590 591 const SkPMColor4f& color() const { return fShapes[0].fColor; } 592 bool usesDistanceField() const { return fUsesDistanceField; } 593 594 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { 595 SmallPathOp* that = t->cast<SmallPathOp>(); 596 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 597 return CombineResult::kCannotCombine; 598 } 599 600 if (this->usesDistanceField() != that->usesDistanceField()) { 601 return CombineResult::kCannotCombine; 602 } 603 604 const SkMatrix& thisCtm = this->fShapes[0].fViewMatrix; 605 const SkMatrix& thatCtm = that->fShapes[0].fViewMatrix; 606 607 if (thisCtm.hasPerspective() != thatCtm.hasPerspective()) { 608 return CombineResult::kCannotCombine; 609 } 610 611 // We can position on the cpu unless we're in perspective, 612 // but also need to make sure local matrices are identical 613 if ((thisCtm.hasPerspective() || fHelper.usesLocalCoords()) && 614 !SkMatrixPriv::CheapEqual(thisCtm, thatCtm)) { 615 return CombineResult::kCannotCombine; 616 } 617 618 // Depending on the ctm we may have a different shader for SDF paths 619 if (this->usesDistanceField()) { 620 if (thisCtm.isScaleTranslate() != thatCtm.isScaleTranslate() || 621 thisCtm.isSimilarity() != thatCtm.isSimilarity()) { 622 return CombineResult::kCannotCombine; 623 } 624 } 625 626 fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin()); 627 fWideColor |= that->fWideColor; 628 return CombineResult::kMerged; 629 } 630 631#if GR_TEST_UTILS 632 SkString onDumpInfo() const override { 633 SkString string; 634 for (const auto& geo : fShapes) { 635 string.appendf("Color: 0x%08x\n", geo.fColor.toBytes_RGBA()); 636 } 637 string += fHelper.dumpInfo(); 638 return string; 639 } 640#endif 641 642 bool fUsesDistanceField; 643 644 struct Entry { 645 SkPMColor4f fColor; 646 GrStyledShape fShape; 647 SkMatrix fViewMatrix; 648 }; 649 650 SkSTArray<1, Entry> fShapes; 651 Helper fHelper; 652 bool fGammaCorrect; 653 bool fWideColor; 654 655 using INHERITED = GrMeshDrawOp; 656}; 657 658} // anonymous namespace 659 660/////////////////////////////////////////////////////////////////////////////////////////////////// 661 662PathRenderer::CanDrawPath SmallPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { 663 if (!args.fCaps->shaderCaps()->shaderDerivativeSupport()) { 664 return CanDrawPath::kNo; 665 } 666 // If the shape has no key then we won't get any reuse. 667 if (!args.fShape->hasUnstyledKey()) { 668 return CanDrawPath::kNo; 669 } 670 // This only supports filled paths, however, the caller may apply the style to make a filled 671 // path and try again. 672 if (!args.fShape->style().isSimpleFill()) { 673 return CanDrawPath::kNo; 674 } 675 // This does non-inverse coverage-based antialiased fills. 676 if (GrAAType::kCoverage != args.fAAType) { 677 return CanDrawPath::kNo; 678 } 679 // TODO: Support inverse fill 680 if (args.fShape->inverseFilled()) { 681 return CanDrawPath::kNo; 682 } 683 684 SkScalar scaleFactors[2] = { 1, 1 }; 685 // TODO: handle perspective distortion 686 if (!args.fViewMatrix->hasPerspective() && !args.fViewMatrix->getMinMaxScales(scaleFactors)) { 687 return CanDrawPath::kNo; 688 } 689 // For affine transformations, too much shear can produce artifacts. 690 if (!scaleFactors[0] || scaleFactors[1]/scaleFactors[0] > 4) { 691 return CanDrawPath::kNo; 692 } 693 // Only support paths with bounds within kMaxDim by kMaxDim, 694 // scaled to have bounds within kMaxSize by kMaxSize. 695 // The goal is to accelerate rendering of lots of small paths that may be scaling. 696 SkRect bounds = args.fShape->styledBounds(); 697 SkScalar minDim = std::min(bounds.width(), bounds.height()); 698 SkScalar maxDim = std::max(bounds.width(), bounds.height()); 699 SkScalar minSize = minDim * SkScalarAbs(scaleFactors[0]); 700 SkScalar maxSize = maxDim * SkScalarAbs(scaleFactors[1]); 701 if (maxDim > kMaxDim || kMinSize > minSize || maxSize > kMaxSize) { 702 return CanDrawPath::kNo; 703 } 704 705 return CanDrawPath::kYes; 706} 707 708bool SmallPathRenderer::onDrawPath(const DrawPathArgs& args) { 709 GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(), 710 "SmallPathRenderer::onDrawPath"); 711 712 // we've already bailed on inverse filled paths, so this is safe 713 SkASSERT(!args.fShape->isEmpty()); 714 SkASSERT(args.fShape->hasUnstyledKey()); 715 716 GrOp::Owner op = SmallPathOp::Make( 717 args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix, 718 args.fGammaCorrect, args.fUserStencilSettings); 719 args.fSurfaceDrawContext->addDrawOp(args.fClip, std::move(op)); 720 721 return true; 722} 723 724} // namespace skgpu::v1 725 726#if GR_TEST_UTILS 727 728GR_DRAW_OP_TEST_DEFINE(SmallPathOp) { 729 SkMatrix viewMatrix = GrTest::TestMatrix(random); 730 bool gammaCorrect = random->nextBool(); 731 732 // This path renderer only allows fill styles. 733 GrStyledShape shape(GrTest::TestPath(random), GrStyle::SimpleFill()); 734 return skgpu::v1::SmallPathOp::Make(context, std::move(paint), shape, viewMatrix, gammaCorrect, 735 GrGetRandomStencil(random, context)); 736} 737 738#endif // GR_TEST_UTILS 739