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