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