Home | History | Annotate | Download | only in tests
      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