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 #if SK_SUPPORT_GPU
11
12 #include "GrBackendSemaphore.h"
13 #include "GrClip.h"
14 #include "GrContextPriv.h"
15 #include "GrDefaultGeoProcFactory.h"
16 #include "GrOnFlushResourceProvider.h"
17 #include "GrProxyProvider.h"
18 #include "GrQuad.h"
19 #include "GrRenderTargetContextPriv.h"
20 #include "GrResourceProvider.h"
21 #include "GrTexture.h"
22
23 #include "SkBitmap.h"
24 #include "SkPointPriv.h"
25 #include "effects/GrSimpleTextureEffect.h"
26 #include "ops/GrSimpleMeshDrawOpHelper.h"
27
28 namespace {
29 // This is a simplified mesh drawing op that can be used in the atlas generation test.
30 // Please see AtlasedRectOp below.
31 class NonAARectOp : public GrMeshDrawOp {
32 protected:
33 using Helper = GrSimpleMeshDrawOpHelper;
34
35 public:
36 DEFINE_OP_CLASS_ID
37
38 // This creates an instance of a simple non-AA solid color rect-drawing Op
Make(GrPaint && paint,const SkRect & r)39 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkRect& r) {
40 return Helper::FactoryHelper<NonAARectOp>(std::move(paint), r, nullptr, ClassID());
41 }
42
43 // This creates an instance of a simple non-AA textured rect-drawing Op
Make(GrPaint && paint,const SkRect & r,const SkRect & local)44 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkRect& r, const SkRect& local) {
45 return Helper::FactoryHelper<NonAARectOp>(std::move(paint), r, &local, ClassID());
46 }
47
color() const48 GrColor color() const { return fColor; }
49
NonAARectOp(const Helper::MakeArgs & helperArgs,GrColor color,const SkRect & r,const SkRect * localRect,int32_t classID)50 NonAARectOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkRect& r,
51 const SkRect* localRect, int32_t classID)
52 : INHERITED(classID)
53 , fColor(color)
54 , fHasLocalRect(SkToBool(localRect))
55 , fRect(r)
56 , fHelper(helperArgs, GrAAType::kNone) {
57 if (fHasLocalRect) {
58 fLocalQuad.set(*localRect);
59 }
60 // Choose some conservative values for aa bloat and zero area.
61 this->setBounds(r, HasAABloat::kYes, IsZeroArea::kYes);
62 }
63
name() const64 const char* name() const override { return "NonAARectOp"; }
65
visitProxies(const VisitProxyFunc & func) const66 void visitProxies(const VisitProxyFunc& func) const override {
67 fHelper.visitProxies(func);
68 }
69
fixedFunctionFlags() const70 FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
71
finalize(const GrCaps & caps,const GrAppliedClip *,GrPixelConfigIsClamped dstIsClamped)72 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip*,
73 GrPixelConfigIsClamped dstIsClamped) override {
74 // Set the color to unknown because the subclass may change the color later.
75 GrProcessorAnalysisColor gpColor;
76 gpColor.setToUnknown();
77 // We ignore the clip so pass this rather than the GrAppliedClip param.
78 static GrAppliedClip kNoClip;
79 return fHelper.xpRequiresDstTexture(caps, &kNoClip, dstIsClamped,
80 GrProcessorAnalysisCoverage::kNone, &gpColor);
81 }
82
83 protected:
84 GrColor fColor;
85 bool fHasLocalRect;
86 GrQuad fLocalQuad;
87 SkRect fRect;
88
89 private:
onCombineIfPossible(GrOp *,const GrCaps &)90 bool onCombineIfPossible(GrOp*, const GrCaps&) override { return false; }
91
onPrepareDraws(Target * target)92 void onPrepareDraws(Target* target) override {
93 using namespace GrDefaultGeoProcFactory;
94
95 // The vertex attrib order is always pos, color, local coords.
96 static const int kColorOffset = sizeof(SkPoint);
97 static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor);
98
99 sk_sp<GrGeometryProcessor> gp =
100 GrDefaultGeoProcFactory::Make(Color::kPremulGrColorAttribute_Type,
101 Coverage::kSolid_Type,
102 fHasLocalRect ? LocalCoords::kHasExplicit_Type
103 : LocalCoords::kUnused_Type,
104 SkMatrix::I());
105 if (!gp) {
106 SkDebugf("Couldn't create GrGeometryProcessor for GrAtlasedOp\n");
107 return;
108 }
109
110 size_t vertexStride = gp->getVertexStride();
111
112 SkASSERT(fHasLocalRect
113 ? vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr)
114 : vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorAttr));
115
116 const GrBuffer* indexBuffer;
117 int firstIndex;
118 uint16_t* indices = target->makeIndexSpace(6, &indexBuffer, &firstIndex);
119 if (!indices) {
120 SkDebugf("Indices could not be allocated for GrAtlasedOp.\n");
121 return;
122 }
123
124 const GrBuffer* vertexBuffer;
125 int firstVertex;
126 void* vertices = target->makeVertexSpace(vertexStride, 4, &vertexBuffer, &firstVertex);
127 if (!vertices) {
128 SkDebugf("Vertices could not be allocated for GrAtlasedOp.\n");
129 return;
130 }
131
132 // Setup indices
133 indices[0] = 0;
134 indices[1] = 1;
135 indices[2] = 2;
136 indices[3] = 2;
137 indices[4] = 1;
138 indices[5] = 3;
139
140 // Setup positions
141 SkPoint* position = (SkPoint*) vertices;
142 SkPointPriv::SetRectTriStrip(position, fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
143 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;
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(GrPrimitiveType::kTriangles);
162 mesh.setIndexed(indexBuffer, 6, firstIndex, 0, 3);
163 mesh.setVertexData(vertexBuffer, firstVertex);
164
165 target->draw(gp.get(), fHelper.makePipeline(target), mesh);
166 }
167
168 Helper fHelper;
169
170 typedef GrMeshDrawOp INHERITED;
171 };
172
173 } // anonymous namespace
174
175 static constexpr SkRect kEmptyRect = SkRect::MakeEmpty();
176
177 namespace {
178
179 /*
180 * Atlased ops just draw themselves as textured rects with the texture pixels being
181 * pulled out of the atlas. Their color is based on their ID.
182 */
183 class AtlasedRectOp final : public NonAARectOp {
184 public:
185 DEFINE_OP_CLASS_ID
186
~AtlasedRectOp()187 ~AtlasedRectOp() override {
188 fID = -1;
189 }
190
name() const191 const char* name() const override { return "AtlasedRectOp"; }
192
id() const193 int id() const { return fID; }
194
Make(GrPaint && paint,const SkRect & r,int id)195 static std::unique_ptr<AtlasedRectOp> Make(GrPaint&& paint, const SkRect& r, int id) {
196 GrDrawOp* op = Helper::FactoryHelper<AtlasedRectOp>(std::move(paint), r, id).release();
197 return std::unique_ptr<AtlasedRectOp>(static_cast<AtlasedRectOp*>(op));
198 }
199
200 // We set the initial color of the NonAARectOp based on the ID.
201 // Note that we force creation of a NonAARectOp that has local coords in anticipation of
202 // pulling from the atlas.
AtlasedRectOp(const Helper::MakeArgs & helperArgs,GrColor color,const SkRect & r,int id)203 AtlasedRectOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkRect& r, int id)
204 : INHERITED(helperArgs, kColors[id], r, &kEmptyRect, ClassID())
205 , fID(id)
206 , fNext(nullptr) {
207 SkASSERT(fID < kMaxIDs);
208 }
209
setColor(GrColor color)210 void setColor(GrColor color) { fColor = color; }
setLocalRect(const SkRect & localRect)211 void setLocalRect(const SkRect& localRect) {
212 SkASSERT(fHasLocalRect); // This should've been created to anticipate this
213 fLocalQuad.set(localRect);
214 }
215
next() const216 AtlasedRectOp* next() const { return fNext; }
setNext(AtlasedRectOp * next)217 void setNext(AtlasedRectOp* next) {
218 fNext = next;
219 }
220
221 private:
222
223 static const int kMaxIDs = 9;
224 static const SkColor kColors[kMaxIDs];
225
226 int fID;
227 // The Atlased ops have an internal singly-linked list of ops that land in the same opList
228 AtlasedRectOp* fNext;
229
230 typedef NonAARectOp INHERITED;
231 };
232
233 } // anonymous namespace
234
235 const GrColor AtlasedRectOp::kColors[kMaxIDs] = {
236 GrColorPackRGBA(255, 0, 0, 255),
237 GrColorPackRGBA(0, 255, 0, 255),
238 GrColorPackRGBA(0, 0, 255, 255),
239 GrColorPackRGBA(0, 255, 255, 255),
240 GrColorPackRGBA(255, 0, 255, 255),
241 GrColorPackRGBA(255, 255, 0, 255),
242 GrColorPackRGBA(0, 0, 0, 255),
243 GrColorPackRGBA(128, 128, 128, 255),
244 GrColorPackRGBA(255, 255, 255, 255)
245 };
246
247 static const int kDrawnTileSize = 16;
248
249 /*
250 * Rather than performing any rect packing, this atlaser just lays out constant-sized
251 * tiles in an Nx1 row
252 */
253 static const int kAtlasTileSize = 2;
254
255 /*
256 * This class aggregates the op information required for atlasing
257 */
258 class AtlasObject final : public GrOnFlushCallbackObject {
259 public:
AtlasObject()260 AtlasObject() : fDone(false) { }
261
~AtlasObject()262 ~AtlasObject() override {
263 SkASSERT(fDone);
264 }
265
markAsDone()266 void markAsDone() {
267 fDone = true;
268 }
269
270 // Insert the new op in an internal singly-linked list for 'opListID'
addOp(uint32_t opListID,AtlasedRectOp * op)271 void addOp(uint32_t opListID, AtlasedRectOp* op) {
272 LinkedListHeader* header = nullptr;
273 for (int i = 0; i < fOps.count(); ++i) {
274 if (opListID == fOps[i].fID) {
275 header = &(fOps[i]);
276 }
277 }
278
279 if (!header) {
280 fOps.push({opListID, nullptr});
281 header = &(fOps[fOps.count()-1]);
282 }
283
284 op->setNext(header->fHead);
285 header->fHead = op;
286 }
287
numOps() const288 int numOps() const { return fOps.count(); }
289
290 // Get the fully lazy proxy that is backing the atlas. Its actual width isn't
291 // known until flush time.
getAtlasProxy(GrProxyProvider * proxyProvider)292 sk_sp<GrTextureProxy> getAtlasProxy(GrProxyProvider* proxyProvider) {
293 if (fAtlasProxy) {
294 return fAtlasProxy;
295 }
296
297 fAtlasProxy = proxyProvider->createFullyLazyProxy(
298 [](GrResourceProvider* resourceProvider) {
299 if (!resourceProvider) {
300 return sk_sp<GrTexture>();
301 }
302
303 GrSurfaceDesc desc;
304 desc.fFlags = kRenderTarget_GrSurfaceFlag;
305 desc.fOrigin = kBottomLeft_GrSurfaceOrigin;
306 // TODO: until partial flushes in MDB lands we're stuck having
307 // all 9 atlas draws occur
308 desc.fWidth = 9 /*this->numOps()*/ * kAtlasTileSize;
309 desc.fHeight = kAtlasTileSize;
310 desc.fConfig = kRGBA_8888_GrPixelConfig;
311
312 return resourceProvider->createTexture(desc, SkBudgeted::kYes,
313 GrResourceProvider::kNoPendingIO_Flag);
314 },
315 GrProxyProvider::Renderable::kYes,
316 kBottomLeft_GrSurfaceOrigin,
317 kRGBA_8888_GrPixelConfig);
318 return fAtlasProxy;
319 }
320
321 /*
322 * This callback creates the atlas and updates the AtlasedRectOps to read from it
323 */
preFlush(GrOnFlushResourceProvider * resourceProvider,const uint32_t * opListIDs,int numOpListIDs,SkTArray<sk_sp<GrRenderTargetContext>> * results)324 void preFlush(GrOnFlushResourceProvider* resourceProvider,
325 const uint32_t* opListIDs, int numOpListIDs,
326 SkTArray<sk_sp<GrRenderTargetContext>>* results) override {
327 SkASSERT(!results->count());
328
329 // Until MDB is landed we will most-likely only have one opList.
330 SkTDArray<LinkedListHeader*> lists;
331 for (int i = 0; i < numOpListIDs; ++i) {
332 if (LinkedListHeader* list = this->getList(opListIDs[i])) {
333 lists.push(list);
334 }
335 }
336
337 if (!lists.count()) {
338 return; // nothing to atlas
339 }
340
341 if (!resourceProvider->instatiateProxy(fAtlasProxy.get())) {
342 return;
343 }
344
345 // At this point all the GrAtlasedOp's should have lined up to read from 'atlasDest' and
346 // there should either be two writes to clear it or no writes.
347 SkASSERT(9 == fAtlasProxy->getPendingReadCnt_TestOnly());
348 SkASSERT(2 == fAtlasProxy->getPendingWriteCnt_TestOnly() ||
349 0 == fAtlasProxy->getPendingWriteCnt_TestOnly());
350 sk_sp<GrRenderTargetContext> rtc = resourceProvider->makeRenderTargetContext(
351 fAtlasProxy,
352 nullptr, nullptr);
353
354 // clear the atlas
355 rtc->clear(nullptr, 0x0, GrRenderTargetContext::CanClearFullscreen::kYes);
356
357 int blocksInAtlas = 0;
358 for (int i = 0; i < lists.count(); ++i) {
359 for (AtlasedRectOp* op = lists[i]->fHead; op; op = op->next()) {
360 SkIRect r = SkIRect::MakeXYWH(blocksInAtlas*kAtlasTileSize, 0,
361 kAtlasTileSize, kAtlasTileSize);
362
363 // For now, we avoid the resource buffer issues and just use clears
364 #if 1
365 rtc->clear(&r, op->color(), GrRenderTargetContext::CanClearFullscreen::kNo);
366 #else
367 GrPaint paint;
368 paint.setColor4f(GrColor4f::FromGrColor(op->color()));
369 std::unique_ptr<GrDrawOp> drawOp(NonAARectOp::Make(std::move(paint),
370 SkRect::Make(r)));
371 rtc->priv().testingOnly_addDrawOp(std::move(drawOp));
372 #endif
373 blocksInAtlas++;
374
375 // Set the atlased Op's color to white (so we know we're not using it for
376 // the final draw).
377 op->setColor(0xFFFFFFFF);
378
379 // Set the atlased Op's localRect to point to where it landed in the atlas
380 op->setLocalRect(SkRect::Make(r));
381 }
382
383 // We've updated all these ops and we certainly don't want to process them again
384 this->clearOpsFor(lists[i]);
385 }
386
387 results->push_back(std::move(rtc));
388 }
389
390 private:
391 typedef struct {
392 uint32_t fID;
393 AtlasedRectOp* fHead;
394 } LinkedListHeader;
395
getList(uint32_t opListID)396 LinkedListHeader* getList(uint32_t opListID) {
397 for (int i = 0; i < fOps.count(); ++i) {
398 if (opListID == fOps[i].fID) {
399 return &(fOps[i]);
400 }
401 }
402 return nullptr;
403 }
404
clearOpsFor(LinkedListHeader * header)405 void clearOpsFor(LinkedListHeader* header) {
406 // The AtlasedRectOps have yet to execute (and this class doesn't own them) so just
407 // forget about them in the laziest way possible.
408 header->fHead = nullptr;
409 header->fID = 0; // invalid opList ID
410 }
411
412 // Each opList containing AtlasedRectOps gets its own internal singly-linked list
413 SkTDArray<LinkedListHeader> fOps;
414
415 // The fully lazy proxy for the atlas
416 sk_sp<GrTextureProxy> fAtlasProxy;
417
418 // Set to true when the testing harness expects this object to be no longer used
419 bool fDone;
420 };
421
422 // 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)423 static sk_sp<GrTextureProxy> make_upstream_image(GrContext* context, AtlasObject* object, int start,
424 sk_sp<GrTextureProxy> atlasProxy) {
425 sk_sp<GrRenderTargetContext> rtc(context->makeDeferredRenderTargetContext(
426 SkBackingFit::kApprox,
427 3*kDrawnTileSize,
428 kDrawnTileSize,
429 kRGBA_8888_GrPixelConfig,
430 nullptr));
431
432 rtc->clear(nullptr, GrColorPackRGBA(255, 0, 0, 255),
433 GrRenderTargetContext::CanClearFullscreen::kYes);
434
435 for (int i = 0; i < 3; ++i) {
436 SkRect r = SkRect::MakeXYWH(i*kDrawnTileSize, 0, kDrawnTileSize, kDrawnTileSize);
437
438 auto fp = GrSimpleTextureEffect::Make(atlasProxy, SkMatrix::I());
439 GrPaint paint;
440 paint.addColorFragmentProcessor(std::move(fp));
441 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
442 std::unique_ptr<AtlasedRectOp> op(AtlasedRectOp::Make(std::move(paint), r, start + i));
443
444 AtlasedRectOp* sparePtr = op.get();
445
446 uint32_t opListID = rtc->priv().testingOnly_addDrawOp(std::move(op));
447
448 object->addOp(opListID, sparePtr);
449 }
450
451 return rtc->asTextureProxyRef();
452 }
453
454 // Enable this if you want to debug the final draws w/o having the atlasCallback create the
455 // atlas
456 #if 0
457 #include "SkImageEncoder.h"
458 #include "SkGrPriv.h"
459 #include "sk_tool_utils.h"
460
461 static void save_bm(const SkBitmap& bm, const char name[]) {
462 bool result = sk_tool_utils::EncodeImageToFile(name, bm, SkEncodedImageFormat::kPNG, 100);
463 SkASSERT(result);
464 }
465
466 sk_sp<GrTextureProxy> pre_create_atlas(GrContext* context) {
467 SkBitmap bm;
468 bm.allocN32Pixels(18, 2, true);
469 bm.erase(SK_ColorRED, SkIRect::MakeXYWH(0, 0, 2, 2));
470 bm.erase(SK_ColorGREEN, SkIRect::MakeXYWH(2, 0, 2, 2));
471 bm.erase(SK_ColorBLUE, SkIRect::MakeXYWH(4, 0, 2, 2));
472 bm.erase(SK_ColorCYAN, SkIRect::MakeXYWH(6, 0, 2, 2));
473 bm.erase(SK_ColorMAGENTA, SkIRect::MakeXYWH(8, 0, 2, 2));
474 bm.erase(SK_ColorYELLOW, SkIRect::MakeXYWH(10, 0, 2, 2));
475 bm.erase(SK_ColorBLACK, SkIRect::MakeXYWH(12, 0, 2, 2));
476 bm.erase(SK_ColorGRAY, SkIRect::MakeXYWH(14, 0, 2, 2));
477 bm.erase(SK_ColorWHITE, SkIRect::MakeXYWH(16, 0, 2, 2));
478
479 #if 1
480 save_bm(bm, "atlas-fake.png");
481 #endif
482
483 GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(bm.info(), *context->caps());
484 desc.fFlags |= kRenderTarget_GrSurfaceFlag;
485
486 sk_sp<GrSurfaceProxy> tmp = GrSurfaceProxy::MakeDeferred(*context->caps(),
487 context->textureProvider(),
488 desc, SkBudgeted::kYes,
489 bm.getPixels(), bm.rowBytes());
490
491 return sk_ref_sp(tmp->asTextureProxy());
492 }
493 #endif
494
495
test_color(skiatest::Reporter * reporter,const SkBitmap & bm,int x,SkColor expected)496 static void test_color(skiatest::Reporter* reporter, const SkBitmap& bm, int x, SkColor expected) {
497 SkColor readback = bm.getColor(x, kDrawnTileSize/2);
498 REPORTER_ASSERT(reporter, expected == readback);
499 if (expected != readback) {
500 SkDebugf("Color mismatch: %x %x\n", expected, readback);
501 }
502 }
503
504 /*
505 * For the atlasing test we make a DAG that looks like:
506 *
507 * RT1 with ops: 0,1,2 RT2 with ops: 3,4,5 RT3 with ops: 6,7,8
508 * \ /
509 * \ /
510 * RT4
511 * We then flush RT4 and expect only ops 0-5 to be atlased together.
512 * Each op is just a solid colored rect so both the atlas and the final image should appear as:
513 * R G B C M Y
514 * with the atlas having width = 6*kAtlasTileSize and height = kAtlasTileSize.
515 *
516 * Note: until partial flushes in MDB lands, the atlas will actually have width= 9*kAtlasTileSize
517 * and look like:
518 * R G B C M Y K Grey White
519 */
DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(OnFlushCallbackTest,reporter,ctxInfo)520 DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(OnFlushCallbackTest, reporter, ctxInfo) {
521 static const int kNumProxies = 3;
522
523 GrContext* context = ctxInfo.grContext();
524 auto proxyProvider = context->contextPriv().proxyProvider();
525
526 AtlasObject object;
527
528 context->contextPriv().addOnFlushCallbackObject(&object);
529
530 sk_sp<GrTextureProxy> proxies[kNumProxies];
531 for (int i = 0; i < kNumProxies; ++i) {
532 proxies[i] = make_upstream_image(context, &object, i*3,
533 object.getAtlasProxy(proxyProvider));
534 }
535
536 static const int kFinalWidth = 6*kDrawnTileSize;
537 static const int kFinalHeight = kDrawnTileSize;
538
539 sk_sp<GrRenderTargetContext> rtc(context->makeDeferredRenderTargetContext(
540 SkBackingFit::kApprox,
541 kFinalWidth,
542 kFinalHeight,
543 kRGBA_8888_GrPixelConfig,
544 nullptr));
545
546 rtc->clear(nullptr, 0xFFFFFFFF, GrRenderTargetContext::CanClearFullscreen::kYes);
547
548 // Note that this doesn't include the third texture proxy
549 for (int i = 0; i < kNumProxies-1; ++i) {
550 SkRect r = SkRect::MakeXYWH(i*3*kDrawnTileSize, 0, 3*kDrawnTileSize, kDrawnTileSize);
551
552 SkMatrix t = SkMatrix::MakeTrans(-i*3*kDrawnTileSize, 0);
553
554 GrPaint paint;
555 auto fp = GrSimpleTextureEffect::Make(std::move(proxies[i]), t);
556 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
557 paint.addColorFragmentProcessor(std::move(fp));
558
559 rtc->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), r);
560 }
561
562 rtc->prepareForExternalIO(0, nullptr);
563
564 SkBitmap readBack;
565 readBack.allocN32Pixels(kFinalWidth, kFinalHeight);
566
567 SkDEBUGCODE(bool result =) rtc->readPixels(readBack.info(), readBack.getPixels(),
568 readBack.rowBytes(), 0, 0);
569 SkASSERT(result);
570
571 context->contextPriv().testingOnly_flushAndRemoveOnFlushCallbackObject(&object);
572
573 object.markAsDone();
574
575 int x = kDrawnTileSize/2;
576 test_color(reporter, readBack, x, SK_ColorRED);
577 x += kDrawnTileSize;
578 test_color(reporter, readBack, x, SK_ColorGREEN);
579 x += kDrawnTileSize;
580 test_color(reporter, readBack, x, SK_ColorBLUE);
581 x += kDrawnTileSize;
582 test_color(reporter, readBack, x, SK_ColorCYAN);
583 x += kDrawnTileSize;
584 test_color(reporter, readBack, x, SK_ColorMAGENTA);
585 x += kDrawnTileSize;
586 test_color(reporter, readBack, x, SK_ColorYELLOW);
587 }
588
589 #endif
590