blob: 773cfde3478d35f0c4185fda35ec4ee573a2af67 [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.
24class NonAARectOp : public GrMeshDrawOp {
25public:
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:
67 void getFragmentProcessorAnalysisInputs(FragmentProcessorAnalysisInputs* input) const override {
68 input->colorInput()->setToUnknown();
69 input->coverageInput()->setToUnknown();
70 }
71
72 void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
73 optimizations.getOverrideColorIfSet(&fColor);
74 }
75
76 bool onCombineIfPossible(GrOp*, const GrCaps&) override { return false; }
77
78 void onPrepareDraws(Target* target) const override {
79 using namespace GrDefaultGeoProcFactory;
80
81 // The vertex attrib order is always pos, color, local coords.
82 static const int kColorOffset = sizeof(SkPoint);
83 static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor);
84
85 sk_sp<GrGeometryProcessor> gp =
86 GrDefaultGeoProcFactory::Make(Color::kPremulGrColorAttribute_Type,
87 Coverage::kSolid_Type,
88 fHasLocalRect ? LocalCoords::kHasExplicit_Type
89 : LocalCoords::kUnused_Type,
90 SkMatrix::I());
91 if (!gp) {
92 SkDebugf("Couldn't create GrGeometryProcessor for GrAtlasedOp\n");
93 return;
94 }
95
96 size_t vertexStride = gp->getVertexStride();
97
98 SkASSERT(fHasLocalRect
99 ? vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr)
100 : vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorAttr));
101
102 const GrBuffer* indexBuffer;
103 int firstIndex;
104 uint16_t* indices = target->makeIndexSpace(6, &indexBuffer, &firstIndex);
105 if (!indices) {
106 SkDebugf("Indices could not be allocated for GrAtlasedOp.\n");
107 return;
108 }
109
110 const GrBuffer* vertexBuffer;
111 int firstVertex;
112 void* vertices = target->makeVertexSpace(vertexStride, 4, &vertexBuffer, &firstVertex);
113 if (!vertices) {
114 SkDebugf("Vertices could not be allocated for GrAtlasedOp.\n");
115 return;
116 }
117
118 // Setup indices
119 indices[0] = 0;
120 indices[1] = 1;
121 indices[2] = 2;
122 indices[3] = 0;
123 indices[4] = 2;
124 indices[5] = 3;
125
126 // Setup positions
127 SkPoint* position = (SkPoint*) vertices;
128 position->setRectFan(fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom, vertexStride);
129
130 // Setup vertex colors
131 GrColor* color = (GrColor*)((intptr_t)vertices + kColorOffset);
132 for (int i = 0; i < 4; ++i) {
133 *color = fColor;
134 color = (GrColor*)((intptr_t)color + vertexStride);
135 }
136
137 // Setup local coords
138 if (fHasLocalRect) {
139 SkPoint* coords = (SkPoint*)((intptr_t) vertices + kLocalOffset);
140 for (int i = 0; i < 4; i++) {
141 *coords = fLocalQuad.point(i);
142 coords = (SkPoint*)((intptr_t) coords + vertexStride);
143 }
144 }
145
146 GrMesh mesh;
147 mesh.initIndexed(kTriangles_GrPrimitiveType,
148 vertexBuffer, indexBuffer,
149 firstVertex, firstIndex,
150 4, 6);
151
152 target->draw(gp.get(), mesh);
153 }
154
155 typedef GrMeshDrawOp INHERITED;
156};
157
158#ifdef SK_DEBUG
159#include "SkImageEncoder.h"
160#include "sk_tool_utils.h"
161
162static void save_bm(const SkBitmap& bm, const char name[]) {
163 bool result = sk_tool_utils::EncodeImageToFile(name, bm, SkEncodedImageFormat::kPNG, 100);
164 SkASSERT(result);
165}
166#endif
167
168/*
169 * Atlased ops just draw themselves as textured rects with the texture pixels being
170 * pulled out of the atlas. Their color is based on their ID.
171 */
172class AtlasedRectOp final : public NonAARectOp {
173public:
174 DEFINE_OP_CLASS_ID
175
176 ~AtlasedRectOp() override {
177 fID = -1;
178 }
179
180 const char* name() const override { return "AtlasedRectOp"; }
181
182 int id() const { return fID; }
183
184 static std::unique_ptr<AtlasedRectOp> Make(const SkRect& r, int id) {
185 return std::unique_ptr<AtlasedRectOp>(new AtlasedRectOp(r, id));
186 }
187
188 void setColor(GrColor color) { fColor = color; }
189 void setLocalRect(const SkRect& localRect) {
190 SkASSERT(fHasLocalRect); // This should've been created to anticipate this
191 fLocalQuad.set(localRect);
192 }
193
194 AtlasedRectOp* next() const { return fNext; }
195 void setNext(AtlasedRectOp* next) {
196 fNext = next;
197 }
198
199private:
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.
203 AtlasedRectOp(const SkRect& r, int id)
204 : INHERITED(ClassID(), r, kColors[id], SkRect::MakeEmpty())
205 , fID(id)
206 , fNext(nullptr) {
207 SkASSERT(fID < kMaxIDs);
208 }
209
210 static const int kMaxIDs = 9;
211 static const SkColor kColors[kMaxIDs];
212
213 int fID;
214 // The Atlased ops have an internal singly-linked list of ops that land in the same opList
215 AtlasedRectOp* fNext;
216
217 typedef NonAARectOp INHERITED;
218};
219
220const GrColor AtlasedRectOp::kColors[kMaxIDs] = {
221 GrColorPackRGBA(255, 0, 0, 255),
222 GrColorPackRGBA(0, 255, 0, 255),
223 GrColorPackRGBA(0, 0, 255, 255),
224 GrColorPackRGBA(0, 255, 255, 255),
225 GrColorPackRGBA(255, 0, 255, 255),
226 GrColorPackRGBA(255, 255, 0, 255),
227 GrColorPackRGBA(0, 0, 0, 255),
228 GrColorPackRGBA(128, 128, 128, 255),
229 GrColorPackRGBA(255, 255, 255, 255)
230};
231
232static const int kDrawnTileSize = 16;
233
234/*
235 * Rather than performing any rect packing, this atlaser just lays out constant-sized
236 * tiles in an Nx1 row
237 */
238static const int kAtlasTileSize = 2;
239
240/*
241 * This class aggregates the op information required for atlasing
242 */
243class AtlasObject final : public GrPreFlushCallbackObject {
244public:
245 AtlasObject() : fDone(false) { }
246
247 ~AtlasObject() override {
248 SkASSERT(fDone);
249 }
250
251 void markAsDone() {
252 fDone = true;
253 }
254
255 // Insert the new op in an internal singly-linked list for 'opListID'
256 void addOp(uint32_t opListID, AtlasedRectOp* op) {
257 LinkedListHeader* header = nullptr;
258 for (int i = 0; i < fOps.count(); ++i) {
259 if (opListID == fOps[i].fID) {
260 header = &(fOps[i]);
261 }
262 }
263
264 if (!header) {
265 fOps.push({opListID, nullptr});
266 header = &(fOps[fOps.count()-1]);
267 }
268
269 op->setNext(header->fHead);
270 header->fHead = op;
271 }
272
273 // For the time being we need to pre-allocate the atlas.
274 void setAtlasDest(sk_sp<GrTextureProxy> atlasDest) {
275 fAtlasDest = atlasDest;
276 }
277
278 void saveRTC(sk_sp<GrRenderTargetContext> rtc) {
279 SkASSERT(!fRTC);
280 fRTC = rtc;
281 }
282
283#ifdef SK_DEBUG
284 void saveAtlasToDisk() {
285 SkBitmap readBack;
286 readBack.allocN32Pixels(fRTC->width(), fRTC->height());
287
288 bool result = fRTC->readPixels(readBack.info(),
289 readBack.getPixels(), readBack.rowBytes(), 0, 0);
290 SkASSERT(result);
291 save_bm(readBack, "atlas-real.png");
292 }
293#endif
294
295 /*
296 * This callback back creates the atlas and updates the AtlasedRectOps to read from it
297 */
298 void preFlush(GrPreFlushResourceProvider* resourceProvider,
299 const uint32_t* opListIDs, int numOpListIDs,
300 SkTArray<sk_sp<GrRenderTargetContext>>* results) override {
301 SkASSERT(!results->count());
302
303 // Until MDB is landed we will most-likely only have one opList.
304 SkTDArray<LinkedListHeader*> lists;
305 for (int i = 0; i < numOpListIDs; ++i) {
306 if (LinkedListHeader* list = this->getList(opListIDs[i])) {
307 lists.push(list);
308 }
309 }
310
311 if (!lists.count()) {
312 return; // nothing to atlas
313 }
314
315 // TODO: right now we have to pre-allocate the atlas bc the TextureSamplers need a
316 // hard GrTexture
317#if 0
318 GrSurfaceDesc desc;
319 desc.fFlags = kRenderTarget_GrSurfaceFlag;
320 desc.fWidth = this->numOps() * kAtlasTileSize;
321 desc.fHeight = kAtlasTileSize;
322 desc.fConfig = kRGBA_8888_GrPixelConfig;
323
324 sk_sp<GrRenderTargetContext> rtc = resourceProvider->makeRenderTargetContext(desc,
325 nullptr,
326 nullptr);
327#else
328 // At this point all the GrAtlasedOp's should have lined up to read from 'atlasDest' and
329 // there should either be two writes to clear it or no writes.
330 SkASSERT(9 == fAtlasDest->getPendingReadCnt_TestOnly());
331 SkASSERT(2 == fAtlasDest->getPendingWriteCnt_TestOnly() ||
332 0 == fAtlasDest->getPendingWriteCnt_TestOnly());
333 sk_sp<GrRenderTargetContext> rtc = resourceProvider->makeRenderTargetContext(
334 fAtlasDest,
335 nullptr, nullptr);
336#endif
337
338 rtc->clear(nullptr, 0xFFFFFFFF, true); // clear the atlas
339
340 int blocksInAtlas = 0;
341 for (int i = 0; i < lists.count(); ++i) {
342 for (AtlasedRectOp* op = lists[i]->fHead; op; op = op->next()) {
343 SkIRect r = SkIRect::MakeXYWH(blocksInAtlas*kAtlasTileSize, 0,
344 kAtlasTileSize, kAtlasTileSize);
345
346 // For now, we avoid the resource buffer issues and just use clears
347#if 1
348 rtc->clear(&r, op->color(), false);
349#else
350 std::unique_ptr<GrDrawOp> drawOp(GrNonAARectOp::Make(SkRect::Make(r),
351 atlasedOp->color()));
352
353 GrPaint paint;
354 rtc->priv().testingOnly_addDrawOp(std::move(paint),
355 GrAAType::kNone,
356 std::move(drawOp));
357#endif
358 blocksInAtlas++;
359
360 // Set the atlased Op's color to white (so we know we're not using it for
361 // the final draw).
362 op->setColor(0xFFFFFFFF);
363
364 // Set the atlased Op's localRect to point to where it landed in the atlas
365 op->setLocalRect(SkRect::Make(r));
366
367 // TODO: we also need to set the op's GrSuperDeferredSimpleTextureEffect to point
368 // to the rtc's proxy!
369 }
370
371 // We've updated all these ops and we certainly don't want to process them again
372 this->clearOpsFor(lists[i]);
373 }
374
375 // Hide a ref to the RTC in AtlasData so we can check on it later
376 this->saveRTC(rtc);
377
378 results->push_back(std::move(rtc));
379 }
380
381private:
382 typedef struct {
383 uint32_t fID;
384 AtlasedRectOp* fHead;
385 } LinkedListHeader;
386
387 LinkedListHeader* getList(uint32_t opListID) {
388 for (int i = 0; i < fOps.count(); ++i) {
389 if (opListID == fOps[i].fID) {
390 return &(fOps[i]);
391 }
392 }
393 return nullptr;
394 }
395
396 void clearOpsFor(LinkedListHeader* header) {
397 // The AtlasedRectOps have yet to execute (and this class doesn't own them) so just
398 // forget about them in the laziest way possible.
399 header->fHead = nullptr;
400 header->fID = 0; // invalid opList ID
401 }
402
403 // Each opList containing AtlasedRectOps gets its own internal singly-linked list
404 SkTDArray<LinkedListHeader> fOps;
405
406 // The RTC used to create the atlas
407 sk_sp<GrRenderTargetContext> fRTC;
408
409 // For the time being we need to pre-allocate the atlas bc the TextureSamplers require
410 // a GrTexture
411 sk_sp<GrTextureProxy> fAtlasDest;
412
413 // Set to true when the testing harness expects this object to be no longer used
414 bool fDone;
415};
416
417// This creates an off-screen rendertarget whose ops which eventually pull from the atlas.
418static sk_sp<GrTextureProxy> make_upstream_image(GrContext* context, AtlasObject* object, int start,
419 sk_sp<GrTextureProxy> fakeAtlas) {
420
421 sk_sp<GrRenderTargetContext> rtc(context->makeRenderTargetContext(SkBackingFit::kApprox,
422 3*kDrawnTileSize,
423 kDrawnTileSize,
424 kRGBA_8888_GrPixelConfig,
425 nullptr));
426
427 rtc->clear(nullptr, GrColorPackRGBA(255, 0, 0, 255), true);
428
429 for (int i = 0; i < 3; ++i) {
430 SkRect r = SkRect::MakeXYWH(i*kDrawnTileSize, 0, kDrawnTileSize, kDrawnTileSize);
431
432 std::unique_ptr<AtlasedRectOp> op(AtlasedRectOp::Make(r, start+i));
433
434 // TODO: here is the blocker for deferring creation of the atlas. The TextureSamplers
435 // created here currently require a hard GrTexture.
436 sk_sp<GrFragmentProcessor> fp = GrSimpleTextureEffect::Make(context->resourceProvider(),
437 fakeAtlas,
438 nullptr, SkMatrix::I());
439
440 GrPaint paint;
441 paint.addColorFragmentProcessor(std::move(fp));
442 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
443
444 AtlasedRectOp* sparePtr = op.get();
445
446 uint32_t opListID = rtc->priv().testingOnly_addMeshDrawOp(std::move(paint),
447 GrAAType::kNone,
448 std::move(op));
449
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