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