• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
name() const27     const char* name() const override { return "NonAARectOp"; }
28 
29     // This creates an instance of a simple non-AA solid color rect-drawing Op
Make(const SkRect & r,GrColor color)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
Make(const SkRect & r,GrColor color,const SkRect & local)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 
color() const39     GrColor color() const { return fColor; }
40 
41 protected:
NonAARectOp(uint32_t classID,const SkRect & r,GrColor color)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 
NonAARectOp(uint32_t classID,const SkRect & r,GrColor color,const SkRect & local)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:
getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor * color,GrPipelineAnalysisCoverage * coverage) const67     void getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor* color,
68                                             GrPipelineAnalysisCoverage* coverage) const override {
69         color->setToUnknown();
70         *coverage = GrPipelineAnalysisCoverage::kSingleChannel;
71     }
72 
applyPipelineOptimizations(const GrPipelineOptimizations & optimizations)73     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
74         optimizations.getOverrideColorIfSet(&fColor);
75     }
76 
onCombineIfPossible(GrOp *,const GrCaps &)77     bool onCombineIfPossible(GrOp*, const GrCaps&) override { return false; }
78 
onPrepareDraws(Target * target) const79     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 
save_bm(const SkBitmap & bm,const char name[])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 
~AtlasedRectOp()177     ~AtlasedRectOp() override {
178         fID = -1;
179     }
180 
name() const181     const char* name() const override { return "AtlasedRectOp"; }
182 
id() const183     int id() const { return fID; }
184 
Make(const SkRect & r,int id)185     static std::unique_ptr<AtlasedRectOp> Make(const SkRect& r, int id) {
186         return std::unique_ptr<AtlasedRectOp>(new AtlasedRectOp(r, id));
187     }
188 
setColor(GrColor color)189     void setColor(GrColor color) { fColor = color; }
setLocalRect(const SkRect & localRect)190     void setLocalRect(const SkRect& localRect) {
191         SkASSERT(fHasLocalRect);    // This should've been created to anticipate this
192         fLocalQuad.set(localRect);
193     }
194 
next() const195     AtlasedRectOp* next() const { return fNext; }
setNext(AtlasedRectOp * next)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.
AtlasedRectOp(const SkRect & r,int id)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:
AtlasObject()246     AtlasObject() : fDone(false) { }
247 
~AtlasObject()248     ~AtlasObject() override {
249         SkASSERT(fDone);
250     }
251 
markAsDone()252     void markAsDone() {
253         fDone = true;
254     }
255 
256     // Insert the new op in an internal singly-linked list for 'opListID'
addOp(uint32_t opListID,AtlasedRectOp * op)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.
setAtlasDest(sk_sp<GrTextureProxy> atlasDest)275     void setAtlasDest(sk_sp<GrTextureProxy> atlasDest) {
276         fAtlasDest = atlasDest;
277     }
278 
saveRTC(sk_sp<GrRenderTargetContext> rtc)279     void saveRTC(sk_sp<GrRenderTargetContext> rtc) {
280         SkASSERT(!fRTC);
281         fRTC = rtc;
282     }
283 
284 #ifdef SK_DEBUG
saveAtlasToDisk()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      */
preFlush(GrPreFlushResourceProvider * resourceProvider,const uint32_t * opListIDs,int numOpListIDs,SkTArray<sk_sp<GrRenderTargetContext>> * results)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 
getList(uint32_t opListID)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 
clearOpsFor(LinkedListHeader * header)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.
make_upstream_image(GrContext * context,AtlasObject * object,int start,sk_sp<GrTextureProxy> fakeAtlas)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.
pre_create_atlas(GrContext * context)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 
test_color(skiatest::Reporter * reporter,const SkBitmap & bm,int x,SkColor expected)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  */
DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(PreFlushCallbackTest,reporter,ctxInfo)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