• 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 "tests/Test.h"
9 
10 #include "include/core/SkBitmap.h"
11 #include "include/gpu/GrBackendSemaphore.h"
12 #include "include/gpu/GrTexture.h"
13 #include "src/core/SkPointPriv.h"
14 #include "src/gpu/GrClip.h"
15 #include "src/gpu/GrContextPriv.h"
16 #include "src/gpu/GrDefaultGeoProcFactory.h"
17 #include "src/gpu/GrOnFlushResourceProvider.h"
18 #include "src/gpu/GrProxyProvider.h"
19 #include "src/gpu/GrRenderTargetContextPriv.h"
20 #include "src/gpu/GrResourceProvider.h"
21 #include "src/gpu/effects/generated/GrSimpleTextureEffect.h"
22 #include "src/gpu/geometry/GrQuad.h"
23 #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
24 
25 namespace {
26 // This is a simplified mesh drawing op that can be used in the atlas generation test.
27 // Please see AtlasedRectOp below.
28 class NonAARectOp : public GrMeshDrawOp {
29 protected:
30     using Helper = GrSimpleMeshDrawOpHelper;
31 
32 public:
33     DEFINE_OP_CLASS_ID
34 
35     // This creates an instance of a simple non-AA solid color rect-drawing Op
Make(GrRecordingContext * context,GrPaint && paint,const SkRect & r)36     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
37                                           GrPaint&& paint,
38                                           const SkRect& r) {
39         return Helper::FactoryHelper<NonAARectOp>(context, std::move(paint), r, nullptr, ClassID());
40     }
41 
42     // This creates an instance of a simple non-AA textured rect-drawing Op
Make(GrRecordingContext * context,GrPaint && paint,const SkRect & r,const SkRect & local)43     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
44                                           GrPaint&& paint,
45                                           const SkRect& r,
46                                           const SkRect& local) {
47         return Helper::FactoryHelper<NonAARectOp>(context, std::move(paint), r, &local, ClassID());
48     }
49 
color() const50     const SkPMColor4f& color() const { return fColor; }
51 
NonAARectOp(const Helper::MakeArgs & helperArgs,const SkPMColor4f & color,const SkRect & r,const SkRect * localRect,int32_t classID)52     NonAARectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, const SkRect& r,
53                 const SkRect* localRect, int32_t classID)
54             : INHERITED(classID)
55             , fColor(color)
56             , fHasLocalRect(SkToBool(localRect))
57             , fRect(r)
58             , fHelper(helperArgs, GrAAType::kNone) {
59         if (fHasLocalRect) {
60             fLocalQuad = GrQuad(*localRect);
61         }
62         // Choose some conservative values for aa bloat and zero area.
63         this->setBounds(r, HasAABloat::kYes, IsZeroArea::kYes);
64     }
65 
name() const66     const char* name() const override { return "NonAARectOp"; }
67 
visitProxies(const VisitProxyFunc & func) const68     void visitProxies(const VisitProxyFunc& func) const override {
69         fHelper.visitProxies(func);
70     }
71 
fixedFunctionFlags() const72     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
73 
finalize(const GrCaps & caps,const GrAppliedClip *,bool hasMixedSampledCoverage,GrClampType clampType)74     GrProcessorSet::Analysis finalize(
75             const GrCaps& caps, const GrAppliedClip*, bool hasMixedSampledCoverage,
76             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(caps, &kNoClip, hasMixedSampledCoverage, clampType,
83                                           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->getDefaultBackendFormat(GrColorType::kRGBA_8888,
305                                                                      GrRenderable::kYes);
306 
307         fAtlasProxy = GrProxyProvider::MakeFullyLazyProxy(
308                 [format](GrResourceProvider* resourceProvider)
309                         -> GrSurfaceProxy::LazyInstantiationResult {
310                     GrSurfaceDesc desc;
311                     // TODO: until partial flushes in MDB lands we're stuck having
312                     // all 9 atlas draws occur
313                     desc.fWidth = 9 /*this->numOps()*/ * kAtlasTileSize;
314                     desc.fHeight = kAtlasTileSize;
315                     desc.fConfig = kRGBA_8888_GrPixelConfig;
316 
317                     return resourceProvider->createTexture(
318                             desc, format, GrRenderable::kYes, 1, SkBudgeted::kYes, GrProtected::kNo,
319                             GrResourceProvider::Flags::kNoPendingIO);
320                 },
321                 format,
322                 GrRenderable::kYes,
323                 1,
324                 GrProtected::kNo,
325                 kBottomLeft_GrSurfaceOrigin,
326                 kRGBA_8888_GrPixelConfig,
327                 *proxyProvider->caps());
328 
329         fAtlasProxy->priv().setIgnoredByResourceAllocator();
330         return fAtlasProxy;
331     }
332 
333     /*
334      * This callback creates the atlas and updates the AtlasedRectOps to read from it
335      */
preFlush(GrOnFlushResourceProvider * resourceProvider,const uint32_t * opListIDs,int numOpListIDs,SkTArray<sk_sp<GrRenderTargetContext>> * results)336     void preFlush(GrOnFlushResourceProvider* resourceProvider,
337                   const uint32_t* opListIDs, int numOpListIDs,
338                   SkTArray<sk_sp<GrRenderTargetContext>>* results) override {
339         SkASSERT(!results->count());
340 
341         // Until MDB is landed we will most-likely only have one opList.
342         SkTDArray<LinkedListHeader*> lists;
343         for (int i = 0; i < numOpListIDs; ++i) {
344             if (LinkedListHeader* list = this->getList(opListIDs[i])) {
345                 lists.push_back(list);
346             }
347         }
348 
349         if (!lists.count()) {
350             return; // nothing to atlas
351         }
352 
353         if (!resourceProvider->instatiateProxy(fAtlasProxy.get())) {
354             return;
355         }
356 
357         // At this point 'fAtlasProxy' should be instantiated and have:
358         //    1 ref from the 'fAtlasProxy' sk_sp
359         //    9 refs from the 9 AtlasedRectOps
360         SkASSERT(10 == fAtlasProxy->priv().getProxyRefCnt());
361         // The backing GrSurface should have only 1 though bc there is only one proxy
362         SkASSERT(1 == fAtlasProxy->testingOnly_getBackingRefCnt());
363         sk_sp<GrRenderTargetContext> rtc = resourceProvider->makeRenderTargetContext(
364                 fAtlasProxy, GrColorType::kRGBA_8888, nullptr, nullptr);
365 
366         // clear the atlas
367         rtc->clear(nullptr, SK_PMColor4fTRANSPARENT,
368                    GrRenderTargetContext::CanClearFullscreen::kYes);
369 
370         int blocksInAtlas = 0;
371         for (int i = 0; i < lists.count(); ++i) {
372             for (AtlasedRectOp* op = lists[i]->fHead; op; op = op->next()) {
373                 SkIRect r = SkIRect::MakeXYWH(blocksInAtlas*kAtlasTileSize, 0,
374                                               kAtlasTileSize, kAtlasTileSize);
375 
376                 // For now, we avoid the resource buffer issues and just use clears
377 #if 1
378                 rtc->clear(&r, op->color(), GrRenderTargetContext::CanClearFullscreen::kNo);
379 #else
380                 GrPaint paint;
381                 paint.setColor4f(op->color());
382                 std::unique_ptr<GrDrawOp> drawOp(NonAARectOp::Make(std::move(paint),
383                                                                    SkRect::Make(r)));
384                 rtc->priv().testingOnly_addDrawOp(std::move(drawOp));
385 #endif
386                 blocksInAtlas++;
387 
388                 // Set the atlased Op's color to white (so we know we're not using it for
389                 // the final draw).
390                 op->setColor(SK_PMColor4fWHITE);
391 
392                 // Set the atlased Op's localRect to point to where it landed in the atlas
393                 op->setLocalRect(SkRect::Make(r));
394             }
395 
396             // We've updated all these ops and we certainly don't want to process them again
397             this->clearOpsFor(lists[i]);
398         }
399 
400         results->push_back(std::move(rtc));
401     }
402 
403 private:
404     typedef struct {
405         uint32_t       fID;
406         AtlasedRectOp* fHead;
407     } LinkedListHeader;
408 
getList(uint32_t opListID)409     LinkedListHeader* getList(uint32_t opListID) {
410         for (int i = 0; i < fOps.count(); ++i) {
411             if (opListID == fOps[i].fID) {
412                 return &(fOps[i]);
413             }
414         }
415         return nullptr;
416     }
417 
clearOpsFor(LinkedListHeader * header)418     void clearOpsFor(LinkedListHeader* header) {
419         // The AtlasedRectOps have yet to execute (and this class doesn't own them) so just
420         // forget about them in the laziest way possible.
421         header->fHead = nullptr;
422         header->fID = 0;            // invalid opList ID
423     }
424 
425     // Each opList containing AtlasedRectOps gets its own internal singly-linked list
426     SkTDArray<LinkedListHeader>  fOps;
427 
428     // The fully lazy proxy for the atlas
429     sk_sp<GrTextureProxy>        fAtlasProxy;
430 
431     // Set to true when the testing harness expects this object to be no longer used
432     bool                         fDone;
433 };
434 
435 // 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)436 static sk_sp<GrTextureProxy> make_upstream_image(GrContext* context, AtlasObject* object, int start,
437                                                  sk_sp<GrTextureProxy> atlasProxy) {
438     sk_sp<GrRenderTargetContext> rtc(
439             context->priv().makeDeferredRenderTargetContext(SkBackingFit::kApprox,
440                                                             3*kDrawnTileSize,
441                                                             kDrawnTileSize,
442                                                             GrColorType::kRGBA_8888,
443                                                             nullptr));
444 
445     rtc->clear(nullptr, { 1, 0, 0, 1 }, GrRenderTargetContext::CanClearFullscreen::kYes);
446 
447     for (int i = 0; i < 3; ++i) {
448         SkRect r = SkRect::MakeXYWH(i*kDrawnTileSize, 0, kDrawnTileSize, kDrawnTileSize);
449 
450         auto fp = GrSimpleTextureEffect::Make(atlasProxy, SkMatrix::I());
451         GrPaint paint;
452         paint.addColorFragmentProcessor(std::move(fp));
453         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
454         std::unique_ptr<AtlasedRectOp> op(AtlasedRectOp::Make(context,
455                                                               std::move(paint), r, start + i));
456 
457         AtlasedRectOp* sparePtr = op.get();
458 
459         uint32_t opListID;
460         rtc->priv().testingOnly_addDrawOp(GrNoClip(), std::move(op),
461                                           [&opListID](GrOp* op, uint32_t id) { opListID = id; });
462         SkASSERT(SK_InvalidUniqueID != opListID);
463 
464         object->addOp(opListID, sparePtr);
465     }
466 
467     return rtc->asTextureProxyRef();
468 }
469 
470 // Enable this if you want to debug the final draws w/o having the atlasCallback create the
471 // atlas
472 #if 0
473 #include "SkGrPriv.h"
474 #include "include/core/SkImageEncoder.h"
475 #include "tools/ToolUtils.h"
476 
477 static void save_bm(const SkBitmap& bm, const char name[]) {
478     bool result = ToolUtils::EncodeImageToFile(name, bm, SkEncodedImageFormat::kPNG, 100);
479     SkASSERT(result);
480 }
481 
482 sk_sp<GrTextureProxy> pre_create_atlas(GrContext* context) {
483     SkBitmap bm;
484     bm.allocN32Pixels(18, 2, true);
485     bm.erase(SK_ColorRED,     SkIRect::MakeXYWH(0, 0, 2, 2));
486     bm.erase(SK_ColorGREEN,   SkIRect::MakeXYWH(2, 0, 2, 2));
487     bm.erase(SK_ColorBLUE,    SkIRect::MakeXYWH(4, 0, 2, 2));
488     bm.erase(SK_ColorCYAN,    SkIRect::MakeXYWH(6, 0, 2, 2));
489     bm.erase(SK_ColorMAGENTA, SkIRect::MakeXYWH(8, 0, 2, 2));
490     bm.erase(SK_ColorYELLOW,  SkIRect::MakeXYWH(10, 0, 2, 2));
491     bm.erase(SK_ColorBLACK,   SkIRect::MakeXYWH(12, 0, 2, 2));
492     bm.erase(SK_ColorGRAY,    SkIRect::MakeXYWH(14, 0, 2, 2));
493     bm.erase(SK_ColorWHITE,   SkIRect::MakeXYWH(16, 0, 2, 2));
494 
495 #if 1
496     save_bm(bm, "atlas-fake.png");
497 #endif
498 
499     GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(bm.info());
500     desc.fFlags |= kRenderTarget_GrSurfaceFlag;
501 
502     sk_sp<GrSurfaceProxy> tmp = GrSurfaceProxy::MakeDeferred(*context->caps(),
503                                                              context->textureProvider(),
504                                                              desc, SkBudgeted::kYes,
505                                                              bm.getPixels(), bm.rowBytes());
506 
507     return sk_ref_sp(tmp->asTextureProxy());
508 }
509 #endif
510 
511 
test_color(skiatest::Reporter * reporter,const SkBitmap & bm,int x,SkColor expected)512 static void test_color(skiatest::Reporter* reporter, const SkBitmap& bm, int x, SkColor expected) {
513     SkColor readback = bm.getColor(x, kDrawnTileSize/2);
514     REPORTER_ASSERT(reporter, expected == readback);
515     if (expected != readback) {
516         SkDebugf("Color mismatch: %x %x\n", expected, readback);
517     }
518 }
519 
520 /*
521  * For the atlasing test we make a DAG that looks like:
522  *
523  *    RT1 with ops: 0,1,2       RT2 with ops: 3,4,5       RT3 with ops: 6,7,8
524  *                     \         /
525  *                      \       /
526  *                         RT4
527  * We then flush RT4 and expect only ops 0-5 to be atlased together.
528  * Each op is just a solid colored rect so both the atlas and the final image should appear as:
529  *           R G B C M Y
530  * with the atlas having width = 6*kAtlasTileSize and height = kAtlasTileSize.
531  *
532  * Note: until partial flushes in MDB lands, the atlas will actually have width= 9*kAtlasTileSize
533  * and look like:
534  *           R G B C M Y K Grey White
535  */
DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(OnFlushCallbackTest,reporter,ctxInfo)536 DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(OnFlushCallbackTest, reporter, ctxInfo) {
537     static const int kNumProxies = 3;
538 
539     GrContext* context = ctxInfo.grContext();
540     auto proxyProvider = context->priv().proxyProvider();
541 
542     AtlasObject object;
543 
544     context->priv().addOnFlushCallbackObject(&object);
545 
546     sk_sp<GrTextureProxy> proxies[kNumProxies];
547     for (int i = 0; i < kNumProxies; ++i) {
548         proxies[i] = make_upstream_image(context, &object, i*3,
549                                          object.getAtlasProxy(proxyProvider,
550                                                               context->priv().caps()));
551     }
552 
553     static const int kFinalWidth = 6*kDrawnTileSize;
554     static const int kFinalHeight = kDrawnTileSize;
555 
556     sk_sp<GrRenderTargetContext> rtc(
557             context->priv().makeDeferredRenderTargetContext(SkBackingFit::kApprox,
558                                                             kFinalWidth,
559                                                             kFinalHeight,
560                                                             GrColorType::kRGBA_8888,
561                                                             nullptr));
562 
563     rtc->clear(nullptr, SK_PMColor4fWHITE, GrRenderTargetContext::CanClearFullscreen::kYes);
564 
565     // Note that this doesn't include the third texture proxy
566     for (int i = 0; i < kNumProxies-1; ++i) {
567         SkRect r = SkRect::MakeXYWH(i*3*kDrawnTileSize, 0, 3*kDrawnTileSize, kDrawnTileSize);
568 
569         SkMatrix t = SkMatrix::MakeTrans(-i*3*kDrawnTileSize, 0);
570 
571         GrPaint paint;
572         auto fp = GrSimpleTextureEffect::Make(std::move(proxies[i]), t);
573         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
574         paint.addColorFragmentProcessor(std::move(fp));
575 
576         rtc->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), r);
577     }
578 
579     rtc->flush(SkSurface::BackendSurfaceAccess::kNoAccess, GrFlushInfo());
580 
581     SkBitmap readBack;
582     readBack.allocN32Pixels(kFinalWidth, kFinalHeight);
583 
584     SkDEBUGCODE(bool result =) rtc->readPixels(readBack.info(), readBack.getPixels(),
585                                                readBack.rowBytes(), {0, 0});
586     SkASSERT(result);
587 
588     context->priv().testingOnly_flushAndRemoveOnFlushCallbackObject(&object);
589 
590     object.markAsDone();
591 
592     int x = kDrawnTileSize/2;
593     test_color(reporter, readBack, x, SK_ColorRED);
594     x += kDrawnTileSize;
595     test_color(reporter, readBack, x, SK_ColorGREEN);
596     x += kDrawnTileSize;
597     test_color(reporter, readBack, x, SK_ColorBLUE);
598     x += kDrawnTileSize;
599     test_color(reporter, readBack, x, SK_ColorCYAN);
600     x += kDrawnTileSize;
601     test_color(reporter, readBack, x, SK_ColorMAGENTA);
602     x += kDrawnTileSize;
603     test_color(reporter, readBack, x, SK_ColorYELLOW);
604 }
605