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