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