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