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