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