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