1 /* 2 * Copyright 2017 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 "Test.h" 9 10 #if SK_SUPPORT_GPU 11 12 #include "GrBackendSemaphore.h" 13 #include "GrClip.h" 14 #include "GrContextPriv.h" 15 #include "GrDefaultGeoProcFactory.h" 16 #include "GrOnFlushResourceProvider.h" 17 #include "GrProxyProvider.h" 18 #include "GrQuad.h" 19 #include "GrRenderTargetContextPriv.h" 20 #include "GrResourceProvider.h" 21 #include "GrTexture.h" 22 23 #include "SkBitmap.h" 24 #include "SkPointPriv.h" 25 #include "effects/GrSimpleTextureEffect.h" 26 #include "ops/GrSimpleMeshDrawOpHelper.h" 27 28 namespace { 29 // This is a simplified mesh drawing op that can be used in the atlas generation test. 30 // Please see AtlasedRectOp below. 31 class NonAARectOp : public GrMeshDrawOp { 32 protected: 33 using Helper = GrSimpleMeshDrawOpHelper; 34 35 public: 36 DEFINE_OP_CLASS_ID 37 38 // This creates an instance of a simple non-AA solid color rect-drawing Op 39 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkRect& r) { 40 return Helper::FactoryHelper<NonAARectOp>(std::move(paint), r, nullptr, ClassID()); 41 } 42 43 // This creates an instance of a simple non-AA textured rect-drawing Op 44 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkRect& r, const SkRect& local) { 45 return Helper::FactoryHelper<NonAARectOp>(std::move(paint), r, &local, ClassID()); 46 } 47 48 GrColor color() const { return fColor; } 49 50 NonAARectOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkRect& r, 51 const SkRect* localRect, int32_t classID) 52 : INHERITED(classID) 53 , fColor(color) 54 , fHasLocalRect(SkToBool(localRect)) 55 , fRect(r) 56 , fHelper(helperArgs, GrAAType::kNone) { 57 if (fHasLocalRect) { 58 fLocalQuad.set(*localRect); 59 } 60 // Choose some conservative values for aa bloat and zero area. 61 this->setBounds(r, HasAABloat::kYes, IsZeroArea::kYes); 62 } 63 64 const char* name() const override { return "NonAARectOp"; } 65 66 void visitProxies(const VisitProxyFunc& func) const override { 67 fHelper.visitProxies(func); 68 } 69 70 FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } 71 72 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip*, 73 GrPixelConfigIsClamped dstIsClamped) override { 74 // Set the color to unknown because the subclass may change the color later. 75 GrProcessorAnalysisColor gpColor; 76 gpColor.setToUnknown(); 77 // We ignore the clip so pass this rather than the GrAppliedClip param. 78 static GrAppliedClip kNoClip; 79 return fHelper.xpRequiresDstTexture(caps, &kNoClip, dstIsClamped, 80 GrProcessorAnalysisCoverage::kNone, &gpColor); 81 } 82 83 protected: 84 GrColor fColor; 85 bool fHasLocalRect; 86 GrQuad fLocalQuad; 87 SkRect fRect; 88 89 private: 90 bool onCombineIfPossible(GrOp*, const GrCaps&) override { return false; } 91 92 void onPrepareDraws(Target* target) override { 93 using namespace GrDefaultGeoProcFactory; 94 95 // The vertex attrib order is always pos, color, local coords. 96 static const int kColorOffset = sizeof(SkPoint); 97 static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor); 98 99 sk_sp<GrGeometryProcessor> gp = 100 GrDefaultGeoProcFactory::Make(Color::kPremulGrColorAttribute_Type, 101 Coverage::kSolid_Type, 102 fHasLocalRect ? LocalCoords::kHasExplicit_Type 103 : LocalCoords::kUnused_Type, 104 SkMatrix::I()); 105 if (!gp) { 106 SkDebugf("Couldn't create GrGeometryProcessor for GrAtlasedOp\n"); 107 return; 108 } 109 110 size_t vertexStride = gp->getVertexStride(); 111 112 SkASSERT(fHasLocalRect 113 ? vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr) 114 : vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorAttr)); 115 116 const GrBuffer* indexBuffer; 117 int firstIndex; 118 uint16_t* indices = target->makeIndexSpace(6, &indexBuffer, &firstIndex); 119 if (!indices) { 120 SkDebugf("Indices could not be allocated for GrAtlasedOp.\n"); 121 return; 122 } 123 124 const GrBuffer* vertexBuffer; 125 int firstVertex; 126 void* vertices = target->makeVertexSpace(vertexStride, 4, &vertexBuffer, &firstVertex); 127 if (!vertices) { 128 SkDebugf("Vertices could not be allocated for GrAtlasedOp.\n"); 129 return; 130 } 131 132 // Setup indices 133 indices[0] = 0; 134 indices[1] = 1; 135 indices[2] = 2; 136 indices[3] = 2; 137 indices[4] = 1; 138 indices[5] = 3; 139 140 // Setup positions 141 SkPoint* position = (SkPoint*) vertices; 142 SkPointPriv::SetRectTriStrip(position, fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom, 143 vertexStride); 144 145 // Setup vertex colors 146 GrColor* color = (GrColor*)((intptr_t)vertices + kColorOffset); 147 for (int i = 0; i < 4; ++i) { 148 *color = fColor; 149 color = (GrColor*)((intptr_t)color + vertexStride); 150 } 151 152 // Setup local coords 153 if (fHasLocalRect) { 154 SkPoint* coords = (SkPoint*)((intptr_t) vertices + kLocalOffset); 155 for (int i = 0; i < 4; i++) { 156 *coords = fLocalQuad.point(i); 157 coords = (SkPoint*)((intptr_t) coords + vertexStride); 158 } 159 } 160 161 GrMesh mesh(GrPrimitiveType::kTriangles); 162 mesh.setIndexed(indexBuffer, 6, firstIndex, 0, 3); 163 mesh.setVertexData(vertexBuffer, firstVertex); 164 165 target->draw(gp.get(), fHelper.makePipeline(target), mesh); 166 } 167 168 Helper fHelper; 169 170 typedef GrMeshDrawOp INHERITED; 171 }; 172 173 } // anonymous namespace 174 175 static constexpr SkRect kEmptyRect = SkRect::MakeEmpty(); 176 177 namespace { 178 179 /* 180 * Atlased ops just draw themselves as textured rects with the texture pixels being 181 * pulled out of the atlas. Their color is based on their ID. 182 */ 183 class AtlasedRectOp final : public NonAARectOp { 184 public: 185 DEFINE_OP_CLASS_ID 186 187 ~AtlasedRectOp() override { 188 fID = -1; 189 } 190 191 const char* name() const override { return "AtlasedRectOp"; } 192 193 int id() const { return fID; } 194 195 static std::unique_ptr<AtlasedRectOp> Make(GrPaint&& paint, const SkRect& r, int id) { 196 GrDrawOp* op = Helper::FactoryHelper<AtlasedRectOp>(std::move(paint), r, id).release(); 197 return std::unique_ptr<AtlasedRectOp>(static_cast<AtlasedRectOp*>(op)); 198 } 199 200 // We set the initial color of the NonAARectOp based on the ID. 201 // Note that we force creation of a NonAARectOp that has local coords in anticipation of 202 // pulling from the atlas. 203 AtlasedRectOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkRect& r, int id) 204 : INHERITED(helperArgs, kColors[id], r, &kEmptyRect, ClassID()) 205 , fID(id) 206 , fNext(nullptr) { 207 SkASSERT(fID < kMaxIDs); 208 } 209 210 void setColor(GrColor color) { fColor = color; } 211 void setLocalRect(const SkRect& localRect) { 212 SkASSERT(fHasLocalRect); // This should've been created to anticipate this 213 fLocalQuad.set(localRect); 214 } 215 216 AtlasedRectOp* next() const { return fNext; } 217 void setNext(AtlasedRectOp* next) { 218 fNext = next; 219 } 220 221 private: 222 223 static const int kMaxIDs = 9; 224 static const SkColor kColors[kMaxIDs]; 225 226 int fID; 227 // The Atlased ops have an internal singly-linked list of ops that land in the same opList 228 AtlasedRectOp* fNext; 229 230 typedef NonAARectOp INHERITED; 231 }; 232 233 } // anonymous namespace 234 235 const GrColor AtlasedRectOp::kColors[kMaxIDs] = { 236 GrColorPackRGBA(255, 0, 0, 255), 237 GrColorPackRGBA(0, 255, 0, 255), 238 GrColorPackRGBA(0, 0, 255, 255), 239 GrColorPackRGBA(0, 255, 255, 255), 240 GrColorPackRGBA(255, 0, 255, 255), 241 GrColorPackRGBA(255, 255, 0, 255), 242 GrColorPackRGBA(0, 0, 0, 255), 243 GrColorPackRGBA(128, 128, 128, 255), 244 GrColorPackRGBA(255, 255, 255, 255) 245 }; 246 247 static const int kDrawnTileSize = 16; 248 249 /* 250 * Rather than performing any rect packing, this atlaser just lays out constant-sized 251 * tiles in an Nx1 row 252 */ 253 static const int kAtlasTileSize = 2; 254 255 /* 256 * This class aggregates the op information required for atlasing 257 */ 258 class AtlasObject final : public GrOnFlushCallbackObject { 259 public: 260 AtlasObject() : fDone(false) { } 261 262 ~AtlasObject() override { 263 SkASSERT(fDone); 264 } 265 266 void markAsDone() { 267 fDone = true; 268 } 269 270 // Insert the new op in an internal singly-linked list for 'opListID' 271 void addOp(uint32_t opListID, AtlasedRectOp* op) { 272 LinkedListHeader* header = nullptr; 273 for (int i = 0; i < fOps.count(); ++i) { 274 if (opListID == fOps[i].fID) { 275 header = &(fOps[i]); 276 } 277 } 278 279 if (!header) { 280 fOps.push({opListID, nullptr}); 281 header = &(fOps[fOps.count()-1]); 282 } 283 284 op->setNext(header->fHead); 285 header->fHead = op; 286 } 287 288 int numOps() const { return fOps.count(); } 289 290 // Get the fully lazy proxy that is backing the atlas. Its actual width isn't 291 // known until flush time. 292 sk_sp<GrTextureProxy> getAtlasProxy(GrProxyProvider* proxyProvider) { 293 if (fAtlasProxy) { 294 return fAtlasProxy; 295 } 296 297 fAtlasProxy = proxyProvider->createFullyLazyProxy( 298 [](GrResourceProvider* resourceProvider) { 299 if (!resourceProvider) { 300 return sk_sp<GrTexture>(); 301 } 302 303 GrSurfaceDesc desc; 304 desc.fFlags = kRenderTarget_GrSurfaceFlag; 305 desc.fOrigin = kBottomLeft_GrSurfaceOrigin; 306 // TODO: until partial flushes in MDB lands we're stuck having 307 // all 9 atlas draws occur 308 desc.fWidth = 9 /*this->numOps()*/ * kAtlasTileSize; 309 desc.fHeight = kAtlasTileSize; 310 desc.fConfig = kRGBA_8888_GrPixelConfig; 311 312 return resourceProvider->createTexture(desc, SkBudgeted::kYes, 313 GrResourceProvider::kNoPendingIO_Flag); 314 }, 315 GrProxyProvider::Renderable::kYes, 316 kBottomLeft_GrSurfaceOrigin, 317 kRGBA_8888_GrPixelConfig); 318 return fAtlasProxy; 319 } 320 321 /* 322 * This callback creates the atlas and updates the AtlasedRectOps to read from it 323 */ 324 void preFlush(GrOnFlushResourceProvider* resourceProvider, 325 const uint32_t* opListIDs, int numOpListIDs, 326 SkTArray<sk_sp<GrRenderTargetContext>>* results) override { 327 SkASSERT(!results->count()); 328 329 // Until MDB is landed we will most-likely only have one opList. 330 SkTDArray<LinkedListHeader*> lists; 331 for (int i = 0; i < numOpListIDs; ++i) { 332 if (LinkedListHeader* list = this->getList(opListIDs[i])) { 333 lists.push(list); 334 } 335 } 336 337 if (!lists.count()) { 338 return; // nothing to atlas 339 } 340 341 if (!resourceProvider->instatiateProxy(fAtlasProxy.get())) { 342 return; 343 } 344 345 // At this point all the GrAtlasedOp's should have lined up to read from 'atlasDest' and 346 // there should either be two writes to clear it or no writes. 347 SkASSERT(9 == fAtlasProxy->getPendingReadCnt_TestOnly()); 348 SkASSERT(2 == fAtlasProxy->getPendingWriteCnt_TestOnly() || 349 0 == fAtlasProxy->getPendingWriteCnt_TestOnly()); 350 sk_sp<GrRenderTargetContext> rtc = resourceProvider->makeRenderTargetContext( 351 fAtlasProxy, 352 nullptr, nullptr); 353 354 // clear the atlas 355 rtc->clear(nullptr, 0x0, GrRenderTargetContext::CanClearFullscreen::kYes); 356 357 int blocksInAtlas = 0; 358 for (int i = 0; i < lists.count(); ++i) { 359 for (AtlasedRectOp* op = lists[i]->fHead; op; op = op->next()) { 360 SkIRect r = SkIRect::MakeXYWH(blocksInAtlas*kAtlasTileSize, 0, 361 kAtlasTileSize, kAtlasTileSize); 362 363 // For now, we avoid the resource buffer issues and just use clears 364 #if 1 365 rtc->clear(&r, op->color(), GrRenderTargetContext::CanClearFullscreen::kNo); 366 #else 367 GrPaint paint; 368 paint.setColor4f(GrColor4f::FromGrColor(op->color())); 369 std::unique_ptr<GrDrawOp> drawOp(NonAARectOp::Make(std::move(paint), 370 SkRect::Make(r))); 371 rtc->priv().testingOnly_addDrawOp(std::move(drawOp)); 372 #endif 373 blocksInAtlas++; 374 375 // Set the atlased Op's color to white (so we know we're not using it for 376 // the final draw). 377 op->setColor(0xFFFFFFFF); 378 379 // Set the atlased Op's localRect to point to where it landed in the atlas 380 op->setLocalRect(SkRect::Make(r)); 381 } 382 383 // We've updated all these ops and we certainly don't want to process them again 384 this->clearOpsFor(lists[i]); 385 } 386 387 results->push_back(std::move(rtc)); 388 } 389 390 private: 391 typedef struct { 392 uint32_t fID; 393 AtlasedRectOp* fHead; 394 } LinkedListHeader; 395 396 LinkedListHeader* getList(uint32_t opListID) { 397 for (int i = 0; i < fOps.count(); ++i) { 398 if (opListID == fOps[i].fID) { 399 return &(fOps[i]); 400 } 401 } 402 return nullptr; 403 } 404 405 void clearOpsFor(LinkedListHeader* header) { 406 // The AtlasedRectOps have yet to execute (and this class doesn't own them) so just 407 // forget about them in the laziest way possible. 408 header->fHead = nullptr; 409 header->fID = 0; // invalid opList ID 410 } 411 412 // Each opList containing AtlasedRectOps gets its own internal singly-linked list 413 SkTDArray<LinkedListHeader> fOps; 414 415 // The fully lazy proxy for the atlas 416 sk_sp<GrTextureProxy> fAtlasProxy; 417 418 // Set to true when the testing harness expects this object to be no longer used 419 bool fDone; 420 }; 421 422 // This creates an off-screen rendertarget whose ops which eventually pull from the atlas. 423 static sk_sp<GrTextureProxy> make_upstream_image(GrContext* context, AtlasObject* object, int start, 424 sk_sp<GrTextureProxy> atlasProxy) { 425 sk_sp<GrRenderTargetContext> rtc(context->makeDeferredRenderTargetContext( 426 SkBackingFit::kApprox, 427 3*kDrawnTileSize, 428 kDrawnTileSize, 429 kRGBA_8888_GrPixelConfig, 430 nullptr)); 431 432 rtc->clear(nullptr, GrColorPackRGBA(255, 0, 0, 255), 433 GrRenderTargetContext::CanClearFullscreen::kYes); 434 435 for (int i = 0; i < 3; ++i) { 436 SkRect r = SkRect::MakeXYWH(i*kDrawnTileSize, 0, kDrawnTileSize, kDrawnTileSize); 437 438 auto fp = GrSimpleTextureEffect::Make(atlasProxy, SkMatrix::I()); 439 GrPaint paint; 440 paint.addColorFragmentProcessor(std::move(fp)); 441 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 442 std::unique_ptr<AtlasedRectOp> op(AtlasedRectOp::Make(std::move(paint), r, start + i)); 443 444 AtlasedRectOp* sparePtr = op.get(); 445 446 uint32_t opListID = rtc->priv().testingOnly_addDrawOp(std::move(op)); 447 448 object->addOp(opListID, sparePtr); 449 } 450 451 return rtc->asTextureProxyRef(); 452 } 453 454 // Enable this if you want to debug the final draws w/o having the atlasCallback create the 455 // atlas 456 #if 0 457 #include "SkImageEncoder.h" 458 #include "SkGrPriv.h" 459 #include "sk_tool_utils.h" 460 461 static void save_bm(const SkBitmap& bm, const char name[]) { 462 bool result = sk_tool_utils::EncodeImageToFile(name, bm, SkEncodedImageFormat::kPNG, 100); 463 SkASSERT(result); 464 } 465 466 sk_sp<GrTextureProxy> pre_create_atlas(GrContext* context) { 467 SkBitmap bm; 468 bm.allocN32Pixels(18, 2, true); 469 bm.erase(SK_ColorRED, SkIRect::MakeXYWH(0, 0, 2, 2)); 470 bm.erase(SK_ColorGREEN, SkIRect::MakeXYWH(2, 0, 2, 2)); 471 bm.erase(SK_ColorBLUE, SkIRect::MakeXYWH(4, 0, 2, 2)); 472 bm.erase(SK_ColorCYAN, SkIRect::MakeXYWH(6, 0, 2, 2)); 473 bm.erase(SK_ColorMAGENTA, SkIRect::MakeXYWH(8, 0, 2, 2)); 474 bm.erase(SK_ColorYELLOW, SkIRect::MakeXYWH(10, 0, 2, 2)); 475 bm.erase(SK_ColorBLACK, SkIRect::MakeXYWH(12, 0, 2, 2)); 476 bm.erase(SK_ColorGRAY, SkIRect::MakeXYWH(14, 0, 2, 2)); 477 bm.erase(SK_ColorWHITE, SkIRect::MakeXYWH(16, 0, 2, 2)); 478 479 #if 1 480 save_bm(bm, "atlas-fake.png"); 481 #endif 482 483 GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(bm.info(), *context->caps()); 484 desc.fFlags |= kRenderTarget_GrSurfaceFlag; 485 486 sk_sp<GrSurfaceProxy> tmp = GrSurfaceProxy::MakeDeferred(*context->caps(), 487 context->textureProvider(), 488 desc, SkBudgeted::kYes, 489 bm.getPixels(), bm.rowBytes()); 490 491 return sk_ref_sp(tmp->asTextureProxy()); 492 } 493 #endif 494 495 496 static void test_color(skiatest::Reporter* reporter, const SkBitmap& bm, int x, SkColor expected) { 497 SkColor readback = bm.getColor(x, kDrawnTileSize/2); 498 REPORTER_ASSERT(reporter, expected == readback); 499 if (expected != readback) { 500 SkDebugf("Color mismatch: %x %x\n", expected, readback); 501 } 502 } 503 504 /* 505 * For the atlasing test we make a DAG that looks like: 506 * 507 * RT1 with ops: 0,1,2 RT2 with ops: 3,4,5 RT3 with ops: 6,7,8 508 * \ / 509 * \ / 510 * RT4 511 * We then flush RT4 and expect only ops 0-5 to be atlased together. 512 * Each op is just a solid colored rect so both the atlas and the final image should appear as: 513 * R G B C M Y 514 * with the atlas having width = 6*kAtlasTileSize and height = kAtlasTileSize. 515 * 516 * Note: until partial flushes in MDB lands, the atlas will actually have width= 9*kAtlasTileSize 517 * and look like: 518 * R G B C M Y K Grey White 519 */ 520 DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(OnFlushCallbackTest, reporter, ctxInfo) { 521 static const int kNumProxies = 3; 522 523 GrContext* context = ctxInfo.grContext(); 524 auto proxyProvider = context->contextPriv().proxyProvider(); 525 526 AtlasObject object; 527 528 context->contextPriv().addOnFlushCallbackObject(&object); 529 530 sk_sp<GrTextureProxy> proxies[kNumProxies]; 531 for (int i = 0; i < kNumProxies; ++i) { 532 proxies[i] = make_upstream_image(context, &object, i*3, 533 object.getAtlasProxy(proxyProvider)); 534 } 535 536 static const int kFinalWidth = 6*kDrawnTileSize; 537 static const int kFinalHeight = kDrawnTileSize; 538 539 sk_sp<GrRenderTargetContext> rtc(context->makeDeferredRenderTargetContext( 540 SkBackingFit::kApprox, 541 kFinalWidth, 542 kFinalHeight, 543 kRGBA_8888_GrPixelConfig, 544 nullptr)); 545 546 rtc->clear(nullptr, 0xFFFFFFFF, GrRenderTargetContext::CanClearFullscreen::kYes); 547 548 // Note that this doesn't include the third texture proxy 549 for (int i = 0; i < kNumProxies-1; ++i) { 550 SkRect r = SkRect::MakeXYWH(i*3*kDrawnTileSize, 0, 3*kDrawnTileSize, kDrawnTileSize); 551 552 SkMatrix t = SkMatrix::MakeTrans(-i*3*kDrawnTileSize, 0); 553 554 GrPaint paint; 555 auto fp = GrSimpleTextureEffect::Make(std::move(proxies[i]), t); 556 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 557 paint.addColorFragmentProcessor(std::move(fp)); 558 559 rtc->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), r); 560 } 561 562 rtc->prepareForExternalIO(0, nullptr); 563 564 SkBitmap readBack; 565 readBack.allocN32Pixels(kFinalWidth, kFinalHeight); 566 567 SkDEBUGCODE(bool result =) rtc->readPixels(readBack.info(), readBack.getPixels(), 568 readBack.rowBytes(), 0, 0); 569 SkASSERT(result); 570 571 context->contextPriv().testingOnly_flushAndRemoveOnFlushCallbackObject(&object); 572 573 object.markAsDone(); 574 575 int x = kDrawnTileSize/2; 576 test_color(reporter, readBack, x, SK_ColorRED); 577 x += kDrawnTileSize; 578 test_color(reporter, readBack, x, SK_ColorGREEN); 579 x += kDrawnTileSize; 580 test_color(reporter, readBack, x, SK_ColorBLUE); 581 x += kDrawnTileSize; 582 test_color(reporter, readBack, x, SK_ColorCYAN); 583 x += kDrawnTileSize; 584 test_color(reporter, readBack, x, SK_ColorMAGENTA); 585 x += kDrawnTileSize; 586 test_color(reporter, readBack, x, SK_ColorYELLOW); 587 } 588 589 #endif 590