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 "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