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