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